如何用内置Python HTTP服务器提供mp3文件
我现在正在尝试用Python来播放MP3文件。问题是我只能播放一次MP3,之后媒体控制就不再响应了,我必须完全重新加载页面才能再次听到MP3。(在Chrome浏览器中测试过)
问题:运行下面的代码,然后在浏览器中输入 http://127.0.0.1/test.mp3,会返回一个MP3文件,但只能在刷新页面后才能重新播放。
注意:
将页面保存为HTML文件并直接用Chrome打开(不通过Python服务器)就不会出现这个问题。
用Apache来提供文件可以解决这个问题,但这太复杂了:我想让这个脚本非常简单,不需要安装Apache。
这是我使用的代码:
import string
import os
import urllib
import socket
# Setup web server import string,cgi,time
import string,cgi,time
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import hashlib
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
try:
# serve mp3 files
if self.path.endswith(".mp3"):
print curdir + sep + self.path
f = open(curdir + sep + self.path, 'rb')
st = os.fstat( f.fileno() )
length = st.st_size
data = f.read()
md5 = hashlib.md5()
md5.update(data)
md5_key = self.headers.getheader('If-None-Match')
if md5_key:
if md5_key[1:-1] == md5.hexdigest():
self.send_response(304)
self.send_header('ETag', '"{0}"'.format(md5.hexdigest()))
self.send_header('Keep-Alive', 'timeout=5, max=100')
self.end_headers()
return
self.send_response(200)
self.send_header('Content-type', 'audio/mpeg')
self.send_header('Content-Length', length )
self.send_header('ETag', '"{0}"'.format(md5.hexdigest()))
self.send_header('Accept-Ranges', 'bytes')
self.send_header('Last-Modified', time.strftime("%a %d %b %Y %H:%M:%S GMT",time.localtime(os.path.getmtime('test.mp3'))))
self.end_headers()
self.wfile.write(data)
f.close()
return
except IOError:
self.send_error(404,'File Not Found: %s' % self.path)
from SocketServer import ThreadingMixIn
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
pass
if __name__ == "__main__":
try:
server = ThreadedHTTPServer(('', 80), MyHandler)
print 'started httpserver...'
server.serve_forever()
except KeyboardInterrupt:
print '^C received, shutting down server'
server.socket.close()
2 个回答
编辑:我写这段内容的时候没意识到Mapadd只是打算在实验室里使用这个。对于他的使用场景,WSGI可能并不是必须的。
如果你愿意把这个当作一个wsgi应用来运行(我建议这样做,因为相比普通的CGI,这样更适合扩展),你可以使用我下面提供的脚本。
我对你的源代码做了一些修改……这个是基于之前的假设来工作的。顺便说一下,你应该花点时间检查一下你的HTML是否符合标准……这样可以帮助你在不同浏览器之间获得更好的兼容性……原来的代码里没有<head>
或<body>
标签……我下面的代码是严格的原型HTML,可以进一步改进。
要运行这个,你只需要在你的命令行中运行Python可执行文件,然后访问机器的IP地址,端口是8080。如果你是为了一个生产网站在做这个,我们应该使用lighttpd或apache来提供文件,但因为这只是为了实验室使用,内嵌的WSGI参考服务器应该就可以了。如果你想在apache或lighttpd上运行,可以替换文件底部的WSGIServer
那一行。
保存为 mp3.py
from webob import Request
import re
import os
import sys
####
#### Run with:
#### twistd -n web --port 8080 --wsgi mp3.mp3_app
_MP3DIV = """<div id="musicHere"></div>"""
_MP3EMBED = """<embed src="mp3/" loop="true" autoplay="false" width="145" height="60"></embed>"""
_HTML = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head></head><body> Hello %s %s</body></html> ''' % (_MP3DIV, _MP3EMBED)
def mp3_html(environ, start_response):
"""This function will be mounted on "/" and refer the browser to the mp3 serving URL."""
start_response('200 OK', [('Content-Type', 'text/html')])
return [_HTML]
def mp3_serve(environ, start_response):
"""Serve the MP3, one chunk at a time with a generator"""
file_path = "/file/path/to/test.mp3"
mimetype = "application/x-mplayer2"
size = os.path.getsize(file_path)
headers = [
("Content-type", mimetype),
("Content-length", str(size)),
]
start_response("200 OK", headers)
return send_file(file_path, size)
def send_file(file_path, size):
BLOCK_SIZE = 4096
fh = open(file_path, 'r')
while True:
block = fh.read(BLOCK_SIZE)
if not block:
fh.close()
break
yield block
def _not_found(environ,start_response):
"""Called if no URL matches."""
start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
return ['Not Found']
def mp3_app(environ,start_response):
"""
The main WSGI application. Dispatch the current request to
the functions andd store the regular expression
captures in the WSGI environment as `mp3app.url_args` so that
the functions from above can access the url placeholders.
If nothing matches call the `not_found` function.
"""
# map urls to functions
urls = [
(r'^$', mp3_html),
(r'mp3/?$', mp3_serve),
]
path = environ.get('PATH_INFO', '').lstrip('/')
for regex, callback in urls:
match = re.search(regex, path)
if match is not None:
# assign http environment variables...
environ['mp3app.url_args'] = match.groups()
return callback(environ, start_response)
return _not_found(environ, start_response)
在bash命令行中运行:twistd -n web --port 8080 --wsgi mp3.mp3_app
,记得在你保存mp3.py的目录下运行(或者把mp3.py放在$PYTHONPATH
的某个地方)。
现在访问外部IP(比如说http://some.ip.local:8080/),它会直接提供mp3文件。
我尝试直接运行你原来的应用,结果没法找到mp3文件,Linux给我报了错……
BaseServer
是单线程的,这意味着它一次只能处理一个连接。如果你想让它能够同时处理多个连接,你需要使用 ForkingMixIn
或者 ThreadingMixIn
。
举个例子,你可以把这一行替换为:
server = HTTPServer(('', 80), MyHandler)
用
from SocketServer import ThreadingMixIn
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
pass
server = ThreadedHTTPServer(('', 80), MyHandler)