Commit 0838a0b4 authored by Denis Bilenko's avatar Denis Bilenko

rewrite http.HTTPServer using BaseServer and the new feature in the core

parent 353135cd
# Copyright (c) 2009-2010 Denis Bilenko. See LICENSE for details.
import sys
import traceback
from gevent import core
from gevent.greenlet import Greenlet
from gevent.event import Event
import _socket
from gevent.baseserver import BaseServer
class HTTPServer(object):
__all__ = ['HTTPServer']
spawn = Greenlet.spawn # set to None to avoid spawning at all
backlog = 256
def __init__(self, handle=None, spawn='default', backlog=None):
if backlog is not None:
self.backlog = backlog
self.listeners = []
self._stopped_event = Event()
self._no_connections_event = Event()
self._requests = {} # maps connection -> list of requests
self.http = core.http()
self.http.set_gencb(self._cb_request)
if handle is not None:
self.handle = handle
if spawn != 'default':
self.spawn = spawn
class HTTPServer(BaseServer):
"""An HTTP server based on libevent-http.
*handle* is called with one argument: an :class:`gevent.core.http_request` instance.
"""
def start(self, listener, backlog=None):
"""Start accepting connections"""
fileno = getattr(listener, 'fileno', None)
if fileno is not None:
fd = fileno()
sock = listener
else:
sock = self.make_listener(listener, backlog=backlog)
fd = sock.fileno()
self.http.accept(fd)
self.listeners.append(sock)
self._stopped_event.clear()
if self._requests:
self._no_connections_event.clear()
else:
self._no_connections_event.set()
return sock
def make_listener(self, address, backlog=None):
if backlog is None:
backlog = self.backlog
sock = _socket.socket()
try:
sock.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, sock.getsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR) | 1)
except _socket.error:
pass
sock.bind(address)
sock.listen(backlog)
sock.setblocking(False)
return sock
def stop(self, timeout=0):
"""Shutdown the server."""
for sock in self.listeners:
sock.close()
self.socket = []
#2. Set "keep-alive" connections to "close"
# TODO
#3a. set low timeout (min(1s, timeout or 1)) on events belonging to connection (to kill long-polling connections)
# TODO
#3. Wait until every connection is closed or timeout expires
if self._requests:
self._no_connections_event.wait(timeout=timeout)
#4. forcefull close all the connections
# TODO
#5. free http instance
def __init__(self, listener, handle=None, backlog=None, spawn='default'):
BaseServer.__init__(self, listener, handle=handle, backlog=backlog, spawn=spawn)
self.http = None
#6. notify event created in serve_forever()
self._stopped_event.set()
def handle(self, request):
request.send_reply(200, 'OK', 'It works!')
@property
def started(self):
return self.http is not None
def _cb_connection_close(self, connection):
# make sure requests belonging to this connection cannot be accessed anymore
# because they've been freed by libevent
requests = self._requests.pop(connection._obj, [])
for request in requests:
request.detach()
if not self._requests:
self._no_connections_event.set()
def _cb_request_processed(self, greenlet):
request = greenlet._request
greenlet._request = None
if request:
if not greenlet.successful():
self.reply_error(request)
requests = self._requests.get(request.connection._obj)
if requests is not None:
requests.remove(request)
def _cb_request(self, request):
try:
spawn = self.spawn
request.connection.set_closecb(self)
self._requests.setdefault(request.connection._obj, []).append(request)
if spawn is None:
self.handle(request)
def _on_request(self, request):
spawn = self.spawn
if spawn is None:
self.handle(request)
else:
if self.full():
request._default_response_code = 503
else:
greenlet = spawn(self.handle, request)
rawlink = getattr(greenlet, 'rawlink', None)
if rawlink is not None:
greenlet._request = request
rawlink(self._cb_request_processed)
except:
traceback.print_exc()
try:
sys.stderr.write('Failed to handle request: %s\n\n' % (request, ))
except:
traceback.print_exc()
if request:
self.reply_error(request)
spawn(self.handle, request)
def reply_error(self, request):
if request.response == (0, None):
request.send_reply(500, 'Internal Server Error', '<h1>Internal Server Error</h1>')
def start_accepting(self):
self.http = core.http(self._on_request)
self.http.accept(self.socket.fileno())
def serve_forever(self, listener, backlog=None, stop_timeout=0):
self.start(listener, backlog=backlog)
try:
self._stopped_event.wait()
finally:
Greenlet.spawn(self.stop, timeout=stop_timeout).join()
def stop_accepting(self):
self.http = None
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