Chrome并不总是将表单数据发送到我的(自行实施)服务器

时间:2019-06-09 00:25:28

标签: java forms http server

今天早上,我用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--

我在这个新选项卡中进行了更多测试,并且大多数时候我都获得了表单数据,但是我已经看到它没有两次或三次发送邮件正文。表单提交不包含邮件正文时,似乎没有可识别的模式。

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

谢谢!

1 个答案:

答案 0 :(得分:1)

您的代码中有两个问题。

第一个最有可能引起您的问题:

扫描仪不是这里的好选择,因为它不会停止在"\r\n\r\n"上读取InputStream。仅当扫描仪是唯一读取您的InputStream时才起作用的扫描仪,而不是您也想直接阅读它时才起作用。扫描程序将尝试先填充其内部缓冲区,然后在其缓冲区中搜索\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。