成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

自己寫一個(gè) wsgi 服務(wù)器運(yùn)行 Django 、Tornado 等框架應(yīng)用

lunaticf / 801人閱讀

摘要:要使用協(xié)議我們不可能自己實(shí)現(xiàn)一個(gè),現(xiàn)在比較流行的解決方案就是使用套接字編程,已經(jīng)幫我們實(shí)現(xiàn)了協(xié)議的細(xì)節(jié),我們可以直接拿來使用不用關(guān)心細(xì)節(jié)。

前幾天寫了 淺談cgi、wsgi、uwsgi 與 uWSGI 等一些 python web 開發(fā)中遇到的一些名詞的理解,今天博主就根據(jù) wsgi 標(biāo)準(zhǔn)實(shí)現(xiàn)一個(gè) web server,并嘗試用它來跑 Django、tornado 框架的 app。

編寫一個(gè)簡(jiǎn)單的 http server

在實(shí)現(xiàn) wsgi server 之前我們先要做一些準(zhǔn)備工作。首先,http server 使用 http 協(xié)議,而 http 協(xié)議封裝在 tcp 協(xié)議中,所以要建立一個(gè) http server 我們先要建立一個(gè) tcp server。要使用 tcp 協(xié)議我們不可能自己實(shí)現(xiàn)一個(gè),現(xiàn)在比較流行的解決方案就是使用 socket 套接字編程, socket 已經(jīng)幫我們實(shí)現(xiàn)了 tcp 協(xié)議的細(xì)節(jié),我們可以直接拿來使用不用關(guān)心細(xì)節(jié)。 socket 編程是語言無關(guān)的,不管是以前博主用 MFC 寫聊天室還是用 C# 寫網(wǎng)絡(luò)延遲計(jì)算還是現(xiàn)在寫 http server,它的使用流程都是一樣的:

server

初始化 socket;

綁定套接字到端口(bind);

監(jiān)聽端口(listen);

接受連接請(qǐng)求(accept);

通信(send/recv);

關(guān)閉連接(close);

client

初始化 socket;

發(fā)出連接請(qǐng)求(connect);

通信(send/recv);

關(guān)閉連接(close);

server 的具體實(shí)現(xiàn):

# coding: utf-8
# server.py

import socket

HOST, PORT = "", 8888
# 初始化
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定
listen_socket.bind((HOST, PORT))
# 監(jiān)聽
listen_socket.listen(1)
print "Serving HTTP on port %s ..." % PORT
while True:
    # 接受請(qǐng)求
    client_connection, client_address = listen_socket.accept()
    # 通信
    request = client_connection.recv(1024)
    print request
 
    http_response = """
HTTP/1.1 200 OK
 
Hello, World!
"""
    client_connection.sendall(http_response)
    # 關(guān)閉連接
    client_connection.close()

而 client 不需要我們自己實(shí)現(xiàn),我們的瀏覽器就是一個(gè) client ,現(xiàn)在運(yùn)行python server.py,然后在瀏覽器中打開 localhost:8888即可看到瀏覽器中顯示 hello world!,這么快就實(shí)現(xiàn)了一個(gè) http server 有木有 hin 激動(dòng)!

然而想要 Django 這類框架的 app 在我們寫的 http server 中運(yùn)行起來還遠(yuǎn)遠(yuǎn)不夠,現(xiàn)在我們就需要引入 wsgi 規(guī)范,根據(jù)這個(gè)規(guī)范我們就可以讓自己的 server 也能運(yùn)行這些框架的 app啦。

編寫一個(gè)標(biāo)準(zhǔn)的 wsgi server

首先,我們要看官方文檔里 wsgi 的解釋:PEP 3333
嗯,就是一篇很長(zhǎng)的英語閱讀理解,大概意思就是如果你想讓你的服務(wù)器和應(yīng)用程序一起好好工作,你要遵循這個(gè)標(biāo)準(zhǔn)來寫你的 web app 和 web server:

server--middleware--application

application

application 是一個(gè)接受接受兩個(gè)參數(shù)environ, start_response的標(biāo)準(zhǔn) wsgi app:

environ:          一個(gè)包含請(qǐng)求信息及環(huán)境信息的字典,server 端會(huì)詳細(xì)說明
start_response:   一個(gè)接受兩個(gè)參數(shù)`status, response_headers`的方法:
status:           返回狀態(tài)碼,如http 200、404等
response_headers: 返回信息頭部列表

具體實(shí)現(xiàn):

def application(environ, start_response):
    status = "200 OK"
    response_headers = [("Content-Type", "text/plain")]
    start_response(status, response_headers)
    return ["Hello world"]

這樣一個(gè)標(biāo)準(zhǔn)的 wsgi app 就寫好了,雖然這看上去和我們寫的 Django app、 tornado app 大相徑庭,但實(shí)際上這些 app 都會(huì)經(jīng)過相應(yīng)的處理來適配 wsgi 標(biāo)準(zhǔn),這個(gè)之后會(huì)詳談。

server

wsgi server 的實(shí)現(xiàn)要復(fù)雜一些,所以我先貼自己實(shí)現(xiàn)的 wsgi server 代碼,然后再講解:

# server.py
# coding: utf-8
from __future__ import unicode_literals

import socket
import StringIO
import sys
import datetime


class WSGIServer(object):
    socket_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    request_queue_size = 10

    def __init__(self, address):
        self.socket = socket.socket(self.socket_family, self.socket_type)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(address)
        self.socket.listen(self.request_queue_size)
        host, port = self.socket.getsockname()[:2]
        self.host = host
        self.port = port

    def set_application(self, application):
        self.application = application

    def serve_forever(self):
        while 1:
            self.connection, client_address = self.socket.accept()
            self.handle_request()

    def handle_request(self):
        self.request_data = self.connection.recv(1024)
        self.request_lines = self.request_data.splitlines()
        try:
            self.get_url_parameter()
            env = self.get_environ()
            app_data = self.application(env, self.start_response)
            self.finish_response(app_data)
            print "[{0}] "{1}" {2}".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                           self.request_lines[0], self.status)
        except Exception, e:
            pass

    def get_url_parameter(self):
        self.request_dict = {"Path": self.request_lines[0]}
        for itm in self.request_lines[1:]:
            if ":" in itm:
                self.request_dict[itm.split(":")[0]] = itm.split(":")[1]
        self.request_method, self.path, self.request_version = self.request_dict.get("Path").split()

    def get_environ(self):
        env = {
            "wsgi.version": (1, 0),
            "wsgi.url_scheme": "http",
            "wsgi.input": StringIO.StringIO(self.request_data),
            "wsgi.errors": sys.stderr,
            "wsgi.multithread": False,
            "wsgi.multiprocess": False,
            "wsgi.run_once": False,
            "REQUEST_METHOD": self.request_method,
            "PATH_INFO": self.path,
            "SERVER_NAME": self.host,
            "SERVER_PORT": self.port,
            "USER_AGENT": self.request_dict.get("User-Agent")
        }
        return env

    def start_response(self, status, response_headers):
        headers = [
            ("Date", datetime.datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT")),
            ("Server", "RAPOWSGI0.1"),
        ]
        self.headers = response_headers + headers
        self.status = status

    def finish_response(self, app_data):
        try:
            response = "HTTP/1.1 {status}
".format(status=self.status)
            for header in self.headers:
                response += "{0}: {1}
".format(*header)
            response += "
"
            for data in app_data:
                response += data
            self.connection.sendall(response)
        finally:
            self.connection.close()


if __name__ == "__main__":
    port = 8888
    if len(sys.argv) < 2:
        sys.exit("請(qǐng)?zhí)峁┛捎玫膚sgi應(yīng)用程序, 格式為: 模塊名.應(yīng)用名 端口號(hào)")
    elif len(sys.argv) > 2:
        port = sys.argv[2]


    def generate_server(address, application):
        server = WSGIServer(address)
        server.set_application(TestMiddle(application))
        return server


    app_path = sys.argv[1]
    module, application = app_path.split(".")
    module = __import__(module)
    application = getattr(module, application)
    httpd = generate_server(("", int(port)), application)
    print "RAPOWSGI Server Serving HTTP service on port {0}".format(port)
    print "{0}".format(datetime.datetime.now().
                       strftime("%a, %d %b %Y %H:%M:%S GMT"))
    httpd.serve_forever()

首先我們看 WSGIServer 類__init__方法主要是初始化 socket 與服務(wù)器地址,綁定并監(jiān)聽端口;
其次,serve_forever(self): 持續(xù)運(yùn)行 server;
handle_request(self):處理請(qǐng)求;
最后,finish_response(self, app_data):返回請(qǐng)求響應(yīng)。
再來看__main__里是如何運(yùn)行 WSGIServer的:
獲得地址和端口后先初始化 WSGIServer:server = WSGIServer(address),然后設(shè)置加載的wsgi app:server.set_application(TestMiddle(application)),接著持續(xù)運(yùn)行 server:httpd.serve_forever()
那么根據(jù)以上信息,可以總結(jié)出 wsgi server 應(yīng)該是這樣一個(gè)過程:

初始化,建立套接字,綁定監(jiān)聽端口;

設(shè)置加載的 web app;

開始持續(xù)運(yùn)行 server;

處理訪問請(qǐng)求(在這里可以加入你自己的處理過程,比如我加入了打印訪問信息,字典化訪問頭部信息等功能);

獲取請(qǐng)求信息及環(huán)境信息(get_environ(self));

environ運(yùn)行加載的 web app 得到返回信息;

構(gòu)造返回信息頭部;

返回信息;

只要實(shí)現(xiàn)了以上過程,一個(gè)標(biāo)準(zhǔn)的 wsgi server 就寫好了。仔細(xì)觀察,其實(shí)一個(gè) wsgi server 的重要之處就在于用environ去跑 web app 得到返回結(jié)果這一步,這一步和前面的 application 實(shí)現(xiàn)相輔相成,然后框架和服務(wù)器都根據(jù)這套標(biāo)準(zhǔn),大家就可以愉快的一起工作了。
現(xiàn)在運(yùn)行python server.py app.app 8000, 然后瀏覽器訪問localhost:8000

后端

瀏覽器

到此,我們的 wsgi server 已經(jīng)可以正常運(yùn)行了,這時(shí)我們?cè)賮砜纯?middleware:

middleware

middleware 中間件的作用就是在server 拿到請(qǐng)求數(shù)據(jù)給 application 前如果想做一些處理或者驗(yàn)證等等功能,這時(shí)候 middleware 就派上用場(chǎng)了,當(dāng)然你愿意的話也可以寫在你的 server 里,只是 wsgi 規(guī)范更建議把這些寫在中間件里,下面我來實(shí)現(xiàn)一個(gè)檢查請(qǐng)求"User-Agent"是否為正常瀏覽器,不是就把請(qǐng)求拒絕掉的中間件:

# coding: utf-8
# middleware.py
from __future__ import unicode_literals


class TestMiddle(object):
    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):
        if "postman" in environ.get("USER_AGENT"):
            start_response("403 Not Allowed", [])
            return ["not allowed!"]
        return self.application(environ, start_response)

初始化用來接收 application,然后在__call__方法里寫入處理過程,最后返回 application 這樣我們的中間件就能像函數(shù)一樣被調(diào)用了。

然后引入中間件:

from middleware import TestMiddle

...

server.set_application(TestMiddle(application))

現(xiàn)在重啟 server 然后用 postman 訪問服務(wù)器:

可以看到,中間件起作用了!

接下來,我們?cè)僬務(wù)?Django 和 tornado 對(duì)于 wsgi 的支持:

Django WSGI: Django WSGI application

django 本身的應(yīng)用體系比較復(fù)雜,所以沒有辦法直接拿來用在我們寫的 wsgi server 上,不過 Django 考慮到了這一點(diǎn), 所以提供了 WSGIHandler:

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super(WSGIHandler, self).__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        try:
            request = self.request_class(environ)
        except UnicodeDecodeError:
            logger.warning(
                "Bad Request (UnicodeDecodeError)",
                exc_info=sys.exc_info(),
                extra={
                    "status_code": 400,
                }
            )
            response = http.HttpResponseBadRequest()
        else:
            response = self.get_response(request)

        response._handler_class = self.__class__

        status = "%d %s" % (response.status_code, response.reason_phrase)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append((str("Set-Cookie"), str(c.output(header=""))))
        start_response(force_str(status), response_headers)
        if getattr(response, "file_to_stream", None) is not None and environ.get("wsgi.file_wrapper"):
            response = environ["wsgi.file_wrapper"](response.file_to_stream)
        return response

可以看到,這里 WSGIHandler 一樣使用start_response(force_str(status), response_headers)把 Django app 封裝成了 標(biāo)準(zhǔn) wsgi app ,然后返回 response。

Django WSGI server

Django 同樣也實(shí)現(xiàn)了 wsgi server:

class WSGIServer(simple_server.WSGIServer, object):
    """BaseHTTPServer that implements the Python WSGI protocol"""

    request_queue_size = 10

    def __init__(self, *args, **kwargs):
        if kwargs.pop("ipv6", False):
            self.address_family = socket.AF_INET6
        self.allow_reuse_address = kwargs.pop("allow_reuse_address", True)
        super(WSGIServer, self).__init__(*args, **kwargs)

    def server_bind(self):
        """Override server_bind to store the server name."""
        super(WSGIServer, self).server_bind()
        self.setup_environ()

    def handle_error(self, request, client_address):
        if is_broken_pipe_error():
            logger.info("- Broken pipe from %s
", client_address)
        else:
            super(WSGIServer, self).handle_error(request, client_address)

基本全部繼承于wsgiref.simple_server.WSGIServer:

class WSGIServer(HTTPServer):

    """BaseHTTPServer that implements the Python WSGI protocol"""

    application = None

    def server_bind(self):
        """Override server_bind to store the server name."""
        HTTPServer.server_bind(self)
        self.setup_environ()

    def setup_environ(self):
        # Set up base environment
        env = self.base_environ = {}
        env["SERVER_NAME"] = self.server_name
        env["GATEWAY_INTERFACE"] = "CGI/1.1"
        env["SERVER_PORT"] = str(self.server_port)
        env["REMOTE_HOST"]=""
        env["CONTENT_LENGTH"]=""
        env["SCRIPT_NAME"] = ""

    def get_app(self):
        return self.application

    def set_app(self,application):
        self.application = application

可以看到,和我們實(shí)現(xiàn)的 wsgi server 是差不多的。

Tornado WSGI

tornado 直接從底層用 epoll 自己實(shí)現(xiàn)了 事件池操作、tcp server、http server,所以它是一個(gè)完全不同當(dāng)異步框架,但 tornado 同樣也提供了對(duì) wsgi 對(duì)支持,不過這種情況下就沒辦法用 tornado 異步的特性了。

與其說 tornado 提供了 wsgi 支持,不如說它只是提供了 wsgi 兼容,tornado 提供兩種方式:

WSGIContainer

其他應(yīng)用要在 tornado server 運(yùn)行, tornado 提供 WSGIContainer。
今天這里主要討論 wsgi ,所以這里就不分析 tornado 這部分代碼,之后做 tornado 源碼分析會(huì)再分析這里。

WSGIAdapter

tornado 應(yīng)用要在 wsgi server 上運(yùn)行, tornado 提供 WSGIAdapter:

class WSGIAdapter(object):
    def __init__(self, application):
        if isinstance(application, WSGIApplication):
            self.application = lambda request: web.Application.__call__(
                application, request)
        else:
            self.application = application

    def __call__(self, environ, start_response):
        method = environ["REQUEST_METHOD"]
        uri = urllib_parse.quote(from_wsgi_str(environ.get("SCRIPT_NAME", "")))
        uri += urllib_parse.quote(from_wsgi_str(environ.get("PATH_INFO", "")))
        if environ.get("QUERY_STRING"):
            uri += "?" + environ["QUERY_STRING"]
        headers = httputil.HTTPHeaders()
        if environ.get("CONTENT_TYPE"):
            headers["Content-Type"] = environ["CONTENT_TYPE"]
        if environ.get("CONTENT_LENGTH"):
            headers["Content-Length"] = environ["CONTENT_LENGTH"]
        for key in environ:
            if key.startswith("HTTP_"):
                headers[key[5:].replace("_", "-")] = environ[key]
        if headers.get("Content-Length"):
            body = environ["wsgi.input"].read(
                int(headers["Content-Length"]))
        else:
            body = b""
        protocol = environ["wsgi.url_scheme"]
        remote_ip = environ.get("REMOTE_ADDR", "")
        if environ.get("HTTP_HOST"):
            host = environ["HTTP_HOST"]
        else:
            host = environ["SERVER_NAME"]
        connection = _WSGIConnection(method, start_response,
                                     _WSGIRequestContext(remote_ip, protocol))
        request = httputil.HTTPServerRequest(
            method, uri, "HTTP/1.1", headers=headers, body=body,
            host=host, connection=connection)
        request._parse_body()
        self.application(request)
        if connection._error:
            raise connection._error
        if not connection._finished:
            raise Exception("request did not finish synchronously")
        return connection._write_buffer

可以看到 tornado 也是將自己的應(yīng)用使用前文那個(gè)流程改為標(biāo)準(zhǔn) wsgi app,最后我們來試試讓我們自己的服務(wù)器運(yùn)行 tornado app:

# coding: utf-8
# tornado_wsgi.py

from __future__ import unicode_literals

import datetime
import tornado.web
import tornado.wsgi

from middleware import TestMiddle
from server import WSGIServer


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("this is a tornado wsgi application")


if __name__ == "__main__":
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    wsgi_app = tornado.wsgi.WSGIAdapter(application)
    server = WSGIServer(("", 9090))
    server.set_application(TestMiddle(wsgi_app))
    print "RAPOWSGI Server Serving HTTP service on port {0}".format(9090)
    print "{0}".format(datetime.datetime.now().
                       strftime("%a, %d %b %Y %H:%M:%S GMT"))
    server.serve_forever()

運(yùn)行:python tornado_wsgi.py,打開瀏覽器:localhost:9090,完美運(yùn)行,中間件也運(yùn)行正常:

文中代碼源碼:simple_wsgi_server
參考資料:Let’s Build A Web Server

原文地址

作者:rapospectre

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/37979.html

相關(guān)文章

  • [零基礎(chǔ)學(xué)python]python開發(fā)框架

    摘要:軟件開發(fā)者通常依據(jù)特定的框架實(shí)現(xiàn)更為復(fù)雜的商業(yè)運(yùn)用和業(yè)務(wù)邏輯。所有,做開發(fā),要用一個(gè)框架。的性能是相當(dāng)優(yōu)異的,因?yàn)樗鼛熗浇鉀Q一個(gè)被稱之為問題,就是處理大于或等于一萬的并發(fā)。 One does not live by bread alone,but by every word that comes from the mouth of God --(MATTHEW4:4) 不...

    lucas 評(píng)論0 收藏0
  • Lunar, 一個(gè)Python網(wǎng)絡(luò)框架的實(shí)現(xiàn)

    摘要:核心的幾個(gè)組件模板引擎,框架,請(qǐng)求和應(yīng)答的處理還是有一些難度,但是經(jīng)過一步步的分析和編碼還是能夠完成功能。模板引擎模板引擎是另外一個(gè)比較大和的模塊。 前前后后,大概兩個(gè)月的時(shí)間,lunar這個(gè)項(xiàng)目終于達(dá)到了一個(gè)很高的完整度。 Lunar是一個(gè)Python語言的網(wǎng)絡(luò)框架,類似于Django,F(xiàn)lask,Tornado等當(dāng)下流行的web framework。最初有這個(gè)想法是在大二下學(xué)期,...

    邱勇 評(píng)論0 收藏0
  • 通過demo學(xué)習(xí)OpenStack開發(fā)所需的基礎(chǔ)知識(shí) -- API服務(wù)(1)

    摘要:通過,也就是通過各個(gè)項(xiàng)目提供的來使用各個(gè)服務(wù)的功能。通過使用的方式是由各個(gè)服務(wù)自己實(shí)現(xiàn)的,比如負(fù)責(zé)計(jì)算的項(xiàng)目實(shí)現(xiàn)了計(jì)算相關(guān)的,負(fù)責(zé)認(rèn)證的項(xiàng)目實(shí)現(xiàn)了認(rèn)證和授權(quán)相關(guān)的。的服務(wù)都是使用的方式來部署的。 使用OpenStack服務(wù)的方式 OpenStack項(xiàng)目作為一個(gè)IaaS平臺(tái),提供了三種使用方式: 通過Web界面,也就是通過Dashboard(面板)來使用平臺(tái)上的功能。 通過命令行,也就...

    Jason_Geng 評(píng)論0 收藏0
  • Tornado運(yùn)行WSGI應(yīng)用

    摘要:之前一直很想知道在上是如何運(yùn)行其他應(yīng)用的例如利用可以作為運(yùn)行的服務(wù)器。需要注意的是由于的并發(fā)模型是建立在單線程異步執(zhí)行的基礎(chǔ)上的因此它運(yùn)行個(gè)應(yīng)用比使用多線程的服務(wù)器要弱很多。當(dāng)然這種方式在和在相同進(jìn)程時(shí)有用否則將減少可擴(kuò)展性。 之前一直很想知道,在Tornado上是如何運(yùn)行其他WSGI應(yīng)用的,例如利用Twisted,可以作為Flask、Bottle、Django運(yùn)行的服務(wù)器。近日在查看...

    TalkingData 評(píng)論0 收藏0
  • flask 源碼解析:簡(jiǎn)介

    摘要:簡(jiǎn)介官網(wǎng)上對(duì)它的定位是一個(gè)微開發(fā)框架。另外一個(gè)必須理解的概念是,簡(jiǎn)單來說就是一套和框架應(yīng)用之間的協(xié)議。功能比較豐富,支持解析自動(dòng)防止攻擊繼承變量過濾器流程邏輯支持代碼邏輯集成等等。那么,從下一篇文章,我們就正式開始源碼之旅了 文章屬于作者原創(chuàng),原文發(fā)布在個(gè)人博客。 flask 簡(jiǎn)介 Flask 官網(wǎng)上對(duì)它的定位是一個(gè)微 python web 開發(fā)框架。 Flask is a micro...

    megatron 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<