Не приходит тело HTTP POST

Написал сервер:

def process_client(client, client_addr):
    received_string = client.recv(4096).decode()
    request_string_splitted = received_string.split("\r\n")[0].split(" ")
    request_method = request_string_splitted[0]
    if request_method != "GET" and request_method != "POST":
        answer = "HTTP/1.1 405 Method not allowed\r\n\r\nOnly GET or POST methods are allowed!"
        client.send(answer.encode())
        return


if __name__ == '__main__':
    try:
        port = 0

        # noinspection PyBroadException
        try:
            port = int(input("Enter a server port number (leave it empty for 5556):"))
        except Exception:
            port = 5556

        server = socket.socket()
        server.bind(('', port))
        server.listen()

        while True:
            client, client_addr = server.accept()
            print(f"Client {client_addr} is connected")
            process_client(client, client_addr)
            client.close()
            print(f"Client {client_addr} is disconnected")
    except Exception as ex:
        print(ex)

Теперь посылаю POST-запрос из C#:


        static void Main(string[] args)
        {
            try
            {
                string url = "http://127.0.0.1:5556/api";
                string body = "{}";

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

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

                    Stream requestStream = httpWebRequest.GetRequestStream();
                    using (StreamWriter streamWriter = new StreamWriter(requestStream))
                    {
                        streamWriter.Write(body);
                    }
                }

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

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

            Console.ReadLine();
        }

Приходит вот что:

А где тело-то? :thinking: Тело не приходит :man_shrugging: Видимо, это как-то связано с заголовком Expect: 100-continue. Но откуда и зачем он берётся-то?
Из Postmanа нормально приходит.

Если написать

   httpWebRequest.ServicePoint.Expect100Continue = false;

тело всё-равно не приходит :man_shrugging:

streamWriter ты закрыл при помощи using, а requestStream нет. Его надо или тоже в using или руками

Вот мой пруф: HttpWebRequest.GetRequestStream Method (System.Net) | Microsoft Learn

You must call the Stream.Close method to close the stream and release the connection for reuse.

С другой стороны
« In C#, when you close a StreamWriter, it also closes the underlying stream. This is because StreamWriter is a wrapper around the underlying stream, and it’s designed to manage the stream’s lifetime.
When you call Close() or Dispose() on a StreamWriter, it will automatically close the underlying stream as well.»

Но я бы попробовал.

Почему у тебя

streamWriter.Write(body);

а не bodyBytes ?

И в примере от микрософта кодировка ASCII, а не UTF-8. Тут не знаю, надо думать.

httpWebRequest.ContentType = "text/html; charset=utf-8";

Твоя проблема не в питоне, а в сишарпе (это я к тому, что тег у темы неправильный).
Тело не не приходит, а не уходит.
Можешь поставить себе wireshark, чтобы всё внимательно рассмотреть. Раньше ещё Fiddler был, но я им давнооо не пользовался.

Переписал:


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

                    using (Stream requestStream = httpWebRequest.GetRequestStream())
                    {
                        using (StreamWriter streamWriter = new StreamWriter(requestStream))
                        {
                            streamWriter.Write(body);
                        }
                    }
                }

Не вижу тела :man_shrugging:

Не помню. Код давно писал. По-другому не получалось, вроде. Но на другие серверы таким образом запрос уходит и всё работает. Если бы не работало, тогда бы и скачивалка с ютуба не работала :man_shrugging: Работает, видимо, потому что тело текстовое.

Но на другие серверы оно прекрасно уходит. Проблему заметил я только сейчас.

Заменил на bodyBytes. Ничего не поменялось :man_shrugging:

«To have a server check the request’s headers, a client must send Expect: 100-continue as a header in its initial request and receive a 100 Continue status code in response before sending the body.»

Предлагают перед модификацией ServicePoint написать

«try setting the proxy to an empty WebProxy, ie:

request.Proxy = new WebProxy();

This should create an empty proxy.»

А так:

httpWebRequest.Proxy = new WebProxy();
httpWebRequest.ServicePoint.Expect100Continue = false;

?

«Изменение значения этого свойства не влияет на существующие подключения. Затрагиваются только новые подключения, созданные после изменения.»

Проблему заметил я только сейчас.

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

Там ещё глобальная настройка есть. Чему она равна?

Various sources around the internet suggest that this can be disabled as follows:

System.Net.ServicePointManager.Expect100Continue = false;

«Поведение 100-Continue не используется для http-запросов 1.0»

Можно ещё протокол задаунгрейдить с 1.1 до 1.0.

Гипотеза 2: предыдущие сервисы не проверяли тело.
Поэтому попробовать прочитать рекомендуемый RFC и сделать в два запроса.

Поведение Expect 100-Continue полностью описано в разделе IETF RFC 2616, раздел 10.1.1.

Гипотеза 3: Может C#-повый код и всё правильно отправляет. А вот питоновый сервер не отрабатывает протокол с этим заголовком Expect.

Оффтоп лютый, но я тут потестил и обнаружил странную штуку.
Для теста я написал сервер на C#:

        static void Main(string[] args)
        {
            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);

                client.Close();
            }

Отправляю на него POST и на строчке Stream requestStream = httpWebRequest.GetRequestStream(); клиент падает в экскепшен с ошибкой Базовое соединение закрыто: Соединение было неожиданно закрыто.. Чё за фигня? :thinking: Какое базовое соединение?
А иногда экскепшен происходит на этой строчке: HttpWebResponse response = (HttpWebResponse)httpWebRequest.GetResponse();. С ошибкой Перед вызовом [Begin]GetResponse необходимо записать ContentLength байт в поток запроса..
Какой ещё ContentLength байт? :thinking: Что за байт такой?
Сейчас проверил код скачивалки с твича. Там POST отправляется таким же образом и всё работает. Правда, я её уже 8 месяцев не трогал. Теперь не знаю, продолжит ли это работать после перекомпиляции :man_shrugging: Но в скачивалке ютуба тоже есть POST, а я её недавно пересобирал. Значит, похоже, что дело не в обновлении C#. Ну или я хз :man_shrugging:

Но если этот заголовок отключить, то тела всё-равно не видно :man_shrugging:

А когда (если) закрываешь streamWriter или requestStream - пишет Запрос был прерван: Запрос отменен. :man_shrugging:

Тему и правда надо перенести в C#