国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

自己寫一個 wsgi 服務器運行 Django 、Tornado 等框架應用

lunaticf / 794人閱讀

摘要:要使用協議我們不可能自己實現一個,現在比較流行的解決方案就是使用套接字編程,已經幫我們實現了協議的細節,我們可以直接拿來使用不用關心細節。

前幾天寫了 淺談cgi、wsgi、uwsgi 與 uWSGI 等一些 python web 開發中遇到的一些名詞的理解,今天博主就根據 wsgi 標準實現一個 web server,并嘗試用它來跑 Django、tornado 框架的 app。

編寫一個簡單的 http server

在實現 wsgi server 之前我們先要做一些準備工作。首先,http server 使用 http 協議,而 http 協議封裝在 tcp 協議中,所以要建立一個 http server 我們先要建立一個 tcp server。要使用 tcp 協議我們不可能自己實現一個,現在比較流行的解決方案就是使用 socket 套接字編程, socket 已經幫我們實現了 tcp 協議的細節,我們可以直接拿來使用不用關心細節。 socket 編程是語言無關的,不管是以前博主用 MFC 寫聊天室還是用 C# 寫網絡延遲計算還是現在寫 http server,它的使用流程都是一樣的:

server

初始化 socket;

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

監聽端口(listen);

接受連接請求(accept);

通信(send/recv);

關閉連接(close);

client

初始化 socket;

發出連接請求(connect);

通信(send/recv);

關閉連接(close);

server 的具體實現:

# 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))
# 監聽
listen_socket.listen(1)
print "Serving HTTP on port %s ..." % PORT
while True:
    # 接受請求
    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)
    # 關閉連接
    client_connection.close()

而 client 不需要我們自己實現,我們的瀏覽器就是一個 client ,現在運行python server.py,然后在瀏覽器中打開 localhost:8888即可看到瀏覽器中顯示 hello world!,這么快就實現了一個 http server 有木有 hin 激動!

然而想要 Django 這類框架的 app 在我們寫的 http server 中運行起來還遠遠不夠,現在我們就需要引入 wsgi 規范,根據這個規范我們就可以讓自己的 server 也能運行這些框架的 app啦。

編寫一個標準的 wsgi server

首先,我們要看官方文檔里 wsgi 的解釋:PEP 3333
嗯,就是一篇很長的英語閱讀理解,大概意思就是如果你想讓你的服務器和應用程序一起好好工作,你要遵循這個標準來寫你的 web app 和 web server:

server--middleware--application

application

application 是一個接受接受兩個參數environ, start_response的標準 wsgi app:

environ:          一個包含請求信息及環境信息的字典,server 端會詳細說明
start_response:   一個接受兩個參數`status, response_headers`的方法:
status:           返回狀態碼,如http 200、404等
response_headers: 返回信息頭部列表

具體實現:

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

這樣一個標準的 wsgi app 就寫好了,雖然這看上去和我們寫的 Django app、 tornado app 大相徑庭,但實際上這些 app 都會經過相應的處理來適配 wsgi 標準,這個之后會詳談。

server

wsgi server 的實現要復雜一些,所以我先貼自己實現的 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("請提供可用的wsgi應用程序, 格式為: 模塊名.應用名 端口號")
    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 與服務器地址,綁定并監聽端口;
其次,serve_forever(self): 持續運行 server;
handle_request(self):處理請求;
最后,finish_response(self, app_data):返回請求響應。
再來看__main__里是如何運行 WSGIServer的:
獲得地址和端口后先初始化 WSGIServer:server = WSGIServer(address),然后設置加載的wsgi app:server.set_application(TestMiddle(application)),接著持續運行 server:httpd.serve_forever()
那么根據以上信息,可以總結出 wsgi server 應該是這樣一個過程:

初始化,建立套接字,綁定監聽端口;

設置加載的 web app;

開始持續運行 server;

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

獲取請求信息及環境信息(get_environ(self));

environ運行加載的 web app 得到返回信息;

構造返回信息頭部;

返回信息;

只要實現了以上過程,一個標準的 wsgi server 就寫好了。仔細觀察,其實一個 wsgi server 的重要之處就在于用environ去跑 web app 得到返回結果這一步,這一步和前面的 application 實現相輔相成,然后框架和服務器都根據這套標準,大家就可以愉快的一起工作了。
現在運行python server.py app.app 8000, 然后瀏覽器訪問localhost:8000

后端

瀏覽器

到此,我們的 wsgi server 已經可以正常運行了,這時我們再來看看 middleware:

middleware

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

# 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 這樣我們的中間件就能像函數一樣被調用了。

然后引入中間件:

from middleware import TestMiddle

...

server.set_application(TestMiddle(application))

現在重啟 server 然后用 postman 訪問服務器:

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

接下來,我們再談談 Django 和 tornado 對于 wsgi 的支持:

Django WSGI: Django WSGI application

django 本身的應用體系比較復雜,所以沒有辦法直接拿來用在我們寫的 wsgi server 上,不過 Django 考慮到了這一點, 所以提供了 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 封裝成了 標準 wsgi app ,然后返回 response。

Django WSGI server

Django 同樣也實現了 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

可以看到,和我們實現的 wsgi server 是差不多的。

Tornado WSGI

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

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

WSGIContainer

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

WSGIAdapter

tornado 應用要在 wsgi server 上運行, 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 也是將自己的應用使用前文那個流程改為標準 wsgi app,最后我們來試試讓我們自己的服務器運行 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()

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

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

原文地址

作者:rapospectre

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37979.html

相關文章

  • [零基礎學python]python開發框架

    摘要:軟件開發者通常依據特定的框架實現更為復雜的商業運用和業務邏輯。所有,做開發,要用一個框架。的性能是相當優異的,因為它師徒解決一個被稱之為問題,就是處理大于或等于一萬的并發。 One does not live by bread alone,but by every word that comes from the mouth of God --(MATTHEW4:4) 不...

    lucas 評論0 收藏0
  • Lunar, 一個Python網絡框架的實現

    摘要:核心的幾個組件模板引擎,框架,請求和應答的處理還是有一些難度,但是經過一步步的分析和編碼還是能夠完成功能。模板引擎模板引擎是另外一個比較大和的模塊。 前前后后,大概兩個月的時間,lunar這個項目終于達到了一個很高的完整度。 Lunar是一個Python語言的網絡框架,類似于Django,Flask,Tornado等當下流行的web framework。最初有這個想法是在大二下學期,...

    邱勇 評論0 收藏0
  • 通過demo學習OpenStack開發所需的基礎知識 -- API服務(1)

    摘要:通過,也就是通過各個項目提供的來使用各個服務的功能。通過使用的方式是由各個服務自己實現的,比如負責計算的項目實現了計算相關的,負責認證的項目實現了認證和授權相關的。的服務都是使用的方式來部署的。 使用OpenStack服務的方式 OpenStack項目作為一個IaaS平臺,提供了三種使用方式: 通過Web界面,也就是通過Dashboard(面板)來使用平臺上的功能。 通過命令行,也就...

    Jason_Geng 評論0 收藏0
  • Tornado運行WSGI應用

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

    TalkingData 評論0 收藏0
  • flask 源碼解析:簡介

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

    megatron 評論0 收藏0

發表評論

0條評論

lunaticf

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<