Commit 52cec359 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

python: Fix NameError if asyncio is not available

parent 13cc3f2f
...@@ -250,7 +250,7 @@ try: ...@@ -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: ...@@ -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 path: '''
raise Exception('path must not be empty') if not status:
raise Exception('status must not be empty')
body = self._wrap_body(body) if not method:
raise Exception('method must not be empty')
promised_handler = self.http2._make_handler(-1) if not path:
promised_handler.pushed = True raise Exception('path must not be empty')
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: body = self._wrap_body(body)
request_headers = []
request_headers = _encode_headers(request_headers) promised_handler = self.http2._make_handler(-1)
request_headers.append((b':scheme', promised_handler.scheme)) promised_handler.pushed = True
request_headers.append((b':method', promised_handler.method)) promised_handler.scheme = self.scheme
request_headers.append((b':authority', promised_handler.host)) promised_handler.method = method.encode('utf-8')
request_headers.append((b':path', promised_handler.path)) promised_handler.host = self.host
promised_handler.path = path.encode('utf-8')
promised_handler._set_response_prop(status, headers, body)
promised_handler.headers = request_headers if request_headers is None:
request_headers = []
self.http2.push(self, promised_handler) 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))
def _set_response_prop(self, status, headers, body): promised_handler.headers = request_headers
self.status = status
if headers is None: self.http2.push(self, promised_handler)
headers = []
self.response_headers = _encode_headers(headers) def _set_response_prop(self, status, headers, body):
self.response_headers.append((b':status', str(status).encode('utf-8'))) self.status = status
self.response_body = body if headers is None:
headers = []
def _wrap_body(self, body): self.response_headers = _encode_headers(headers)
if body is None: self.response_headers.append((b':status', str(status)\
return body .encode('utf-8')))
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): self.response_body = body
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]
class _HTTP2Session(asyncio.Protocol): def _wrap_body(self, 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 __init__(self, RequestHandlerClass): def _encode_headers(headers):
asyncio.Protocol.__init__(self) return [(k if isinstance(k, bytes) else k.encode('utf-8'),
self.RequestHandlerClass = RequestHandlerClass v if isinstance(v, bytes) else v.encode('utf-8')) \
self.http2 = None for k, v in headers]
def connection_made(self, transport): class _HTTP2Session(asyncio.Protocol):
self.transport = transport
self.connection_header = cnghttp2.NGHTTP2_CLIENT_CONNECTION_HEADER def __init__(self, RequestHandlerClass):
sock = self.transport.get_extra_info('socket') asyncio.Protocol.__init__(self)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self.RequestHandlerClass = RequestHandlerClass
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_received(self, data):
nread = min(len(data), len(self.connection_header))
if self.connection_header.startswith(data[:nread]):
data = data[nread:]
self.connection_header = self.connection_header[nread:]
if len(self.connection_header) == 0:
try:
self.http2 = _HTTP2SessionCore\
(self.transport,
self.RequestHandlerClass)
except Exception as err:
sys.stderr.write(traceback.format_exc())
self.transport.abort()
return
self.data_received = self.data_received2
self.resume_writing = self.resume_writing2
self.data_received(data)
else:
self.transport.abort()
def data_received2(self, data): def data_received2(self, data):
try: try:
self.http2.data_received(data) self.http2.data_received(data)
except Exception as err: except Exception as err:
sys.stderr.write(traceback.format_exc()) sys.stderr.write(traceback.format_exc())
self.transport.close() self.transport.close()
return return
def resume_writing2(self): def resume_writing2(self):
try: try:
self.http2.send_data() self.http2.send_data()
except Exception as err: except Exception as err:
sys.stderr.write(traceback.format_exc()) sys.stderr.write(traceback.format_exc())
self.transport.close() self.transport.close()
return return
class HTTP2Server: class HTTP2Server:
'''HTTP/2 server. '''HTTP/2 server.
This class builds on top of the asyncio event loop. On This class builds on top of the asyncio event loop. On
construction, RequestHandlerClass must be given, which must be a construction, RequestHandlerClass must be given, which must be a
subclass of BaseRequestHandler class. subclass of BaseRequestHandler class.
''' '''
def __init__(self, address, RequestHandlerClass, ssl=None): def __init__(self, address, RequestHandlerClass, ssl=None):
'''address is a tuple of the listening address and port (e.g., '''address is a tuple of the listening address and port (e.g.,
('127.0.0.1', 8080)). RequestHandlerClass must be a subclass ('127.0.0.1', 8080)). RequestHandlerClass must be a subclass
of BaseRequestHandler class to handle a HTTP/2 stream. The of BaseRequestHandler class to handle a HTTP/2 stream. The
ssl can be ssl.SSLContext instance. If it is not None, the ssl can be ssl.SSLContext instance. If it is not None, the
resulting server is SSL/TLS capable. resulting server is SSL/TLS capable.
''' '''
def session_factory(): def session_factory():
return _HTTP2Session(RequestHandlerClass) return _HTTP2Session(RequestHandlerClass)
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
if ssl: if ssl:
ssl.set_npn_protocols([cnghttp2.NGHTTP2_PROTO_VERSION_ID\ ssl.set_npn_protocols([cnghttp2.NGHTTP2_PROTO_VERSION_ID\
.decode('utf-8')]) .decode('utf-8')])
coro = self.loop.create_server(session_factory, coro = self.loop.create_server(session_factory,
host=address[0], port=address[1], host=address[0], port=address[1],
ssl=ssl) ssl=ssl)
self.server = self.loop.run_until_complete(coro) self.server = self.loop.run_until_complete(coro)
def serve_forever(self): def serve_forever(self):
try: try:
self.loop.run_forever() self.loop.run_forever()
finally: finally:
self.server.close() self.server.close()
self.loop.close() self.loop.close()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment