从套接字读取时,如何检测客户端何时完成请求发送?

3

我正在编写一个http服务器,但在从套接字中读取时遇到了问题。我的问题是来自客户端的inputStream永远不会结束,并且它会一直读取直到客户端关闭。我知道客户端在发送http请求后不会立即关闭与服务器的连接。当客户端发送完所有请求数据(即头部+正文)时,如何退出while循环

while (in.hasNextLine()) {
    String line = in.nextLine();

    if (line.equals("")){ // last line of request header is blank
        break; // quit while loop when last line of header is reached
    } else {
        request = request + line + "\n";
    }
}

在阅读了大家的评论和答案后,我总结出以下内容:

     is = incoming.getInputStream();
            os = incoming.getOutputStream();
            in = new Scanner(is);
            out = new DataOutputStream(os);

            RequestHandler rh = new RequestHandler();
            int length = 0;
            while (in.hasNextLine()) {
                String line = in.nextLine();
                if (line.equals("")) { // last line of request message
                                        // header is a
                                        // blank line
                    break; // quit while loop when last line of header is
                            // reached
                }

                if (line.startsWith("Content-Length: ")) { // get the
                                                            // content-length
                    int index = line.indexOf(':') + 1;
                    String len = line.substring(index).trim();
                    length = Integer.parseInt(len);
                }

                request = request + line + "\n";
            }

             byte[] body = new byte[length];
             int i = 0;
             while (i < length) {
             byte b = in.nextByte();
             body[i] = b;
             i++;
             }

但是,关于按字节读取的问题我还不是很明白。我可以编写代码一直读取到 -1,但如果没有EOF并且客户端没有关闭连接,仍然会卡住。


3
如果是 GET 请求,你可以在获取到 \r\n\r\n 后停止;如果有请求体,则在从请求体中读取了 Content-Length 字节后停止。 - Esailija
1
http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 - sjr
Scanner.nextByte() 根据当前基数进行解析;它不会返回原始字节。你应该使用 DataInputStream 来完成这个任务。是的,我知道它的 readLine() 方法已经被弃用了。 - user207421
3个回答

6

根据您所处理的请求类型,有三种检测流结束的方法:

  1. 如果是 GETHEAD 请求,只需读取 HTTP 头部,如果存在请求正文,则通常会被忽略,因此当遇到 \r\n\r\n 时,就到达了请求(实际上是请求头)的结尾。
  2. 如果是 POST 方法,请读取头部中的 Content-Length,并读取最多 Content-Length 字节。
  3. 如果是 POST 方法且缺少 Content-Length 头部(这是最可能发生的情况),请读取直到返回 -1,这是流结束的信号。

对于所有方法,包括GET、HEAD、PUT、POST、DELETE等,省略(1),只使用(2)和(3)。如果客户端正在进行keepalive,则必须读取所有请求的末尾,否则下一个请求将无法正确获取。 - user207421
嗨,@EJP,感谢提到keepalive。如果没有Content-Length,客户端正在执行keepalive,我如何检测请求的结束? - neevek
这不是一个合法的情况。如果客户端正在执行keep-alive,则必须发送内容长度,正如您在(3)中所述。 - user207421
1
大多数非GET/HEAD请求都可以有消息体,因此仅将#2和#3限制为POST是错误的。另外,"如果客户端正在执行keep-alive,则必须发送content-length"也不正确。如果使用Transfer-Encoding: chunkedContent-Type: multipart/...,则Content-Length是可选的。而且,仅对响应而言,读取到EOF是无效的。请参考RFC 2616第4.4节"消息长度",了解您需要遵循的确切规则,以检测所有情况下的消息结束,无论是否使用keep-alive。 - Remy Lebeau

4

我懂了 :) 感谢大家的评论和答案...

     is = incoming.getInputStream(); // initiating inputStream
            os = incoming.getOutputStream(); // initiating outputStream

            in = new BufferedReader(new InputStreamReader(is)); // initiating
                                                                // bufferReader
            out = new DataOutputStream(os); // initiating DataOutputSteream

            RequestHandler rh = new RequestHandler(); // create a
                                                        // requestHandler
                                                        // object

            String line;
            while ((line = in.readLine()) != null) {
                if (line.equals("")) { // last line of request message
                                        // header is a
                                        // blank line (\r\n\r\n)
                    break; // quit while loop when last line of header is
                            // reached
                }

                // checking line if it has information about Content-Length
                // weather it has message body or not
                if (line.startsWith("Content-Length: ")) { // get the
                                                            // content-length
                    int index = line.indexOf(':') + 1;
                    String len = line.substring(index).trim();
                    length = Integer.parseInt(len);
                }

                request.append(line + "\n"); // append the request
            } // end of while to read headers

            // if there is Message body, go in to this loop
            if (length > 0) {
                int read;
                while ((read = in.read()) != -1) {
                    body.append((char) read);
                    if (body.length() == length)
                        break;
                }
            }

            request.append(body); // adding the body to request

0

我想分享一下我的代码,它运行得非常好:

private static String getClientRequest(Socket client) throws IOException, SocketException {
    System.out.println("Debug: got new client " + client.toString());
    BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));

    StringBuilder requestBuilder = new StringBuilder();
    String line;
    int contectLegnth = 0;
    
    while (!(line = br.readLine()).isBlank()) {
        requestBuilder.append(line + "\r\n");
        if (line.toLowerCase().startsWith("content-length")) {
            contectLegnth = Integer.parseInt(line.split(":")[1].trim());
        }
    }
    

    StringBuilder requestBodyBuilder = new StringBuilder();
    if (contectLegnth > 0) {
        int read;
        while ((read = br.read()) != -1) {
            requestBodyBuilder.append((char) read);
            if (requestBodyBuilder.length() == contectLegnth)
                break;
        }
        requestBuilder.append("\r\n" + requestBodyBuilder);
    }
   
    return requestBuilder.toString();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接