使用Python套接字接收大HTTP请求
我正在使用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 个回答
你好
首先,我想重申一下之前回答的内容
不幸的是,你无法在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']
的?
首先,recvheaders
。HTTP/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解析器。
很遗憾,你无法在TCP层面上解决这个问题——HTTP自己定义了连接管理的方式,具体可以参考RFC 2616。这基本上意味着你需要解析数据流(至少是头部信息),才能判断什么时候可以关闭连接。
你可以查看相关的问题,这里有个链接 - https://stackoverflow.com/search?q=http+connection