简体   繁体   English

Python快速静态文件服务

[英]Python fast static file serving

What's the fastest way to serve static files in Python? 在Python中提供静态文件的最快方法是什么? I'm looking for something equal or close enough to Nginx's static file serving. 我正在寻找与Nginx的静态文件服务相同或足够接近的东西。

I know of SimpleHTTPServer but not sure if it can handle serving multiple files efficiently and reliably. 我知道SimpleHTTPServer但不确定它是否能够高效可靠地处理多个文件。

Also, I don't mind it being a part of a lib/framework of some sort as long as its lib/framework is lightweight. 另外,我不介意它只是lib /框架的一部分,只要它的lib / framework是轻量级的。

What about FAPWS3 ? 那么FAPWS3呢? One of the selling points: 其中一个卖点:

Static file server 静态文件服务器

FAPWS can be used to serve a huge amount of static file requests. FAPWS可用于提供大量静态文件请求。 With the help of a async database in the backend, you can use FAPWS as your own Amazon S3. 借助后端中的异步数据库,您可以将FAPWS用作您自己的Amazon S3。

I would highly recommend using a 3rd party HTTP server to serve static files. 我强烈建议使用第三方HTTP服务器来提供静态文件。

Servers like nginx are heavily optimized for the task at hand, parallelized and written in fast languages. 像nginx这样的服务器针对手头的任务进行了大量优化,并以快速语言进行并行化和编写。

Python is tied to one processor and interpreted. Python与一个处理器绑定并进行解释。

Original SimpleHTTPServer from python standard library does NOT " handle serving multiple files efficiently and reliably ". 来自python标准库的原始SimpleHTTPServer不“ 有效且可靠地处理多个文件 ”。 For instance, if you are downloading one file from it, another HTTP access to it must be hovering since SimpleHTTPServer.py is a simple singal-thread HTTP server which could only support one connecting simultaneously . 例如,如果您从中下载一个文件,则对它的另一个HTTP访问必须悬停,因为SimpleHTTPServer.py一个简单的单线程HTTP服务器 ,它只能同时支持一个连接

Fortunately, note that SimpleHTTPServer.py use BaseHTTPServer.HTTPServer as handler, which can be wrapped by SocketServer.ForkingMixIn and SocketServer.ThreadingMixIn also from python standard library to support multi-process and multi-thread mode, which could highly enhance simple HTTP server's " efficience and reliability ". 幸运的是,请注意SimpleHTTPServer.py使用BaseHTTPServer.HTTPServer作为处理程序,它可以由SocketServer.ForkingMixInSocketServer.ThreadingMixIn包装,也可以从python标准库中支持多进程和多线程模式,这可以高度增强简单的HTTP服务器的“ 效率和可靠性 “。

According to this idea, a SimpleHTTPServer with multi-thread/multi-process support modified from original one is given as follows: 根据这个想法,具有从原始支持修改的多线程/多进程支持SimpleHTTPServer如下:

$ python2.7 ModifiedSimpleHTTPServer.py
usage: ModifiedSimpleHTTPServer.py [-h] [--pydoc] [--port PORT]
                                   [--type {process,thread}] [--root ROOT]
                                   [--run]

Modified SimpleHTTPServer with MultiThread/MultiProcess and IP bind support.

Original:    https://docs.python.org/2.7/library/simplehttpserver.html
Modified by: vbem@163.com

optional arguments:
  -h, --help            show this help message and exit
  --pydoc               show this module's pydoc

run arguments:

  --port PORT           specify server port (default: 8000)
  --type {process,thread}
                        specify server type (default: 'thread')
  --root ROOT           specify root directory (default: cwd '/home/vbem')
  --run                 run http server foreground

NOTE: stdin for input, stdout for result, stderr for logging

For example, ModifiedSimpleHTTPServer.py --run --root /var/log --type process will run a multi-process HTTP static files server with '/var/log' as its root directory. 例如, ModifiedSimpleHTTPServer.py --run --root /var/log --type process将运行一个多进程HTTP静态文件服务器,其中“/ var / log”作为其根目录。

Modified codes are: 修改后的代码是:

#! /usr/bin/env python2.7
# -*- coding: utf-8 -*-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
r"""Modified SimpleHTTPServer with MultiThread/MultiProcess and IP bind support.

Original:    https://docs.python.org/2.7/library/simplehttpserver.html
Modified by: vbem@163.com
"""

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
import os, sys, pwd, posixpath, BaseHTTPServer, urllib, cgi, shutil, mimetypes, socket, SocketServer, BaseHTTPServer
from cStringIO import StringIO

USERNAME = pwd.getpwuid(os.getuid()).pw_name
HOSTNAME = socket.gethostname()
PORT_DFT = 8000

class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):

    server_version = "SimpleHTTP/0.6"

    def do_GET(self):
        f = self.send_head()
        if f:
            self.copyfile(f, self.wfile)
            f.close()

    def do_HEAD(self):
        f = self.send_head()
        if f:
            f.close()

    def send_head(self):
        path = self.translate_path(self.path)
        f = None
        if os.path.isdir(path):
            if not self.path.endswith('/'):
                self.send_response(301)
                self.send_header("Location", self.path + "/")
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)
        try:
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", ctype)
        fs = os.fstat(f.fileno())
        self.send_header("Content-Length", str(fs[6]))
        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
        self.end_headers()
        return f

    def list_directory(self, path):
        try:
            list = ['..'] + os.listdir(path) # 
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list.sort(key=lambda a: a.lower())
        f = StringIO()
        displaypath = cgi.escape(urllib.unquote(self.path))
        f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write("<html>\n<title>%s %s</title>\n<body>" % (HOSTNAME, displaypath))
        f.write("%s@%s:<strong>%s</strong>\n" % (USERNAME, HOSTNAME, path.rstrip('/')+'/'))
        f.write("<hr>\n<ul>\n")
        for name in list:
            fullname = os.path.join(path, name)
            displayname = linkname = name
            if os.path.isdir(fullname):
                displayname = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                displayname = name + "@"
            f.write('<li><a href="%s">%s</a>\n'
                    % (urllib.quote(linkname), cgi.escape(displayname)))
        f.write("</ul>\n<hr>\n<pre>%s</pre>\n</body>\n</html>\n" % __doc__)
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        encoding = sys.getfilesystemencoding()
        self.send_header("Content-type", "text/html; charset=%s" % encoding)
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def translate_path(self, path):
        path = path.split('?',1)[0]
        path = path.split('#',1)[0]
        path = posixpath.normpath(urllib.unquote(path))
        words = path.split('/')
        words = filter(None, words)
        path = os.getcwd()
        for word in words:
            drive, word = os.path.splitdrive(word)
            head, word = os.path.split(word)
            if word in (os.curdir, os.pardir): continue
            path = os.path.join(path, word)
        return path

    def copyfile(self, source, outputfile):
        shutil.copyfileobj(source, outputfile)

    def guess_type(self, path):
        base, ext = posixpath.splitext(path)
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        ext = ext.lower()
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        else:
            return self.extensions_map['']

    if not mimetypes.inited:
        mimetypes.init()
    extensions_map = mimetypes.types_map.copy()
    extensions_map.update({'': 'text/plain'})

class ProcessedHTTPServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer):
    r"""Handle requests in multi process."""

class ThreadedHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
    r"""Handle requests in a separate thread."""

SERVER_DICT = {
    'thread'    : ThreadedHTTPServer,
    'process'   : ProcessedHTTPServer,
}
SERVER_DFT = 'thread'

def run(sCwd=None, sServer=SERVER_DFT, nPort=PORT_DFT, *lArgs, **dArgs):
    r"""
    """
    sys.stderr.write('start with %r\n' % sys._getframe().f_locals)
    if sCwd is not None:
        os.chdir(sCwd)
    cServer = SERVER_DICT[sServer]
    oHttpd = cServer(("", nPort), SimpleHTTPRequestHandler)
    sys.stderr.write('http://%s:%s/\n' % (HOSTNAME, nPort))
    oHttpd.serve_forever()

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# main

def _main():
    r"""Main.
    """
    import argparse

    oParser = argparse.ArgumentParser(
        description = __doc__,
        formatter_class = argparse.RawTextHelpFormatter,
        epilog = 'NOTE: stdin for input, stdout for result, stderr for logging',
    )
    oParser.add_argument('--pydoc', action='store_true',
        help = "show this module's pydoc",
    )

    oGroupR = oParser.add_argument_group(title='run arguments', description='')
    oGroupR.add_argument('--port', action='store', type=int, default=PORT_DFT,
        help = 'specify server port (default: %(default)r)',
    )
    oGroupR.add_argument('--type', action='store', default=SERVER_DFT, choices=SERVER_DICT.keys(),
        help = 'specify server type (default: %(default)r)',
    )
    oGroupR.add_argument('--root', action='store', default=os.getcwd(),
        help = 'specify root directory (default: cwd %(default)r)',
    )
    oGroupR.add_argument('--run', action='store_true',
        help = '\n'.join((
            'run http server foreground',
    )))

    oArgs = oParser.parse_args()

    if oArgs.pydoc:
        help(os.path.splitext(os.path.basename(__file__))[0])
    elif oArgs.run:
        return run(sCwd=oArgs.root, sServer=oArgs.type, nPort=oArgs.port)
    else:
        oParser.print_help()
        return 1

    return 0

if __name__ == "__main__":
    exit(_main())

Meanwhile, the single python file with only 200 lines may satisfy your " in Python " and " lightweight " demands. 同时,只有200行的单个python文件可以满足您的“ Python ”和“ 轻量级 ”需求。

Last but not least, this ModifiedSimpleHTTPServer.py may be a "killer app" by hand for temporary use, however, Nginx is advised for long term use. 最后但并非最不重要的是,这个ModifiedSimpleHTTPServer.py可能是一个临时使用的“杀手级应用程序”,但建议Nginx长期使用。

If you look for a oneliner you can do the following: 如果您寻找oneliner,您可以执行以下操作:

$> python -m SimpleHTTPServer $> python -m SimpleHTTPServer

This will not fullfil all the task required but worth mentioning that this is the simplest way :-) 这不会满足所有需要的任务,但值得一提的是这最简单的方法:-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM