自建http服务器无法处理postman请求

Self created http server cannot handle postman request

我正在尝试在 Java 中实现一个简单的 http 服务器,以了解 http 服务器的工作原理。现在,我可以从任何浏览器发送请求并收到正确的响应,但是,当我尝试模拟来自 Postman 的请求时,它总是抛出 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 发送的任何类型的请求都会立即触发该异常,并且调试告诉套接字连接在 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...,浏览器和邮递员都可以正常工作。这真的很扯我的头发,但为什么呢?

异常意味着对方已经关闭连接,而您仍在尝试写入。

目前这只是推测,但 Postman 可能不喜欢它收到的响应而放弃了。浏览器可能仍会以最大努力的方式接受您正在做的事情,但这并不一定意味着响应是正确的。我不太了解整个 http 规范,所以我不能立即告诉你是否是这样。要继续,也许你可以看看像 Fiddler 这样的工具,看看在基本请求后通常从 http 服务器返回什么,然后看看你是否遗漏了什么。

一方面,我可以看到内容长度是硬编码的,但我不希望它给你那个例外。

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

对于您发布的服务器,当来自 Postman 的第一个连接关闭时,它会崩溃。由于 Postman 没有等待第一次连接的完整响应就关闭了连接,因此在您的服务器中多次 outStream.write() 调用将抛出异常 (java.net.SocketException: Broken pipe (Write failed)) 导致服务器崩溃。所以Postman发送的第二个请求不会得到任何响应。

我认为这是应该修复的 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();
        }
    }
}