Python发送HTTP响应
我正在尝试写一个非常简单的HTTP服务器,用来发送服务器端正在播放的视频。当一个客户端连接时,我会启动一个叫做get_video的程序(这是个虚构的程序),它会在另一个进程中运行,并把它的输出通过管道传给我们(假设get_video会把视频发送到标准输出)。我使用的是subprocess.Popen()
来实现这个。
import subprocess, socket
def send_video(sock, programme_id):
p = subprocess.Popen(["get_video","--pid",programme_id], stdout=subprocess.PIPE)
sock.send("HTTP/1.1 200 OK\nContent-type: application/octet-stream\n\n")
while True:
chunk = p.stdout.read(1024)
if chunk:
try:
sock.send(chunk)
except Exception:
pass
else:
break
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('',8080))
s.listen(5)
while True:
client, address = s.accept()
data = client.recv(1024)
send_video(client, "123456")
client.close()
if __name__ == "__main__":
main()
这个基本上是可以工作的。如果我用wget http://localhost:8080/blah.mp4
发起一个HTTP请求,整个过程就像预期的那样——视频会被流式传输到客户端的套接字,并且被追加到新文件blah.mp4中。
但是,如果我创建一个虚拟的HTML页面,里面有<a href="http://localhost:8080/blah/mp4">download</a>
的链接,然后尝试在这个链接上选择“另存为...”,get_video程序会被调用两次。第二次是在视频实际发送到客户端的套接字时。这里面肯定有某种缓冲机制在起作用。
注意send_video()
中的try/except
块。我之前在使用虚拟HTML页面的方法时遇到了“broken pipe”(断开的管道)错误,这表明客户端的套接字在写入时并不存在。我把try/except
放在那里是为了尝试忽略这个错误。
我有点困惑。HTTP请求看起来是一样的,我不太确定我的浏览器(Firefox)做了什么不同的事情导致了这个问题。
有什么想法吗?
编辑:
来自wget的请求头:
GET /blah.mp4 HTTP/1.0
User-Agent: Wget/1.12 (linux-gnu)
Accept: */*
Host: 192.168.1.2:8080
Connection: Keep-Alive
来自HTML虚拟页面的请求头:
GET /blah.mp4 HTTP/1.1
Host: 192.168.1.2:8080
User-Agent: Mozilla/5.0 (blah) Gecko/blah Firefox/3.6.16
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Python服务器只报告了每个请求一次,但Wireshark却报告了虚拟HTML链接方法的两个GET请求。我在这里没有看到明显的问题...
1 个回答
我突然意识到,浏览器在点击“另存为...”链接后,会先发送一个GET请求,然后再发送一个FIN, ACK。这就是导致断管错误的原因。当你选择文件保存的位置并在浏览器中点击“确定”按钮时,浏览器会再发一个GET请求。之前发生的事情是,我在检测到断管错误后,仍然卡在那个while True
循环里。这样我一直在尝试从Popen
对象读取数据,但每次都没法成功发送给客户端。等到这个循环结束后,下一次的GET请求就能继续进行,文件传输也就成功了。呼,终于解决了。
简而言之,工作代码如下:
import subprocess, socket
def send_video(sock, programme_id):
p = subprocess.Popen(["get_video","--pid",programme_id], stdout=subprocess.PIPE)
sock.send("HTTP/1.1 200 OK\nContent-type: application/octet-stream\n\n")
while True:
chunk = p.stdout.read(1024)
if chunk:
try:
sock.send(chunk)
except socket.error, e:
sock.close()
break
else:
break
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('',8080))
s.listen(5)
while True:
client, address = s.accept()
data = client.recv(1024)
send_video(client, "123456")
client.close()
if __name__ == "__main__":
main()
谢谢。