The previous article didn’t go into details about how bottles handle HTTP requests when reading the source code for bottles, which requires reading the python-HTTP source code first. This week we’ll look at the source code for Python-HTTP and add to that foundation by learning how Python builds HTTP services and responds to HTTP requests. This article will be divided into the following parts:

  • HTTP code structure
  • socket
  • selector
  • socketserver
  • http-server

1. Code structure related to HTTP-server

Python version 3.6.5 or above is used for this code reading. Python environment is required (Windows and MAC systems may have differences in the select section). The code involved is as follows:

file describe
socket.py The socket API
select.py Select. Py is a stub file that provides the underlying implementation of multiplexed asynchronous IO
selectors.py A high-level implementation of SELECT, recommended
socketserver.py Tcpserver/udpServer is implemented by default
http/server.py A simple HTTP service implementation
http/client.py Simple HTTP client

2. socket

Socket part of the basic knowledge, it is recommended to directly check reference link 1, the introduction is very detailed. This article is based on the topic of source reading, first, the creation of the socket object:

class socket(_socket.socket):

    """A subclass of _socket.socket adding the makefile() method."""

    __slots__ = ["__weakref__", "_io_refs", "_closed"]

    def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None):
        _socket.socket.__init__(self, family, type, proto, fileno)
        self._io_refs = 0
        self._closed = False

    def __enter__(self):
        return self

    def __exit__(self, *args):
        if not self._closed:
            self.close()
Copy the code

From the socket class, you can see:

  • __slots__ can optimize an object
  • __enter__ and __exit__ allow socket objects to be used in context
  • Family and type are two very important parameters that determine the resource type of the socket. Family: AF_INET IPV4 protocol, AF_INET6 IPV6 protocol. Type The SOCK_STREAM of TCP and THE SOCK_DGRAM of UPD are available.

There are many apis for sockets, as you can see from socket.pyi or _socket.py. Let’s focus on the apis involved in the TCP Socket Flow shown in the figure below.

All other apis are implemented at the bottom, except accept. The acecept function is run on the server and receives a new incoming socket object to represent the new connection.

def accept(self): Type = self.type & ~globals().get("SOCK_NONBLOCK", "SOCK_NONBLOCK", "SOCK_NONBLOCK", 0) sock = socket(self.family, type, self.proto, Fileno =fd) # Wrap the new socket and return if getDefaultTimeout () is None and self.getTimeout (): sock.setblocking(True) return sock, addrCopy the code

Socket code also provides the SocketIO class. Examples of how to wrap a socket for read and write operations:

class SocketIO(io.RawIOBase): def __init__(self, sock, mode): if mode not in ("r", "w", "rw", "rb", "wb", "rwb"): Raise ValueError("invalid mode: %r" % mode) io.RawIOBase.__init__(self) self._sock = sock if "b" not in mode: _writing = "w" in mode # write def. Self._writing = "w" in mode # write def Readinto (self, b): # readinto(self, b): # readinto(self, b): # readinto(self, b): # readinto(self, b): # readinto(self, b): # readinto(self, b): # readinto(self, b): # readinto(self, b): # Write data to socket return self._sock.send(b)Copy the code

2.1 the socket sample

This is two sets of examples, respectively, to demonstrate the use of socket TCP and UDP protocol to send and receive data.

# tcp-server import socket HOST = '127.0.0.1' PORT = 65432 with socket.socket(family= socket.af_inet, type=socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) s.listen() conn, addr = s.accept() with conn: print('Connected by', addr) while True: data = conn.recv(1024) if not data: break print("recv data", data, len(data)) conn.sendall(data)Copy the code

The TCP-server socket has three procedures: bind, Listen, and Accept. Recv is used to receive data, and sendall is used to send data. Tcp-client needs to connect to the server.

# tcp-client

import socket

HOST = '127.0.0.1'  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as sock:
    sock.connect((HOST, PORT))
    sock.sendall(b'Hello, world')
    data = sock.recv(1024)
    print('Received', repr(data))
Copy the code

TCP example output log:

# tcp-server Connected by ('127.0.0.1', 64203) recv data b'Hello, world' 12 # TCP-client b'Hello, world'Copy the code

There is no listen or accept process for the server and client.

# udp-sever import socket HOST = 'localhost' PORT = 65432 with socket.socket(family=socket.AF_INET, # Bind the socket to the port sock.bind((HOST, port)) while True: Print ("recv data", data, address) if data: sock.sendto(data, # udp-client import socket HOST = '127.0.0.1' # Standard loopback interface address (localhost) PORT = 65432 # Port to listen on (non-privileged ports are > 1023) # Create a UDP socket with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as sock: # Send data sock.sendto(b'Hello, world', (HOST, PORT)) # Receive response data, server = sock.recvfrom(4096) print("recv data", data, server)Copy the code

Udp example logs:

# udp-client recv data b'Hello, world' ('127.0.0.1', 65432)Copy the code

3. selector

In the previous example, the server was only able to handle reads and writes to one connection, not to serve multiple connections at the same time. Select is required to switch between multiple client connections at the same time. The following is from the official Chinese document:

Select (rlist, wList, xlist[, timeout]) This is an intuitive Unix select() system call interface. The first three arguments are a sequence of 'awaitable objects' : either an integer representing the file descriptor, or an object with an invisible parameter method named fileno() that returns such an integer: Rlist: wait until you can start reading wlist: Wait until you can start writing Xlist: Wait for "exception cases" (see the current system's manual for what are called exception cases) to allow empty iterables, but whether to accept three empty iterables depends on the platform. (Known to work on Unix but not on Windows.) The optional timeout argument represents the number of timeout seconds as a floating point number. When the timeout argument is omitted, the function blocks until at least one file descriptor is ready. A timeout value of zero means polling is performed and never blocks. The return values are three lists containing the ready objects, and the three returned lists are subsets of the first three arguments. When the timeout period is up and no file descriptors are ready, three empty lists are returned. Acceptable object types in iterables include Python file objects (such as sys.stdin and those returned by open() or os.popen()), socket objects returned by socket.socket(), and so on. You can also customize a wrapper class, as long as it has the appropriate fileno() method (which actually returns a file descriptor, not just a random integer).Copy the code

We can simply think of SELECT as an event center that manages multiple connections, receives system network calls, and sends different read and write events to notify applications. So the actual application of select is still looking at the high-level API in the selector.

3.1 Implementation of Selectors

Read and write events defined by selectors:

EVENT_READ = (1 << 0)
EVENT_WRITE = (1 << 1)
Copy the code

Define SelectorKey with a named ancestor:

def _fileobj_to_fd(fileobj): if isinstance(fileobj, int): fd = fileobj else: try: Fd = int(fileobj. Fileno ()) = int(fileobj. Fileno ()) raise ValueError("Invalid file object: " "{! r}".format(fileobj)) from None if fd < 0: raise ValueError("Invalid file descriptor: {}".format(fd)) return fd SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data'])Copy the code

BaseSelector is a metaclass that requires all subclasses to implement register,unregister, and select methods:

class BaseSelector(metaclass=ABCMeta):
    @abstractmethod
    def register(self, fileobj, events, data=None):
        raise NotImplementedError
    
    @abstractmethod
    def unregister(self, fileobj):
        raise NotImplementedError
    
    @abstractmethod
    def select(self, timeout=None):
        raise NotImplementedError
    ...
Copy the code

The implementation of Register and Unregister also looks simple, using a dictionary to manage the corresponding SelectorKey object.

class _BaseSelectorImpl(BaseSelector):
    
    def __init__(self):
        # this maps file descriptors to keys
        self._fd_to_key = {}
        # read-only mapping returned by get_map()
        self._map = _SelectorMapping(self)
        
    def register(self, fileobj, events, data=None):

        key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data)

        self._fd_to_key[key.fd] = key
        return key
    
    def unregister(self, fileobj):
        key = self._fd_to_key.pop(self._fileobj_lookup(fileobj))
        return key
Copy the code

Different operating systems have different implementations of SELECT:

class SelectSelector(_BaseSelectorImpl):
    """Select-based selector."""
    
if hasattr(select, 'poll'):

    class PollSelector(_BaseSelectorImpl):
        """Poll-based selector."""
        
if hasattr(select, 'epoll'):

    class EpollSelector(_BaseSelectorImpl):
        """Epoll-based selector."""
        
if hasattr(select, 'devpoll'):

    class DevpollSelector(_BaseSelectorImpl):
        """Solaris /dev/poll selector."""
        
if hasattr(select, 'kqueue'):
    class KqueueSelector(_BaseSelectorImpl):
        """Kqueue-based selector."""

# Choose the best implementation, roughly:
#    epoll|kqueue|devpoll > poll > select.
if 'KqueueSelector' in globals():
    DefaultSelector = KqueueSelector
elif 'EpollSelector' in globals():
    DefaultSelector = EpollSelector
elif 'DevpollSelector' in globals():
    DefaultSelector = DevpollSelector
elif 'PollSelector' in globals():
    DefaultSelector = PollSelector
else:
    DefaultSelector = SelectSelector
Copy the code

Comments are given in efficiency and epoll | kqueue | devpoll > poll > select. Let’s take a look at the simplest SelectSelector, using two additional collections to manage the fileobj it holds:

class SelectSelector(_BaseSelectorImpl):
    """Select-based selector."""

    def __init__(self):
        super().__init__()
        self._readers = set()
        self._writers = set()

    def register(self, fileobj, events, data=None):
        key = super().register(fileobj, events, data)
        if events & EVENT_READ:
            self._readers.add(key.fd)
        if events & EVENT_WRITE:
            self._writers.add(key.fd)
        return key

    def unregister(self, fileobj):
        key = super().unregister(fileobj)
        self._readers.discard(key.fd)
        self._writers.discard(key.fd)
        return key
Copy the code

The important thing is that the select function encapsulates _select:

_select = select.select def select(self, timeout=None): timeout = None if timeout is None else max(timeout, 0) ready = [] try: r, w, _ = self._select(self._readers, self._writers, [], timeout) except InterruptedError: return ready r = set(r) w = set(w) for fd in r | w: events = 0 if fd in r: events |= EVENT_READ if fd in w: Events | = EVENT_WRITE key = self. _fd_to_key (fd) if the key: work append ((key, events & key events)) return the object ready # is readyCopy the code

In terms of the more efficient implementation of ePoll Pool, the internal implementation is different, which you’ll understand later, but generally the application uses the DefaultSelector API, and the system automatically selects the most efficient way.

3.2 selecotr sample

Servers that can support multiple client links using selectors:

# multi - server import socket import selectors HOST = '127.0.0.1' PORT = 65432 sel = selectors. DefaultSelector (def) accept(sock, mask): Conn, addr = socket.accept () # Should be ready print('accepted', conn, 'from', Addr) conn.setblocking(False) sel.register(conn, selectors.EVENT_READ, read) Conn. Recv (1000) # Should be ready if data: print('echoing', repr(data), 'to', conn) conn.send(data) # Hope it won't block else: print('closing', conn) sel.unregister(conn) conn.close() serverd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverd.bind((HOST, Sele.register (serverd, selectors.EVENT_READ, PORT)) serverD. listen(100) serverD. setblocking(False) Sel.select () for key, mask in events: callback = key.data callback(key.fileobj, mask)Copy the code

The client is similar to the previous TCP-client, except that the sleep time is increased to facilitate manual operation.

# multi-client

import socket
import time

HOST = '127.0.0.1'  # The server's hostname or IP address
PORT = 65432  # The port used by the server

with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as sock:
    sock.connect((HOST, PORT))
    for x in range(10):
        sock.sendall(b'Hello, world')
        data = sock.recv(1024)
        print('Received', repr(data))
        time.sleep(1)
Copy the code

After the server is enabled, you can start multiple clients to observe server logs:

Socket fd=5, family=AddressFamily.AF_INET, type= socketkind.sock_stream, proto=0, laddr=('127.0.0.1', 65432), raddr=('127.0.0.1', 63288)> from ('127.0.0.1', 63288) descended at ('127.0.0.1', 63288) Hello, world' to <socket.socket fd=5, Addressfamily.af_inet, type= socketkind.sock_stream, proto=0, laddr=('127.0.0.1', 65432), raddr=('127.0.0.1', 63288)> echoing b'Hello, world' to <socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, Proto =0, laddr=('127.0.0.1', 65432), raddr=('127.0.0.1', 63288)> Accepted <socket. Socket fd=6, Addressfamily.af_inet, type= socketkind.sock_stream, proto=0, laddr=('127.0.0.1', 65432), raddr=('127.0.0.1', 63295)> from ('127.0.0.1', 63295) descended b'Hello, world' to <socket.socket fd=6, family=AddressFamily.AF_INET, Type = socketkind.sock_stream, proto=0, laddr=('127.0.0.1', 65432), raddr=('127.0.0.1', 63295)> descended b'Hello, World 'to <socket.socket fd=5, family=AddressFamily.AF_INET, type= socketkind.sock_stream, proto=0, laddr=('127.0.0.1', 65432), raddr = (' 127.0.0.1, 63288) >... echoing b'Hello, world' to <socket.socket fd=6, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, Laddr =('127.0.0.1', 65432), raddr=('127.0.0.1', 63295)> closing <socket.socket fd=5, family= addressfamily.af_inet, Type = socketkinkind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 65432), raddr=('127.0.0.1', 63288)> descended b'Hello, World 'to <socket.socket fd=6, family=AddressFamily.AF_INET, type= socketkind.sock_stream, proto=0, laddr=('127.0.0.1', 65432), raddr=('127.0.0.1', 63295)> closing <socket.socket fd=6, family= addressfamily.af_inet, Type = socketkind.sock_stream, proto=0, laddr=('127.0.0.1', 65432), raddr=('127.0.0.1', 63295)>Copy the code

Logs clearly show the process of accessing, sending and receiving data, and closing and leaving the two clients.

4. socketserver

Socketserver annotations are very detailed and are the best way to understand SocketServer code. The following section, for example, visually introduces the structure of SocketServer, focusing on the implementation of TCPServer.

        +------------+
        | BaseServer |
        +------------+
              |
              v
        +-----------+        +------------------+
        | TCPServer |------->| UnixStreamServer |
        +-----------+        +------------------+
              |
              v
        +-----------+        +--------------------+
        | UDPServer |------->| UnixDatagramServer |
        +-----------+        +--------------------+
Copy the code

4.1 TCPServer

BaseServer defines a basic socket service model that initializes objects after receiving service parameters and client classes. It uses serve_forever to continuously listen for connections and to agree on how to handle requests:

class BaseServer: def __init__(self, server_address, RequestHandlerClass): self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass ... Def selve_forever (self, poll_interval=0.5): with selectors.SelectSelector() as selector selector.register(self, selectors.EVENT_READ) while not self.__shutdown_request: ready = selector.select(poll_interval) if ready: self._handle_request_noblock() def _handle_request_noblock(self): try: Request, client_address = self.get_request() Return self.requesthandlerClass (request, client_address, selfCopy the code

TCP: bind, listen, and accept

class TCPServer(BaseServer):
    
    address_family = socket.AF_INET

    socket_type = socket.SOCK_STREAM
    
    request_queue_size = 5

    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise
                
    def server_bind(self):
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()

    def server_activate(self):
        self.socket.listen(self.request_queue_size)
    
    def get_request(self):
        return self.socket.accept()
Copy the code

4.2 ThreadingMixIn

ThreadingMixIn is also important, demonstrating the use of multiple threads to provide services:

class ThreadingMixIn: """Mix-in class to handle each request in a new thread.""" def process_request_thread(self, request, client_address): Try: self.finish_request(request, client_address) self.handle_error(request, client_address) finally: self.shutdown_request(request) def process_request(self, request, client_address): """Start a new thread to process the request.""" t = threading.Thread(target = self.process_request_thread, Args = (request, client_address)) # self.daemon_threads t.daemon = self.daemon_threads.Copy the code

4.3 RequestHandler

Request logic is handled by RequestHandler. The base class is BaseRequestHandler, which defines the main process of request processing setup -> handler -> finish:

class BaseRequestHandler: def __init__(self, request, client_address, server): Self.request = request self.client_address = client_address self.server = server self.setup() Self.handle () finally: self.finish(Copy the code

StreamRequestHandler, which wraps connection(socket) :

class StreamRequestHandler(BaseRequestHandler): rbufsize = -1 wbufsize = 0 def setup(self): Self.connection = self.request self.rfile = self.connection.makefile('rb', self.rbufsize) Self.wfile = _SocketWriter(self.connection) def finish(self): if not self.wfile.closed: self.wfile.flush() self.wfile.close() self.rfile.close() class _SocketWriter(BufferedIOBase): def __init__(self, sock): _sock = sock def write(self, b): # write data self._sock. Sendall (b) with memoryView (b) as view: return view.nbytesCopy the code

The most important handler is left blank for the application to implement.

5. http-server

After three levels of socket, selector and tcpserver, we finally enter our topic http-server. We can start a simple HTTP service using the following methods:

python3 -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
Copy the code

I’ll break down the startup process into the following code:

HandlerClass = SimpleHTTPRequestHandler # Define the RequestHandler class handlerClass. protocol_version = "HTTP/1.0" # HTTP protocol version with ThreadingHTTPServer(addr, HandlerClass) as httpd: # Create HTTP server host, port = httpd.socket.getsockname()[:2] url_host = f'[{host}]' if ':' in host else host print( f"Serving HTTP on {host} port {port} " f"(http://{url_host}:{port}/) ..." ) httpd.serve_forever() # Start the serviceCopy the code

The implementation of ThreadingHTTPServer is very simple, thanks to previous good encapsulation, and needs no introduction:

class HTTPServer(socketserver.TCPServer): def server_bind(self): """Override server_bind to store the server name.""" socketserver.TCPServer.server_bind(self) host, port = self.server_address[:2] self.server_name = socket.getfqdn(host) self.server_port = port class ThreadingHTTPServer (socketserver ThreadingMixIn HTTPServer) : daemon_threads = True # daemonCopy the code

5.1 HTTPRequestHandler

From HTTPServer, you can see that the HTTP service implementation is mainly in SimpleHTTPRequestHandler. First look at its parent BaseHTTPRequestHandler:

class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): # Inherits from StreamRequestHandler, it is not surprising that HTTP services are a subset of TCP services, and the corresponding request implementation should also be based on stream. def handle(self): """Handle multiple requests if necessary.""" self.close_connection = True self.handLE_one_request () # Handle multiple requests if necessary Self.handle_one_request () self.handle_one_request()Copy the code

BaseHTTPRequestHandler focuses on implementing the StreamRequestHandler white-space handler method (the name is a reference to the HTTP service, where each request is one-time).

def handle_one_request(self): Self.parse_request () # Handle HTTP header self.parse_request() # Handle HTTP header mname = 'do_' + Self.com mand method = getattr(self, mname) method() # Handle HTTP protocol method self.wfile.flush() # Respond requestCopy the code

Using curl to access our HTTP service:

Curl -v * Trying 127.0.0.1... * TCP_NODELAY set * Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0) > GET/HTTP/1.1 > Host: 127.0.0.1:8000 > user-agent: curl/7.64.1 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Server: SimpleHTTP/0.6 Python/3.8.5 < Date: Wed, 27 Jan 2021 11:03:08 GMT < Content-type: text/html; charset=utf-8 < Content-Length: 1570 < <! PUBLIC DOCTYPE HTML "- 4.01 / / / / / / W3C DTD HTML EN" "http://www.w3.org/TR/html4/strict.dtd" > < HTML > < head > < meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Directory listing for /</title> </head> <body> <h1>Directory listing for /</h1> <hr> <ul> ... </ul> <hr> </body> </html> * Closing connection 0Copy the code

The > part of the log is the request, and the < part is the response. For details on the HTTP protocol, see reference connection 2. An illustration of an HTTP request:

Using the figure above, we can see that the first line of our request is GET/HTTP/1.1, with some header information below. Back to the implementation of parse_request code

def parse_request(self): Self.raw_requestline = self.rfile.readline(self.raw_requestline, self.raw_requestline) 'iso-8859-1') requestLine = requestline.rstrip('\r\n') self.requestLine = requestLine words = requestline.split() # Split If len(words) == 0 or len(words) >= 3: # Enough to determine protocol version version = words[-1] if not version.startswith('HTTP/'): raise ValueError base_version_number = version.split('/', 1)[1] version_number = base_version_number.split(".") if len(version_number) ! = 2: raise ValueError ... [:2] self.com command, self. Path = command, self. Rfile, _class= self.messageclass)Copy the code

Based on the code, you can deduce the requirements of the first line of the HTTP protocol: The triples separated by Spaces correspond to the HTTP method, path, and HTTP version. The HTTP version consists of the HTTP keyword prefix and the protocol version. Continue parsing the remaining HTTP headers:

# http/client.py def parse_headers(fp, _class=HTTPMessage): headers = [] while True: If len(line) > _MAXLINE: raise LineTooLong("header line") headers.append(line) if len(headers) > _MAXHEADERS: raise HTTPException("got more than %d headers" % _MAXHEADERS) if line in (b'\r\n', b'\n', b''): Break hString = b ". Join (headers).decode(' ISO-8859-1 ') return Email.parser.parser (_class=_class).parsestr(hstring) # Parse the wrapper headerCopy the code

The HTTP method implementation logic is in the subclass SimpleHTTPRequestHandler:

def do_GET(self): """Serve a GET request.""" f = self.send_head() if f: try: Finally: f.close() def send_head(self):... path = self.translate_path(self.path) f = None if os.path.isdir(path): ... return self.list_directory(path) ...Copy the code

Show the output of the directory:

def list_directory(self, path): list = os.listdir(path) displaypath = urllib.parse.unquote(self.path, errors='surrogatepass') enc = sys.getfilesystemencoding() title = 'Directory listing for %s' % displaypath r.append('<! PUBLIC DOCTYPE HTML "- / / / / / DTD/W3C HTML 4.01 EN" "'" http://www.w3.org/TR/html4/strict.dtd "> ') state Richard armitage ppend (' < HTML > \ n < head > ')  r.append('<meta http-equiv="Content-Type" ' 'content="text/html; charset=%s">' % enc) r.append('<title>%s</title>\n</head>' % title) r.append('<body>\n<h1>%s</h1>' % title) r.append('<hr>\n<ul>') for name in list: fullname = os.path.join(path, name) displayname = linkname = name # Append / for directories or @ for symbolic links if os.path.isdir(fullname): displayname = name + "/" linkname = name + "/" if os.path.islink(fullname): displayname = name + "@" # Note: a link to a directory displays with @ and links with / r.append('<li><a href="%s">%s</a></li>' % (urllib.parse.quote(linkname, errors='surrogatepass'), html.escape(displayname, quote=False))) r.append('</ul>\n<hr>\n</body>\n</html>\n') encoded = '\n'.join(r).encode(enc, 'surrogateescape') f = io.BytesIO() f.write(encoded) f.seek(0) self.send_response(HTTPStatus.OK) self.send_header("Content-type", "text/html; charset=%s" % enc) self.send_header("Content-Length", str(len(encoded))) self.end_headers() return fCopy the code

You can see that the list_Directory performs file directory operations and is converted into HTML text for output. Set both HTTP headers and response handling:

def send_response(self, code, message=None): self.send_response_only(code, message) self.send_header('Server', self.version_string()) self.send_header('Date', self.date_time_string()) def send_response_only(self, code, message=None): """Send the response header only.""" message = self.responses[code][0] self._headers_buffer.append(("%s %d %s\r\n" % (self.protocol_version, code, message).encode(' Latin-1 ', 'strict')) keyword, value): """Send a MIME header to the headers buffer.""" if self.request_version ! = 'HTTP/0.9': if not hasattr(self, '_headers_buffer'): self._headers_buffer = [] self._headers_buffer. Append (("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict')) def end_headers(self): """Send the blank line ending the MIME headers.""" if self.request_version ! = 'HTTP/0.9': self._headers_buffer.append(b"\r\n") # flush_headers(self): if hasattr(self, '_headers_buffer'): Self.wfile.write (b"".join(self._headers_buffer))Copy the code

HTTP status is generated as follows:

responses = { v: (v.phrase, v.description) for v in httpstatus.__members__. Values ()} def __new__(cls, value, phrase, description=''): obj = int.__new__(cls, value) obj._value_ = value obj.phrase = phrase obj.description = description return obj OK = 200, 'OK', 'Request fulfilled, document follows'Copy the code

The main process of HTTP-server has been sorted out, and many implementation details can be left to the specific issues.

tip

Using the Mixin pattern, implementation classes can be well organized. The following 22 combinations form six implementation classes:

if hasattr(os, "fork"):
    class ForkingUDPServer(ForkingMixIn, UDPServer): pass
    class ForkingTCPServer(ForkingMixIn, TCPServer): pass

class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

if hasattr(socket, 'AF_UNIX'):

    class UnixStreamServer(TCPServer):
        address_family = socket.AF_UNIX

    class UnixDatagramServer(UDPServer):
        address_family = socket.AF_UNIX

    class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): pass

    class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer): pass
Copy the code

Use metaclasses to force the creation of subclasses:

Class BaseSelector(metaclass=ABCMeta): # @abstractMethod def register(self, fileobj, events, data=None): raise NotImplementedError @abstractmethod def unregister(self, fileobj): raise NotImplementedError @abstractmethod def select(self, timeout=None): raise NotImplementedErrorCopy the code

Reference links:

  1. Realpython.com/python-sock…
  2. Developer.mozilla.org/zh-CN/docs/…