【问题标题】:Chrome does not always send form data to my (self-implemented) serverChrome 并不总是将表单数据发送到我的(自行实现的)服务器
【发布时间】:2019-06-09 00:25:28
【问题描述】:

今天早上我用 Java 编写了一个自定义服务器。最终,我想将它用作 react-native 应用程序的后端,因此我致力于实现文件上传。我一直在用一个简单的 HTML 表单对此进行测试,该表单将其数据提交到我的本地机器。在解析 HTTP 请求标头时,我提取请求的数据部分的 Content-Length(以下称为请求的“消息体”)。有时,HTTP 请求的消息体包含文件名和内容,但通常它是空的,即使 Content-Length 和 Content-Type(包括表单边界)设置正确(非零长度, “--WebKitBoundary...” 边界)。我可以检测到这个和超时(不,增加我的超时不会让我读取更多数据),但是 HTTP 请求似乎表明应该有数据时没有收到数据这一事实似乎是一个重大问题。

here 的帖子似乎正是我所看到的,但截至发帖时尚未得到答复。

这是我用来从已建立的 Socket 连接的 InputStream 中读取数据的类:

public class HTTPRequest {

    // full request, HTTP verb + URI, meta-data, message body
    public final String request, requestline, headers, data;

    // regex to find the length of the message body
    private static final Pattern contentlength = Pattern.compile("Content-Length\\s*:\\s*(\\d+)");


    /*
     * when an object is created it reads the entire HTTP request from the stream and sets its constant strings accordingly
     */
    public HTTPRequest(InputStream is) throws Exception {

        /*
         * get everything except the message body
         */
        @SuppressWarnings("resource") // closing the Scanner closes the stream, so suppressing the resource leak warning
        Scanner s = new Scanner(is);
        requestline = s.nextLine();
        s.useDelimiter("\r\n\r\n");
        headers = s.next();

        // get the reported length of the message body, 0 if not present
        Matcher m = contentlength.matcher(headers);
        int length = 0;
        if(m.find()) {
            length = Integer.parseInt(m.group(1));
        }

        // if there is a message body, read it
        if(length > 0) {

            // this will contain the message bytes
            byte[] b = new byte[length];

            // number of bytes read, number of consecutive times I read 0 bytes
            int read = 0;
            int numzeros = 0;

            // read until I've read the entire message
            while(read < length) {

                // read however many bytes are available
                int numread = is.read(b, read, is.available());
                read += numread;

                if(numread == 0) {
                    numzeros++;
                }else {
                    numzeros = 0;
                }

                // timeout after not getting any data for 1 second
                if(numzeros > 100) {
                    break;
                }
                Thread.sleep(10);
            }

            data = new String(b, 0, read);

        // otherwise, no message body
        }else {
            data = "";
        }

        // combine all of the parts of the request
        request = requestline + "\r\n" + headers + "\r\n\r\n" + data;
    }
}

这是我用来上传文件的 HTML:

<head>
</head>
<body>
<form action="http://localhost:54600/api/test/test/uploadFile" method="post" enctype="multipart/form-data">
<input name="name" type="text" />
<input name="upload" type="file" />
<input type="submit" />
</form>
</body>

这是我从 InputStream 中读取的内容:

POST /api/test/test/uploadFile HTTP/1.1
Host: localhost:54600
Connection: keep-alive
Content-Length: 531
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzkLQnlCjBb2a5sOP
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9


除了没有任何消息正文之外,这似乎是正确的。今天早些时候,我发现如果我每次提交表单时都上传一个唯一的文件,我通常会得到一个消息正文(多次提交同一个文件往往没有消息正文,尽管偶尔会有)。现在,我似乎根本无法获得消息正文。

更新,在写完最后一段之后,我决定在另一个标签中进行更多测试。这一次,我得到了一个消息体:

POST /api/test/test/uploadFile HTTP/1.1
Host: localhost:54600
Connection: keep-alive
Content-Length: 1162
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryErwo4zpzcDBuyDo5
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

------WebKitFormBoundaryErwo4zpzcDBuyDo5
Content-Disposition: form-data; name="name"

test_name
------WebKitFormBoundaryErwo4zpzcDBuyDo5
Content-Disposition: form-data; name="upload"; filename="hello_world.o"
Content-Type: application/octet-stream

ELF [... binary data]
------WebKitFormBoundaryErwo4zpzcDBuyDo5--

我在这个新标签中测试了更多,大部分时间我得到了表单数据,但我已经看到它没有发送消息正文两三次。当表单提交不包含消息正文时,似乎没有可辨别的模式。

有人对可能发生的事情有任何想法吗?

谢谢!

【问题讨论】:

    标签: java forms http server


    【解决方案1】:

    你的代码有两个问题。

    第一个最有可能导致您的问题:

    扫描仪在这里不是一个好的选择,因为它不会停止读取您在 "\r\n\r\n" 的 InputStream。扫描仪只有在阅读您的InputStream 时才能正常工作,而不是在您想直接阅读时。 Scanner 将首先尝试填充其内部缓冲区,然后在其缓冲区中搜索\r\n\r\n。所以它总是会超出这个范围。在您的函数的第二部分中,这些字节将不再在 InputStream 中可用。

    所以你不能使用Scanner——你需要直接从InputStream读取,直到你看到\r\n\r\n;只有这样你才能确定你已经正确阅读了请求,而没有阅读任何请求正文。

    第二个问题是使用InputStream.available() 是一种脆弱的数据读取方式。您在很大程度上取决于网络时间,这可能会偏离很多。如果数据很快进入,那么您将等待很长时间,因为每次读取都执行 Thread.sleep。如果它进来的速度不够快,你可能会超时太快。

    一种更可靠的阅读方式是:

    while(read < length) {
        int numread = is.read(b, read, length - read);
        if (numread < 0)
            break;
        read += numread;
    }
    

    然后在传入的 Socket 对象上,您使用 Socket.setSoTimeout (link) 将读取超时设置为您的目的的合理超时。我需要至少 30 秒或一分钟 - 你不知道客户端和服务器之间的网络是什么。

    作为一个玩具项目,编写自己的 HTTP 服务器当然很有趣。但是,我不建议将它用于生产代码。 HTTP 协议现在相当复杂,有很多优秀的开源 HTTP 服务器可以直接在应用程序中使用(Tomcat、Jetty、Undertow 等都允许你这样做)——你不需要部署 WAR文件或类似的东西。您可以使用 servlet API,但是如果您想使用高度可扩展的异步 HTTP,例如,您可以看看 Netty。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-30
      • 2019-04-19
      相关资源
      最近更新 更多