Exemple #1
0
def test_wsgi_environ(path=None, method=None, headers=None, extra=None,
                      secure=False, loop=None, body=None):
    '''An function to create a WSGI environment dictionary for testing.

    :param url: the resource in the ``PATH_INFO``.
    :param method: the ``REQUEST_METHOD``.
    :param headers: optional request headers
    :params secure: a secure connection?
    :param extra: additional dictionary of parameters to add.
    :return: a valid WSGI environ dictionary.
    '''
    parser = http_parser(kind=0)
    method = (method or 'GET').upper()
    path = iri_to_uri(path or '/')
    request_headers = Headers(headers, kind='client')
    # Add Host if not available
    parsed = urlparse(path)
    if 'host' not in request_headers:
        if not parsed.netloc:
            path = '%s%s' % ('https://:443' if secure else 'http://:80', path)
        else:
            request_headers['host'] = parsed.netloc
    #
    data = '%s %s HTTP/1.1\r\n\r\n' % (method, path)
    data = data.encode('latin1')
    parser.execute(data, len(data))
    #
    stream = io.BytesIO(body or b'')
    return wsgi_environ(stream, parser, request_headers,
                        ('127.0.0.1', 8060), '255.0.1.2:8080',
                        Headers(), https=secure, extra=extra)
Exemple #2
0
def test_wsgi_environ(path='/', method=None, headers=None, extra=None,
                      secure=False, loop=None):
    '''An function to create a WSGI environment dictionary for testing.

    :param url: the resource in the ``PATH_INFO``.
    :param method: the ``REQUEST_METHOD``.
    :param headers: optional request headers
    :params secure: a secure connection?
    :param extra: additional dictionary of parameters to add.
    :return: a valid WSGI environ dictionary.
    '''
    parser = http_parser(kind=0)
    method = (method or 'GET').upper()
    path = iri_to_uri(path)
    data = '%s %s HTTP/1.1\r\n\r\n' % (method, path)
    data = data.encode('latin1')
    parser.execute(data, len(data))
    request_headers = Headers(headers, kind='client')
    # Add Host if not available
    parsed = urlparse(path)
    if parsed.netloc and 'host' not in request_headers:
        request_headers['host'] = parsed.netloc
    #
    headers = Headers()
    stream = StreamReader(request_headers, parser)
    extra = extra or {}
    extra['pulsar.connection'] = FakeConnection(loop=loop)
    return wsgi_environ(stream, ('127.0.0.1', 8060), '777.777.777.777:8080',
                        headers, https=secure, extra=extra)
Exemple #3
0
 def test_remove_header(self):
     h = Headers([('Content-type', 'text/html')])
     self.assertEqual(len(h), 1)
     self.assertEqual(h.remove_header('foo'), None)
     self.assertEqual(h.remove_header('content-length'), None)
     self.assertEqual(h.remove_header('content-type'), ['text/html'])
     self.assertEqual(len(h), 0)
Exemple #4
0
def test_wsgi_environ(url='/',
                      method=None,
                      headers=None,
                      extra=None,
                      secure=False):
    '''An function to create a WSGI environment dictionary for testing.

    :param url: the resource in the ``PATH_INFO``.
    :param method: the ``REQUEST_METHOD``.
    :param headers: optional request headers
    :params secure: a secure connection?
    :param extra: additional dictionary of parameters to add.
    :return: a valid WSGI environ dictionary.
    '''
    parser = http_parser(kind=0)
    method = (method or 'GET').upper()
    data = '%s %s HTTP/1.1\r\n\r\n' % (method, url)
    data = data.encode('utf-8')
    parser.execute(data, len(data))
    request_headers = Headers(headers, kind='client')
    headers = Headers()
    stream = StreamReader(request_headers, parser)
    return wsgi_environ(stream, ('127.0.0.1', 8060),
                        '777.777.777.777:8080',
                        request_headers,
                        headers,
                        https=secure,
                        extra=extra)
Exemple #5
0
 def test_remove_header(self):
     h = Headers([('Content-type', 'text/html')])
     self.assertEqual(len(h), 1)
     self.assertEqual(h.remove_header('foo'), None)
     self.assertEqual(h.remove_header('content-length'), None)
     self.assertEqual(h.remove_header('content-type'), ['text/html'])
     self.assertEqual(len(h), 0)
Exemple #6
0
 def __init__(self, client, url, method, inp_params=None, headers=None,
              data=None, files=None, json=None, history=None, auth=None,
              charset=None, max_redirects=10, source_address=None,
              allow_redirects=False, decompress=True, version=None,
              wait_continue=False, websocket_handler=None, cookies=None,
              params=None, stream=False, proxies=None, verify=True,
              **ignored):
     self.client = client
     self.method = method.upper()
     self.inp_params = inp_params or {}
     self.unredirected_headers = Headers()
     self.history = history
     self.wait_continue = wait_continue
     self.max_redirects = max_redirects
     self.allow_redirects = allow_redirects
     self.charset = charset or 'utf-8'
     self.version = version
     self.decompress = decompress
     self.websocket_handler = websocket_handler
     self.source_address = source_address
     self.stream = stream
     self.verify = verify
     self.new_parser()
     if auth and not isinstance(auth, Auth):
         auth = HTTPBasicAuth(*auth)
     self.auth = auth
     self.headers = client._get_headers(headers)
     self.url = full_url(url, params, method=self.method)
     self.body = self._encode_body(data, files, json)
     self._set_proxy(proxies, ignored)
     cookies = cookiejar_from_dict(client.cookies, cookies)
     if cookies:
         cookies.add_cookie_header(self)
Exemple #7
0
 def __init__(self, wsgi_callable, cfg, server_software=None, loop=None):
     super(HttpServerResponse, self).__init__(loop=loop)
     self.wsgi_callable = wsgi_callable
     self.cfg = cfg
     self.parser = http_parser(kind=0)
     self.headers = Headers()
     self.keep_alive = False
     self.SERVER_SOFTWARE = server_software or self.SERVER_SOFTWARE
Exemple #8
0
 def test_init_int(self):
     h = Headers(kind=1)
     self.assertEqual(h.kind, 'server')
     self.assertEqual(h.kind_number, 1)
     h = Headers(kind=0)
     self.assertEqual(h.kind, 'client')
     self.assertEqual(h.kind_number, 0)
     h = Headers(kind=56)
     self.assertEqual(h.kind, 'both')
     self.assertEqual(h.kind_number, 2)
Exemple #9
0
 def test_override(self):
     h = Headers([('Accept-encoding', 'gzip'),
                  ('Accept-encoding', 'deflate'), ('Accept', '*/*')],
                 kind=2)
     h.override([('Accept-encoding', 'gzip2'),
                 ('Accept-encoding', 'deflate2'), ('Accept', 'text/html'),
                 ('Accept', '*/*; q=0.8')])
     self.assertEqual(len(h), 2)
     self.assertEqual(h['accept-encoding'], 'gzip2, deflate2')
     self.assertEqual(h['accept'], 'text/html, */*; q=0.8')
Exemple #10
0
 def __init__(self, status=None, content=None, response_headers=None,
              content_type=None, encoding=None, environ=None):
     self.environ = environ
     self.status_code = status or self.DEFAULT_STATUS_CODE
     self.encoding = encoding
     self.cookies = SimpleCookie()
     self.headers = Headers(response_headers, kind='server')
     self.content = content
     if content_type is not None:
         self.content_type = content_type
Exemple #11
0
 def __init__(self,
              client,
              url,
              method,
              inp_params=None,
              headers=None,
              data=None,
              files=None,
              history=None,
              charset=None,
              encode_multipart=True,
              multipart_boundary=None,
              source_address=None,
              allow_redirects=False,
              max_redirects=10,
              decompress=True,
              version=None,
              wait_continue=False,
              websocket_handler=None,
              cookies=None,
              urlparams=None,
              **ignored):
     self.client = client
     self._data = None
     self.files = files
     self.urlparams = urlparams
     self.inp_params = inp_params or {}
     self.unredirected_headers = Headers(kind='client')
     self.method = method.upper()
     self.full_url = url
     if urlparams:
         self._encode_url(urlparams)
     self.set_proxy(None)
     self.history = history
     self.wait_continue = wait_continue
     self.max_redirects = max_redirects
     self.allow_redirects = allow_redirects
     self.charset = charset or 'utf-8'
     self.version = version
     self.decompress = decompress
     self.encode_multipart = encode_multipart
     self.multipart_boundary = multipart_boundary
     self.websocket_handler = websocket_handler
     self.source_address = source_address
     self.new_parser()
     if self._scheme in tls_schemes:
         self._ssl = client.ssl_context(**ignored)
     self.headers = client.get_headers(self, headers)
     cookies = cookiejar_from_dict(client.cookies, cookies)
     if cookies:
         cookies.add_cookie_header(self)
     self.unredirected_headers['host'] = host_no_default_port(
         self._scheme, self._netloc)
     client.set_proxy(self)
     self.data = data
Exemple #12
0
 def test_cookies(self):
     h = Headers()
     cookies = SimpleCookie({'bla': 'foo', 'pippo': 'pluto'})
     self.assertEqual(len(cookies), 2)
     for c in cookies.values():
         v = c.OutputString()
         h.add_header('Set-Cookie', v)
     h = str(h)
     self.assertTrue(
         h in ('Set-Cookie: bla=foo\r\nSet-Cookie: pippo=pluto\r\n\r\n',
               'Set-Cookie: pippo=pluto\r\nSet-Cookie: bla=foo\r\n\r\n'))
Exemple #13
0
 def test_override(self):
     h = Headers([('Accept-encoding', 'gzip'),
                  ('Accept-encoding', 'deflate'),
                  ('Accept', '*/*')], kind=2)
     h.override([('Accept-encoding', 'gzip2'),
                 ('Accept-encoding', 'deflate2'),
                 ('Accept', 'text/html'),
                 ('Accept', '*/*; q=0.8')])
     self.assertEqual(len(h), 2)
     self.assertEqual(h['accept-encoding'], 'gzip2, deflate2')
     self.assertEqual(h['accept'], 'text/html, */*; q=0.8')
Exemple #14
0
 def test_client_header(self):
     h = Headers()
     self.assertEqual(len(h), 0)
     h['content-type'] = 'text/html'
     self.assertEqual(h.get_all('content-type'), ['text/html'])
     self.assertEqual(len(h), 1)
     h['server'] = 'bla'
     self.assertEqual(len(h), 2)
     del h['content-type']
     self.assertEqual(len(h), 1)
     self.assertEqual(h.get_all('content-type', []), [])
Exemple #15
0
 def test_cookies(self):
     h = Headers()
     cookies = SimpleCookie({'bla': 'foo', 'pippo': 'pluto'})
     self.assertEqual(len(cookies), 2)
     for c in cookies.values():
         v = c.OutputString()
         h.add_header('Set-Cookie', v)
     h = str(h)
     self.assertTrue(
         h in ('Set-Cookie: bla=foo\r\nSet-Cookie: pippo=pluto\r\n\r\n',
               'Set-Cookie: pippo=pluto\r\nSet-Cookie: bla=foo\r\n\r\n'))
Exemple #16
0
 def testClientHeader(self):
     h = Headers(kind='client')
     self.assertEqual(h.kind, 'client')
     self.assertEqual(len(h), 0)
     h['content-type'] = 'text/html'
     self.assertEqual(h.get_all('content-type'), ['text/html'])
     self.assertEqual(len(h), 1)
     h['server'] = 'bla'
     self.assertEqual(len(h), 1)
     del h['content-type']
     self.assertEqual(len(h), 0)
     self.assertEqual(h.get_all('content-type', []), [])
Exemple #17
0
 def test_remove_header_value(self):
     h = Headers([('Accept-encoding', 'gzip'),
                  ('Accept-encoding', 'deflate'),
                  ('Accept', '*/*')], kind=2)
     self.assertEqual(len(h), 2)
     self.assertEqual(h['accept-encoding'], 'gzip, deflate')
     self.assertEqual(h.remove_header('accept-encoding', 'x'), None)
     self.assertEqual(h['accept-encoding'], 'gzip, deflate')
     self.assertEqual(h.remove_header('accept-encoding', 'deflate'),
                      'deflate')
     self.assertEqual(len(h), 2)
     self.assertEqual(h['accept-encoding'], 'gzip')
Exemple #18
0
 def testClientHeader(self):
     h = Headers(kind='client')
     self.assertEqual(h.kind, 'client')
     self.assertEqual(len(h), 0)
     h['content-type'] = 'text/html'
     self.assertEqual(h.get_all('content-type'), ['text/html'])
     self.assertEqual(len(h), 1)
     h['server'] = 'bla'
     self.assertEqual(len(h), 1)
     del h['content-type']
     self.assertEqual(len(h), 0)
     self.assertEqual(h.get_all('content-type', []), [])
Exemple #19
0
 def test_remove_header_value(self):
     h = Headers([('Accept-encoding', 'gzip'),
                  ('Accept-encoding', 'deflate'), ('Accept', '*/*')],
                 kind=2)
     self.assertEqual(len(h), 2)
     self.assertEqual(h['accept-encoding'], 'gzip, deflate')
     self.assertEqual(h.remove_header('accept-encoding', 'x'), None)
     self.assertEqual(h['accept-encoding'], 'gzip, deflate')
     self.assertEqual(h.remove_header('accept-encoding', 'deflate'),
                      'deflate')
     self.assertEqual(len(h), 2)
     self.assertEqual(h['accept-encoding'], 'gzip')
Exemple #20
0
 def get_headers(self, request, headers=None):
     # Returns a :class:`Header` obtained from combining
     # :attr:`headers` with *headers*. Can handle websocket requests.
     if request.scheme in ('ws', 'wss'):
         d = Headers(
             (('Connection', 'Upgrade'), ('Upgrade', 'websocket'),
              ('Sec-WebSocket-Version', str(max(SUPPORTED_VERSIONS))),
              ('Sec-WebSocket-Key', self.websocket_key),
              ('user-agent', self.client_version)),
             kind='client')
     else:
         d = self.headers.copy()
     if headers:
         d.override(headers)
     return d
Exemple #21
0
 def test_CacheControl(self):
     headers = Headers()
     c = CacheControl()
     self.assertFalse(c.private)
     self.assertFalse(c.maxage)
     c(headers)
     self.assertEqual(headers['cache-control'], 'no-cache')
     c = CacheControl(maxage=3600)
     c(headers)
     self.assertEqual(headers['cache-control'], 'max-age=3600, public')
     c = CacheControl(maxage=3600, private=True)
     c(headers)
     self.assertEqual(headers['cache-control'], 'max-age=3600, private')
     c = CacheControl(maxage=3600, must_revalidate=True)
     c(headers)
     self.assertEqual(headers['cache-control'],
                      'max-age=3600, public, must-revalidate')
     c = CacheControl(maxage=3600, proxy_revalidate=True)
     c(headers)
     self.assertEqual(headers['cache-control'],
                      'max-age=3600, public, proxy-revalidate')
     c = CacheControl(maxage=3600, proxy_revalidate=True,
                      nostore=True)
     c(headers)
     self.assertEqual(headers['cache-control'],
                      'no-store, no-cache, must-revalidate, max-age=0')
Exemple #22
0
    def data_received(self, data):
        '''Implements :meth:`~.ProtocolConsumer.data_received` method.

        Once we have a full HTTP message, build the wsgi ``environ`` and
        delegate the response to the :func:`wsgi_callable` function.
        '''
        parser = self.parser
        processed = parser.execute(data, len(data))
        if not self._stream and parser.is_headers_complete():
            headers = Headers(parser.get_headers(), kind='client')
            self._stream = StreamReader(headers, parser, self.transport)
            self._response(self.wsgi_environ())
        #
        if parser.is_message_complete():
            #
            # Stream has the whole body
            if not self._stream.on_message_complete.done():
                self._stream.on_message_complete.set_result(None)

            if processed < len(data):
                if not self._buffer:
                    self._buffer = data[processed:]
                    self.bind_event('post_request', self._new_request)
                else:
                    self._buffer += data[processed:]
        #
        elif processed < len(data):
            # This is a parsing error, the client must have sent
            # bogus data
            raise ProtocolError
Exemple #23
0
 def testHeaderBytes(self):
     h = Headers(kind=None)
     h['content-type'] = 'text/html'
     h['server'] = 'bla'
     self.assertTrue(repr(h).startswith('both '))
     self.assertEqual(bytes(h), b'Server: bla\r\n'
                      b'Content-Type: text/html\r\n\r\n')
Exemple #24
0
 def __init__(self, client, url, method, inp_params=None, headers=None,
              data=None, files=None, json=None, history=None, auth=None,
              charset=None, max_redirects=10, source_address=None,
              allow_redirects=False, decompress=True, version=None,
              wait_continue=False, websocket_handler=None, cookies=None,
              params=None, stream=False, proxies=None, verify=True,
              **ignored):
     self.client = client
     self.method = method.upper()
     self.inp_params = inp_params or {}
     self.unredirected_headers = Headers(kind='client')
     self.history = history
     self.wait_continue = wait_continue
     self.max_redirects = max_redirects
     self.allow_redirects = allow_redirects
     self.charset = charset or 'utf-8'
     self.version = version
     self.decompress = decompress
     self.websocket_handler = websocket_handler
     self.source_address = source_address
     self.stream = stream
     self.verify = verify
     self.new_parser()
     if auth and not isinstance(auth, Auth):
         auth = HTTPBasicAuth(*auth)
     self.auth = auth
     self.headers = client._get_headers(headers)
     self.url = full_url(url, params, method=self.method)
     self.body = self._encode_body(data, files, json)
     self._set_proxy(proxies, ignored)
     cookies = cookiejar_from_dict(client.cookies, cookies)
     if cookies:
         cookies.add_cookie_header(self)
Exemple #25
0
 def get_headers(self, request, headers=None):
     # Returns a :class:`Header` obtained from combining
     # :attr:`headers` with *headers*. Can handle websocket requests.
     if request.scheme in ('ws', 'wss'):
         d = Headers((
             ('Connection', 'Upgrade'),
             ('Upgrade', 'websocket'),
             ('Sec-WebSocket-Version', str(max(SUPPORTED_VERSIONS))),
             ('Sec-WebSocket-Key', self.websocket_key),
             ('user-agent', self.client_version)
             ), kind='client')
     else:
         d = self.headers.copy()
     if headers:
         d.override(headers)
     return d
 def __init__(self, url, username, password, **kwargs):
     super(GerritSession, self).__init__(
         headers=Headers([('Content-Type', 'application/json')]),
         **kwargs
     )
     self.url = url
     self.auth = HTTPDigestAuth(username, password)
Exemple #27
0
 def __init__(self,
              status=None,
              content=None,
              response_headers=None,
              content_type=None,
              encoding=None,
              environ=None,
              can_store_cookies=True):
     self.environ = environ
     self.status_code = status or self.DEFAULT_STATUS_CODE
     self.encoding = encoding
     self.cookies = SimpleCookie()
     self.headers = Headers(response_headers or ())
     self.content = content
     self._can_store_cookies = can_store_cookies
     if content_type is not None:
         self.content_type = content_type
Exemple #28
0
    def request_headers(self, environ):
        '''Modify request headers via the list of :attr:`headers_middleware`.
The returned headers will be sent to the target uri.'''
        headers = Headers(kind='client')
        for k in environ:
            if k.startswith('HTTP_'):
                head = k[5:].replace('_','-')
                headers[head] = environ[k]
        for head in ENVIRON_HEADERS:
            k = head.replace('-','_').upper()
            v = environ.get(k)
            if v:
                headers[head] = v
        headers.update(self.headers)
        for middleware in self.headers_middleware:
            middleware(environ, headers)
        return headers
Exemple #29
0
 def __init__(self, wsgi_callable, cfg, server_software=None):
     super(HttpServerResponse, self).__init__()
     self.wsgi_callable = wsgi_callable
     self.cfg = cfg
     self.parser = http_parser(kind=0)
     self.headers = Headers()
     self.keep_alive = False
     self.SERVER_SOFTWARE = server_software or self.SERVER_SOFTWARE
Exemple #30
0
 def test_multiple_entry(self):
     h = Headers([('Connection', 'Keep-Alive'),
                  ('Accept-Encoding', 'identity'),
                  ('Accept-Encoding', 'deflate'),
                  ('Accept-Encoding', 'compress'),
                  ('Accept-Encoding', 'gzip')],
                 kind='client')
     accept = h['accept-encoding']
     self.assertEqual(accept, 'identity, deflate, compress, gzip')
Exemple #31
0
    def request_start_response(self,
                               method,
                               path,
                               HTTP_ACCEPT=None,
                               headers=None,
                               data=None,
                               json=None,
                               content_type=None,
                               token=None,
                               oauth=None,
                               jwt=None,
                               cookie=None,
                               params=None,
                               **extra):
        method = method.upper()
        extra['REQUEST_METHOD'] = method.upper()
        path = path or '/'
        extra['HTTP_ACCEPT'] = HTTP_ACCEPT or '*/*'
        extra['pulsar.connection'] = mock.MagicMock()
        heads = []
        if headers:
            heads.extend(headers)
        if json is not None:
            content_type = 'application/json'
            assert not data
            data = json
        if content_type:
            heads.append(('content-type', content_type))
        if token:
            heads.append(('Authorization', 'Bearer %s' % token))
        elif oauth:
            heads.append(('Authorization', 'OAuth %s' % oauth))
        elif jwt:
            heads.append(('Authorization', 'JWT %s' % jwt))
        if cookie:
            heads.append(('Cookie', cookie))

        if params:
            path = full_url(path, params)

        # Encode data
        if (method in ENCODE_BODY_METHODS and data is not None
                and not isinstance(data, bytes)):
            content_type = Headers(heads).get('content-type')
            if content_type is None:
                data, content_type = encode_multipart_formdata(data)
                heads.append(('content-type', content_type))
            elif content_type == 'application/json':
                data = _json.dumps(data).encode('utf-8')

        request = self.app.wsgi_request(path=path,
                                        headers=heads,
                                        body=data,
                                        **extra)
        request.environ['SERVER_NAME'] = 'localhost'
        start_response = mock.MagicMock()
        return request, start_response
Exemple #32
0
 def __init__(
     self,
     client,
     url,
     method,
     inp_params=None,
     headers=None,
     data=None,
     files=None,
     history=None,
     charset=None,
     encode_multipart=True,
     multipart_boundary=None,
     source_address=None,
     allow_redirects=False,
     max_redirects=10,
     decompress=True,
     version=None,
     wait_continue=False,
     websocket_handler=None,
     cookies=None,
     urlparams=None,
     **ignored
 ):
     self.client = client
     self._data = None
     self.files = files
     self.urlparams = urlparams
     self.inp_params = inp_params or {}
     self.unredirected_headers = Headers(kind="client")
     self.method = method.upper()
     self.full_url = url
     if urlparams:
         self._encode_url(urlparams)
     self.set_proxy(None)
     self.history = history
     self.wait_continue = wait_continue
     self.max_redirects = max_redirects
     self.allow_redirects = allow_redirects
     self.charset = charset or "utf-8"
     self.version = version
     self.decompress = decompress
     self.encode_multipart = encode_multipart
     self.multipart_boundary = multipart_boundary
     self.websocket_handler = websocket_handler
     self.source_address = source_address
     self.new_parser()
     if self._scheme in tls_schemes:
         self._ssl = client.ssl_context(**ignored)
     self.headers = client.get_headers(self, headers)
     cookies = cookiejar_from_dict(client.cookies, cookies)
     if cookies:
         cookies.add_cookie_header(self)
     self.unredirected_headers["host"] = host_no_default_port(self._scheme, self._netloc)
     client.set_proxy(self)
     self.data = data
Exemple #33
0
 def __init__(self, status=None, content=None, response_headers=None,
              content_type=None, encoding=None, environ=None):
     self.environ = environ
     self.status_code = status or self.DEFAULT_STATUS_CODE
     self.encoding = encoding
     self.cookies = SimpleCookie()
     self.headers = Headers(response_headers, kind='server')
     self.content = content
     if content_type is not None:
         self.content_type = content_type
Exemple #34
0
 def get_headers(self, request, headers=None):
     # Returns a :class:`Header` obtained from combining
     # :attr:`headers` with *headers*. Can handle websocket requests.
     if request.scheme in ("ws", "wss"):
         d = Headers(
             (
                 ("Connection", "Upgrade"),
                 ("Upgrade", "websocket"),
                 ("Sec-WebSocket-Version", str(max(SUPPORTED_VERSIONS))),
                 ("Sec-WebSocket-Key", self.websocket_key),
                 ("user-agent", self.client_version),
             ),
             kind="client",
         )
     else:
         d = self.headers.copy()
     if headers:
         d.override(headers)
     return d
Exemple #35
0
 def test_non_standard_request_headers(self):
     h = Headers(kind='client')
     h['accept'] = 'text/html'
     self.assertEqual(len(h), 1)
     h['server'] = 'bla'
     self.assertEqual(len(h), 1)
     h['proxy-connection'] = 'keep-alive'
     self.assertEqual(len(h), 2)
     headers = str(h)
     self.assertTrue('Proxy-Connection:' in headers)
Exemple #36
0
 def __init__(self, status=None, content=None, response_headers=None,
              content_type=None, encoding=None, environ=None,
              can_store_cookies=True):
     self.environ = environ
     self.status_code = status or self.DEFAULT_STATUS_CODE
     self.encoding = encoding
     self.cookies = SimpleCookie()
     self.headers = Headers(response_headers or ())
     self.content = content
     self._can_store_cookies = can_store_cookies
     if content_type is not None:
         self.content_type = content_type
Exemple #37
0
 def __init__(self, status=None, content=None, response_headers=None,
              content_type=None, encoding=None, environ=None,
              can_store_cookies=True):
     self.environ = environ
     self.status_code = status or self.DEFAULT_STATUS_CODE
     self.encoding = encoding
     self.cookies = SimpleCookie()
     self.headers = Headers(response_headers, kind='server')
     self.content = content
     self._can_store_cookies = can_store_cookies
     if content_type is not None:
         self.content_type = content_type
     if environ:
         cookie = environ.get('HTTP_COOKIE')
         if cookie:
             self.cookies.load(cookie)
Exemple #38
0
 def request_headers(self, environ):
     '''Fill request headers from the environ dictionary and
     modify them via the list of :attr:`headers_middleware`.
     The returned headers will be sent to the target uri.
     '''
     headers = Headers(kind='client')
     for k in environ:
         if k.startswith('HTTP_'):
             head = k[5:].replace('_', '-')
             headers[head] = environ[k]
     for head in ENVIRON_HEADERS:
         k = head.replace('-', '_').upper()
         v = environ.get(k)
         if v:
             headers[head] = v
     return headers
Exemple #39
0
 def data_processed(self, response, exc=None, **kw):
     '''Receive data from the requesting HTTP client.'''
     status = response.get_status()
     if status == '100 Continue':
         stream = self.environ.get('wsgi.input') or io.BytesIO()
         body = yield stream.read()
         response.transport.write(body)
     if response.parser.is_headers_complete():
         if self._headers is None:
             headers = self.remove_hop_headers(response.headers)
             self._headers = Headers(headers, kind='server')
             # start the response
             self.start_response(status, list(self._headers))
         body = response.recv_body()
         if response.parser.is_message_complete():
             self._done = True
         self.queue.put_nowait(body)
Exemple #40
0
 def __init__(
     self,
     status=None,
     content=None,
     response_headers=None,
     content_type=None,
     encoding=None,
     environ=None,
     start_response=None,
 ):
     super(WsgiResponse, self).__init__(environ, start_response)
     self.status_code = status or self.DEFAULT_STATUS_CODE
     self.encoding = encoding
     self.cookies = SimpleCookie()
     self.headers = Headers(response_headers, kind="server")
     self.content = content
     if content_type is not None:
         self.content_type = content_type
Exemple #41
0
    def data_received(self, data):
        '''Implements :meth:`~.ProtocolConsumer.data_received` method.

        Once we have a full HTTP message, build the wsgi ``environ`` and
        delegate the response to the :func:`wsgi_callable` function.
        '''
        p = self.parser
        if p.execute(bytes(data), len(data)) == len(data):
            if self._request_headers is None and p.is_headers_complete():
                self._request_headers = Headers(p.get_headers(), kind='client')
                stream = StreamReader(self._request_headers, p, self.transport)
                self.bind_event('data_processed', stream.data_processed)
                environ = self.wsgi_environ(stream)
                self.event_loop. async (self._response(environ))
        else:
            # This is a parsing error, the client must have sent
            # bogus data
            raise ProtocolError
Exemple #42
0
 def __init__(self, client, url, method, inp_params=None, headers=None,
              data=None, files=None, history=None, auth=None,
              charset=None, encode_multipart=True, multipart_boundary=None,
              source_address=None, allow_redirects=False, max_redirects=10,
              decompress=True, version=None, wait_continue=False,
              websocket_handler=None, cookies=None, urlparams=None,
              stream=False, proxies=None, verify=True, **ignored):
     self.client = client
     self._data = None
     self.files = files
     self.urlparams = urlparams
     self.inp_params = inp_params or {}
     self.unredirected_headers = Headers(kind='client')
     self.method = method.upper()
     self.full_url = url
     if urlparams:
         self._encode_url(urlparams)
     self.history = history
     self.wait_continue = wait_continue
     self.max_redirects = max_redirects
     self.allow_redirects = allow_redirects
     self.charset = charset or 'utf-8'
     self.version = version
     self.decompress = decompress
     self.encode_multipart = encode_multipart
     self.multipart_boundary = multipart_boundary
     self.websocket_handler = websocket_handler
     self.source_address = source_address
     self.stream = stream
     self.verify = verify
     self.new_parser()
     if self._scheme in tls_schemes:
         self._ssl = client.ssl_context(verify=self.verify, **ignored)
     if auth and not isinstance(auth, Auth):
         auth = HTTPBasicAuth(*auth)
     self.auth = auth
     self.headers = client.get_headers(self, headers)
     cookies = cookiejar_from_dict(client.cookies, cookies)
     if cookies:
         cookies.add_cookie_header(self)
     self.unredirected_headers['host'] = host_no_default_port(self._scheme,
                                                              self._netloc)
     self.data = data
     self._set_proxy(proxies)
Exemple #43
0
    def data_received(self, data):
        '''Implements :meth:`~.ProtocolConsumer.data_received` method.

        Once we have a full HTTP message, build the wsgi ``environ`` and
        delegate the response to the :func:`wsgi_callable` function.
        '''
        parser = self.parser
        processed = parser.execute(data, len(data))
        if parser.is_headers_complete():
            if not self._body_reader:
                headers = Headers(parser.get_headers(), kind='client')
                self._body_reader = HttpBodyReader(headers,
                                                   parser,
                                                   self.transport,
                                                   self.cfg.stream_buffer,
                                                   loop=self._loop)
                ensure_future(self._response(self.wsgi_environ()),
                              loop=self._loop)
            body = parser.recv_body()
            if body:
                self._body_reader.feed_data(body)
        #
        if parser.is_message_complete():
            #
            self._body_reader.feed_eof()

            if processed < len(data):
                if not self._buffer:
                    self._buffer = data[processed:]
                    self.bind_event('post_request', self._new_request)
                else:
                    self._buffer += data[processed:]
        #
        elif processed < len(data):
            # This is a parsing error, the client must have sent
            # bogus data
            raise ProtocolError
Exemple #44
0
 def headers(self):
     if not hasattr(self, '_headers'):
         if self.parser and self.parser.is_headers_complete():
             self._headers = Headers(self.parser.get_headers())
     return getattr(self, '_headers', None)
Exemple #45
0
class HttpServerResponse(ProtocolConsumer):
    '''Server side WSGI :class:`.ProtocolConsumer`.

    .. attribute:: wsgi_callable

        The wsgi callable handling requests.
    '''
    _status = None
    _headers_sent = None
    _stream = None
    _buffer = None
    SERVER_SOFTWARE = pulsar.SERVER_SOFTWARE
    ONE_TIME_EVENTS = ProtocolConsumer.ONE_TIME_EVENTS + ('on_headers',)

    def __init__(self, wsgi_callable, cfg, server_software=None):
        super(HttpServerResponse, self).__init__()
        self.wsgi_callable = wsgi_callable
        self.cfg = cfg
        self.parser = http_parser(kind=0)
        self.headers = Headers()
        self.keep_alive = False
        self.SERVER_SOFTWARE = server_software or self.SERVER_SOFTWARE

    def data_received(self, data):
        '''Implements :meth:`~.ProtocolConsumer.data_received` method.

        Once we have a full HTTP message, build the wsgi ``environ`` and
        delegate the response to the :func:`wsgi_callable` function.
        '''
        parser = self.parser
        processed = parser.execute(data, len(data))
        if not self._stream and parser.is_headers_complete():
            headers = Headers(parser.get_headers(), kind='client')
            self._stream = StreamReader(headers, parser, self.transport)
            self._response(self.wsgi_environ())
        #
        done = parser.is_message_complete()
        if done and not self._stream.on_message_complete.done():
            self._stream.on_message_complete.set_result(None)
        #
        if processed < len(data):
            if not done:
                # This is a parsing error, the client must have sent
                # bogus data
                raise ProtocolError
            else:
                if not self._buffer:
                    self._buffer = data[processed:]
                    self.bind_event('post_request', self._new_request)
                else:
                    self._buffer += data[processed:]

    @property
    def status(self):
        return self._status

    @property
    def upgrade(self):
        return self.headers.get('upgrade')

    @property
    def chunked(self):
        return self.headers.get('Transfer-Encoding') == 'chunked'

    @property
    def content_length(self):
        c = self.headers.get('Content-Length')
        if c:
            return int(c)

    @property
    def version(self):
        return self.parser.get_version()

    def start_response(self, status, response_headers, exc_info=None):
        '''WSGI compliant ``start_response`` callable, see pep3333_.

        The application may call start_response more than once, if and only
        if the ``exc_info`` argument is provided.
        More precisely, it is a fatal error to call ``start_response`` without
        the ``exc_info`` argument if start_response has already been called
        within the current invocation of the application.

        :parameter status: an HTTP ``status`` string like ``200 OK`` or
            ``404 Not Found``.
        :parameter response_headers: a list of ``(header_name, header_value)``
            tuples. It must be a Python list. Each header_name must be a valid
            HTTP header field-name (as defined by RFC 2616_, Section 4.2),
            without a trailing colon or other punctuation.
        :parameter exc_info: optional python ``sys.exc_info()`` tuple.
            This argument should be supplied by the application only if
            ``start_response`` is being called by an error handler.
        :return: The :meth:`write` method.

        ``HOP_HEADERS`` are not considered but no error is raised.

        .. _pep3333: http://www.python.org/dev/peps/pep-3333/
        .. _2616: http://www.faqs.org/rfcs/rfc2616.html
        '''
        if exc_info:
            try:
                if self._headers_sent:
                    # if exc_info is provided, and the HTTP headers have
                    # already been sent, start_response must raise an error,
                    # and should re-raise using the exc_info tuple
                    reraise(*exc_info)
            finally:
                # Avoid circular reference
                exc_info = None
        elif self._status:
            # Headers already set. Raise error
            raise HttpException("Response headers already set!")
        self._status = status
        if type(response_headers) is not list:
            raise TypeError("Headers must be a list of name/value tuples")
        for header, value in response_headers:
            if header.lower() in HOP_HEADERS:
                # These features are the exclusive province of this class,
                # this should be considered a fatal error for an application
                # to attempt sending them, but we don't raise an error,
                # just log a warning
                self.logger.warning('Application passing hop header "%s"',
                                    header)
                continue
            self.headers.add_header(header, value)
        return self.write

    def write(self, data, force=False):
        '''The write function returned by the :meth:`start_response` method.

        Required by the WSGI specification.

        :param data: bytes to write
        :param force: Optional flag used internally.
        '''
        if not self._headers_sent:
            tosend = self.get_headers()
            self._headers_sent = tosend.flat(self.version, self.status)
            self.fire_event('on_headers')
            self.transport.write(self._headers_sent)
        if data:
            if self.chunked:
                chunks = []
                while len(data) >= MAX_CHUNK_SIZE:
                    chunk, data = data[:MAX_CHUNK_SIZE], data[MAX_CHUNK_SIZE:]
                    chunks.append(chunk_encoding(chunk))
                if data:
                    chunks.append(chunk_encoding(data))
                self.transport.write(b''.join(chunks))
            else:
                self.transport.write(data)
        elif force and self.chunked:
            self.transport.write(chunk_encoding(data))

    ########################################################################
    ##    INTERNALS
    @in_loop
    def _response(self, environ):
        exc_info = None
        response = None
        done = False
        while not done:
            done = True
            try:
                if exc_info is None:
                    if 'SERVER_NAME' not in environ:
                        raise HttpException(status=400)
                    response = self.wsgi_callable(environ, self.start_response)
                else:
                    response = handle_wsgi_error(environ, exc_info)
                #
                if isinstance(response, Future):
                    response = yield response
                #
                if exc_info:
                    self.start_response(response.status,
                                        response.get_headers(), exc_info)
                #
                for chunk in response:
                    if isinstance(chunk, Future):
                        chunk = yield chunk
                    self.write(chunk)
                #
                # make sure we write headers
                self.write(b'', True)

            except IOError:     # client disconnected, end this connection
                self.finished()
            except Exception as exc:
                if wsgi_request(environ).cache.handle_wsgi_error:
                    self.keep_alive = False
                    self.connection.close()
                    self.finished()
                else:
                    done = False
                    exc_info = sys.exc_info()
            else:
                if not self.keep_alive:
                    self.connection.close()
                self.finished()
            finally:
                if hasattr(response, 'close'):
                    try:
                        response.close()
                    except Exception:
                        self.logger.exception(
                            'Error while closing wsgi iterator')

    def is_chunked(self):
        '''Check if the response uses chunked transfer encoding.

        Only use chunked responses when the client is speaking HTTP/1.1
        or newer and there was no Content-Length header set.
        '''
        if (self.version <= (1, 0) or
                self._status == '200 Connection established' or
                has_empty_content(int(self.status[:3]))):
            return False
        elif self.headers.get('Transfer-Encoding') == 'chunked':
            return True
        else:
            return self.content_length is None

    def get_headers(self):
        '''Get the headers to send to the client.
        '''
        if not self._status:
            # we are sending headers but the start_response was not called
            raise HttpException('Headers not set.')
        headers = self.headers
        # Set chunked header if needed
        if self.is_chunked():
            headers['Transfer-Encoding'] = 'chunked'
            headers.pop('content-length', None)
        else:
            headers.pop('Transfer-Encoding', None)
        if self.keep_alive:
            self.keep_alive = keep_alive_with_status(self._status, headers)
        if not self.keep_alive:
            headers['connection'] = 'close'
        return headers

    def wsgi_environ(self):
        #return a the WSGI environ dictionary
        transport = self.transport
        https = True if is_tls(transport.get_extra_info('socket')) else False
        multiprocess = (self.cfg.concurrency == 'process')
        environ = wsgi_environ(self._stream,
                               transport.get_extra_info('sockname'),
                               self.address, self.headers,
                               self.SERVER_SOFTWARE,
                               https=https,
                               extra={'pulsar.connection': self.connection,
                                      'pulsar.cfg': self.cfg,
                                      'wsgi.multiprocess': multiprocess})
        self.keep_alive = keep_alive(self.headers, self.parser.get_version())
        self.headers.update([('Server', self.SERVER_SOFTWARE),
                             ('Date', format_date_time(time.time()))])
        return environ

    def _new_request(self, response):
        connection = response._connection
        if not connection.closed:
            connection.data_received(response._buffer)
            return connection._current_consumer
        return response
Exemple #46
0
class HttpRequest(RequestBase):
    '''An :class:`HttpClient` request for an HTTP resource.

    This class has a similar interface to :class:`urllib.request.Request`.

    :param files: optional dictionary of name, file-like-objects.
    :param allow_redirects: allow the response to follow redirects.

    .. attribute:: method

        The request method

    .. attribute:: version

        HTTP version for this request, usually ``HTTP/1.1``

    .. attribute:: encode_multipart

        If ``True`` (default), defaults POST data as ``multipart/form-data``.
        Pass ``encode_multipart=False`` to default to
        ``application/x-www-form-urlencoded``. In any case, this parameter is
        overwritten by passing the correct content-type header.

    .. attribute:: history

        List of past :class:`.HttpResponse` (collected during redirects).

    .. attribute:: wait_continue

        if ``True``, the :class:`HttpRequest` includes the
        ``Expect: 100-Continue`` header.

    '''
    CONNECT = 'CONNECT'
    _proxy = None
    _ssl = None
    _tunnel = None

    def __init__(self, client, url, method, inp_params=None, headers=None,
                 data=None, files=None, history=None,
                 charset=None, encode_multipart=True, multipart_boundary=None,
                 source_address=None, allow_redirects=False, max_redirects=10,
                 decompress=True, version=None, wait_continue=False,
                 websocket_handler=None, cookies=None, urlparams=None,
                 **ignored):
        self.client = client
        self._data = None
        self.files = files
        self.urlparams = urlparams
        self.inp_params = inp_params or {}
        self.unredirected_headers = Headers(kind='client')
        self.method = method.upper()
        self.full_url = url
        if urlparams:
            self._encode_url(urlparams)
        self.set_proxy(None)
        self.history = history
        self.wait_continue = wait_continue
        self.max_redirects = max_redirects
        self.allow_redirects = allow_redirects
        self.charset = charset or 'utf-8'
        self.version = version
        self.decompress = decompress
        self.encode_multipart = encode_multipart
        self.multipart_boundary = multipart_boundary
        self.websocket_handler = websocket_handler
        self.source_address = source_address
        self.new_parser()
        if self._scheme in tls_schemes:
            self._ssl = client.ssl_context(**ignored)
        self.headers = client.get_headers(self, headers)
        cookies = cookiejar_from_dict(client.cookies, cookies)
        if cookies:
            cookies.add_cookie_header(self)
        self.unredirected_headers['host'] = host_no_default_port(self._scheme,
                                                                 self._netloc)
        client.set_proxy(self)
        self.data = data

    @property
    def address(self):
        '''``(host, port)`` tuple of the HTTP resource'''
        return self._tunnel.address if self._tunnel else (self.host, self.port)

    @property
    def target_address(self):
        return (self.host, int(self.port))

    @property
    def ssl(self):
        '''Context for TLS connections.

        If this is a tunneled request and the tunnel connection is not yet
        established, it returns ``None``.
        '''
        if not self._tunnel:
            return self._ssl

    @property
    def key(self):
        return (self.scheme, self.host, self.port)

    @property
    def proxy(self):
        '''Proxy server for this request.'''
        return self._proxy

    @property
    def netloc(self):
        if self._proxy:
            return self._proxy.netloc
        else:
            return self._netloc

    def __repr__(self):
        return self.first_line()
    __str__ = __repr__

    @property
    def full_url(self):
        '''Full url of endpoint'''
        return urlunparse((self._scheme, self._netloc, self.path,
                           self.params, self.query, self.fragment))

    @full_url.setter
    def full_url(self, url):
        self._scheme, self._netloc, self.path, self.params,\
            self.query, self.fragment = urlparse(url)
        if not self._netloc and self.method == 'CONNECT':
            self._scheme, self._netloc, self.path, self.params,\
                self.query, self.fragment = urlparse('http://%s' % url)

    @property
    def data(self):
        '''Body of request'''
        return self._data

    @data.setter
    def data(self, data):
        self._data = self._encode_data(data)

    def first_line(self):
        if not self._proxy and self.method != self.CONNECT:
            url = urlunparse(('', '', self.path or '/', self.params,
                              self.query, self.fragment))
        else:
            url = self.full_url
        return '%s %s %s' % (self.method, url, self.version)

    def new_parser(self):
        self.parser = self.client.http_parser(kind=1,
                                              decompress=self.decompress,
                                              method=self.method)

    def set_proxy(self, scheme, *host):
        if not host and scheme is None:
            self.scheme = self._scheme
            self._set_hostport(self._scheme, self._netloc)
        else:
            le = 2 + len(host)
            if not le == 3:
                raise TypeError(
                    'set_proxy() takes exactly three arguments (%s given)'
                    % le)
            if not self._ssl:
                self.scheme = scheme
                self._set_hostport(scheme, host[0])
                self._proxy = scheme_host(scheme, host[0])
            else:
                self._tunnel = HttpTunnel(self, scheme, host[0])

    def _set_hostport(self, scheme, host):
        self._tunnel = None
        self._proxy = None
        self.host, self.port = get_hostport(scheme, host)

    def encode(self):
        '''The bytes representation of this :class:`HttpRequest`.

        Called by :class:`HttpResponse` when it needs to encode this
        :class:`HttpRequest` before sending it to the HTTP resource.
        '''
        # Call body before fist_line in case the query is changes.
        first_line = self.first_line()
        body = self.data
        if body and self.wait_continue:
            self.headers['expect'] = '100-continue'
            body = None
        headers = self.headers
        if self.unredirected_headers:
            headers = self.unredirected_headers.copy()
            headers.update(self.headers)
        buffer = [first_line.encode('ascii'), b'\r\n',  bytes(headers)]
        if body:
            buffer.append(body)
        return b''.join(buffer)

    def add_header(self, key, value):
        self.headers[key] = value

    def has_header(self, header_name):
        '''Check ``header_name`` is in this request headers.
        '''
        return (header_name in self.headers or
                header_name in self.unredirected_headers)

    def get_header(self, header_name, default=None):
        '''Retrieve ``header_name`` from this request headers.
        '''
        return self.headers.get(
            header_name, self.unredirected_headers.get(header_name, default))

    def remove_header(self, header_name):
        '''Remove ``header_name`` from this request.
        '''
        self.headers.pop(header_name, None)
        self.unredirected_headers.pop(header_name, None)

    def add_unredirected_header(self, header_name, header_value):
        self.unredirected_headers[header_name] = header_value

    # INTERNAL ENCODING METHODS
    def _encode_data(self, data):
        body = None
        if self.method in ENCODE_URL_METHODS:
            self.files = None
            self._encode_url(data)
        elif isinstance(data, bytes):
            assert self.files is None, ('data cannot be bytes when files are '
                                        'present')
            body = data
        elif isinstance(data, str):
            assert self.files is None, ('data cannot be string when files are '
                                        'present')
            body = to_bytes(data, self.charset)
        elif data or self.files:
            if self.files:
                body, content_type = self._encode_files(data)
            else:
                body, content_type = self._encode_params(data)
            # set files to None, Important!
            self.files = None
            self.headers['Content-Type'] = content_type
        if body:
            self.headers['content-length'] = str(len(body))
        elif 'expect' not in self.headers:
            self.headers.pop('content-length', None)
            self.headers.pop('content-type', None)
        return body

    def _encode_url(self, data):
        query = self.query
        if data:
            data = native_str(data)
            if isinstance(data, str):
                data = parse_qsl(data)
            else:
                data = mapping_iterator(data)
            query = parse_qsl(query)
            query.extend(data)
            query = urlencode(query)
        self.query = query

    def _encode_files(self, data):
        fields = []
        for field, val in mapping_iterator(data or ()):
            if (isinstance(val, str) or isinstance(val, bytes) or
                    not hasattr(val, '__iter__')):
                val = [val]
            for v in val:
                if v is not None:
                    if not isinstance(v, bytes):
                        v = str(v)
                    fields.append((field.decode('utf-8') if
                                   isinstance(field, bytes) else field,
                                   v.encode('utf-8') if isinstance(v, str)
                                   else v))
        for (k, v) in mapping_iterator(self.files):
            # support for explicit filename
            ft = None
            if isinstance(v, (tuple, list)):
                if len(v) == 2:
                    fn, fp = v
                else:
                    fn, fp, ft = v
            else:
                fn = guess_filename(v) or k
                fp = v
            if isinstance(fp, bytes):
                fp = BytesIO(fp)
            elif isinstance(fp, str):
                fp = StringIO(fp)
            if ft:
                new_v = (fn, fp.read(), ft)
            else:
                new_v = (fn, fp.read())
            fields.append((k, new_v))
        #
        return encode_multipart_formdata(fields, charset=self.charset)

    def _encode_params(self, data):
        content_type = self.headers.get('content-type')
        # No content type given, chose one
        if not content_type:
            if self.encode_multipart:
                content_type = MULTIPART_FORM_DATA
            else:
                content_type = FORM_URL_ENCODED

        if content_type in JSON_CONTENT_TYPES:
            body = json.dumps(data).encode(self.charset)
        elif content_type == FORM_URL_ENCODED:
            body = urlencode(data).encode(self.charset)
        elif content_type == MULTIPART_FORM_DATA:
            body, content_type = encode_multipart_formdata(
                data, boundary=self.multipart_boundary, charset=self.charset)
        else:
            raise ValueError("Don't know how to encode body for %s" %
                             content_type)
        return body, content_type
Exemple #47
0
class WsgiResponse(object):
    '''A WSGI response.

    Instances are callable using the standard WSGI call and, importantly,
    iterable::

        response = WsgiResponse(200)

    A :class:`WsgiResponse` is an iterable over bytes to send back to the
    requesting client.

    .. attribute:: status_code

        Integer indicating the HTTP status, (i.e. 200)

    .. attribute:: response

        String indicating the HTTP status (i.e. 'OK')

    .. attribute:: status

        String indicating the HTTP status code and response (i.e. '200 OK')

    .. attribute:: content_type

        The content type of this response. Can be ``None``.

    .. attribute:: headers

        The :class:`pulsar.utils.httpurl.Headers` container for this response.

    .. attribute:: environ

        The dictionary of WSGI environment if passed to the constructor.

    '''
    _started = False
    DEFAULT_STATUS_CODE = 200

    def __init__(self, status=None, content=None, response_headers=None,
                 content_type=None, encoding=None, environ=None):
        self.environ = environ
        self.status_code = status or self.DEFAULT_STATUS_CODE
        self.encoding = encoding
        self.cookies = SimpleCookie()
        self.headers = Headers(response_headers, kind='server')
        self.content = content
        if content_type is not None:
            self.content_type = content_type

    @property
    def started(self):
        return self._started

    @property
    def path(self):
        if self.environ:
            return self.environ.get('PATH_INFO', '')

    @property
    def method(self):
        if self.environ:
            return self.environ.get('REQUEST_METHOD')

    @property
    def connection(self):
        if self.environ:
            return self.environ.get('pulsar.connection')

    def _get_content(self):
        return self._content

    def _set_content(self, content):
        if not self._started:
            if content is None:
                content = ()
            elif ispy3k:
                if isinstance(content, str):
                    content = content.encode(self.encoding or 'utf-8')
            else:   # pragma    nocover
                if isinstance(content, unicode):
                    content = content.encode(self.encoding or 'utf-8')
            if isinstance(content, bytes):
                content = (content,)
            self._content = content
        else:
            raise RuntimeError('Cannot set content. Already iterated')
    content = property(_get_content, _set_content)

    def _get_content_type(self):
        return self.headers.get('content-type')

    def _set_content_type(self, typ):
        if typ:
            self.headers['content-type'] = typ
        else:
            self.headers.pop('content-type', None)
    content_type = property(_get_content_type, _set_content_type)

    def length(self):
        if not self.is_streamed:
            return reduce(lambda x, y: x+len(y), self.content, 0)

    @property
    def response(self):
        return responses.get(self.status_code)

    @property
    def status(self):
        return '%s %s' % (self.status_code, self.response)

    def __str__(self):
        return self.status

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self)

    @property
    def is_streamed(self):
        """If the response is streamed (the response is not an iterable with
length information) this property is `True`.  In this case streamed
means that there is no information about the number of iterations.
This is usually `True` if a generator is passed to the response object."""
        return is_streamed(self.content)

    def __iter__(self):
        if self._started:
            raise RuntimeError('WsgiResponse can be iterated once only')
        self._started = True
        if is_streamed(self.content):
            return wsgi_encoder(self.content, self.encoding or 'utf-8')
        else:
            return iter(self.content)

    def __len__(self):
        return len(self.content)

    def set_cookie(self, key, **kwargs):
        """
        Sets a cookie.

        ``expires`` can be a string in the correct format or a
        ``datetime.datetime`` object in UTC. If ``expires`` is a datetime
        object then ``max_age`` will be calculated.
        """
        set_cookie(self.cookies, key, **kwargs)

    def delete_cookie(self, key, path='/', domain=None):
        set_cookie(self.cookies, key, max_age=0, path=path, domain=domain,
                   expires='Thu, 01-Jan-1970 00:00:00 GMT')

    def get_headers(self):
        headers = self.headers
        if has_empty_content(self.status_code, self.method):
            headers.pop('content-type', None)
            headers.pop('content-length', None)
            self._content = ()
        else:
            if not self.is_streamed:
                cl = 0
                for c in self.content:
                    cl += len(c)
                if cl == 0 and self.content_type in JSON_CONTENT_TYPES:
                    self._content = (b'{}',)
                    cl = len(self._content[0])
                headers['Content-Length'] = str(cl)
            if not self.content_type:
                headers['Content-Type'] = 'text/plain'
        for c in self.cookies.values():
            headers['Set-Cookie'] = c.OutputString()
        return list(headers)

    def has_header(self, header):
        return header in self.headers
    __contains__ = has_header

    def __setitem__(self, header, value):
        self.headers[header] = value

    def __getitem__(self, header):
        return self.headers[header]
Exemple #48
0
 def test_add_header_with_params(self):
     h = Headers()
     h.add_header('content-type', 'text/html', charset=DEFAULT_CHARSET)
     self.assertEqual(h['content-type'], 'text/html; charset=ISO-8859-1')
Exemple #49
0
class WsgiResponse(WsgiResponseGenerator):
    """A WSGI response wrapper initialized by a WSGI request middleware.
Instances are callable using the standard WSGI call::

    response = WsgiResponse(200)
    response(environ, start_response)

A :class:`WsgiResponse` is an iterable over bytes to send back to the requesting
client.

.. attribute:: status_code

    Integer indicating the HTTP status, (i.e. 200)

.. attribute:: response

    String indicating the HTTP status (i.e. 'OK')

.. attribute:: status

    String indicating the HTTP status code and response (i.e. '200 OK')

.. attribute:: environ

    The dictionary of WSGI environment if passed to the constructor.

"""

    _started = False
    DEFAULT_STATUS_CODE = 200

    def __init__(
        self,
        status=None,
        content=None,
        response_headers=None,
        content_type=None,
        encoding=None,
        environ=None,
        start_response=None,
    ):
        super(WsgiResponse, self).__init__(environ, start_response)
        self.status_code = status or self.DEFAULT_STATUS_CODE
        self.encoding = encoding
        self.cookies = SimpleCookie()
        self.headers = Headers(response_headers, kind="server")
        self.content = content
        if content_type is not None:
            self.content_type = content_type

    @property
    def started(self):
        return self._started

    @property
    def path(self):
        if self.environ:
            return self.environ.get("PATH_INFO", "")

    @property
    def method(self):
        if self.environ:
            return self.environ.get("REQUEST_METHOD")

    @property
    def connection(self):
        if self.environ:
            return self.environ.get("pulsar.connection")

    def _get_content(self):
        return self._content

    def _set_content(self, content):
        if not self._started:
            if content is None:
                content = ()
            elif ispy3k:  # what a f*****g pain
                if isinstance(content, str):
                    content = bytes(content, "latin-1")
            else:  # pragma    nocover
                if isinstance(content, unicode):
                    content = bytes(content, "latin-1")
            if isinstance(content, bytes):
                content = (content,)
            self._content = content
        else:
            raise RuntimeError("Cannot set content. Already iterated")

    content = property(_get_content, _set_content)

    def _get_content_type(self):
        return self.headers.get("content-type")

    def _set_content_type(self, typ):
        if typ:
            self.headers["content-type"] = typ
        else:
            self.headers.pop("content-type", None)

    content_type = property(_get_content_type, _set_content_type)

    def __call__(self, environ, start_response, exc_info=None):
        """Make sure the headers are set."""
        if not exc_info:
            for rm in self.middleware:
                try:
                    rm(environ, self)
                except:
                    LOGGER.error("Exception in response middleware", exc_info=True)
        start_response(self.status, self.get_headers(), exc_info=exc_info)
        return self

    def start(self):
        self.__call__(self.environ, self.start_response)

    def length(self):
        if not self.is_streamed:
            return reduce(lambda x, y: x + len(y), self.content, 0)

    @property
    def response(self):
        return responses.get(self.status_code)

    @property
    def status(self):
        return "%s %s" % (self.status_code, self.response)

    def __str__(self):
        return self.status

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, self)

    @property
    def is_streamed(self):
        """If the response is streamed (the response is not an iterable with
length information) this property is `True`.  In this case streamed
means that there is no information about the number of iterations.
This is usually `True` if a generator is passed to the response object."""
        return is_streamed(self.content)

    def __iter__(self):
        if self._started:
            raise RuntimeError("WsgiResponse can be iterated only once")
        self._started = True
        if self.is_streamed:
            return wsgi_iterator(self.content, self.encoding)
        else:
            return iter(self.content)

    def __len__(self):
        return len(self.content)

    def set_cookie(self, key, **kwargs):
        """
        Sets a cookie.

        ``expires`` can be a string in the correct format or a
        ``datetime.datetime`` object in UTC. If ``expires`` is a datetime
        object then ``max_age`` will be calculated.
        """
        set_cookie(self.cookies, key, **kwargs)

    def delete_cookie(self, key, path="/", domain=None):
        set_cookie(self.cookies, key, max_age=0, path=path, domain=domain, expires="Thu, 01-Jan-1970 00:00:00 GMT")

    def get_headers(self):
        headers = self.headers
        if has_empty_content(self.status_code, self.method):
            headers.pop("content-type", None)
            headers.pop("content-length", None)
        elif not self.is_streamed:
            cl = 0
            for c in self.content:
                cl += len(c)
            headers["Content-Length"] = str(cl)
        for c in self.cookies.values():
            headers["Set-Cookie"] = c.OutputString()
        return list(headers)
Exemple #50
0
class HttpClient(AbstractClient):
    '''A client for HTTP/HTTPS servers.

    It handles pool of asynchronous connections.

    :param encode_multipart: optional flag for setting the
        :attr:`encode_multipart` attribute
    :param pool_size: set the :attr:`pool_size` attribute.
    :param store_cookies: set the :attr:`store_cookies` attribute

    .. attribute:: headers

        Default headers for this :class:`HttpClient`.

        Default: :attr:`DEFAULT_HTTP_HEADERS`.

    .. attribute:: cookies

        Default cookies for this :class:`HttpClient`.

    .. attribute:: store_cookies

        If ``True`` it remebers response cookies and send them back to
        serves.

        Default: ``True``

    .. attribute:: timeout

        Default timeout for requests. If None or 0, no timeout on requests

    .. attribute:: encode_multipart

        Flag indicating if body data is by default encoded using the
        ``multipart/form-data`` or ``application/x-www-form-urlencoded``
        encoding.

        It can be overwritten during a :meth:`request`.

        Default: ``True``

    .. attribute:: proxy_info

        Dictionary of proxy servers for this client.

    .. attribute:: pool_size

        The size of a pool of connection for a given host.

    .. attribute:: connection_pools

        Dictionary of connection pools for different hosts

    .. attribute:: DEFAULT_HTTP_HEADERS

        Default headers for this :class:`HttpClient`

    '''
    MANY_TIMES_EVENTS = ('connection_made', 'pre_request', 'on_headers',
                         'post_request', 'connection_lost')
    protocol_factory = partial(Connection, HttpResponse)
    allow_redirects = False
    max_redirects = 10
    '''Maximum number of redirects.

    It can be overwritten on :meth:`request`.'''
    connection_pool = Pool
    '''Connection :class:`.Pool` factory
    '''
    client_version = pulsar.SERVER_SOFTWARE
    '''String for the ``User-Agent`` header.'''
    version = 'HTTP/1.1'
    '''Default HTTP request version for this :class:`HttpClient`.

    It can be overwritten on :meth:`request`.'''
    DEFAULT_HTTP_HEADERS = Headers([('Connection', 'Keep-Alive'),
                                    ('Accept', '*/*'),
                                    ('Accept-Encoding', 'deflate'),
                                    ('Accept-Encoding', 'gzip')],
                                   kind='client')
    DEFAULT_TUNNEL_HEADERS = Headers([('Connection', 'Keep-Alive'),
                                      ('Proxy-Connection', 'Keep-Alive')],
                                     kind='client')
    request_parameters = ('encode_multipart', 'max_redirects', 'decompress',
                          'allow_redirects', 'multipart_boundary', 'version',
                          'websocket_handler')
    # Default hosts not affected by proxy settings. This can be overwritten
    # by specifying the "no" key in the proxy_info dictionary
    no_proxy = set(('localhost', platform.node()))

    def __init__(self,
                 proxy_info=None,
                 cache=None,
                 headers=None,
                 encode_multipart=True,
                 multipart_boundary=None,
                 keyfile=None,
                 certfile=None,
                 cert_reqs=CERT_NONE,
                 ca_certs=None,
                 cookies=None,
                 store_cookies=True,
                 max_redirects=10,
                 decompress=True,
                 version=None,
                 websocket_handler=None,
                 parser=None,
                 trust_env=True,
                 loop=None,
                 client_version=None,
                 timeout=None,
                 pool_size=10,
                 frame_parser=None):
        super().__init__(loop)
        self.client_version = client_version or self.client_version
        self.connection_pools = {}
        self.pool_size = pool_size
        self.trust_env = trust_env
        self.timeout = timeout
        self.store_cookies = store_cookies
        self.max_redirects = max_redirects
        self.cookies = cookiejar_from_dict(cookies)
        self.decompress = decompress
        self.version = version or self.version
        dheaders = self.DEFAULT_HTTP_HEADERS.copy()
        dheaders['user-agent'] = self.client_version
        if headers:
            dheaders.override(headers)
        self.headers = dheaders
        self.tunnel_headers = self.DEFAULT_TUNNEL_HEADERS.copy()
        self.proxy_info = dict(proxy_info or ())
        if not self.proxy_info and self.trust_env:
            self.proxy_info = get_environ_proxies()
            if 'no' not in self.proxy_info:
                self.proxy_info['no'] = ','.join(self.no_proxy)
        self.encode_multipart = encode_multipart
        self.multipart_boundary = multipart_boundary or choose_boundary()
        self.websocket_handler = websocket_handler
        self.https_defaults = {
            'keyfile': keyfile,
            'certfile': certfile,
            'cert_reqs': cert_reqs,
            'ca_certs': ca_certs
        }
        self.http_parser = parser or http_parser
        self.frame_parser = frame_parser or websocket.frame_parser
        # Add hooks
        self.bind_event('pre_request', Tunneling(self._loop))
        self.bind_event('on_headers', handle_101)
        self.bind_event('on_headers', handle_100)
        self.bind_event('on_headers', handle_cookies)
        self.bind_event('post_request', handle_redirect)

    @property
    def websocket_key(self):
        if not hasattr(self, '_websocket_key'):
            self._websocket_key = native_str(b64encode(os.urandom(16)),
                                             DEFAULT_CHARSET)
        return self._websocket_key

    def connect(self, address):
        if isinstance(address, tuple):
            address = ':'.join(('%s' % v for v in address))
        return self.request('CONNECT', address)

    def get(self, url, **kwargs):
        '''Sends a GET request and returns a :class:`HttpResponse` object.

        :params url: url for the new :class:`HttpRequest` object.
        :param \*\*kwargs: Optional arguments for the :meth:`request` method.
        '''
        kwargs.setdefault('allow_redirects', True)
        return self.request('GET', url, **kwargs)

    def options(self, url, **kwargs):
        '''Sends a OPTIONS request and returns a :class:`HttpResponse` object.

        :params url: url for the new :class:`HttpRequest` object.
        :param \*\*kwargs: Optional arguments for the :meth:`request` method.
        '''
        kwargs.setdefault('allow_redirects', True)
        return self.request('OPTIONS', url, **kwargs)

    def head(self, url, **kwargs):
        '''Sends a HEAD request and returns a :class:`HttpResponse` object.

        :params url: url for the new :class:`HttpRequest` object.
        :param \*\*kwargs: Optional arguments for the :meth:`request` method.
        '''
        return self.request('HEAD', url, **kwargs)

    def post(self, url, **kwargs):
        '''Sends a POST request and returns a :class:`HttpResponse` object.

        :params url: url for the new :class:`HttpRequest` object.
        :param \*\*kwargs: Optional arguments for the :meth:`request` method.
        '''
        return self.request('POST', url, **kwargs)

    def put(self, url, **kwargs):
        '''Sends a PUT request and returns a :class:`HttpResponse` object.

        :params url: url for the new :class:`HttpRequest` object.
        :param \*\*kwargs: Optional arguments for the :meth:`request` method.
        '''
        return self.request('PUT', url, **kwargs)

    def patch(self, url, **kwargs):
        '''Sends a PATCH request and returns a :class:`HttpResponse` object.

        :params url: url for the new :class:`HttpRequest` object.
        :param \*\*kwargs: Optional arguments for the :meth:`request` method.
        '''
        return self.request('PATCH', url, **kwargs)

    def delete(self, url, **kwargs):
        '''Sends a DELETE request and returns a :class:`HttpResponse` object.

        :params url: url for the new :class:`HttpRequest` object.
        :param \*\*kwargs: Optional arguments for the :meth:`request` method.
        '''
        return self.request('DELETE', url, **kwargs)

    def request(self, method, url, timeout=None, **params):
        '''Constructs and sends a request to a remote server.

        It returns a :class:`.Future` which results in a
        :class:`HttpResponse` object.

        :param method: request method for the :class:`HttpRequest`.
        :param url: URL for the :class:`HttpRequest`.
        :parameter response: optional pre-existing :class:`HttpResponse` which
            starts a new request (for redirects, digest authentication and
            so forth).
        :param params: optional parameters for the :class:`HttpRequest`
            initialisation.

        :rtype: a :class:`.Future`
        '''
        response = self._request(method, url, **params)
        if timeout is None:
            timeout = self.timeout
        if timeout:
            response = wait_for(response, timeout, loop=self._loop)
        if not self._loop.is_running():
            return self._loop.run_until_complete(response)
        else:
            return response

    def _request(self, method, url, **params):
        nparams = params.copy()
        nparams.update(((name, getattr(self, name))
                        for name in self.request_parameters
                        if name not in params))
        request = HttpRequest(self, url, method, params, **nparams)
        pool = self.connection_pools.get(request.key)
        if pool is None:
            host, port = request.address
            pool = self.connection_pool(partial(self._connect, host, port,
                                                request.ssl),
                                        pool_size=self.pool_size,
                                        loop=self._loop)
            self.connection_pools[request.key] = pool
        conn = yield from pool.connect()
        with conn:
            consumer = conn.current_consumer()
            # bind request-specific events
            consumer.bind_events(**request.inp_params)
            consumer.start(request)
            response = yield from consumer.on_finished
            if response is not None:
                consumer = response
            if consumer.request_again:
                if isinstance(consumer.request_again, Exception):
                    raise consumer.request_again
                elif isinstance(consumer.request_again, ProtocolConsumer):
                    consumer = consumer.request_again
            headers = consumer.headers
            if (not headers or not headers.has('connection', 'keep-alive')
                    or consumer.status_code == 101):
                conn.detach()
        if isinstance(consumer.request_again, tuple):
            method, url, params = consumer.request_again
            consumer = yield from self._request(method, url, **params)
        return consumer

    def close(self, async=True):
        '''Close all connections.

        Fire the ``finish`` :ref:`one time event <one-time-event>` once done.
        Return the :class:`.Future` fired by the ``finish`` event.
        '''
        for p in self.connection_pools.values():
            p.close()
        self.connection_pools.clear()
Exemple #51
0
class HttpRequest(RequestBase):
    """An :class:`HttpClient` request for an HTTP resource.

    This class has a similar interface to :class:`urllib.request.Request`.

    :param files: optional dictionary of name, file-like-objects.
    :param allow_redirects: allow the response to follow redirects.

    .. attribute:: method

        The request method

    .. attribute:: version

        HTTP version for this request, usually ``HTTP/1.1``

    .. attribute:: encode_multipart

        If ``True`` (default), defaults POST data as ``multipart/form-data``.
        Pass ``encode_multipart=False`` to default to
        ``application/x-www-form-urlencoded``. In any case, this parameter is
        overwritten by passing the correct content-type header.

    .. attribute:: history

        List of past :class:`.HttpResponse` (collected during redirects).

    .. attribute:: wait_continue

        if ``True``, the :class:`HttpRequest` includes the
        ``Expect: 100-Continue`` header.

    .. attribute:: stream

        Allow for streaming body

    """
    _proxy = None
    _ssl = None
    _tunnel = None
    _write_done = False

    def __init__(self, client, url, method, inp_params=None, headers=None,
                 data=None, files=None, history=None, auth=None,
                 charset=None, encode_multipart=True, multipart_boundary=None,
                 source_address=None, allow_redirects=False, max_redirects=10,
                 decompress=True, version=None, wait_continue=False,
                 websocket_handler=None, cookies=None, urlparams=None,
                 stream=False, proxies=None, verify=True, **ignored):
        self.client = client
        self._data = None
        self.files = files
        self.urlparams = urlparams
        self.inp_params = inp_params or {}
        self.unredirected_headers = Headers(kind='client')
        self.method = method.upper()
        self.full_url = url
        if urlparams:
            self._encode_url(urlparams)
        self.history = history
        self.wait_continue = wait_continue
        self.max_redirects = max_redirects
        self.allow_redirects = allow_redirects
        self.charset = charset or 'utf-8'
        self.version = version
        self.decompress = decompress
        self.encode_multipart = encode_multipart
        self.multipart_boundary = multipart_boundary
        self.websocket_handler = websocket_handler
        self.source_address = source_address
        self.stream = stream
        self.verify = verify
        self.new_parser()
        if self._scheme in tls_schemes:
            self._ssl = client.ssl_context(verify=self.verify, **ignored)
        if auth and not isinstance(auth, Auth):
            auth = HTTPBasicAuth(*auth)
        self.auth = auth
        self.headers = client.get_headers(self, headers)
        cookies = cookiejar_from_dict(client.cookies, cookies)
        if cookies:
            cookies.add_cookie_header(self)
        self.unredirected_headers['host'] = host_no_default_port(self._scheme,
                                                                 self._netloc)
        self.data = data
        self._set_proxy(proxies)

    @property
    def address(self):
        """``(host, port)`` tuple of the HTTP resource
        """
        return self._tunnel.address if self._tunnel else (self.host, self.port)

    @property
    def target_address(self):
        return (self.host, int(self.port))

    @property
    def ssl(self):
        """Context for TLS connections.

        If this is a tunneled request and the tunnel connection is not yet
        established, it returns ``None``.
        """
        if not self._tunnel:
            return self._ssl

    @property
    def key(self):
        tunnel = self._tunnel.full_url if self._tunnel else None
        return (self.scheme, self.host, self.port, tunnel, self.verify)

    @property
    def proxy(self):
        """Proxy server for this request.
        """
        return self._proxy

    @property
    def tunnel(self):
        """Tunnel for this request.
        """
        return self._tunnel

    @property
    def netloc(self):
        if self._proxy:
            return self._proxy.netloc
        else:
            return self._netloc

    def __repr__(self):
        return self.first_line()
    __str__ = __repr__

    @property
    def full_url(self):
        """Full url of endpoint
        """
        return urlunparse((self._scheme, self._netloc, self.path,
                           self.params, self.query, self.fragment))

    @full_url.setter
    def full_url(self, url):
        self._scheme, self._netloc, self.path, self.params,\
            self.query, self.fragment = urlparse(url)
        if not self._netloc and self.method == 'CONNECT':
            self._scheme, self._netloc, self.path, self.params,\
                self.query, self.fragment = urlparse('http://%s' % url)

    @property
    def data(self):
        """Body of request
        """
        return self._data

    @data.setter
    def data(self, data):
        self._data = self._encode_data(data)

    def first_line(self):
        if self._proxy:
            if self.method == 'CONNECT':
                url = self._netloc
            else:
                url = self.full_url
        else:
            url = urlunparse(('', '', self.path or '/', self.params,
                              self.query, self.fragment))
        return '%s %s %s' % (self.method, url, self.version)

    def new_parser(self):
        self.parser = self.client.http_parser(kind=1,
                                              decompress=self.decompress,
                                              method=self.method)

    def is_chunked(self):
        return self.data and 'content-length' not in self.headers

    def encode(self):
        """The bytes representation of this :class:`HttpRequest`.

        Called by :class:`HttpResponse` when it needs to encode this
        :class:`HttpRequest` before sending it to the HTTP resource.
        """
        # Call body before fist_line in case the query is changes.
        first_line = self.first_line()
        if self.data and self.wait_continue:
            self.headers['expect'] = '100-continue'
        headers = self.headers
        if self.unredirected_headers:
            headers = self.unredirected_headers.copy()
            headers.update(self.headers)
        buffer = [first_line.encode('ascii'), b'\r\n',  bytes(headers)]
        return b''.join(buffer)

    def add_header(self, key, value):
        self.headers[key] = value

    def has_header(self, header_name):
        """Check ``header_name`` is in this request headers.
        """
        return (header_name in self.headers or
                header_name in self.unredirected_headers)

    def get_header(self, header_name, default=None):
        """Retrieve ``header_name`` from this request headers.
        """
        return self.headers.get(
            header_name, self.unredirected_headers.get(header_name, default))

    def remove_header(self, header_name):
        """Remove ``header_name`` from this request.
        """
        val1 = self.headers.pop(header_name, None)
        val2 = self.unredirected_headers.pop(header_name, None)
        return val1 or val2

    def add_unredirected_header(self, header_name, header_value):
        self.unredirected_headers[header_name] = header_value

    def write_body(self, transport):
        assert not self._write_done, 'Body already sent'
        self._write_done = True
        if not self.data:
            return
        if is_streamed(self.data):
            ensure_future(self._write_streamed_data(transport),
                          loop=transport._loop)
        else:
            self._write_body_data(transport, self.data, True)

    # INTERNAL ENCODING METHODS
    def _encode_data(self, data):
        body = None
        if self.method in ENCODE_URL_METHODS:
            self.files = None
            self._encode_url(data)
        elif isinstance(data, bytes):
            assert self.files is None, ('data cannot be bytes when files are '
                                        'present')
            body = data
        elif isinstance(data, str):
            assert self.files is None, ('data cannot be string when files are '
                                        'present')
            body = to_bytes(data, self.charset)
        elif data and is_streamed(data):
            assert self.files is None, ('data cannot be an iterator when '
                                        'files are present')
            if 'content-type' not in self.headers:
                self.headers['content-type'] = 'application/octet-stream'
            if 'content-length' not in self.headers:
                self.headers['transfer-encoding'] = 'chunked'
            return data
        elif data or self.files:
            if self.files:
                body, content_type = self._encode_files(data)
            else:
                body, content_type = self._encode_params(data)
            # set files to None, Important!
            self.files = None
            self.headers['Content-Type'] = content_type
        if body:
            self.headers['content-length'] = str(len(body))
        elif 'expect' not in self.headers:
            self.headers.pop('content-length', None)
            self.headers.pop('content-type', None)
        return body

    def _encode_url(self, data):
        query = self.query
        if data:
            data = native_str(data)
            if isinstance(data, str):
                data = parse_qsl(data)
            else:
                data = mapping_iterator(data)
            query = parse_qsl(query)
            query.extend(data)
            query = urlencode(query)
        self.query = query

    def _encode_files(self, data):
        fields = []
        for field, val in mapping_iterator(data or ()):
            if (isinstance(val, str) or isinstance(val, bytes) or
                    not hasattr(val, '__iter__')):
                val = [val]
            for v in val:
                if v is not None:
                    if not isinstance(v, bytes):
                        v = str(v)
                    fields.append((field.decode('utf-8') if
                                   isinstance(field, bytes) else field,
                                   v.encode('utf-8') if isinstance(v, str)
                                   else v))
        for (k, v) in mapping_iterator(self.files):
            # support for explicit filename
            ft = None
            if isinstance(v, (tuple, list)):
                if len(v) == 2:
                    fn, fp = v
                else:
                    fn, fp, ft = v
            else:
                fn = guess_filename(v) or k
                fp = v
            if isinstance(fp, bytes):
                fp = BytesIO(fp)
            elif isinstance(fp, str):
                fp = StringIO(fp)
            if ft:
                new_v = (fn, fp.read(), ft)
            else:
                new_v = (fn, fp.read())
            fields.append((k, new_v))
        #
        return encode_multipart_formdata(fields, charset=self.charset)

    def _encode_params(self, data):
        content_type = self.headers.get('content-type')
        # No content type given, chose one
        if not content_type:
            if self.encode_multipart:
                content_type = MULTIPART_FORM_DATA
            else:
                content_type = FORM_URL_ENCODED

        if content_type in JSON_CONTENT_TYPES:
            body = json.dumps(data).encode(self.charset)
        elif content_type == FORM_URL_ENCODED:
            body = urlencode(data).encode(self.charset)
        elif content_type == MULTIPART_FORM_DATA:
            body, content_type = encode_multipart_formdata(
                data, boundary=self.multipart_boundary, charset=self.charset)
        else:
            raise ValueError("Don't know how to encode body for %s" %
                             content_type)
        return body, content_type

    def _write_body_data(self, transport, data, finish=False):
        if self.is_chunked():
            data = http_chunks(data, finish)
        elif data:
            data = (data,)
        else:
            return
        for chunk in data:
            transport.write(chunk)

    @asyncio.coroutine
    def _write_streamed_data(self, transport):
        for data in self.data:
            if isawaitable(data):
                data = yield from data
            self._write_body_data(transport, data)
        self._write_body_data(transport, b'', True)

    # PROXY INTERNALS
    def _set_proxy(self, proxies):
        request_proxies = self.client.proxies.copy()
        if proxies:
            request_proxies.update(proxies)
        self.proxies = request_proxies
        self.scheme = self._scheme
        self._set_hostport(self._scheme, self._netloc)
        #
        if self.scheme in request_proxies:
            hostonly = self.host
            no_proxy = [n for n in request_proxies.get('no', '').split(',')
                        if n]
            if not any(map(hostonly.endswith, no_proxy)):
                url = request_proxies[self.scheme]
                p = urlparse(url)
                if not p.scheme:
                    raise ValueError('Could not understand proxy %s' % url)
                scheme = p.scheme
                host = p.netloc
                if not self._ssl:
                    self.scheme = scheme
                    self._set_hostport(scheme, host)
                    self._proxy = scheme_host(scheme, host)
                else:
                    self._tunnel = HttpTunnel(self, scheme, host)

    def _set_hostport(self, scheme, host):
        self._tunnel = None
        self._proxy = None
        self.host, self.port = get_hostport(scheme, host)
Exemple #52
0
class WsgiResponse:
    """A WSGI response.

    Instances are callable using the standard WSGI call and, importantly,
    iterable::

        response = WsgiResponse(200)

    A :class:`WsgiResponse` is an iterable over bytes to send back to the
    requesting client.

    .. attribute:: status_code

        Integer indicating the HTTP status, (i.e. 200)

    .. attribute:: response

        String indicating the HTTP status (i.e. 'OK')

    .. attribute:: status

        String indicating the HTTP status code and response (i.e. '200 OK')

    .. attribute:: content_type

        The content type of this response. Can be ``None``.

    .. attribute:: headers

        The :class:`.Headers` container for this response.

    .. attribute:: environ

        The dictionary of WSGI environment if passed to the constructor.

    .. attribute:: cookies

        A python :class:`SimpleCookie` container of cookies included in the
        request as well as cookies set during the response.
    """
    _iterated = False
    _started = False
    DEFAULT_STATUS_CODE = 200

    def __init__(self, status=None, content=None, response_headers=None,
                 content_type=None, encoding=None, environ=None,
                 can_store_cookies=True):
        self.environ = environ
        self.status_code = status or self.DEFAULT_STATUS_CODE
        self.encoding = encoding
        self.cookies = SimpleCookie()
        self.headers = Headers(response_headers, kind='server')
        self.content = content
        self._can_store_cookies = can_store_cookies
        if content_type is not None:
            self.content_type = content_type

    @property
    def started(self):
        return self._started

    @property
    def iterated(self):
        return self._iterated

    @property
    def path(self):
        if self.environ:
            return self.environ.get('PATH_INFO', '')

    @property
    def method(self):
        if self.environ:
            return self.environ.get('REQUEST_METHOD')

    @property
    def connection(self):
        if self.environ:
            return self.environ.get('pulsar.connection')

    @property
    def content(self):
        return self._content

    @content.setter
    def content(self, content):
        if not self._iterated:
            if content is None:
                content = ()
            else:
                if isinstance(content, str):
                    if not self.encoding:   # use utf-8 if not set
                        self.encoding = 'utf-8'
                    content = content.encode(self.encoding)

            if isinstance(content, bytes):
                content = (content,)
            self._content = content
        else:
            raise RuntimeError('Cannot set content. Already iterated')

    def _get_content_type(self):
        return self.headers.get('content-type')

    def _set_content_type(self, typ):
        if typ:
            self.headers['content-type'] = typ
        else:
            self.headers.pop('content-type', None)
    content_type = property(_get_content_type, _set_content_type)

    @property
    def response(self):
        return responses.get(self.status_code)

    @property
    def status(self):
        return '%s %s' % (self.status_code, self.response)

    def __str__(self):
        return self.status

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self)

    @property
    def is_streamed(self):
        """Check if the response is streamed.

        A streamed response is an iterable with no length information.
        In this case streamed means that there is no information about
        the number of iterations.

        This is usually `True` if a generator is passed to the response object.
        """
        try:
            len(self.content)
        except TypeError:
            return True
        return False

    def can_set_cookies(self):
        if self.status_code < 400:
            return self._can_store_cookies

    def length(self):
        if not self.is_streamed:
            return reduce(lambda x, y: x+len(y), self.content, 0)

    def start(self, start_response):
        assert not self._started
        self._started = True
        return start_response(self.status, self.get_headers())

    def __iter__(self):
        if self._iterated:
            raise RuntimeError('WsgiResponse can be iterated once only')
        self._started = True
        self._iterated = True
        if self.is_streamed:
            return wsgi_encoder(self.content, self.encoding or 'utf-8')
        else:
            return iter(self.content)

    def close(self):
        """Close this response, required by WSGI
        """
        if self.is_streamed:
            if hasattr(self.content, 'close'):
                self.content.close()

    def set_cookie(self, key, **kwargs):
        """
        Sets a cookie.

        ``expires`` can be a string in the correct format or a
        ``datetime.datetime`` object in UTC. If ``expires`` is a datetime
        object then ``max_age`` will be calculated.
        """
        set_cookie(self.cookies, key, **kwargs)

    def delete_cookie(self, key, path='/', domain=None):
        set_cookie(self.cookies, key, max_age=0, path=path, domain=domain,
                   expires='Thu, 01-Jan-1970 00:00:00 GMT')

    def get_headers(self):
        """The list of headers for this response
        """
        headers = self.headers
        if has_empty_content(self.status_code):
            headers.pop('content-type', None)
            headers.pop('content-length', None)
            self._content = ()
        else:
            if not self.is_streamed:
                cl = 0
                for c in self.content:
                    cl += len(c)
                if cl == 0 and self.content_type in JSON_CONTENT_TYPES:
                    self._content = (b'{}',)
                    cl = len(self._content[0])
                headers['Content-Length'] = str(cl)
            ct = self.content_type
            # content type encoding available
            if self.encoding:
                ct = ct or 'text/plain'
                if 'charset=' not in ct:
                    ct = '%s; charset=%s' % (ct, self.encoding)
            if ct:
                headers['Content-Type'] = ct
            if self.method == HEAD:
                self._content = ()
        if self.can_set_cookies():
            for c in self.cookies.values():
                headers.add_header('Set-Cookie', c.OutputString())
        return list(headers)

    def has_header(self, header):
        return header in self.headers
    __contains__ = has_header

    def __setitem__(self, header, value):
        self.headers[header] = value

    def __getitem__(self, header):
        return self.headers[header]
Exemple #53
0
class HttpServerResponse(ProtocolConsumer):
    '''Server side WSGI :class:`.ProtocolConsumer`.

    .. attribute:: wsgi_callable

        The wsgi callable handling requests.
    '''
    _status = None
    _headers_sent = None
    _body_reader = None
    _buffer = None
    _logger = LOGGER
    SERVER_SOFTWARE = pulsar.SERVER_SOFTWARE
    ONE_TIME_EVENTS = ProtocolConsumer.ONE_TIME_EVENTS + ('on_headers',)

    def __init__(self, wsgi_callable, cfg, server_software=None, loop=None):
        super().__init__(loop=loop)
        self.wsgi_callable = wsgi_callable
        self.cfg = cfg
        self.parser = http_parser(kind=0)
        self.headers = Headers()
        self.keep_alive = False
        self.SERVER_SOFTWARE = server_software or self.SERVER_SOFTWARE

    @property
    def headers_sent(self):
        '''Available once the headers have been sent to the client.

        These are the bytes representing the first response line and
        the headers
        '''
        return self._headers_sent

    def data_received(self, data):
        '''Implements :meth:`~.ProtocolConsumer.data_received` method.

        Once we have a full HTTP message, build the wsgi ``environ`` and
        delegate the response to the :func:`wsgi_callable` function.
        '''
        parser = self.parser
        processed = parser.execute(data, len(data))
        if parser.is_headers_complete():
            if not self._body_reader:
                headers = Headers(parser.get_headers(), kind='client')
                self._body_reader = HttpBodyReader(headers,
                                                   parser,
                                                   self.transport,
                                                   loop=self._loop)
                self._response(self.wsgi_environ())
            body = parser.recv_body()
            if body:
                self._body_reader.feed_data(body)
        #
        if parser.is_message_complete():
            #
            self._body_reader.feed_eof()

            if processed < len(data):
                if not self._buffer:
                    self._buffer = data[processed:]
                    self.bind_event('post_request', self._new_request)
                else:
                    self._buffer += data[processed:]
        #
        elif processed < len(data):
            # This is a parsing error, the client must have sent
            # bogus data
            raise ProtocolError

    @property
    def status(self):
        return self._status

    @property
    def upgrade(self):
        return self.headers.get('upgrade')

    @property
    def chunked(self):
        return self.headers.get('Transfer-Encoding') == 'chunked'

    @property
    def content_length(self):
        c = self.headers.get('Content-Length')
        if c:
            return int(c)

    @property
    def version(self):
        return self.parser.get_version()

    def start_response(self, status, response_headers, exc_info=None):
        '''WSGI compliant ``start_response`` callable, see pep3333_.

        The application may call start_response more than once, if and only
        if the ``exc_info`` argument is provided.
        More precisely, it is a fatal error to call ``start_response`` without
        the ``exc_info`` argument if start_response has already been called
        within the current invocation of the application.

        :parameter status: an HTTP ``status`` string like ``200 OK`` or
            ``404 Not Found``.
        :parameter response_headers: a list of ``(header_name, header_value)``
            tuples. It must be a Python list. Each header_name must be a valid
            HTTP header field-name (as defined by RFC 2616_, Section 4.2),
            without a trailing colon or other punctuation.
        :parameter exc_info: optional python ``sys.exc_info()`` tuple.
            This argument should be supplied by the application only if
            ``start_response`` is being called by an error handler.
        :return: The :meth:`write` method.

        ``HOP_HEADERS`` are not considered but no error is raised.

        .. _pep3333: http://www.python.org/dev/peps/pep-3333/
        .. _2616: http://www.faqs.org/rfcs/rfc2616.html
        '''
        if exc_info:
            try:
                if self._headers_sent:
                    # if exc_info is provided, and the HTTP headers have
                    # already been sent, start_response must raise an error,
                    # and should re-raise using the exc_info tuple
                    reraise(*exc_info)
            finally:
                # Avoid circular reference
                exc_info = None
        elif self._status:
            # Headers already set. Raise error
            raise HttpException("Response headers already set!")
        self._status = status
        if type(response_headers) is not list:
            raise TypeError("Headers must be a list of name/value tuples")
        for header, value in response_headers:
            if header.lower() in HOP_HEADERS:
                # These features are the exclusive province of this class,
                # this should be considered a fatal error for an application
                # to attempt sending them, but we don't raise an error,
                # just log a warning
                self.logger.warning('Application passing hop header "%s"',
                                    header)
                continue
            self.headers.add_header(header, value)
        return self.write

    def write(self, data, force=False):
        '''The write function returned by the :meth:`start_response` method.

        Required by the WSGI specification.

        :param data: bytes to write
        :param force: Optional flag used internally
        :return: a :class:`~asyncio.Future` or the number of bytes written
        '''
        write = super().write
        chunks = []
        if not self._headers_sent:
            tosend = self.get_headers()
            self._headers_sent = tosend.flat(self.version, self.status)
            self.fire_event('on_headers')
            chunks.append(self._headers_sent)
        if data:
            if self.chunked:
                chunks.extend(http_chunks(data))
            else:
                chunks.append(data)
        elif force and self.chunked:
            chunks.extend(http_chunks(data, True))
        if chunks:
            return write(b''.join(chunks))

    ########################################################################
    #    INTERNALS
    @task
    def _response(self, environ):
        exc_info = None
        response = None
        done = False
        alive = self.cfg.keep_alive or 15
        while not done:
            done = True
            try:
                if exc_info is None:
                    if (not environ.get('HTTP_HOST') and
                            environ['SERVER_PROTOCOL'] != 'HTTP/1.0'):
                        raise BadRequest
                    response = self.wsgi_callable(environ, self.start_response)
                    if isfuture(response):
                        response = yield from wait_for(response, alive)
                else:
                    response = handle_wsgi_error(environ, exc_info)
                    if isfuture(response):
                        response = yield from wait_for(response, alive)
                #
                if exc_info:
                    self.start_response(response.status,
                                        response.get_headers(), exc_info)
                #
                # Do the actual writing
                loop = self._loop
                start = loop.time()
                for chunk in response:
                    if isfuture(chunk):
                        chunk = yield from wait_for(chunk, alive)
                        start = loop.time()
                    result = self.write(chunk)
                    if isfuture(result):
                        yield from wait_for(result, alive)
                        start = loop.time()
                    else:
                        time_in_loop = loop.time() - start
                        if time_in_loop > MAX_TIME_IN_LOOP:
                            self.logger.debug(
                                'Released the event loop after %.3f seconds',
                                time_in_loop)
                            yield None
                            start = loop.time()
                #
                # make sure we write headers and last chunk if needed
                self.write(b'', True)

            # client disconnected, end this connection
            except (IOError, AbortWsgi):
                self.finished()
            except Exception:
                if wsgi_request(environ).cache.handle_wsgi_error:
                    self.keep_alive = False
                    self._write_headers()
                    self.connection.close()
                    self.finished()
                else:
                    done = False
                    exc_info = sys.exc_info()
            else:
                log_wsgi_info(self.logger.info, environ, self.status)
                self.finished()
                if not self.keep_alive:
                    self.logger.debug('No keep alive, closing connection %s',
                                      self.connection)
                    self.connection.close()
            finally:
                if hasattr(response, 'close'):
                    try:
                        response.close()
                    except Exception:
                        self.logger.exception(
                            'Error while closing wsgi iterator')

    def is_chunked(self):
        '''Check if the response uses chunked transfer encoding.

        Only use chunked responses when the client is speaking HTTP/1.1
        or newer and there was no Content-Length header set.
        '''
        if (self.version <= (1, 0) or
                self._status == '200 Connection established' or
                has_empty_content(int(self.status[:3]))):
            return False
        elif self.headers.get('Transfer-Encoding') == 'chunked':
            return True
        else:
            return self.content_length is None

    def get_headers(self):
        '''Get the headers to send to the client.
        '''
        if not self._status:
            # we are sending headers but the start_response was not called
            raise HttpException('Headers not set.')
        headers = self.headers
        # Set chunked header if needed
        if self.is_chunked():
            headers['Transfer-Encoding'] = 'chunked'
            headers.pop('content-length', None)
        else:
            headers.pop('Transfer-Encoding', None)
        if self.keep_alive:
            self.keep_alive = keep_alive_with_status(self._status, headers)
        if not self.keep_alive:
            headers['connection'] = 'close'
        return headers

    def wsgi_environ(self):
        # return a the WSGI environ dictionary
        transport = self.transport
        https = True if is_tls(transport.get_extra_info('socket')) else False
        multiprocess = (self.cfg.concurrency == 'process')
        environ = wsgi_environ(self._body_reader,
                               self.parser,
                               self._body_reader.headers,
                               transport.get_extra_info('sockname'),
                               self.address,
                               self.headers,
                               self.SERVER_SOFTWARE,
                               https=https,
                               extra={'pulsar.connection': self.connection,
                                      'pulsar.cfg': self.cfg,
                                      'wsgi.multiprocess': multiprocess})
        self.keep_alive = keep_alive(self.headers, self.parser.get_version(),
                                     environ['REQUEST_METHOD'])
        self.headers.update([('Server', self.SERVER_SOFTWARE),
                             ('Date', format_date_time(time.time()))])
        return environ

    def _new_request(self, _, exc=None):
        connection = self._connection
        connection.data_received(self._buffer)

    def _write_headers(self):
        if not self._headers_sent:
            if self.content_length:
                self.headers['Content-Length'] = '0'
            self.write(b'')
Exemple #54
0
class WsgiResponse:
    """A WSGI response.

    Instances are callable using the standard WSGI call and, importantly,
    iterable::

        response = WsgiResponse(200)

    A :class:`WsgiResponse` is an iterable over bytes to send back to the
    requesting client.

    .. attribute:: status_code

        Integer indicating the HTTP status, (i.e. 200)

    .. attribute:: response

        String indicating the HTTP status (i.e. 'OK')

    .. attribute:: status

        String indicating the HTTP status code and response (i.e. '200 OK')

    .. attribute:: content_type

        The content type of this response. Can be ``None``.

    .. attribute:: headers

        The :class:`.Headers` container for this response.

    .. attribute:: environ

        The dictionary of WSGI environment if passed to the constructor.

    .. attribute:: cookies

        A python :class:`SimpleCookie` container of cookies included in the
        request as well as cookies set during the response.
    """
    _iterated = False
    _started = False
    DEFAULT_STATUS_CODE = 200

    def __init__(self,
                 status=None,
                 content=None,
                 response_headers=None,
                 content_type=None,
                 encoding=None,
                 environ=None,
                 can_store_cookies=True):
        self.environ = environ
        self.status_code = status or self.DEFAULT_STATUS_CODE
        self.encoding = encoding
        self.cookies = SimpleCookie()
        self.headers = Headers(response_headers, kind='server')
        self.content = content
        self._can_store_cookies = can_store_cookies
        if content_type is not None:
            self.content_type = content_type

    @property
    def started(self):
        return self._started

    @property
    def iterated(self):
        return self._iterated

    @property
    def path(self):
        if self.environ:
            return self.environ.get('PATH_INFO', '')

    @property
    def method(self):
        if self.environ:
            return self.environ.get('REQUEST_METHOD')

    @property
    def connection(self):
        if self.environ:
            return self.environ.get('pulsar.connection')

    @property
    def content(self):
        return self._content

    @content.setter
    def content(self, content):
        if not self._iterated:
            if content is None:
                content = ()
            else:
                if isinstance(content, str):
                    if not self.encoding:  # use utf-8 if not set
                        self.encoding = 'utf-8'
                    content = content.encode(self.encoding)

            if isinstance(content, bytes):
                content = (content, )
            self._content = content
        else:
            raise RuntimeError('Cannot set content. Already iterated')

    def _get_content_type(self):
        return self.headers.get('content-type')

    def _set_content_type(self, typ):
        if typ:
            self.headers['content-type'] = typ
        else:
            self.headers.pop('content-type', None)

    content_type = property(_get_content_type, _set_content_type)

    @property
    def response(self):
        return responses.get(self.status_code)

    @property
    def status(self):
        return '%s %s' % (self.status_code, self.response)

    def __str__(self):
        return self.status

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self)

    @property
    def is_streamed(self):
        """Check if the response is streamed.

        A streamed response is an iterable with no length information.
        In this case streamed means that there is no information about
        the number of iterations.

        This is usually `True` if a generator is passed to the response object.
        """
        try:
            len(self.content)
        except TypeError:
            return True
        return False

    def can_set_cookies(self):
        if self.status_code < 400:
            return self._can_store_cookies

    def length(self):
        if not self.is_streamed:
            return reduce(lambda x, y: x + len(y), self.content, 0)

    def start(self, start_response):
        assert not self._started
        self._started = True
        return start_response(self.status, self.get_headers())

    def __iter__(self):
        if self._iterated:
            raise RuntimeError('WsgiResponse can be iterated once only')
        self._started = True
        self._iterated = True
        if self.is_streamed:
            return wsgi_encoder(self.content, self.encoding or 'utf-8')
        else:
            return iter(self.content)

    def close(self):
        """Close this response, required by WSGI
        """
        if self.is_streamed:
            if hasattr(self.content, 'close'):
                self.content.close()

    def set_cookie(self, key, **kwargs):
        """
        Sets a cookie.

        ``expires`` can be a string in the correct format or a
        ``datetime.datetime`` object in UTC. If ``expires`` is a datetime
        object then ``max_age`` will be calculated.
        """
        set_cookie(self.cookies, key, **kwargs)

    def delete_cookie(self, key, path='/', domain=None):
        set_cookie(self.cookies,
                   key,
                   max_age=0,
                   path=path,
                   domain=domain,
                   expires='Thu, 01-Jan-1970 00:00:00 GMT')

    def get_headers(self):
        """The list of headers for this response
        """
        headers = self.headers
        if has_empty_content(self.status_code):
            headers.pop('content-type', None)
            headers.pop('content-length', None)
            self._content = ()
        else:
            if not self.is_streamed:
                cl = 0
                for c in self.content:
                    cl += len(c)
                if cl == 0 and self.content_type in JSON_CONTENT_TYPES:
                    self._content = (b'{}', )
                    cl = len(self._content[0])
                headers['Content-Length'] = str(cl)
            ct = self.content_type
            # content type encoding available
            if self.encoding:
                ct = ct or 'text/plain'
                if 'charset=' not in ct:
                    ct = '%s; charset=%s' % (ct, self.encoding)
            if ct:
                headers['Content-Type'] = ct
            if self.method == HEAD:
                self._content = ()
        if self.can_set_cookies():
            for c in self.cookies.values():
                headers.add_header('Set-Cookie', c.OutputString())
        return list(headers)

    def has_header(self, header):
        return header in self.headers

    __contains__ = has_header

    def __setitem__(self, header, value):
        self.headers[header] = value

    def __getitem__(self, header):
        return self.headers[header]
Exemple #55
0
class HttpServerResponse(ProtocolConsumer):
    """Server side WSGI :class:`.ProtocolConsumer`.

    .. attribute:: wsgi_callable

        The wsgi callable handling requests.
    """

    _status = None
    _headers_sent = None
    _stream = None
    _buffer = None
    _logger = LOGGER
    SERVER_SOFTWARE = pulsar.SERVER_SOFTWARE
    ONE_TIME_EVENTS = ProtocolConsumer.ONE_TIME_EVENTS + ("on_headers",)

    def __init__(self, wsgi_callable, cfg, server_software=None, loop=None):
        super().__init__(loop=loop)
        self.wsgi_callable = wsgi_callable
        self.cfg = cfg
        self.parser = http_parser(kind=0)
        self.headers = Headers()
        self.keep_alive = False
        self.SERVER_SOFTWARE = server_software or self.SERVER_SOFTWARE

    @property
    def headers_sent(self):
        """Available once the headers have been sent to the client.

        These are the bytes representing the first response line and
        the headers
        """
        return self._headers_sent

    def data_received(self, data):
        """Implements :meth:`~.ProtocolConsumer.data_received` method.

        Once we have a full HTTP message, build the wsgi ``environ`` and
        delegate the response to the :func:`wsgi_callable` function.
        """
        parser = self.parser
        processed = parser.execute(data, len(data))
        if not self._stream and parser.is_headers_complete():
            headers = Headers(parser.get_headers(), kind="client")
            self._stream = StreamReader(headers, parser, self.transport)
            self._response(self.wsgi_environ())
        #
        if parser.is_message_complete():
            #
            # Stream has the whole body
            if not self._stream.on_message_complete.done():
                self._stream.on_message_complete.set_result(None)

            if processed < len(data):
                if not self._buffer:
                    self._buffer = data[processed:]
                    self.bind_event("post_request", self._new_request)
                else:
                    self._buffer += data[processed:]
        #
        elif processed < len(data):
            # This is a parsing error, the client must have sent
            # bogus data
            raise ProtocolError

    @property
    def status(self):
        return self._status

    @property
    def upgrade(self):
        return self.headers.get("upgrade")

    @property
    def chunked(self):
        return self.headers.get("Transfer-Encoding") == "chunked"

    @property
    def content_length(self):
        c = self.headers.get("Content-Length")
        if c:
            return int(c)

    @property
    def version(self):
        return self.parser.get_version()

    def start_response(self, status, response_headers, exc_info=None):
        """WSGI compliant ``start_response`` callable, see pep3333_.

        The application may call start_response more than once, if and only
        if the ``exc_info`` argument is provided.
        More precisely, it is a fatal error to call ``start_response`` without
        the ``exc_info`` argument if start_response has already been called
        within the current invocation of the application.

        :parameter status: an HTTP ``status`` string like ``200 OK`` or
            ``404 Not Found``.
        :parameter response_headers: a list of ``(header_name, header_value)``
            tuples. It must be a Python list. Each header_name must be a valid
            HTTP header field-name (as defined by RFC 2616_, Section 4.2),
            without a trailing colon or other punctuation.
        :parameter exc_info: optional python ``sys.exc_info()`` tuple.
            This argument should be supplied by the application only if
            ``start_response`` is being called by an error handler.
        :return: The :meth:`write` method.

        ``HOP_HEADERS`` are not considered but no error is raised.

        .. _pep3333: http://www.python.org/dev/peps/pep-3333/
        .. _2616: http://www.faqs.org/rfcs/rfc2616.html
        """
        if exc_info:
            try:
                if self._headers_sent:
                    # if exc_info is provided, and the HTTP headers have
                    # already been sent, start_response must raise an error,
                    # and should re-raise using the exc_info tuple
                    reraise(*exc_info)
            finally:
                # Avoid circular reference
                exc_info = None
        elif self._status:
            # Headers already set. Raise error
            raise HttpException("Response headers already set!")
        self._status = status
        if type(response_headers) is not list:
            raise TypeError("Headers must be a list of name/value tuples")
        for header, value in response_headers:
            if header.lower() in HOP_HEADERS:
                # These features are the exclusive province of this class,
                # this should be considered a fatal error for an application
                # to attempt sending them, but we don't raise an error,
                # just log a warning
                self.logger.warning('Application passing hop header "%s"', header)
                continue
            self.headers.add_header(header, value)
        return self.write

    def write(self, data, force=False):
        """The write function returned by the :meth:`start_response` method.

        Required by the WSGI specification.

        :param data: bytes to write
        :param force: Optional flag used internally
        :return: a :class:`~asyncio.Future` or the number of bytes written
        """
        write = super().write
        chunks = []
        if not self._headers_sent:
            tosend = self.get_headers()
            self._headers_sent = tosend.flat(self.version, self.status)
            self.fire_event("on_headers")
            chunks.append(self._headers_sent)
        if data:
            if self.chunked:
                while len(data) >= MAX_CHUNK_SIZE:
                    chunk, data = data[:MAX_CHUNK_SIZE], data[MAX_CHUNK_SIZE:]
                    chunks.append(chunk_encoding(chunk))
                if data:
                    chunks.append(chunk_encoding(data))
            else:
                chunks.append(data)
        elif force and self.chunked:
            chunks.append(chunk_encoding(data))
        if chunks:
            return write(b"".join(chunks))

    ########################################################################
    #    INTERNALS
    @task
    def _response(self, environ):
        exc_info = None
        response = None
        done = False
        alive = self.cfg.keep_alive or 15
        while not done:
            done = True
            try:
                if exc_info is None:
                    if "SERVER_NAME" not in environ:
                        raise HttpException(status=400)
                    response = self.wsgi_callable(environ, self.start_response)
                    if isfuture(response):
                        response = yield from wait_for(response, alive)
                else:
                    response = handle_wsgi_error(environ, exc_info)
                    if isfuture(response):
                        response = yield from wait_for(response, alive)
                #
                if exc_info:
                    self.start_response(response.status, response.get_headers(), exc_info)
                #
                # Do the actual writing
                loop = self._loop
                start = loop.time()
                for chunk in response:
                    if isfuture(chunk):
                        chunk = yield from wait_for(chunk, alive)
                        start = loop.time()
                    result = self.write(chunk)
                    if isfuture(result):
                        yield from wait_for(result, alive)
                        start = loop.time()
                    else:
                        time_in_loop = loop.time() - start
                        if time_in_loop > MAX_TIME_IN_LOOP:
                            self.logger.debug("Released the event loop after %.3f seconds", time_in_loop)
                            yield None
                            start = loop.time()
                #
                # make sure we write headers and last chunk if needed
                self.write(b"", True)

            except IOError:  # client disconnected, end this connection
                self.finished()
            except Exception:
                if wsgi_request(environ).cache.handle_wsgi_error:
                    self.keep_alive = False
                    self._write_headers()
                    self.connection.close()
                    self.finished()
                else:
                    done = False
                    exc_info = sys.exc_info()
            else:
                if not self.keep_alive:
                    self.connection.close()
                self.finished()
                log_wsgi_info(self.logger.info, environ, self.status)
            finally:
                if hasattr(response, "close"):
                    try:
                        response.close()
                    except Exception:
                        self.logger.exception("Error while closing wsgi iterator")

    def is_chunked(self):
        """Check if the response uses chunked transfer encoding.

        Only use chunked responses when the client is speaking HTTP/1.1
        or newer and there was no Content-Length header set.
        """
        if (
            self.version <= (1, 0)
            or self._status == "200 Connection established"
            or has_empty_content(int(self.status[:3]))
        ):
            return False
        elif self.headers.get("Transfer-Encoding") == "chunked":
            return True
        else:
            return self.content_length is None

    def get_headers(self):
        """Get the headers to send to the client.
        """
        if not self._status:
            # we are sending headers but the start_response was not called
            raise HttpException("Headers not set.")
        headers = self.headers
        # Set chunked header if needed
        if self.is_chunked():
            headers["Transfer-Encoding"] = "chunked"
            headers.pop("content-length", None)
        else:
            headers.pop("Transfer-Encoding", None)
        if self.keep_alive:
            self.keep_alive = keep_alive_with_status(self._status, headers)
        if not self.keep_alive:
            headers["connection"] = "close"
        return headers

    def wsgi_environ(self):
        # return a the WSGI environ dictionary
        transport = self.transport
        https = True if is_tls(transport.get_extra_info("socket")) else False
        multiprocess = self.cfg.concurrency == "process"
        environ = wsgi_environ(
            self._stream,
            self.parser,
            self._stream.headers,
            transport.get_extra_info("sockname"),
            self.address,
            self.headers,
            self.SERVER_SOFTWARE,
            https=https,
            extra={"pulsar.connection": self.connection, "pulsar.cfg": self.cfg, "wsgi.multiprocess": multiprocess},
        )
        self.keep_alive = keep_alive(self.headers, self.parser.get_version())
        self.headers.update([("Server", self.SERVER_SOFTWARE), ("Date", format_date_time(time.time()))])
        return environ

    def _new_request(self, _, exc=None):
        connection = self._connection
        connection.data_received(self._buffer)

    def _write_headers(self):
        if not self._headers_sent:
            if self.content_length:
                self.headers["Content-Length"] = "0"
            self.write(b"")