自行创建的http服务器无法处理邮递员请求

时间:2018-10-15 05:56:25

标签: java sockets postman serversocket httpserver

我正在尝试用Java实现一个简单的http服务器,以了解http服务器如何工作。现在,我可以从任何浏览器发送请求并接收正确的响应,但是,当我尝试模拟来自邮递员的请求时,它总是会引发java.net.SocketException: Broken pipe (Write failed)异常。

我的http服务器非常简单:收到请求后,将消息回显给发件人。

实现如下,Source Code

package com.ont.http;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class SingleThreadHttpServer implements HttpServer {

    public void run(int port) throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        try {
            while (true) {
                Socket socket = serverSocket.accept();

                BufferedReader inBufferReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                StringBuilder stringBuffer = new StringBuilder();

                String inputLine;
                while ((inputLine = inBufferReader.readLine()) != null && !inputLine.equals("")) {
                    stringBuffer.append(inputLine);
                    stringBuffer.append("\r\n");
                }

                System.out.println(stringBuffer.toString());

                OutputStream outStream = socket.getOutputStream();
                BufferedReader bufferedReader = new BufferedReader(new StringReader("A Message from server."));

                // Header should be ended with '\r\n' at each line.
                outStream.write("HTTP/1.1 200 OK\r\n".getBytes());
                outStream.write("Main: OneServer 0.1\r\n".getBytes());
                outStream.write("Content-length: 22\r\n".getBytes()); // if text/plain the length is required
                outStream.write("Content-Type: text/plain\r\n".getBytes());

                // An empty line is required after the header
                outStream.write("\r\n".getBytes());

                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    outStream.write(line.getBytes());
                }

                inBufferReader.close();
                bufferedReader.close();
                outStream.flush();
                outStream.close(); // Socket will close automatically once output stream is closed.
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

从Postman发送的任何类型的请求都将立即触发该异常,并且debug告知套接字连接在outStream.write(...)期间丢失。如果我是从浏览器发送的,则永远不会触发该问题。

我不明白的是,不应对Postman进行任何设置,因为当我尝试模拟对tomcat的请求时,它会像处理魅力一样处理一切,我的东西一定出了问题代码,但我不知道在哪里以及为什么。

任何帮助或提示都将不胜感激,谢谢。

-----更新----

从Postman控制台中,我可以看到此异常,

Error: read ECONNRESET
Request Headers:
accept:"text/plain"
cache-control:"no-cache"

如果我只剩下outStream.write("HTTP/1.1 200 OK\r\n".getBytes())这一行,并删除所有其他outStream.write...,则浏览器和邮递员的工作就像一个咒语。这真的拉着我的头发,但是为什么呢?

2 个答案:

答案 0 :(得分:1)

我能够重现该问题并确定其根本原因。对于所有localhost URL,邮递员两次连接到服务器。第一个连接只是为了检查服务器的可达性,它会立即关闭,而无需等待服务器的响应。如果服务器可达,它将再次连接并发送第二个请求并显示第二个请求的响应。

如果您已发布服务器,则在关闭来自Postman的第一个连接时会崩溃。由于Postman关闭连接时没有等待第一个连接的完整响应,因此服务器中的多个outStream.write()调用将引发异常(java.net.SocketException: Broken pipe (Write failed)),从而导致服务器崩溃。因此,邮递员发送的第二个请求将不会得到任何响应。

我认为这是Postman App的问题,应解决。但是,另一方面,也应在服务器端正确处理此类错误,以防止崩溃。如果客户端破坏了发送响应的服务器中间的连接(即使用任何浏览器发出请求,发送非常大的响应或在后续两次发送之间保持延迟),则不仅Postman应用程序而且使用其他工具发送的请求也会使服务器崩溃。 outStream.write()调用,在收到完整响应之前关闭浏览器选项卡,这将使您的服务器崩溃。

请参阅下面发布的修改后的服务器代码,该代码在这种情况下将很好地工作:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class SingleThreadHttpServer implements HttpServer {

    public void run(int port) throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        try {
            while (true) {
                Socket socket = serverSocket.accept();

                BufferedReader inBufferReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                StringBuilder stringBuffer = new StringBuilder();

                String inputLine;
                while ((inputLine = inBufferReader.readLine()) != null && !inputLine.equals("")) {
                    stringBuffer.append(inputLine);
                    stringBuffer.append("\r\n");
                }

                System.out.println(stringBuffer.toString());

                OutputStream outStream = socket.getOutputStream();
                BufferedReader bufferedReader = new BufferedReader(new StringReader("A Message from server."));

                try {
                    // Header should be ended with '\r\n' at each line
                    outStream.write("HTTP/1.1 200 OK\r\n".getBytes());
                    //outStream.write("Main: OneServer 0.1\r\n".getBytes());
                    outStream.write("Content-Length: 22\r\n".getBytes()); // if text/plain the length is required
                    outStream.write("Content-Type: text/plain\r\n".getBytes());
                    //outStream.write("Connection: close\r\n".getBytes());

                    // An empty line is required after the header
                    outStream.write("\r\n".getBytes());

                    String line;
                    while ((line = bufferedReader.readLine()) != null) {
                        outStream.write(line.getBytes());
                    }

                    outStream.flush();
                    outStream.close(); // Socket will close automatically once output stream is closed.
                } catch (SocketException e) {
                    // Handle the case where client closed the connection while server was writing to it
                    socket.close();
                }

                bufferedReader.close();
                inBufferReader.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

答案 1 :(得分:0)

该异常表示您在尝试写入连接时,另一端已经关闭了该连接。

这只是猜测,但Postman可能不喜欢收到的答复,只是放弃了。浏览器可能仍会尽力而为地接受您的操作,但这并不一定意味着响应正确。我不是一个人了解整个http规范,所以我无法立即告诉您是否是这种情况。要继续,也许您可​​以看一下Fiddler之类的工具,查看基本请求后通常从http服务器返回的内容,然后查看是否缺少某些内容。

我可以看到,内容长度是硬编码的,尽管我不希望这样会给您带来异常。