自己写一个web服务器(1)

web服务器

一个运行在物理服务器上的网络服务器,它等待客户端发送的请求。当它收到一个请求,它会生成一个回复并传回到客户端。一个客户端和服务器的通信可以通过HTTP协议实现。

HelloWorld

# coding: utf-8
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()

上面的代码会监听本机的8888端口,并且对于所有的请求都返回一个HTTP Response。

  • 使用tenlent来模拟浏览器发起一个网络请求

    [图片上传失败...(image-bdc02e-1551094101777)]

一个HTTP请求由HTTP方法(GET),路径(/)指向服务器的一个“页面”和协议版本(HTTP/1.1)组成。

一个HTTP响应由 1. 状态行 HTTP/1.1(协议) 200 OK(返回码), 2. 空白行 3. HTTP响应主体(HelloWorld)组成

总地来说就是:

  • web服务器创建一个监听socket持续地接受新的连接。
  • 客户端发起一个TCP连接,然后成功建立连接之后,客户端发出一个HTTP请求给服务器
  • 服务器用HTTP响应来做回复,最后呈现给用户。
  • 建立TCP连接的过程中客户端和服务器都使用了 socket。

WSGI

我们刚刚建立了一个简单的Web服务器,对所有的请求都返回一个HelloWorld。那么,我们应该怎么在刚建立的Web服务器上运行一个Django应用,Flask应用,Torondo应用或者其他框架呢?即:怎么可以在不修改服务器和框架代码的情况下来确保可以在多个框架下运行web服务器呢?

  • 一个Web应用的本质就是:
    • 浏览器发送一个HTTP请求;
    • 服务器收到请求,生成一个HTML文档;
    • 服务器把HTML文档作为HTTP响应的Body发送给浏览器;
    • 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示

所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。

如果要动态地生成 HTML,我们就需要自己去实现上述的步骤:接受HTTP请求、解析HTTP请求、发送HTTP响应等等都需要手动来完成。

然而我们实际上并不关心这些流程是怎么实现的。正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。

  • Python Web Server Gateway Interface(简称 WSGI)

    • WSGI是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口。
    • 简单地说:WSGI就像一座桥梁,一边连着用户应用,一遍连着Web服务器


      WSGI
  • 所有的现代Web框架都已经实现了WSGI接口,可以在不用对代码作任何修改的情况下就能使服务器和特点的web框架协同工作。其他语言中也有类似的接口,例如 Java 的Servlet API。

一个极简的WSGI服务器实现

# coding: utf-8

import socket
import StringIO
import sys


class WSGIServer(object):
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    request_queue_size = 1

    def __init__(self, server_address):
        # Create a listening socket
        # 创建socket,利用socket获取客户端的请求
        self.listen_socket = listen_socket = socket.socket(
            self.address_family,
            self.socket_type
        )
        # Allow to reuse the same address
         # 设置socket的工作模式
        listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # Bind
        listen_socket.bind(server_address)
        # Activate
        listen_socket.listen(self.request_queue_size)
        # Get server host name and port
        host, port = self.listen_socket.getsockname()[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port
        # Return headers set by Web framework/Web application
        self.headers_set = []

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

     #启动WSGI server服务,不停的监听并获取socket数据。
    def serve_forever(self):
        listen_socket = self.listen_socket
        while True:
            # New client connection
            self.client_connection, client_address = listen_socket.accept()
            # Handle one request and close the client connection. Then
            # loop over to wait for another client connection
            self.handle_one_request()

    def handle_one_request(self):
        self.request_data = request_data = self.client_connection.recv(1024)
        # Print formatted request data a la 'curl -v'
        print(''.join(
            '< {line}\n'.format(line=line)
            for line in request_data.splitlines()
        ))

        self.parse_request(request_data)

        # Construct environment dictionary using request data
        env = self.get_environ()

        # It's time to call our application callable and get
        # back a result that will become HTTP response body
        #给应用传递两个参数,environ,start_response
        result = self.application(env, self.start_response)

        # Construct a response and send it back to the client
        self.finish_response(result)


    def parse_request(self, text):
        request_line = text.splitlines()[0]
        request_line = request_line.rstrip('\r\n')
        # Break down the request line into components
        (self.request_method,  # GET
         self.path,  # /hello
         self.request_version  # HTTP/1.1
         ) = request_line.split()
    
    # 获取environ数据并设置当前server的工作模式
    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.server_name,
            'SERVER_PORT': str(self.server_port)
        }
        # The following code snippet does not follow PEP8 conventions
        # but it's formatted the way it is for demonstration purposes
        # to emphasize the required variables and their values
        #
        # Required WSGI variables
        # Required CGI variables
        return env

    def start_response(self, status, response_headers, exc_info=None):
        # Add necessary server headers
        server_headers = [
            ('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'),
            ('Server', 'WSGIServer 0.2'),
        ]
        self.headers_set = [status, response_headers + server_headers]
        # To adhere to WSGI specification the start_response must return
        # a 'write' callable. We simplicity's sake we'll ignore that detail
        # for now.
        # return self.finish_response

    def finish_response(self, result):
        try:
            status, response_headers = self.headers_set
            response = 'HTTP/1.1 {status}\r\n'.format(status=status)
            for header in response_headers:
                response += '{0}: {1}\r\n'.format(*header)
            response += '\r\n'
            for data in result:
                response += data
            # Print formatted response data a la 'curl -v'
            print(''.join(
                '> {line}\n'.format(line=line)
                for line in response.splitlines()
            ))
            self.client_connection.sendall(response)
        finally:
            self.client_connection.close()


SERVER_ADDRESS = (HOST, PORT) = '', 8888


def make_server(server_address, application):
    server = WSGIServer(server_address)
    server.set_app(application)
    return server


if __name__ == '__main__':
    if len(sys.argv) < 2:
        sys.exit('Provide a WSGI application object as module:callable')
    app_path = sys.argv[1]
    _module, _application = app_path.split(':')
    module = __import__(_module)
    _application = getattr(module, _application)
    httpd = make_server(SERVER_ADDRESS, _application)
    print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))
    httpd.serve_forever()
  • 各个方法的意义
    • init: wsgi server的初始化操作
    • serve_forever: 可以让wsgi server一直监听客户端请求
    • handle_one_request: 对每一次请求进行参数解析,包括parse_request和get_environ
    • start_response: 传递给application的回调函数,根据PEP333,start_response需要包含status, response_headers, exc_info三个参数。status是http的状态码,如“200 OK”,”404 Not Found”。response_headers是一个(header_name, header_value) 元组的列表,在进行application开发时需要注意这点。exc_info通常不需要,直接设置为None即可。具体的一些参数的解释可以参考协议的详细解释。
    • finish_response: 解析一次请求后,需要关闭socket端口,同时将application返回的data返回至客户端。

flask代码

# coding: utf-8
from flask import Flask
from flask import Response

flask_app = Flask('flaskapp')


@flask_app.route('/hello')
def hello_world():
    return Response(
        'Hello world from Flask!\n',
        mimetype='text/plain'
    )


app = flask_app.wsgi_app # 调用Flask暴露的wsgi_app,提供给wsgi server作为application,而不是直接run(port)
  • 启动服务

    $ python server_2.py flask_app:app
    WSGIServer: Serving HTTP on port 8888 ...
    
  • 用curl获取Flask 应用生成的消息:

    $ curl -v localhost:8888/hello
    *   Trying 127.0.0.1...
    * TCP_NODELAY set
    * Connected to localhost (127.0.0.1) port 8888 (#0)
    > GET /hello HTTP/1.1
    > Host: localhost:8888
    > User-Agent: curl/7.64.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Content-Type: text/plain; charset=utf-8
    < Content-Length: 24
    < Date: Tue, 31 Mar 2015 12:54:48 GMT
    < Server: WSGIServer 0.2
    < 
    Hello world from Flask!
    * Connection #0 to host localhost left intact
    
  • WSGI处理流程

  • 首先,服务器启动然后载入你的web框架/应用提供的‘application’调用

  • 然后,服务器读取请求并解析

  • 然后,根据请求数据创建一个‘environ’ 字典

  • 然后,用‘environ’ 字典调用‘application’,‘start_response’ 做参数同时得到一个响应体

  • 然后,通过‘start_response’ 用‘application’返回的数据和状态及响应头创建一个HTTP响应。

  • 最后,服务器将HTTP响应传回到客户端。

你可能感兴趣的:(自己写一个web服务器(1))