• python 实现web框架simfish


    python 实现web框架simfish

    本文主要记录本人利用python实现web框架simfish的过程。源码github地址:simfish

    WSGI HTTP Server

    wsgi模块提供了简单的simple_server,

    wsgiref.simple_server.make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler)

    官方提供的例子,

    from wsgiref.simple_server import make_server, demo_app
    
    httpd = make_server('', 8000, demo_app)
    print "Serving HTTP on port 8000..."
    
    # Respond to requests until process is killed
    httpd.serve_forever()
    
    # Alternative: serve one request, then exit
    httpd.handle_request()

    访问http://127.0.0.1:8000来检查是否正常运行。

    因此,有了wsgi的帮助,我们只需要实现我们自己的demo_app了。

    demo_app

    demo_app接受两个参数environ和start_response,其中environ包含包含所有cgi和wsgi的参数变量,start_response是一个函数,参数为status和headers,返回结果为列表或__iter__的可迭代实例。

    def demo_app(environ,start_response):
        from StringIO import StringIO
        stdout = StringIO()
        print >>stdout, "Hello world!"
        print >>stdout
        h = environ.items(); h.sort()
        for k,v in h:
            print >>stdout, k,'=', repr(v)
        start_response("200 OK", [('Content-Type','text/plain')])
        return [stdout.getvalue()]

    实现自己的hello_app,替换demo_app即可。

    def hello_app(environ, start_response):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        start_response(status, response_headers)
        return ['Hello world!
    ']

    更多的基础细节请参考:http://www.tuicool.com/articles/aYBRBz

    simfish的实现

    web框架其实并不复杂,仅仅负责请求的分发和处理,让web后台开发变得简单、规范的一种方法。

    本框架主要参考了bottle和webpy的源码。

    路由

    使用过web框架的人对路由并不陌生,路由就是用来记录url和callback function的映射关系。

    在simfish中实现了三种添加路由的方法(装饰器、一次加载、随时添加),源码如下(具体内容参考注释):

    # Route
    class Routes:
        """FrameWork Routes"""
        ROUTES = {} #存储所有的url到callback function的映射
    
        @classmethod
        def add(cls, url, handler): 
            """add route and handler to ROUTES"""
            if not url.startswith('/'):
                url = '/' + url
            if re.match(r'^/(w+/)*w*$', url): #这里需要使用re模块来获取正确的url
                cls.ROUTES[url] = handler
    
        @classmethod
        def match(cls, url): #用来寻找url对象的处理函数
            """match url in ROUTES"""
            if not url:
                return None
            url = url.strip()
            route = cls.ROUTES.get(url,None) #从ROUTES中查找结果
            return route
    
        @classmethod
        def load_urls(cls, urls): #用于类似webpy中urls加载的方式
            for item in urls:
                cls.add(item[0], item[1])
    
    
    def route(url, **kargs): #这是一个装饰器,@route('/')
        """Decorator for request handler. Same as Routes.route(url, handler)."""
        def wrapper(handler):
            Routes.add(url, handler, **kargs)
            return handler
        return wrapper

    具体使用方法参考:routing

    封装request

    在demo_app中的参数environ(它是一个字典啊!!!)中包含了request中需要的所有信息,那么我们需要把environ添加到request类中,

    class Request(threading.local):
        """Represents a single request using thread-local namespace"""
        def bind(self, environ):
            """Bind the enviroment"""
            self._environ = environ

    添加获取请求方方法的方法,使用@propery让method方法可以直接调用,注意保持方法的大写(GET/POST)

    @property
    def method(self):
        """Returns the request method (GET,POST,PUT,DELETE,...)"""
        return self._environ.get('REQUEST_METHOD', 'GET').upper()

    如果获取请求参数呢?在django中使用如下的方法,

    request.GET.get('param', '')
    request.POST.get('param', '')

    那么,我们需要把get和post的参数全部添加到一个字典中,在environ中"QUERY_STRING"包含了get的所有参数,而post的参数需要通过"wsgi.input"获取。

    @property
    def GET(self):
        """Returns a dict with GET parameters."""
        if self._GET is None:
            raw_dict = parse_qs(self.query_string, keep_blank_values=1)
            self._GET = {}
            for key, value in raw_dict.items():
                if len(value) == 1:
                    self._GET[key] = value[0]
                else:
                    self._GET[key] = value
        return self._GET

    其中,parse_qs是解析get参数的,推荐使用urlparse.parse_qs 和 urlparse.parse_qsl,目前cgi的已经废弃但保留是为了向后兼容。

    与get请求不同之处,在post请求中需要调用cgi模块的FieldStorage来解析post请求参数。

    raw_data = cgi.FieldStorage(fp=self._environ['wsgi.input'], environ=self._environ)

    具体参考源码:simfish.py

    封装response

    在这里主要实现了response-header和response-status的封装。

    class Response(threading.local):
        """Represents a single response using thread-local namespace."""
        def bind(self):
            """Clears old data and creates a brand new Response object"""
            self.status = 200
            self.header = HeaderDict() #继承dict的header类
            self.header['Content-type'] = 'text/plain'

    继承自threading.local可以保证每个每个线程拥有自己的request和response,并不会相互影响。

    实现template

    这里使用bottle默认的模板,使用方法参考:template

    发送文件

    文件的发送与普通的string返回并不相同。首先,需要判断文件的权限,

    if not filename.startswith(root):
            response.status = 401
            return "Access denied."
        if not os.path.exists(filename) or not os.path.isfile(filename):
            response.status = 404
            return "File does not exist."
        if not os.access(filename, os.R_OK):
            response.status = 401
            return "You do not have permission to access this file."

    获取文件的类型,这里需要使用mimetypes模块,

    if mimetype:
        response.header['Content-type'] = mimetype
    elif guessmime:
        guess = mimetypes.guess_type(filename)[0]
        if guess:
            response.header['Content-type'] = guess

    最后返回文件对象

    if mimetype == 'application/octet-stream' and "Content-Disposition" not in response.header:
        response.header["Content-Disposition"] = "attachment;filename=%s"%name
    elif 'Last-Modified' not in response.header:
        ts = time.gmtime(stats.st_mtime)
        ts = time.strftime("%a, %d %b %Y %H:%M:%S +0000", ts)
        response.header["Content-Length"] = stats.st_size
        response.header['Last-Modified'] = ts
    return open(filename, 'r')

    跳转

    # Redirect to another url
    def redirect(url, code=307):
        """ Aborts execution and causes a 307 redirect """
        response.status = code
        response.header['Location'] = url
        raise SimFishException("")

    异常处理

    # Exceptions
    class SimFishException(Exception):
        """A base class for exception"""
        pass
    
    class HTTPError(SimFishException):
        """Jump out to error handler"""
        def __init__(self, status, text):
            self.output = text
            self.http_status = status
    
        def __str__(self):
            return self.output
    
    class BreakSimFish(SimFishException):
        """Jump out of execution"""
        def __init__(self, text):
            self.output = text

    路由分发

    在上面已经有了如何添加路由,接下来就要实现路由的分发。

    class Simfish:
        def __init__(self, environ, start_response):
            self.environ = environ
            self.start = start_response
            request.bind(environ) #绑定request和response
            response.bind()
    
        def __iter__(self):
            path = request.path
            handler = Routes.match(path) #Routes类中的match方法,获取处理的callback function
            result = ""
            if not handler:
                response.status = 404
                result = "not Found"
            else:
                try:
                    result = handler(request)
                except SimFishException,output: #捕获异常情况
                    result = output
            if isinstance(result, tuple) and len(result) == 2: #返回(response_string, mimetype),自定义返回类型
                response.header['Content-type'] = result[1]
                result = result[0]
            status = '%d %s' % (response.status, HTTP_CODES[response.status]) #获取返回status
            self.start(status, list(response.header.items())) #调用start_response
            
            if hasattr(result, 'read'): #用于返回文件
                if 'wsgi.file_wrapper' in self.environ:
                    return self.environ['wsgi.file_wrapper'](result)
                else:
                    return iter(lambda: result.read(8192), '')
                return iter(lambda: result.read(8192), '')
            elif isinstance(result, basestring):
                return iter([result])
            else:
                return iter(result)

    实例

    #!/usr/bin/env python
    # encoding:utf8
    
    from simfish import application, route
    
    @route('/')
    def hello(request):
        return "hello world"
    
    app = application(port=8086)
    app.run()

    参考

    http://www.tuicool.com/articles/aYBRBz

    http://www.bottlepy.org/docs/dev/index.html

    http://webpy.org/

  • 相关阅读:
    用户价值和RFM模型
    产品生命周期(Product Life Circle,PLC)
    金字塔原理(Pyramid Principle)
    docker 技术
    网易实战+scrapy-redis配置
    uiautomator工具使用(7)
    adb命令行工具(6)
    Android 开发工具安装(5)
    appium 移动端自动化测试工具(4)
    mitmdump 详解(3)
  • 原文地址:https://www.cnblogs.com/coder2012/p/4457473.html
Copyright © 2020-2023  润新知