【问题标题】:Why browser received nothing while curl does? (When sending HTTP header response)为什么浏览器在 curl 时什么也没收到? (发送 HTTP 标头响应时)
【发布时间】:2017-11-04 14:10:22
【问题描述】:

我编写了使用 Sockets 的 Java 程序,以便从客户端接收 http 请求(请注意:一切都发生在 localhost 中),如果选择了 GET,我使用 Socket 库的程序会输出标头响应和 html 正文由客户。让我举个例子:

我在端口 12345 上运行我的服务器,我的源文件由 java 文件和示例 html 文件组成。

客户端,使用网络浏览器输入:http://localhost:12345/path-to-source/File.html

服务器通过 InputStream 获取输入:GET path-to-file/File.html HTTP/1.1

所以它通过 OutputStream 响应 HTTP 状态(200 OK),其他字段,如内容类型、内容长度,然后是正文(html 源代码)。

由于某种原因,它不会向浏览器输出任何内容。另一方面,当我使用终端并输入:

curl -s -I -X GET localhost:12345/path-to-file/File.html

它实际上向客户端显示正确的输出:

HTTP/1.1 200 OK
Host: localhost:12345
User-Agent: curl/7.51.0
Accept: */*
Content-Type: */*
Content-Length: 18900

但由于某种原因,Accept/ 事件,尽管我已经预料到 text/html

所以要清楚,我想要的输出是:

HTTP/1.1 200 OK
Accept: text/html
Content-Type: text/html
Content-Length: 18900
Content: .....

你能看看我的代码并找出我在这做错了什么吗?另外,我不想使用其他库,因为我想了解套接字的工作原理和低级别:

public class WebServerMain {/** This method prints to the output stream (to the client) the necessary fields, like Content type, Content length and etc.
 It only serves GET and HEAD requests and if the file exists (200). **/

    public static void printToClient(int length,BufferedReader in, PrintWriter out) {
        String request;
        String response;
        try {
            while ((request = in.readLine()) != null) {
                out.println(request);
                if (request.split(" ")[0].equals("Accept:")) {
                    response = request.split(" ")[1].split(",")[0];
                    out.print("Content-Type: " + response + "\r\n");
                    out.print("Content-Length: " + length + "\r\n");
                    break;

                }
            }
        } catch(IOException e){
            System.err.println("Usage: java WebServerMain <document_root> <port>");
        }
    }



    public static void main(String args[]) {
        try {
            String cd = "/"+ args[0]; // This is the string that user chooses to pick the directory.
            int port;
            port = Integer.parseInt(args[1]);
            int length = 0; // In order to find the length of the content.
            String request;
            String response;

            final String dir = System.getProperty("user.dir");
            System.out.println("current dir = " + dir+cd);
            // Create a ServerSocket to listen on that port.
            ServerSocket ss = new ServerSocket(port);
            // Now enter an infinite loop, waiting for & handling connections.
            for (;;) {
                // Wait for a client to connect. The method will block;
                // when it returns the socket will be connected to the client
                Socket socket = ss.accept();
                // Get input and outputstreams to talk to the client
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                PrintWriter out = new PrintWriter(socket.getOutputStream());

                String userInput = in.readLine();
                System.out.println(userInput);
                System.out.println("Final dir is " + dir+userInput.split(" ")[1]);
                String str;
                String content = "";
                if(!userInput.split(" ")[0].equals("GET") && !userInput.split(" ")[0].equals("HEAD")){
                    out.println("HTTP/1.1 501 Not Implemented");
                } // Checking, if the user chose correct options, GET or HEAD. If neither, we shall print 501 status code.
                else {
                    try {
                        BufferedReader readContent = new BufferedReader(new FileReader(dir+cd+userInput.split(" ")[1]));
                        while ((str = readContent.readLine()) != null) {
                            content += str;
                        } // If the file exists, we are reading its content to the string called content. String str is a temporary string.
                        length = content.length();
                        out.println(userInput.split(" ")[2] + " 200 OK");
                    } catch (FileNotFoundException f) {
                        out.println(userInput.split(" ")[2] + " 404 Not Found");
                        out.close(); // Flush and close the output stream
                        in.close(); // Close the input stream
                        socket.close(); // Close the socket itself
                    }

                    if (userInput.split(" ")[0].equals("GET")) {
                        printToClient(length, in, out);
                        out.println(content);
                    } else if (userInput.split(" ")[0].equals("HEAD")) {
                        printToClient(length, in, out);
                    }
                }

                // Close socket, breaking the connection to the client, and
                // closing the input and output streams
                out.close(); // Flush and close the output stream
                in.close(); // Close the input stream
                socket.close(); // Close the socket itself
            } // Now loop again, waiting for the next connection
        }
        // If anything goes wrong, print an error message
        catch (IOException e) {
            System.err.println(e.getMessage());
        }
        catch (ArrayIndexOutOfBoundsException a){
            System.err.println("Usage: java WebServerMain <document_root> <port>");
        }
    }
}

【问题讨论】:

    标签: java sockets curl


    【解决方案1】:

    您可以使用开发人员控制台检查 Web 浏览器发送的标头(右键单击 -> 检查,然后转到网络选项卡)。在那里你可以看到正在发送的内容。

    关于您发布的代码,我将重组两部分:

    1) 在循环中,接受连接后,将读取和响应逻辑分支到新线程。

    2) 不要将 in.readln() 分散在代码中,而是在一个地方读取所有内容,然后对其进行解析。

    如果您这样做,将很容易记录您收到的整个请求,并查看您对到达服务器的内容的假设是否正确。

    【讨论】:

    • 有没有办法提取HTTP响应头?
    • 在浏览器中?是的,你得到了这两件事。在该选项卡中,您会收到请求和响应,并且会看到已发送的标头以及已接收的标头。
    • 我不确定如何处理它。我可以看到标题,但我仍然不知道,为什么页面是空白的。
    • 在关闭连接之前添加一个 out.flush() 以确保没有任何东西被缓冲。 (还要做一个 System.out.println 的内容看它实际上有文件的内容)
    【解决方案2】:
    public static void printToClient(...
    ...
                    out.print("Content-Type: " + response + "\r\n");
                    out.print("Content-Length: " + length + "\r\n");
                    break;
    
    public static void main(String args[]) {
    ...
                        printToClient(length, in, out);
                        out.println(content);
    

    在 HTTP 响应标头的末尾似乎没有 \r\n(即空行),这是 HTTP/1.x 中要求的,用于指示 HTTP 标头的结束。这意味着您发送的响应无效。

    除了你处理请求对我来说看起来很奇怪:

    • 客户端不需要发送 Accept 标头(尽管至少浏览器会这样做),但您需要一个。
    • 无论出于何种原因,您都会将所有请求标头(包括 Accept 标头)反映回客户端,即使这些标头在响应中没有任何意义。
    • 您仅使用println 发送状态行,该行仅以\n 结束,而不是所需的\r\n
    • 您的错误响应 (404, 501) 错过了标题顶部末尾的 \r\n,即使后面没有正文也是必需的。

    【讨论】:

    • 对于接受头,我只想获取有关内容类型的信息,例如“text/html”。否则我怎样才能获得它?
    • 您能否澄清一下关于“\r\n”的问题?你的意思是 out.println(content) 还是 out.print("Content-Length: " + length + "\r\n");?
    • @AlisherKassymov:像out.println(userInput.split(" ")[2] + " 200 OK"); 这样的行仅打印\n 作为行尾而不是\r\n。至于 Accept 标头:根据 URL 决定提供哪个资源。服务器可能使用这个optional标头来决定在相同资源以不同格式(即JSON,XML,...)可用的情况下提供什么服务。绝不需要在响应中反映此或其他请求标头。
    • 哦,好的。我认为我的代码有点误导你。我不是试图在响应中显示接受行,我只是在打印以检查 Accept 标头包含的内容,对此感到抱歉,我忘了将其删除。我只是在提取内容类型的接受标头的第一个内容。出于某种原因,curl 给了我 / 但浏览器给了我 text/html。你能告诉我我做错了什么吗?
    • @AlisherKassymov:Accept 标头与 Content-Type 不同。使用 optional Accept 标头,客户端可以指定它期望的内容类型,即*/*(所有)在 curl 的情况下,通常类似于 text/html, application/xhtml+xml, .... 的浏览器如果他们期望HTML 资源。而 Content-Type 则给出了资源的实际类型。
    猜你喜欢
    • 1970-01-01
    • 2020-07-30
    • 2018-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-03
    相关资源
    最近更新 更多