def build( self, req: Union[Request, ASGIRequest], resp: Union[_Response, _ASGIResponse] ): self.build_content(req, resp) resp.content_type = self.content_type if isinstance(self.status, int): self.status = code_to_http_status(self.status) resp.status = self.status resp.set_headers(self.headers) for name, params in self._stacked.items(): method = getattr(resp, name) for args, kwds in params: method(*args, **kwds)
def __call__(self, env, start_response): # noqa: C901 """WSGI `app` method. Makes instances of App callable from a WSGI server. May be used to host an App or called directly in order to simulate requests when testing the App. (See also: PEP 3333) Args: env (dict): A WSGI environment dictionary start_response (callable): A WSGI helper function for setting status and headers on a response. """ req = self._request_type(env, options=self.req_options) resp = self._response_type(options=self.resp_options) resource = None responder = None params = {} dependent_mw_resp_stack = [] mw_req_stack, mw_rsrc_stack, mw_resp_stack = self._middleware req_succeeded = False try: # NOTE(ealogar): The execution of request middleware # should be before routing. This will allow request mw # to modify the path. # NOTE: if flag set to use independent middleware, execute # request middleware independently. Otherwise, only queue # response middleware after request middleware succeeds. if self._independent_middleware: for process_request in mw_req_stack: process_request(req, resp) if resp.complete: break else: for process_request, process_response in mw_req_stack: if process_request and not resp.complete: process_request(req, resp) if process_response: dependent_mw_resp_stack.insert(0, process_response) if not resp.complete: # NOTE(warsaw): Moved this to inside the try except # because it is possible when using object-based # traversal for _get_responder() to fail. An example is # a case where an object does not have the requested # next-hop child resource. In that case, the object # being asked to dispatch to its child will raise an # HTTP exception signalling the problem, e.g. a 404. responder, params, resource, req.uri_template = self._get_responder( req) except Exception as ex: if not self._handle_exception(req, resp, ex, params): raise else: try: # NOTE(kgriffs): If the request did not match any # route, a default responder is returned and the # resource is None. In that case, we skip the # resource middleware methods. Resource will also be # None when a middleware method already set # resp.complete to True. if resource: # Call process_resource middleware methods. for process_resource in mw_rsrc_stack: process_resource(req, resp, resource, params) if resp.complete: break if not resp.complete: responder(req, resp, **params) req_succeeded = True except Exception as ex: if not self._handle_exception(req, resp, ex, params): raise # Call process_response middleware methods. for process_response in mw_resp_stack or dependent_mw_resp_stack: try: process_response(req, resp, resource, req_succeeded) except Exception as ex: if not self._handle_exception(req, resp, ex, params): raise req_succeeded = False body = [] length = 0 try: body, length = self._get_body(resp, env.get('wsgi.file_wrapper')) except Exception as ex: if not self._handle_exception(req, resp, ex, params): raise req_succeeded = False resp_status = code_to_http_status(resp.status) default_media_type = self.resp_options.default_media_type if req.method == 'HEAD' or resp_status in _BODILESS_STATUS_CODES: body = [] # PERF(vytas): move check for the less common and much faster path # of resp_status being in {204, 304} here; NB: this builds on the # assumption _TYPELESS_STATUS_CODES <= _BODILESS_STATUS_CODES. # NOTE(kgriffs): Based on wsgiref.validate's interpretation of # RFC 2616, as commented in that module's source code. The # presence of the Content-Length header is not similarly # enforced. if resp_status in _TYPELESS_STATUS_CODES: default_media_type = None elif (length is not None and req.method == 'HEAD' and resp_status not in _BODILESS_STATUS_CODES and 'content-length' not in resp._headers): # NOTE(kgriffs): We really should be returning a Content-Length # in this case according to my reading of the RFCs. By # optionally using len(data) we let a resource simulate HEAD # by turning around and calling it's own on_get(). resp._headers['content-length'] = str(length) else: # PERF(kgriffs): Böse mußt sein. Operate directly on resp._headers # to reduce overhead since this is a hot/critical code path. # NOTE(kgriffs): We always set content-length to match the # body bytes length, even if content-length is already set. The # reason being that web servers and LBs behave unpredictably # when the header doesn't match the body (sometimes choosing to # drop the HTTP connection prematurely, for example). if length is not None: resp._headers['content-length'] = str(length) headers = resp._wsgi_headers(default_media_type) # Return the response per the WSGI spec. start_response(resp_status, headers) return body