Chrome не всегда отправляет данные формы на мой (собственный) сервер

Сегодня утром я написал собственный сервер на 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--

Я протестировал еще кое-что на этой новой вкладке, и большую часть времени я получал данные формы, но уже видел, что он не отправляет тело сообщения два или три раза. Кажется, не существует заметного шаблона, когда отправка формы не включает тело сообщения.

У кого-нибудь есть мысли о том, что может происходить?

Спасибо!

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
269
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

Сканер здесь не лучший выбор, так как он не перестанет читать ваш 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.

Другие вопросы по теме