The last article briefly described the webpy response process. Here we focus on the details that you may care about.
A socket (default 8080 port) is started at runtime. When the browser requests the url to initiate, it sends the http protocol message to this socket. After the socket is received, it will be parsed and packaged into a request object, and then the method of the application will be called to return.
Let's see what the analysis does later? How is the response done? My main idea is:
- Where to parse out the information in the url (such as path, param)
- Where to find the corresponding handler class based on the parsed information (mapped to handlerClass according to web.ctx.path)
Because the main process has been explained in the previous article, so here we directly focus on the analysis part.httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))
-->#全局变量,后面的server.start()里面就用self.server方式把server放入到WorkThread中。 server = None def runsimple(func, server_address=("0.0.0.0", 8080)): global server func = StaticMiddleware(func) func = LogMiddleware(func) #这里会把wsgi_app(即func)填充到server,这样后面respond响应时就可以调用self.server.wsgi_app了 server = WSGIServer(server_address, func) if server.ssl_adapter: print "https://%s:%d/" % server_address else: print "http://%s:%d/" % server_address try: server.start() except (KeyboardInterrupt, SystemExit): server.stop() server = None
-------------------->#server.start() def start(self): self._interrupt = None if self.software is None: self.software = "%s Server" % self.version if (self.ssl_adapter is None and getattr(self, 'ssl_certificate', None) and getattr(self, 'ssl_private_key', None)): warnings.warn( "SSL attributes are deprecated in CherryPy 3.2, and will " "be removed in CherryPy 3.3. Use an ssl_adapter attribute " "instead.", DeprecationWarning ) try: from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter except ImportError: pass else: self.ssl_adapter = pyOpenSSLAdapter( self.ssl_certificate, self.ssl_private_key, getattr(self, 'ssl_certificate_chain', None)) if isinstance(self.bind_addr, basestring): try: os.unlink(self.bind_addr) except: pass try: os.chmod(self.bind_addr, 0777) except: pass info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)] else: host, port = self.bind_addr try: info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE) except socket.gaierror: if ':' in self.bind_addr[0]: info = [(socket.AF_INET6, socket.SOCK_STREAM, 0, "", self.bind_addr + (0, 0))] else: info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)] self.socket = None msg = "No socket could be created" for res in info: af, socktype, proto, canonname, sa = res try: self.bind(af, socktype, proto) except socket.error: if self.socket: self.socket.close() self.socket = None continue break if not self.socket: raise socket.error(msg) self.socket.settimeout(1) self.socket.listen(self.request_queue_size) #由这里进入看request的解析 self.requests.start() self.ready = True self._start_time = time.time() while self.ready: self.tick() if self.interrupt: while self.interrupt is True: time.sleep(0.1) if self.interrupt: raise self.interrupt
------------------>def start(self): for i in range(self.min): #可以看到WorkerThread是一个线程,主要用来处理request self._threads.append(WorkerThread(self.server)) for worker in self._threads: worker.setName("CP Server " + worker.getName()) worker.start() for worker in self._threads: while not worker.ready: time.sleep(.1)
-------------------->def run(self): self.server.stats['Worker Threads'][self.getName()] = self.stats try: self.ready = True while True: conn = self.server.requests.get() if conn is _SHUTDOWNREQUEST: return self.conn = conn if self.server.stats['Enabled']: self.start_time = time.time() try: #这里是解析入口 conn.communicate() finally: conn.close() if self.server.stats['Enabled']: self.requests_seen += self.conn.requests_seen self.bytes_read += self.conn.rfile.bytes_read self.bytes_written += self.conn.wfile.bytes_written self.work_time += time.time() - self.start_time self.start_time = None self.conn = None except (KeyboardInterrupt, SystemExit), exc: self.server.interrupt = exc
------------------>def communicate(self): request_seen = False try: while True: req = None req = self.RequestHandlerClass(self.server, self) #解析入口 req.parse_request() if self.server.stats['Enabled']: self.requests_seen += 1 if not req.ready: return request_seen = True #响应入口 req.respond() if req.close_connection: return
-------------------->def parse_request(self): self.rfile = SizeCheckWrapper(self.conn.rfile, self.server.max_request_header_size) try: #解析入口 self.read_request_line() except MaxSizeExceeded: self.simple_response("414 Request-URI Too Long", "The Request-URI sent with the request exceeds the maximum " "allowed bytes.") return try: success = self.read_request_headers() except MaxSizeExceeded: self.simple_response("413 Request Entity Too Large", "The headers sent with the request exceed the maximum " "allowed bytes.") return else: if not success: return self.ready = True
-------------------->def read_request_line(self): request_line = self.rfile.readline() self.started_request = True if not request_line: self.ready = False return if request_line == CRLF: request_line = self.rfile.readline() if not request_line: self.ready = False return if not request_line.endswith(CRLF): self.simple_response("400 Bad Request", "HTTP requires CRLF terminators") return try: method, uri, req_protocol = request_line.strip().split(" ", 2) rp = int(req_protocol[5]), int(req_protocol[7]) except (ValueError, IndexError): self.simple_response("400 Bad Request", "Malformed Request-Line") return self.uri = uri self.method = method scheme, authority, path = self.parse_request_uri(uri) if '#' in path: self.simple_response("400 Bad Request", "Illegal #fragment in Request-URI.") return if scheme: self.scheme = scheme qs = '' if '?' in path: path, qs = path.split('?', 1) try: atoms = [unquote(x) for x in quoted_slash.split(path)] except ValueError, ex: self.simple_response("400 Bad Request", ex.args[0]) return path = "%2F".join(atoms) #重点在这里有个说明,因为后面去做respond是根据path映射到对应的处理类上 #后面在回调wsgi_app时,会用self.path填充web.ctx.path #比如你的项目里有如此路径映射配置'/getJsonData', 'getJsonDataHandler';那么会根据web.ctx.path的请求路径/getJsonData找到对应的getJsonDataHandler处理类 self.path = path self.qs = qs sp = int(self.server.protocol[5]), int(self.server.protocol[7]) if sp[0] != rp[0]: self.simple_response("505 HTTP Version Not Supported") return self.request_protocol = req_protocol self.response_protocol = "HTTP/%s.%s" % min(rp, sp)