使用Python套接字接收大HTTP请求

1 投票
2 回答
2395 浏览
提问于 2025-04-16 05:51

我正在使用Python的套接字来接收网页请求和SOAP请求。我的代码是

import socket
svrsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = socket.gethostname();
svrsocket.bind((host,8091))
svrsocket.listen(1)
clientSocket, clientAddress = svrsocket.accept()
message = clientSocket.recv(4096)

不过,我收到的一些SOAP请求非常大,达到65万字节,甚至可能变成几兆字节。为了处理这些大请求,我尝试了

message = ''
while True:
  data = clientSocket.recv(4096)
  if len(data) == 0:
   break;
  message = message + data

但是我在使用Firefox或Safari时,从来没有收到过0字节的数据块,尽管Python套接字的使用说明上说我应该能收到。

我该怎么做才能解决这个问题呢?

2 个回答

1

你好

首先,我想重申一下之前回答的内容

不幸的是,你无法在TCP层面解决这个问题

这确实是事实,你无法做到。不过,你可以在TCP连接的基础上实现一个HTTP解析器。这就是我想在这里探讨的内容。让我们开始吧。

问题和期望结果

现在我们面临的困难是找不到数据流的结束位置。我们原本以为数据流会有一个固定的结束标志,但现在我们知道HTTP并没有定义任何消息的后缀

尽管如此,我们还是要继续前进。

我们现在可以问一个问题:“我们能提前知道消息的长度吗?”答案是:有时候可以!

你看,HTTP/1.1定义了一个叫做Content-Length的头部,正如你所期待的,它确实包含了我们想要的内容长度;但还有另一个东西在暗处:Transfer-Encoding: chunked。除非你真的想了解它,否则我们暂时不讨论它。

解决方案

这里有一个解决方案。你可能一开始不太明白这些函数的作用,但如果你跟着我,我会解释清楚。好了……深呼吸一下。

假设conn是与目标HTTP服务器的连接

...

    rawheaders = recvheaders(conn,end=CRLF)
    headers = dict_headers(io.StringIO(rawheaders))
    l_content = headers['Content-Length']

    #okay. we've got content length by magic

    buffersize = 4096
    while True:
        if l_content <= 0: break

        data = clientSocket.recv(buffersize)
        message += data
        
        l_content -= len(data)

...

如你所见,我们在循环中已经知道了Content-Length的值,记作l_content

在迭代的过程中,我们通过从l_content中减去clientSocket.recv(buff)的长度来跟踪剩余内容。

当我们读取的数据至少与l_content相等时,我们就完成了

if l_content <= 0: break

挫折感

注意:接下来我会给出伪代码,因为代码可能有点复杂

所以现在你可能在问,rawheaders = recvheaders(conn)是什么,
headers = dict_headers(io.StringIO(rawheaders))又是什么,
我们是怎么得到headers['Content-Length']的?

首先,recvheadersHTTP/1.1规范没有定义消息的后缀,但它定义了一些有用的东西:HTTP头部的后缀!这个后缀就是CRLF,也就是\r\n。这意味着当我们读取到CRLF时,就知道头部已经接收完毕。因此我们可以写一个这样的函数:

def recvheaders(sock):
    rawheaders = ''
    until we read crlf:
        rawheaders = sock.recv()
    return rawheaders

接下来,解析头部。

def dict_header(ioheaders:io.StringIO):
    """
    parses an http response into the status-line and headers
    """
    #here I expect ioheaders to be io.StringIO
    #the status line is always the first line
    status = ioheaders.readline().strip()
    headers = {}
    for line in ioheaders:
        item = line.strip()
        if not item:
            break
        //headers look like this 
        //'Header-Name' : 'Value'
        item = item.split(':', 1)
        if len(item) == 2:
            key, value = item
            headers[key] = value
    return status, headers

在这里,我们读取状态行,然后继续遍历每一行,构建[键,值]对,格式为Header: Value,使用:

    item = line.strip()
    item = item.split(':', 1)
    # We do split(':',1) to avoid cases like
    # 'Header' : 'foo:bar' -> ['Header','foo','bar']
    # when we want ---------> ['Header','foo:bar']
    

然后我们把这个列表添加到headers字典中

    #unpacking
    #key = item[0], value = item[1]
    key, value = item
    header[key] = value

好了,我们已经创建了一个头部的映射

从这里,headers['Content-Length']就可以直接得到了。

所以,

只要你能保证总是接收到Content-Length,这个结构就能正常工作。如果你能看到这里,哇,感谢你花时间阅读,希望这对你有帮助!

总结一下;如果你想知道HTTP消息的长度,使用套接字的话,就写一个HTTP解析器。

1

很遗憾,你无法在TCP层面上解决这个问题——HTTP自己定义了连接管理的方式,具体可以参考RFC 2616。这基本上意味着你需要解析数据流(至少是头部信息),才能判断什么时候可以关闭连接。

你可以查看相关的问题,这里有个链接 - https://stackoverflow.com/search?q=http+connection

撰写回答