如何实现一个python的web后端框架

2016-06-03 09:26:42

本博客采用创作共用版权协议, 要求署名、非商业用途和保持一致. 转载本博客文章必须也遵循署名-非商业用途-保持一致的创作共用协议

最近特别忙很久没更博了。。。。那就写一篇还算很干货的吧。

因为最近写了个基于gevent的python的web框架,所以写一篇简单博文,讲讲这个东西的设计。

其实总的来说,虽同是动态语言,不同于ruby方面ruby on rails一枝独秀(我知道还有sinatra),而反观py这边,各个框架各领风骚,大而全的有Django,小而美的有 flask , bottle,要性能的有tornado(虽然这货“啥”都有。。),以及极致简单,被guido叔称赞为最pythonic的webpy, 那么为什么py领域的web框架这个之多呢?

首先要说明的是,py领域的web框架就我的使用体验来说,和ruby on rails相比还是有点差距的,这张图可以说明一下。

pic

可以看出,py领域还是不那么风骚,而且rails都要出5.0 version了,django才刚刚1.9。

但是不否认django是个优秀的框架,不然类似pinterest和instagram这种神牛公司怎么会用它呢?

WSGI


要写一个py的web框架,就必须要理解wsgi.

那么什么是wsgi呢?

这里是官网给他的解释(wsgi是pep 333规范里面)

WSGI is the Web Server Gateway Interface. It is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request.

翻译过来就是,这个东西做了个嫁接层,网络通信基本是基于http协议,而http 协议是基于TCP协议的。

这里好冗长,,,而对于暂时我们就先不管什么TCP协议,在这之上我们使用socket 做网络协议。

上面这段话对我们有用的,总结起来就是,wsgi 包装了socket的东西,使之能与py的application做网络交互(如你的socket网络层可以是c写的,但是你暴露出socket来,我可以对他基于wsgi协议写个py层)

wsgi 详情感兴趣的同学可以看 这里

以上的东西,暂时对我们的框架理解没太大的影响,你可以理解为wsgi是个黑盒子,它暴露出了很多东西,让你你能去操作你要的事情

我们来看看他暴露了什么

在py的标准库里面有一个wsgi叫wsgiref ,我们基于他写个最简单的application:

    from wsgiref.simple_server import make_server

    def simple_app(environ, start_response):
        """Simplest possible application object"""
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        start_response(status, response_headers)
        return ['Hello world!\n']

    app=make_server('127.0.0.1',8000,simple_app)
    app.serve_forever()

好的,这个时候我们跑这个程序,然后在terminal 输入:http http://127.0.0.1:8000

可以看到:

    aljundeMacBook-Pro:~ aljun$ http http://127.0.0.1:8000
    HTTP/1.0 200 OK
    Content-Length: 13
    Content-type: text/plain
    Date: Fri, 03 Jun 2016 08:32:55 GMT
    Server: WSGIServer/0.1 Python/2.7.11

    Hello world!

好的,我们这里看到你的simple_app里面有两个参数,这个是wsgi协议里面规定的两个。

首先是environ这个参数,这个参数包括很多环境参数。

官方对他的解释为:

The environ dictionary is required to contain these CGI environment variables, as defined by the Common Gateway Interface specification

我们更改下刚刚的程序,看看有写什么参数:

    from wsgiref.simple_server import make_server

    def simple_app(environ, start_response):
        """Simplest possible application object"""
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        start_response(status, response_headers)
        for key,value in environ.items():
            print key,value
        return ['Hello world!\n']

    app=make_server('127.0.0.1',8000,simple_app)
    app.serve_forever()

可以得到,我们的environ参数:

    rvm_version 1.26.11 (latest)
    SERVER_PROTOCOL HTTP/1.1
    SERVER_SOFTWARE WSGIServer/0.1 Python/2.7.11
    rvm_path /Users/aljun/.rvm
    TERM_PROGRAM_VERSION 361.1
    REQUEST_METHOD GET
    LOGNAME aljun
    USER aljun
    HOME /Users/aljun
    QUERY_STRING 
    PATH /Users/aljun/anaconda2/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Users/aljun/.rvm/bin
    wsgi.errors <open file '<stderr>', mode 'w' at 0x1002a61e0>
    TERM_PROGRAM Apple_Terminal
    LANG zh_CN.UTF-8
    TERM xterm-256color
    SHELL /bin/bash
    HTTP_CONNECTION keep-alive
    SERVER_NAME 1.0.0.127.in-addr.arpa
    REMOTE_ADDR 127.0.0.1
    SHLVL 1
    PWD /Users/aljun/example
    XPC_FLAGS 0x0
    wsgi.url_scheme http
    _ /Users/aljun/anaconda2/bin/python
    SERVER_PORT 8000
    _system_arch x86_64
    rvm_bin_path /Users/aljun/.rvm/bin
    CONTENT_LENGTH 
    TERM_SESSION_ID 221B75E7-EC58-4152-B581-0CB21C2B1C72
    XPC_SERVICE_NAME 0
    CONTENT_TYPE text/plain
    rvm_prefix /Users/aljun
    SSH_AUTH_SOCK /private/tmp/com.apple.launchd.VOLZRZ7eY6/Listeners
    _system_type Darwin
    wsgi.input <socket._fileobject object at 0x1006cf950>
    Apple_PubSub_Socket_Render /private/tmp/com.apple.launchd.RPrxYoySso/Render
    HTTP_HOST 127.0.0.1:8000
    SCRIPT_NAME 
    wsgi.multithread True
    TMPDIR /var/folders/df/cw8ny49n25vb0z0_0wp3yvt80000gn/T/
    HTTP_ACCEPT */*
    wsgi.version (1, 0)
    HTTP_USER_AGENT HTTPie/0.9.3
    GATEWAY_INTERFACE CGI/1.1
    wsgi.run_once False
    OLDPWD /Users/aljun
    wsgi.multiprocess False
    __CF_USER_TEXT_ENCODING 0x1F5:0x19:0x34
    _system_name OSX
    _system_version 10.11
    wsgi.file_wrapper wsgiref.util.FileWrapper
    REMOTE_HOST 1.0.0.127.in-addr.arpa
    HTTP_ACCEPT_ENCODING gzip, deflate
    PATH_INFO /

可以看到非常多,这里说明一下wsgi.input 就是我们的表单传值

可以看到我们做web通常需要的PATH_INFOHTTP_USER_AGENT,QUERY_STRING,REQUEST_METHOD,SERVER_PROTOCAOL都在里面了

即是说,这个environ参数给我提供了我们要的网络的所有环境参数

而对于start_response这个参数,官方的说明是:

The second parameter passed to the application object is a callable of the form startresponse(status, responseheaders, exc_info=None)

他基本上要两个参数,一个是状态,即是HTTP状态,例如我们这个程序的200 OK,一个是response_header即是我们传给client端的header,例如我们这里加了一个('Content-type', 'text/plain')

最后,是你返回的(return)的东西,可以是html也可以是json等等,这次我们返回了一个hello world

那么,你理解了wsgi在干什么,写一个web框架就真的易如反掌了。

实现一个web框架


    from wsgiref.simple_server import make_server


    class MyApplication():

        def __init__(self, environ, start_response):
            self.start_response = start_response
            self.path = environ['PATH_INFO']
            self.request_method = environ['REQUEST_METHOD']

        def __iter__(self):
            if self.path == '/':
                status = '200 OK'
                response_headers = [('Content-type', 'text/plain')]
                self.start_response(status, response_headers)
                yield "I'm in the home"

            elif self.path == '/aljun':
                status = '200 OK'
                response_headers = [('Content-type', 'text/plain')]
                self.start_response(status, response_headers)
                yield "I'm in the aljun's home,it's pretty"

            else:
                status = '404 NOT FOUND'
                response_headers = [('Content-type', 'text/plain')]
                self.start_response(status, response_headers)
                yield "404 NOT FOUND"

    if __name__ == "__main__":
        app = make_server('127.0.0.1', 8000, MyApplication)
        app.serve_forever()

这样,我们控制了路由

在我们的terminal里面跑:,就可以看到:

    aljundeMacBook-Pro:~ aljun$ http http://127.0.0.1:8000
    HTTP/1.0 200 OK
    Content-type: text/plain
    Date: Fri, 03 Jun 2016 09:15:03 GMT
    Server: WSGIServer/0.1 Python/2.7.11

    I'm in the home

    aljundeMacBook-Pro:~ aljun$ http http://127.0.0.1:8000/aljun
    HTTP/1.0 200 OK
    Content-type: text/plain
    Date: Fri, 03 Jun 2016 09:16:01 GMT
    Server: WSGIServer/0.1 Python/2.7.11

    I'm in the aljun's home,it's pretty

    aljundeMacBook-Pro:~ aljun$ http http://127.0.0.1:8000/www
    HTTP/1.0 404 NOT FOUND
    Content-type: text/plain
    Date: Fri, 03 Jun 2016 09:16:06 GMT
    Server: WSGIServer/0.1 Python/2.7.11

    404 NOT FOUND

不同的路由,做了不同的事情。

“搞个大新闻”


当然了,其实最简单的web框架都应该没有上面的MyApplication简陋。

这里是我做web框架的一些扩大化的架构想法

首先是路由,你可以使用正则表达式(re)去做,另外你最好做成像flask那种很人性化的route系统如(r'/xxx/int:id/string:name') 然后这些值我们都可以获得

接着是你要足够的满足HTTP状态,今天我们只是用了404和200.

然后,http是需要传输东西的,如值,如文件等等,你可以基于request_method 做不同值的RESTful设计。

对于query_string也要做好

header里面有很多状态,甚至是set-cookies这种,对于实现session至关重要的

把这些做好,基本的一个可以做业务的web框架也就渐渐成型了。

因为最近太忙了。这篇文章没有写太多。不好意思啊

最后

打个广告

自己最近写的web 框架

pic

文档在这里 中文文档

安装也很简单

pip install jolla

希望大家多多支持

web开发 返回首页

Designed and built with all the love in the world by the Mr.ALJUN.

@SERVER BY NGINX AND POWER BY DIGITALOCEAN.

© COPYRIGHT BY GAGASALAMER 2015