def parse_streams(inbound, outbound, scheme=None): """Parse one or two HTTP/1.x streams. Note that parsing an outbound stream without an inbound stream is unreliable, because response framing depends on the request. :param inbound: The inbound (request) stream as a :class:`~httpolice.stream.Stream`, or `None`. :param outbound: The outbound (response) stream as a :class:`~httpolice.stream.Stream`, or `None`. :param scheme: The scheme of the request URI, as a Unicode string, or `None` if unknown. :return: An iterable of :class:`Exchange` objects. Some of the exchanges may be "empty" (aka "complaint boxes"): containing neither request nor responses, but only a notice that indicates some general problem with the streams. """ while inbound and inbound.good: (req, req_box) = _parse_request(inbound, scheme) (resps, resp_box) = ([], None) if req: if outbound and outbound.good: (resps, resp_box) = _parse_responses(outbound, req) if resps: if resps[-1].status == st.switching_protocols: inbound.sane = False if req.method == m.CONNECT and resps[-1].status.successful: inbound.sane = False yield Exchange(req, resps) if req_box: yield req_box if resp_box: yield resp_box if inbound and not inbound.eof: # Some data remains on the inbound stream, but we can't parse it. yield complaint_box(1007, stream=inbound, offset=inbound.tell()) if outbound and outbound.good: if inbound: # We had some requests, but we ran out of them. # We'll still try to parse the remaining responses on their own. yield complaint_box(1008, stream=outbound) while outbound.good: (resps, resp_box) = _parse_responses(outbound, None) if resps: yield Exchange(None, resps) if resp_box: yield resp_box if outbound and not outbound.eof: # Some data remains on the outbound stream, but we can't parse it. yield complaint_box(1010, stream=outbound, offset=outbound.tell())
def _parse_request(stream, scheme=None): req = _parse_request_heading(stream, scheme) if req is Unavailable: box = Exchange(None, []) stream.dump_complaints(box.complain, place=u'request heading') return (None, box) else: _parse_request_body(req, stream) return (req, None)
def test_construct_exchange(): req = Request(u'http', u'GET', u'/', u'HTTP/1.1', [(u'Host', b'example.com')], None) assert repr(req) == '<Request GET>' resp1 = Response(u'HTTP/1.1', 123, u'Please wait', [], None) assert repr(resp1) == '<Response 123>' resp2 = Response(u'HTTP/1.1', 200, u'OK', [(u'Content-Length', b'14')], b'Hello world!\r\n', None) exch = Exchange(req, [resp1, resp2]) assert repr(exch) == \ 'Exchange(<Request GET>, [<Response 123>, <Response 200>])' assert isinstance(exch.request.method, Method) assert isinstance(exch.request.version, HTTPVersion) assert isinstance(exch.request.header_entries[0].name, FieldName) assert isinstance(exch.responses[0].version, HTTPVersion) assert isinstance(exch.responses[0].status, StatusCode) assert isinstance(exch.responses[1].header_entries[0].name, FieldName)
def _parse_responses(stream, req): resps = [] while stream.sane: # Parse all responses corresponding to one request. # RFC 7230 section 3.3. resp = _parse_response_heading(req, stream) if resp is Unavailable: box = Exchange(None, []) stream.dump_complaints(box.complain, place=u'response heading') return (resps, box) else: resps.append(resp) _parse_response_body(resp, stream) if (not resp.status.informational) or \ (resp.status == st.switching_protocols): # This is the final response for this request. break return (resps, None)
def _process_entry(data, creator, path): req = _process_request(data['request'], creator, path) resp = _process_response(data['response'], req, creator, path) return Exchange(req, [resp] if resp is not None else [])