【问题标题】:Self created http server cannot handle postman request自建http服务器无法处理邮递员请求
【发布时间】:2019-03-19 12:17:36
【问题描述】:

我正在尝试用 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 发送的任何类型的请求都会立即触发该异常,并且 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...,浏览器和邮递员都会像魅力一样工作。这真是拉扯我的头发,但是为什么呢?

【问题讨论】:

  • 邮递员请求是否说它接受text/plain?我怀疑它没有,然后它会看到标头并关闭请求。
  • @BoristheSpider 谢谢,但我不认为这是问题所在,因为如果你不指定它的接受,邮递员会接受 /
  • 您的 HTTP 响应缺少 Connection: close 标头,以让客户端知道在发送响应后连接将被有意关闭。此外,Content-length 应该是 Content-Length。而且您没有指出 哪个 调用outStream.write() 是引发异常的那个。
  • 你是对的,有一个错字,但是我试过Content-lengthContent-Length没有区别,而且outStream.write("HTTP/1.1 200 OK\r\n".getBytes());这个调用之后出现异常,不管是哪一种将其他outStream.write(...) 的订单放在此行之后,它会引发相同的异常。这意味着我只能将所有内容放在一行中,然后在第一次 outStream.write() 调用后关闭连接。

标签: java sockets postman serversocket httpserver


【解决方案1】:

异常意味着当您仍在尝试写入时,另一端已经关闭了连接。

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

首先,我可以看到 content-length 是硬编码的,但我不希望这会给您带来异常。

【讨论】:

  • 内容长度可能不是问题,我已经与邮递员可以正确处理的正常响应进行了比较,并将我的响应更改为这个“正常”响应,异常仍然发生,因为一个人将任何超过 outStream.write("HTTP/1.1 200 OK\r\n".getBytes()); 的内容写入 outStream Broken pipe
  • 如果在写入并刷新输出后关闭 inBufferedReader 会怎样?对方可能已经关闭了连接。
  • 什么都没有改变,通过调试执行永远不会到达关闭部分,如果只写outStream.write("HTTP/1.1 200 OK\r\n".getBytes());到outStream然后邮递员开始工作,似乎在这一行之后socket连接断开了,不知道为什么。
【解决方案2】:

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

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

我认为这是 Postman App 的问题,应该修复。但另一方面,这种类型的错误也应该在服务器端进行适当的处​​理,以防止崩溃。如果客户端破坏了发送响应的服务器中间的连接(即使用任何浏览器发出请求、发送非常大的响应或在随后的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();
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-30
    • 2017-05-03
    • 1970-01-01
    相关资源
    最近更新 更多