Python SSL - recv смещение, когда данные больше 1400 - PullRequest
0 голосов
/ 02 сентября 2018

Я использую модуль ssl в Python и столкнулся с небольшой проблемой, связанной с буферами.

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

RECV_BUFFER = 131072
def handle(client_socket):
    try:
        rxdata = client_socket.recv(RECV_BUFFER)
        if rxdata:
                print("Rx: " + rxdata.decode())
                while(client_socket.pending()):
                    rxdata = client_socket.recv(RECV_BUFFER)
                    sys.stdout.write(rxdata.decode())
    except Exception as e:
        print("Exception: " + str(e))

Для целей тестирования я настроил пользовательский ввод, чтобы я мог тестировать напрямую. GET / возвращает «Hello World», а GET /other возвращает длинную строку. При каждом переполнении буфера возвращаемые значения смещаются на единицу, как показано ниже.

Command>GET /
Tx: GET /
Rx: HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Cloud-Trace-Context: a65f614b75674fa723b7d69c1af03a0e;o=1
Date: Sun, 02 Sep 2018 16:00:19 GMT
Server: My Frontend
Content-Length: 12

Hello World!
Command>GET /other
Tx: GET /other
Rx: HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Cloud-Trace-Context: 90033f7e308e07508106359c3e7c76d1
Date: Sun, 02 Sep 2018 16:00:23 GMT
Server: My Frontend
Content-Length: 1924

This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. T
Command>GET /
Tx: GET /
Rx: his is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. End.
Command>GET /other
Tx: GET /other
Rx: HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Cloud-Trace-Context: 160b0cd5f80982bf1e7ab7dd5d94996d
Date: Sun, 02 Sep 2018 16:00:26 GMT
Server: My Frontend
Content-Length: 12

Hello World!

Что здесь происходит и как это нужно исправить?

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Это окончательное решение, на котором я остановился. Я чувствую, что это более правильное решение, чем ранее опубликованное. У него также есть опции для удаления заголовков или оставления на месте, указав True в качестве второго аргумента:

def handle(client_socket, raw=False):
    data = client_socket.recv()
    reCL = re.search('Content-Length: (\d+)', data.decode(), re.MULTILINE)
    contentLength = int(reCL.group(1))
    contentLengthEndChar = reCL.end()+4
    dataSize = contentLength 
    if raw == True: dataSize += contentLengthEndChar
    sslRecordPending = math.ceil(dataSize / 16384) - 1 #SSL records left; not used
    socket_active = True
    rxdata = b''
    if raw == True: rxdata = data[:contentLengthEndChar]
    rxdata += data[contentLengthEndChar:]
    while True:
        try:
            if len(rxdata) == dataSize: break
            rxdata += client_socket.recv()
        except socket.timeout:
            break
    return rxdata.decode()
0 голосов
/ 02 сентября 2018

Я не совсем уверен, что вы пытаетесь сделать, но я думаю, что ваш сервер работает в основном так:

  1. Прочитать команду (одна строка).
  2. Отправить полный ответ сразу.

Учитывая, что вы используете pending, который только проверяет, все ли еще есть расшифрованные данные в сокете SSL, я предполагаю, что вы предполагаете, что если данные отправляются сервером в одном sent, то они будут прочитаны клиентом тоже сразу. Но это не так. То, что на самом деле происходит здесь, выглядит примерно так:

  1. Сервер отправляет много суток, скажем, 20000 байт.
  2. На уровне SSL это как минимум две записи SSL, поскольку одна запись может иметь размер только 16384. Таким образом, предположим, что она сделает запись 16384 и запись для остальных (3616 байт).
  3. ssl_socket.revc(RECV_BUFFER) будет по крайней мере считывать столько данных из базового TCP-соединения, сколько ему нужно для полной записи SSL. Затем он расшифрует запись SSL и вернет не более RECV_BUFFER байт дешифрованных данных.
  4. ssl_socket.pending() сообщит вам, если в сокете SSL все еще есть непрочитанные расшифрованные данные. Он не будет проверять, доступны ли данные в базовом сокете TCP. Если в сокете SSL все еще есть данные, следующий ssl_socket.recv(...) вернется из этих данных, но не будет пытаться прочитать больше данных из основного сокета TCP. Только если в сокете SSL больше нет дешифрованных, но непрочитанных данных, recv будет читать больше из базового сокета TCP - но в этом случае pending вернет false, поэтому вы никогда не будете пытаться читать больше данных.

Это означает, что может случиться так, что только первая запись SSL будет прочитана, расшифрована и возвращена в ваш recv. Таким образом, если вы отправите следующую команду, вы не получите новый ответ, но фактически прочитаете оставшиеся данные ответа из предыдущего запроса.

Чтобы исправить код, вам нужно исправить свое предположение: SSL должен рассматриваться как поток данных, а не как протокол сообщений (то же самое для TCP). Это означает, что вы не можете предполагать, что сообщение будет прочитано полностью и что оно будет возвращено полностью или что оно меньше всего уже полностью в объекте SSL. Вместо этого вам нужно либо заранее знать размер ответа (например, добавить в ответ префикс с длиной), либо иметь какой-то четкий маркер окончания ответа и читать его до этого маркера.

...