python: Fix NameError if asyncio is not available
This commit is contained in:
parent
13cc3f2fe9
commit
52cec35906
|
@ -250,7 +250,7 @@ try:
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
asyncio = None
|
||||||
|
|
||||||
cdef _get_stream_user_data(cnghttp2.nghttp2_session *session,
|
cdef _get_stream_user_data(cnghttp2.nghttp2_session *session,
|
||||||
int32_t stream_id):
|
int32_t stream_id):
|
||||||
|
@ -659,308 +659,314 @@ cdef class _HTTP2SessionCore:
|
||||||
datestr, method, path, handler.status,
|
datestr, method, path, handler.status,
|
||||||
'P' if handler.pushed else '-'))
|
'P' if handler.pushed else '-'))
|
||||||
|
|
||||||
class BaseRequestHandler:
|
if asyncio:
|
||||||
|
|
||||||
"""HTTP/2 request (stream) handler base class.
|
class BaseRequestHandler:
|
||||||
|
|
||||||
The class is used to handle the HTTP/2 stream. By default, it does
|
"""HTTP/2 request (stream) handler base class.
|
||||||
not nothing. It must be subclassed to handle each event callback
|
|
||||||
method.
|
|
||||||
|
|
||||||
The first callback method invoked is on_headers(). It is called
|
The class is used to handle the HTTP/2 stream. By default, it does
|
||||||
when HEADERS frame, which includes request header fields, is
|
not nothing. It must be subclassed to handle each event callback
|
||||||
arrived.
|
method.
|
||||||
|
|
||||||
If request has request body, on_data(data) is invoked for each
|
The first callback method invoked is on_headers(). It is called
|
||||||
chunk of received data.
|
when HEADERS frame, which includes request header fields, is
|
||||||
|
arrived.
|
||||||
|
|
||||||
When whole request is received, on_request_done() is invoked.
|
If request has request body, on_data(data) is invoked for each
|
||||||
|
chunk of received data.
|
||||||
|
|
||||||
When stream is closed, on_close(error_code) is called.
|
When whole request is received, on_request_done() is invoked.
|
||||||
|
|
||||||
The application can send response using send_response() method. It
|
When stream is closed, on_close(error_code) is called.
|
||||||
can be used in on_headers(), on_data() or on_request_done().
|
|
||||||
|
|
||||||
The application can push resource using push() method. It must be
|
The application can send response using send_response() method. It
|
||||||
used before send_response() call.
|
can be used in on_headers(), on_data() or on_request_done().
|
||||||
|
|
||||||
The following instance variables are available:
|
The application can push resource using push() method. It must be
|
||||||
|
used before send_response() call.
|
||||||
|
|
||||||
client_address
|
The following instance variables are available:
|
||||||
Contains a tuple of the form (host, port) referring to the client's
|
|
||||||
address.
|
|
||||||
|
|
||||||
stream_id
|
client_address
|
||||||
Stream ID of this stream
|
Contains a tuple of the form (host, port) referring to the client's
|
||||||
|
address.
|
||||||
|
|
||||||
scheme
|
stream_id
|
||||||
Scheme of the request URI. This is a value of :scheme header field.
|
Stream ID of this stream
|
||||||
|
|
||||||
method
|
scheme
|
||||||
Method of this stream. This is a value of :method header field.
|
Scheme of the request URI. This is a value of :scheme header field.
|
||||||
|
|
||||||
host
|
method
|
||||||
This is a value of :authority or host header field.
|
Method of this stream. This is a value of :method header field.
|
||||||
|
|
||||||
path
|
host
|
||||||
This is a value of :path header field.
|
This is a value of :authority or host header field.
|
||||||
|
|
||||||
"""
|
path
|
||||||
|
This is a value of :path header field.
|
||||||
|
|
||||||
def __init__(self, http2, stream_id):
|
"""
|
||||||
self.headers = []
|
|
||||||
self.cookies = []
|
|
||||||
# Stream ID. For promised stream, it is initially -1.
|
|
||||||
self.stream_id = stream_id
|
|
||||||
self.http2 = http2
|
|
||||||
# address of the client
|
|
||||||
self.client_address = self.http2._get_client_address()
|
|
||||||
# :scheme header field in request
|
|
||||||
self.scheme = None
|
|
||||||
# :method header field in request
|
|
||||||
self.method = None
|
|
||||||
# :authority or host header field in request
|
|
||||||
self.host = None
|
|
||||||
# :path header field in request
|
|
||||||
self.path = None
|
|
||||||
# HTTP status
|
|
||||||
self.status = None
|
|
||||||
# True if this is a handler for pushed resource
|
|
||||||
self.pushed = False
|
|
||||||
|
|
||||||
def on_headers(self):
|
def __init__(self, http2, stream_id):
|
||||||
|
self.headers = []
|
||||||
|
self.cookies = []
|
||||||
|
# Stream ID. For promised stream, it is initially -1.
|
||||||
|
self.stream_id = stream_id
|
||||||
|
self.http2 = http2
|
||||||
|
# address of the client
|
||||||
|
self.client_address = self.http2._get_client_address()
|
||||||
|
# :scheme header field in request
|
||||||
|
self.scheme = None
|
||||||
|
# :method header field in request
|
||||||
|
self.method = None
|
||||||
|
# :authority or host header field in request
|
||||||
|
self.host = None
|
||||||
|
# :path header field in request
|
||||||
|
self.path = None
|
||||||
|
# HTTP status
|
||||||
|
self.status = None
|
||||||
|
# True if this is a handler for pushed resource
|
||||||
|
self.pushed = False
|
||||||
|
|
||||||
'''Called when request HEADERS is arrived.
|
def on_headers(self):
|
||||||
|
|
||||||
'''
|
'''Called when request HEADERS is arrived.
|
||||||
pass
|
|
||||||
|
|
||||||
def on_data(self, data):
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
'''Called when a chunk of request body is arrived. This method will be
|
def on_data(self, data):
|
||||||
called multiple times until all data are received.
|
|
||||||
|
|
||||||
'''
|
'''Called when a chunk of request body is arrived. This method
|
||||||
pass
|
will be called multiple times until all data are received.
|
||||||
|
|
||||||
def on_request_done(self):
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
'''Called when whole request was received
|
def on_request_done(self):
|
||||||
|
|
||||||
'''
|
'''Called when whole request was received
|
||||||
pass
|
|
||||||
|
|
||||||
def on_close(self, error_code):
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
'''Called when stream is about to close.
|
def on_close(self, error_code):
|
||||||
|
|
||||||
'''
|
'''Called when stream is about to close.
|
||||||
pass
|
|
||||||
|
|
||||||
def send_response(self, status=200, headers=None, body=None):
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
'''Send response. The status is HTTP status code. The headers is
|
def send_response(self, status=200, headers=None, body=None):
|
||||||
additional response headers. The :status header field is
|
|
||||||
appended by the library. The body is the response body. It
|
|
||||||
could be None if response body is empty. Or it must be
|
|
||||||
instance of either str, bytes or io.IOBase. If instance of str
|
|
||||||
is specified, it is encoded using UTF-8.
|
|
||||||
|
|
||||||
The headers is a list of tuple of the form (name, value). The
|
'''Send response. The status is HTTP status code. The headers is
|
||||||
name and value are byte string.
|
additional response headers. The :status header field is
|
||||||
|
appended by the library. The body is the response body. It
|
||||||
|
could be None if response body is empty. Or it must be
|
||||||
|
instance of either str, bytes or io.IOBase. If instance of str
|
||||||
|
is specified, it is encoded using UTF-8.
|
||||||
|
|
||||||
On error, exception was thrown.
|
The headers is a list of tuple of the form (name, value). The
|
||||||
|
name and value are byte string.
|
||||||
|
|
||||||
'''
|
On error, exception was thrown.
|
||||||
if self.status is not None:
|
|
||||||
raise Exception('response has already been sent')
|
|
||||||
|
|
||||||
if not status:
|
'''
|
||||||
raise Exception('status must not be empty')
|
if self.status is not None:
|
||||||
|
raise Exception('response has already been sent')
|
||||||
|
|
||||||
body = self._wrap_body(body)
|
if not status:
|
||||||
|
raise Exception('status must not be empty')
|
||||||
|
|
||||||
self._set_response_prop(status, headers, body)
|
body = self._wrap_body(body)
|
||||||
self.http2.send_response(self)
|
|
||||||
|
|
||||||
def push(self, path, method='GET', request_headers=None,
|
self._set_response_prop(status, headers, body)
|
||||||
status=200, headers=None, body=None):
|
self.http2.send_response(self)
|
||||||
|
|
||||||
'''Push a resource. The path is a path portion of request URI for this
|
def push(self, path, method='GET', request_headers=None,
|
||||||
resource. The method is a method to access this resource. The
|
status=200, headers=None, body=None):
|
||||||
request_headers is additional request headers to access this
|
|
||||||
resource. The :scheme, :method, :authority and :path are
|
|
||||||
appended by the library. The :scheme and :authority are
|
|
||||||
inherited from the request (not request_headers parameter).
|
|
||||||
|
|
||||||
The status is HTTP status code. The headers is additional
|
'''Push a resource. The path is a path portion of request URI
|
||||||
response headers. The :status header field is appended by the
|
for this
|
||||||
library. The body is the response body. It could be None if
|
resource. The method is a method to access this
|
||||||
response body is empty. Or it must be instance of either str,
|
resource. The request_headers is additional request
|
||||||
bytes or io.IOBase. If instance of str is specified, it is
|
headers to access this resource. The :scheme, :method,
|
||||||
encoded using UTF-8.
|
:authority and :path are appended by the library. The
|
||||||
|
:scheme and :authority are inherited from the request (not
|
||||||
|
request_headers parameter).
|
||||||
|
|
||||||
The headers and request_headers are a list of tuple of the
|
The status is HTTP status code. The headers is additional
|
||||||
form (name, value). The name and value are byte string.
|
response headers. The :status header field is appended by the
|
||||||
|
library. The body is the response body. It could be None if
|
||||||
|
response body is empty. Or it must be instance of either str,
|
||||||
|
bytes or io.IOBase. If instance of str is specified, it is
|
||||||
|
encoded using UTF-8.
|
||||||
|
|
||||||
On error, exception was thrown.
|
The headers and request_headers are a list of tuple of the
|
||||||
|
form (name, value). The name and value are byte string.
|
||||||
|
|
||||||
'''
|
On error, exception was thrown.
|
||||||
if not status:
|
|
||||||
raise Exception('status must not be empty')
|
|
||||||
|
|
||||||
if not method:
|
'''
|
||||||
raise Exception('method must not be empty')
|
if not status:
|
||||||
|
raise Exception('status must not be empty')
|
||||||
|
|
||||||
if not path:
|
if not method:
|
||||||
raise Exception('path must not be empty')
|
raise Exception('method must not be empty')
|
||||||
|
|
||||||
body = self._wrap_body(body)
|
if not path:
|
||||||
|
raise Exception('path must not be empty')
|
||||||
|
|
||||||
promised_handler = self.http2._make_handler(-1)
|
body = self._wrap_body(body)
|
||||||
promised_handler.pushed = True
|
|
||||||
promised_handler.scheme = self.scheme
|
|
||||||
promised_handler.method = method.encode('utf-8')
|
|
||||||
promised_handler.host = self.host
|
|
||||||
promised_handler.path = path.encode('utf-8')
|
|
||||||
promised_handler._set_response_prop(status, headers, body)
|
|
||||||
|
|
||||||
if request_headers is None:
|
promised_handler = self.http2._make_handler(-1)
|
||||||
request_headers = []
|
promised_handler.pushed = True
|
||||||
|
promised_handler.scheme = self.scheme
|
||||||
|
promised_handler.method = method.encode('utf-8')
|
||||||
|
promised_handler.host = self.host
|
||||||
|
promised_handler.path = path.encode('utf-8')
|
||||||
|
promised_handler._set_response_prop(status, headers, body)
|
||||||
|
|
||||||
request_headers = _encode_headers(request_headers)
|
if request_headers is None:
|
||||||
request_headers.append((b':scheme', promised_handler.scheme))
|
request_headers = []
|
||||||
request_headers.append((b':method', promised_handler.method))
|
|
||||||
request_headers.append((b':authority', promised_handler.host))
|
|
||||||
request_headers.append((b':path', promised_handler.path))
|
|
||||||
|
|
||||||
promised_handler.headers = request_headers
|
request_headers = _encode_headers(request_headers)
|
||||||
|
request_headers.append((b':scheme', promised_handler.scheme))
|
||||||
|
request_headers.append((b':method', promised_handler.method))
|
||||||
|
request_headers.append((b':authority', promised_handler.host))
|
||||||
|
request_headers.append((b':path', promised_handler.path))
|
||||||
|
|
||||||
self.http2.push(self, promised_handler)
|
promised_handler.headers = request_headers
|
||||||
|
|
||||||
def _set_response_prop(self, status, headers, body):
|
self.http2.push(self, promised_handler)
|
||||||
self.status = status
|
|
||||||
|
|
||||||
if headers is None:
|
def _set_response_prop(self, status, headers, body):
|
||||||
headers = []
|
self.status = status
|
||||||
|
|
||||||
self.response_headers = _encode_headers(headers)
|
if headers is None:
|
||||||
self.response_headers.append((b':status', str(status).encode('utf-8')))
|
headers = []
|
||||||
|
|
||||||
self.response_body = body
|
self.response_headers = _encode_headers(headers)
|
||||||
|
self.response_headers.append((b':status', str(status)\
|
||||||
|
.encode('utf-8')))
|
||||||
|
|
||||||
def _wrap_body(self, body):
|
self.response_body = body
|
||||||
if body is None:
|
|
||||||
return body
|
|
||||||
elif isinstance(body, str):
|
|
||||||
return io.BytesIO(body.encode('utf-8'))
|
|
||||||
elif isinstance(body, bytes):
|
|
||||||
return io.BytesIO(body)
|
|
||||||
elif isinstance(body, io.IOBase):
|
|
||||||
return body
|
|
||||||
else:
|
|
||||||
raise Exception(('body must be None or instance of str or bytes '
|
|
||||||
'or io.IOBase'))
|
|
||||||
|
|
||||||
def _encode_headers(headers):
|
def _wrap_body(self, body):
|
||||||
return [(k if isinstance(k, bytes) else k.encode('utf-8'),
|
if body is None:
|
||||||
v if isinstance(v, bytes) else v.encode('utf-8')) \
|
return body
|
||||||
for k, v in headers]
|
elif isinstance(body, str):
|
||||||
|
return io.BytesIO(body.encode('utf-8'))
|
||||||
|
elif isinstance(body, bytes):
|
||||||
|
return io.BytesIO(body)
|
||||||
|
elif isinstance(body, io.IOBase):
|
||||||
|
return body
|
||||||
|
else:
|
||||||
|
raise Exception(('body must be None or instance of str or '
|
||||||
|
'bytes or io.IOBase'))
|
||||||
|
|
||||||
class _HTTP2Session(asyncio.Protocol):
|
def _encode_headers(headers):
|
||||||
|
return [(k if isinstance(k, bytes) else k.encode('utf-8'),
|
||||||
|
v if isinstance(v, bytes) else v.encode('utf-8')) \
|
||||||
|
for k, v in headers]
|
||||||
|
|
||||||
def __init__(self, RequestHandlerClass):
|
class _HTTP2Session(asyncio.Protocol):
|
||||||
asyncio.Protocol.__init__(self)
|
|
||||||
self.RequestHandlerClass = RequestHandlerClass
|
|
||||||
self.http2 = None
|
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def __init__(self, RequestHandlerClass):
|
||||||
self.transport = transport
|
asyncio.Protocol.__init__(self)
|
||||||
self.connection_header = cnghttp2.NGHTTP2_CLIENT_CONNECTION_HEADER
|
self.RequestHandlerClass = RequestHandlerClass
|
||||||
sock = self.transport.get_extra_info('socket')
|
|
||||||
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
||||||
ssl_ctx = self.transport.get_extra_info('sslcontext')
|
|
||||||
if ssl_ctx:
|
|
||||||
if sock.selected_npn_protocol().encode('utf-8') != \
|
|
||||||
cnghttp2.NGHTTP2_PROTO_VERSION_ID:
|
|
||||||
self.transport.abort()
|
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
|
||||||
if self.http2:
|
|
||||||
self.http2 = None
|
self.http2 = None
|
||||||
|
|
||||||
def data_received(self, data):
|
def connection_made(self, transport):
|
||||||
nread = min(len(data), len(self.connection_header))
|
self.transport = transport
|
||||||
|
self.connection_header = cnghttp2.NGHTTP2_CLIENT_CONNECTION_HEADER
|
||||||
if self.connection_header.startswith(data[:nread]):
|
sock = self.transport.get_extra_info('socket')
|
||||||
data = data[nread:]
|
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
self.connection_header = self.connection_header[nread:]
|
ssl_ctx = self.transport.get_extra_info('sslcontext')
|
||||||
if len(self.connection_header) == 0:
|
if ssl_ctx:
|
||||||
try:
|
if sock.selected_npn_protocol().encode('utf-8') != \
|
||||||
self.http2 = _HTTP2SessionCore(self.transport,
|
cnghttp2.NGHTTP2_PROTO_VERSION_ID:
|
||||||
self.RequestHandlerClass)
|
|
||||||
except Exception as err:
|
|
||||||
sys.stderr.write(traceback.format_exc())
|
|
||||||
self.transport.abort()
|
self.transport.abort()
|
||||||
return
|
|
||||||
|
|
||||||
self.data_received = self.data_received2
|
def connection_lost(self, exc):
|
||||||
self.resume_writing = self.resume_writing2
|
if self.http2:
|
||||||
self.data_received(data)
|
self.http2 = None
|
||||||
else:
|
|
||||||
self.transport.abort()
|
|
||||||
|
|
||||||
def data_received2(self, data):
|
def data_received(self, data):
|
||||||
try:
|
nread = min(len(data), len(self.connection_header))
|
||||||
self.http2.data_received(data)
|
|
||||||
except Exception as err:
|
|
||||||
sys.stderr.write(traceback.format_exc())
|
|
||||||
self.transport.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
def resume_writing2(self):
|
if self.connection_header.startswith(data[:nread]):
|
||||||
try:
|
data = data[nread:]
|
||||||
self.http2.send_data()
|
self.connection_header = self.connection_header[nread:]
|
||||||
except Exception as err:
|
if len(self.connection_header) == 0:
|
||||||
sys.stderr.write(traceback.format_exc())
|
try:
|
||||||
self.transport.close()
|
self.http2 = _HTTP2SessionCore\
|
||||||
return
|
(self.transport,
|
||||||
|
self.RequestHandlerClass)
|
||||||
|
except Exception as err:
|
||||||
|
sys.stderr.write(traceback.format_exc())
|
||||||
|
self.transport.abort()
|
||||||
|
return
|
||||||
|
|
||||||
class HTTP2Server:
|
self.data_received = self.data_received2
|
||||||
|
self.resume_writing = self.resume_writing2
|
||||||
|
self.data_received(data)
|
||||||
|
else:
|
||||||
|
self.transport.abort()
|
||||||
|
|
||||||
'''HTTP/2 server.
|
def data_received2(self, data):
|
||||||
|
try:
|
||||||
|
self.http2.data_received(data)
|
||||||
|
except Exception as err:
|
||||||
|
sys.stderr.write(traceback.format_exc())
|
||||||
|
self.transport.close()
|
||||||
|
return
|
||||||
|
|
||||||
This class builds on top of the asyncio event loop. On
|
def resume_writing2(self):
|
||||||
construction, RequestHandlerClass must be given, which must be a
|
try:
|
||||||
subclass of BaseRequestHandler class.
|
self.http2.send_data()
|
||||||
|
except Exception as err:
|
||||||
|
sys.stderr.write(traceback.format_exc())
|
||||||
|
self.transport.close()
|
||||||
|
return
|
||||||
|
|
||||||
'''
|
class HTTP2Server:
|
||||||
def __init__(self, address, RequestHandlerClass, ssl=None):
|
|
||||||
|
|
||||||
'''address is a tuple of the listening address and port (e.g.,
|
'''HTTP/2 server.
|
||||||
('127.0.0.1', 8080)). RequestHandlerClass must be a subclass
|
|
||||||
of BaseRequestHandler class to handle a HTTP/2 stream. The
|
This class builds on top of the asyncio event loop. On
|
||||||
ssl can be ssl.SSLContext instance. If it is not None, the
|
construction, RequestHandlerClass must be given, which must be a
|
||||||
resulting server is SSL/TLS capable.
|
subclass of BaseRequestHandler class.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
def session_factory():
|
def __init__(self, address, RequestHandlerClass, ssl=None):
|
||||||
return _HTTP2Session(RequestHandlerClass)
|
|
||||||
|
|
||||||
self.loop = asyncio.get_event_loop()
|
'''address is a tuple of the listening address and port (e.g.,
|
||||||
|
('127.0.0.1', 8080)). RequestHandlerClass must be a subclass
|
||||||
|
of BaseRequestHandler class to handle a HTTP/2 stream. The
|
||||||
|
ssl can be ssl.SSLContext instance. If it is not None, the
|
||||||
|
resulting server is SSL/TLS capable.
|
||||||
|
|
||||||
if ssl:
|
'''
|
||||||
ssl.set_npn_protocols([cnghttp2.NGHTTP2_PROTO_VERSION_ID\
|
def session_factory():
|
||||||
.decode('utf-8')])
|
return _HTTP2Session(RequestHandlerClass)
|
||||||
|
|
||||||
coro = self.loop.create_server(session_factory,
|
self.loop = asyncio.get_event_loop()
|
||||||
host=address[0], port=address[1],
|
|
||||||
ssl=ssl)
|
|
||||||
self.server = self.loop.run_until_complete(coro)
|
|
||||||
|
|
||||||
def serve_forever(self):
|
if ssl:
|
||||||
try:
|
ssl.set_npn_protocols([cnghttp2.NGHTTP2_PROTO_VERSION_ID\
|
||||||
self.loop.run_forever()
|
.decode('utf-8')])
|
||||||
finally:
|
|
||||||
self.server.close()
|
coro = self.loop.create_server(session_factory,
|
||||||
self.loop.close()
|
host=address[0], port=address[1],
|
||||||
|
ssl=ssl)
|
||||||
|
self.server = self.loop.run_until_complete(coro)
|
||||||
|
|
||||||
|
def serve_forever(self):
|
||||||
|
try:
|
||||||
|
self.loop.run_forever()
|
||||||
|
finally:
|
||||||
|
self.server.close()
|
||||||
|
self.loop.close()
|
||||||
|
|
Loading…
Reference in New Issue