Chrome не всегда отправляет данные формы на мой (самостоятельно реализованный) сервер - PullRequest
0 голосов
/ 09 июня 2019

Этим утром я написал собственный сервер на Java.В конце концов, я хочу использовать его в качестве бэкэнда для реактивного приложения, поэтому я работал над реализацией загрузки файлов.Я тестировал это с помощью простой формы HTML, которая отправляет свои данные на мой локальный компьютер.Когда я анализирую заголовки HTTP-запроса, я извлекаю Content-Length секции данных запроса (далее именуемой «телом сообщения» запроса).Иногда тело сообщения HTTP-запроса содержит имя файла и его содержимое, но чаще всего оно пустое, даже если Content-Length и Content-Type (включая границу формы) установлены правильно (ненулевая длина,Граница "--WebKitBoundary ...").Я могу обнаружить это и тайм-аут (и нет, увеличение моего тайм-аута не позволяет мне читать больше данных), но тот факт, что HTTP-запрос указывает на то, что должны быть данные, когда ничего не получено, кажется существенной проблемой.

Пост здесь , кажется, именно то, что я вижу, но на этот пост он не получил ответа.

Это класс, который я использую для чтения данных из InputStreamустановленного соединения Socket:

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 Ответ

1 голос
/ 09 июня 2019

В вашем коде две проблемы.

Первый из них, скорее всего, вызовет вашу проблему:

Сканер здесь не очень хороший выбор , поскольку он не остановит чтение вашего InputStream при "\r\n\r\n". Сканер работает хорошо только тогда, когда он читает только 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 (ссылка) на разумное время ожидания для ваших целей. Я бы взял хотя бы 30 секунд или минуту - вы не знаете, какая сеть находится между клиентом и вашим сервером.

В качестве игрушечного проекта, конечно, интересно написать свой собственный HTTP-сервер. Однако я бы не советовал это для производственного кода. Протокол HTTP в настоящее время довольно сложен, и существует множество хороших HTTP-серверов с открытым исходным кодом, которые вы можете использовать в своем приложении напрямую (Tomcat, Jetty, Undertow и другие все это позволяют) - вам не нужно развертывать WAR файл или что-нибудь в этом роде. Вы можете использовать API сервлета, но если вы хотите, например, использовать масштабируемый асинхронный HTTP, вы можете взглянуть на Netty.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...