Отправить POST-запрос с телом на сервер

Не дождался, пока админы перенесут тему. По-этому, ещё раз обнаглею и пересоздам её сам.
Имею код сервера:

            const int serverPort = 5556;
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, serverPort);
            Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            server.Bind(endPoint);
            server.Listen((int)SocketOptionName.MaxConnections);

            Console.WriteLine($"Server started on port {serverPort}");

            while (true)
            {
                Socket client = server.Accept();
                byte[] buffer = new byte[ushort.MaxValue];
                int bytesRead = client.Receive(buffer, 0, buffer.Length, SocketFlags.None);
                if (bytesRead == 0)
                {
                    Console.WriteLine($"Zero bytes received from {client.RemoteEndPoint}");
                    client.Close();
                    continue;
                }

                string msg = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                Console.WriteLine(msg);
                string answer = "HTTP/1.1 200 OK\r\n\r\n";
                byte[] answerBytes = Encoding.UTF8.GetBytes(answer);
                client.Send(answerBytes);

                client.Close();
            }

Теперь на этот сервер надо отправить POST-запрос с некоторыми заголовками и телом.
Код:

            try
            {
                string url = "http://127.0.0.1:5556/api";
                string body = "{}";

                HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
                httpWebRequest.Method = "POST";
                httpWebRequest.ContentType = "application/json";

                if (!string.IsNullOrEmpty(body))
                {
                    byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
                    httpWebRequest.ContentLength = bodyBytes.Length;

                    using (Stream requestStream = httpWebRequest.GetRequestStream())
                    {
                        requestStream.Write(bodyBytes, 0, bodyBytes.Length);
                    }
                }

                HttpWebResponse response = (HttpWebResponse)httpWebRequest.GetResponse();
                int resultErrorCode = (int)response.StatusCode;

                Console.WriteLine(resultErrorCode);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadLine();
        }

Поскольку сервер мой - заголовки, в теории, можно вообще не отправлять. Но раз уж это HTTP-сервер - они должны быть на месте.
Проблема в том, что запрос приходит на сервер без тела. То есть, все заголовки на месте, а тела нет.
Причём, в числе заголовков появляются ещё два, которые я не отправляю. А именно:

Expect: 100-continue
Connection: keep-alive

Первый можно отключить вот так:

    httpWebRequest.ServicePoint.Expect100Continue = false;

или так:

    System.Net.ServicePointManager.Expect100Continue = false;

Но это не помогает. Тело всё-равно не доходит.
Хотя, если отправлять запрос на другие серверы (например: ютуб, твич) - всё нормально. Они не выдают ошибок и возвращают то что надо. Значит, им тело уходит правильно.
Если отправлять запрос через нативный сокет:

                Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                client.Connect("127.0.0.1", 5556);
                string t = "POST /api HTTP/1.1\r\nContent-Length: 2\r\n\r\n{}";
                byte[] bytes = Encoding.UTF8.GetBytes(t);
                client.Send(bytes);

То всё нормально. На моём сервере тело видно. Но есть проблема. Если писать скачивалку на нативных сокетах, то скачивать с HTTPS будет нельзя. Будут нужны сертификаты и прочая лабуда.
А если отправлять запрос из Postmanа - всё нормально. Тело на месте.
Как я понял, проблема в этих строчках:

                    using (Stream requestStream = httpWebRequest.GetRequestStream())
                    {
                        requestStream.Write(bodyBytes, 0, bodyBytes.Length);
                    }

Здесь тело не хочет добавляться.
И ещё я заметил, что когда клиент выполнил строчку Stream requestStream = httpWebRequest.GetRequestStream(), сервер уже начинает обрабатывать запрос. То есть, он не ждёт, пока клиент отправит ему тело. Может по-этому тела и нет :man_shrugging:
Но почему так-то? :thinking:
p.s. Могу тестовый проект на гитгаб залить.

Если поставить точку останова на сервере на строчку

                int bytesRead = client.Receive(buffer, 0, buffer.Length, SocketFlags.None);

потом выполнить

using (Stream requestStream = httpWebRequest.GetRequestStream())
{
    requestStream.Write(bodyBytes, 0, bodyBytes.Length);
}

а потом сделать шаг дебаггером на сервере, то можно увидеть, что тело таки дошло. Это так же работает с сервером на питоне.
То есть, метод httpWebRequest.GetRequestStream() служит для отправки данных. Получается, что прикрепить тело нужно ещё до его вызова. Но как это сделать? :thinking:

Надо было отредактировать пост и добавить ему тег. Но в любом случае тут все (и нас мало) читают все темы. Неважно где они размещены…

Это не проблема, это поведение по стандарту. Два запроса, два ответа.
А документация MS рекомендует использовать класс WebClient вместо WebRequest, видимо потому что там это автоматизировано.

Нет, не значит. Могут быть другие причины, почему так происходит. Я же советовал поставить wireshark и проверить наверняка, что происходит в точности.

Поставил его на ноутбук. Написал фильтр tcp.port == 5556. Запускаю сервер, потом запускаю клиент. Сервер принимает сообщение от клиента. Но wireshark этого не видит. В списке ничего нет. Я не знаю, что с этим делать :man_shrugging:

Ок, пакеты пошли

и кажется, что уходят только заголовки
Снимок экрана 2024-08-06 120924
Куда именно тут надо ещё смотреть?

Чего в итоге вы хотите добиться? Почему не следуете рекомендациям Microsoft и не применяете класс WebClient ?

Куда именно тут надо ещё смотреть?

Надо отключить

    System.Net.ServicePointManager.Expect100Continue = false;

и

    httpWebRequest.ServicePoint.Expect100Continue = false;

и смотреть, какой будет запрос. (если там не будет этого Expect, то хорошо, и можно пытаться отправить тело).

Если не поможет (expect останется), то читать спецификацию протокола более внимательно.

не уверен, но теперь тело вроде начало уходить


Снимок экрана 2024-08-06 173816
и заголовок expect пропал:
Снимок экрана 2024-08-06 174108
Но на сервере тела всё-равно нет. Что дальше?

А с чего вы взяли, что проблема именно в протоколе? Что это конкретно значит? И что именно нужно искать в спецификации?
Я вот сейчас написал код на C++ + WinAPI. Отправил такой же запрос и всё нормально сработало. Тело пришло, а заголовок Expect - нет. Уверен, что если написать код на JavaScriptе, то оно бы тоже сработало.
И на C# через нативный сокет оно тоже работает (вчера уже проверял). Не работает только с HttpWebRequest.

В очередной раз не пойму вашу логику. Тут уже очевидно, что проблема в HttpWebRequest, а не в протоколе. Похоже, что у него глюк при отправке POST либо на HTTP (без S), либо на localhost. Это дополнительно протестить надо.

Чёт странно, хрень какая-то :thinking: Это как так? Как тогда логику программы реализовывать, с такими выворотами? И сервер как узнает, к какому запросу относится тело без заголовков?
Уже сколько лет заголовки и тело в POSTе одним запросом шлю - и всё нормально. И другие проекты видел, где так же делают.
Покажите мне код, где POST делают двумя запросами. Я попробую разобраться и понять.

Не понял вопроса.

Когда я только начал писать скачивалку, я загуглил, как отправлять запросы. HttpWebRequest показался мне более гибким, чем WebClient. Ну правильно - это же нативка :man_shrugging: Нативка всегда более гибкая, чем обёртка.
Я бы вообще не отказался от нативных сокетов, но с возможностью работы с HTTPS (если бы такие были). Хотя, может я плохо искал :thinking:

теперь тело вроде начало уходить
и заголовок expect пропал

Отлично, полдела сделали, - привели поведение к желаемому.

не уверен, но

А вы впишите в тело что-нибудь по-юникодски (например русскими буквами), да посмотрите, что реально передаётся (какие байты).

на C++ + WinAPI всё нормально сработало. Тело пришло, а заголовок Expect - нет.

Ну вы хотели добиться такого на C#, и добились. Всё же хорошо стало?

После этого стало совсем очевидно, что проблема не в протоколе и не в спецификации, а в C#. Если точнее, то в HttpWebRequestе.

Ну, русские буквы уходят. Теперь это точно. Правда, в Wireshark их не видно (видно только их HEX-байты).
Дальше?

стало совсем очевидно, что проблема не в протоколе и не в спецификации, а в C#. Если точнее, то в HttpWebRequestе.

Мне неясно, в чём проблема. Тело уходит? Уходит. Проблемы нет.

Давайте ещё раз - код с HttpWebRequest и настройками (при которых уходит), и скрин из Wireshark (на котором байты, проверить - правильные они или нет, декодировав вручную).

на C++ + WinAPI … сработало.
на C# через нативный сокет … работает
Не работает только с HttpWebRequest.

Не верится. Но исходники открыты, значит дебужится.

«override BeginGetRequestStream. HttpWebRequest has a private method MakeRequest and in it the Expect100Continue header is added to the request header collection.»

«.Net Core does not support ServicePointManager settings»

request.ProtocolVersion = HttpVersion.Version10;

AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);

«After this switch, handler from .net framework is used. It looks like that SocketsHttpHandler (at least with combination with HttpWebRequest) doesn’t support separated headers and bodies, even when HTTP 1.0 is used.»

« even if you try to remove the Expect header from the _HttpRequestHeaders collection, the header will get added back when the request is actually made.»

Можно ещё попробовать чанкинг запретить.

Тело-то уходит, я не спорю. Но на сервере-то его нет. На сервер приходит только строчка POST /api HTTP/1.1\r\n + заголовки + \r\n\r\n и всё. Никакого тела там нет.
Как я уже тестил ранее, это происходит потому что когда на клиенте выполняется строчка Stream requestStream = httpWebRequest.GetRequestStream() заголовки уже уходят на сервер и начинается обработка. Потом клиент выполняет строчку requestStream.Write(bodyBytes, 0, bodyBytes.Length);, которая, как я понимаю, отправляет тело. Но сервер уже обработал сообщение без тела и никакого тела уже не ждёт. При этом не важно, присутствует ли заголовок Expect или нет.
Это только моё предположение. Я его сделал, исходя из результатов произведённого теста. А что и как там реально происходит - я хз :man_shrugging:
Однако, можно провести дополнительный эксперимент. На сервере можно поставить breakpoint на строчку приёма сообщения. То есть, на recv() или её обёртки в других языках. Потом клиент отправляет заголовки и тело. Потом снимаем breakpoint (вернее, делаем шаг) на сервере и видим, что тело пришло. А без breakpoint’а тела нет.
Где тело, Лебовски?

                string url = "http://127.0.0.1:5556/api";
                string body = "яюэьыщшчцхфутсрпонмлкйизжедгвба";

                HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
                httpWebRequest.Method = "POST";
                httpWebRequest.ServicePoint.Expect100Continue = false;

                if (!string.IsNullOrEmpty(body))
                {
                    httpWebRequest.ContentType = "application/json";
                    byte[] bodyBytes = Encoding.Default.GetBytes(body);
                    httpWebRequest.ContentLength = bodyBytes.Length;

                    using (Stream requestStream = httpWebRequest.GetRequestStream())
                    {
                        requestStream.Write(bodyBytes, 0, bodyBytes.Length);
                    }
                }


Снимок экрана 2024-08-07 114909
Тут видно, что байты правильные. ff это буква я и т.д. в обратном порядке алфавита. Только я ъ пропустил. Переделывать не буду, потому что и так видно, что всё правильно.

Тут видно, что байты правильные.

  1. Encoding.Default в C# на Windows использует кодировку Windows-1251. Буква «я» имеет код 0xFF в этой кодировке.

  2. UTF-8 кодирует русские буквы не так.
    изображение

  3. Ты не показал, что заготовка Expect нет дампе Wireshark

то есть строчку POST /api HTTP/1.1 , JSON (application/json) я вижу, но это неубедительно. Вот раньше ты байты показывал, там было лучше видно.

  1. Напиши, пожалуйста, какой у тебя рантайм, .Net Core это или что?

У меня в предыдущем посте не UTF-8, а обычная однобайтовая. Так нагляднее просто.

Вот и я о том же.

Снимок экрана 2024-08-07 132608
Это из четвёртой строчки в логе.

Так, а тогда что именно надо было показать?

4.7.2 не-Core, вроде. Где это точно посмотреть?

«Чтобы программно запрашивать версию приложения .NET, можно использовать RuntimeInformation.FrameworkDescription»