beeseek-devs team mailing list archive
-
beeseek-devs team
-
Mailing list archive
-
Message #00124
[Branch ~beeseek-devs/beeseek/trunk] Rev 208: Add a base class for network servers.
------------------------------------------------------------
revno: 208
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: trunk
timestamp: Wed 2009-02-04 17:35:54 +0100
message:
Add a base class for network servers.
BaseApplication were used for two aims: as servers, to handle
incoming requests, and as connectors to interact with online resources.
This behavior was not so good because it was doing too much things so it
was a bit slow and not so comfortable for developers. The new class
introduced splits the old BaseApplication in two classes, one for each
scope.
added:
beeseek/network/server.py
modified:
beeseek/honeybee/handler.py
beeseek/honeybee/main.py
beeseek/network/__init__.py
beeseek/network/highlevel.py
beeseek/network/http.py
beeseek/network/lowlevel.py
beeseek/session.py
------------------------------------------------------------
revno: 206.1.11
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Mon 2009-02-02 18:38:17 +0100
message:
Update the rest of ProxyServer.
modified:
beeseek/honeybee/handler.py
------------------------------------------------------------
revno: 206.1.10
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Mon 2009-02-02 18:37:10 +0100
message:
Re-add _fileno attribute to network objects.
modified:
beeseek/network/lowlevel.py
------------------------------------------------------------
revno: 206.1.9
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Mon 2009-02-02 18:07:23 +0100
message:
Update handler.
modified:
beeseek/honeybee/handler.py
beeseek/honeybee/main.py
------------------------------------------------------------
revno: 206.1.8
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Mon 2009-02-02 18:06:24 +0100
message:
Update session.
modified:
beeseek/network/server.py
beeseek/session.py
------------------------------------------------------------
revno: 206.1.7
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Mon 2009-02-02 17:46:09 +0100
message:
Random fixes and cleanup.
modified:
beeseek/network/highlevel.py
beeseek/network/http.py
beeseek/network/server.py
------------------------------------------------------------
revno: 206.1.6
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Sun 2009-02-01 21:03:00 +0100
message:
Remove unuseful attributes from lowlevel network objects.
modified:
beeseek/network/lowlevel.py
------------------------------------------------------------
revno: 206.1.5
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Sun 2009-02-01 21:01:37 +0100
message:
Remove methods from handler that are now implemented in IServer.
modified:
beeseek/network/highlevel.py
------------------------------------------------------------
revno: 206.1.4
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Sun 2009-02-01 21:00:45 +0100
message:
Add bind and listen.
modified:
beeseek/network/server.py
------------------------------------------------------------
revno: 206.1.3
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Sun 2009-02-01 20:39:08 +0100
message:
Add __all__ to beeseek.network.server.
modified:
beeseek/network/__init__.py
beeseek/network/server.py
------------------------------------------------------------
revno: 206.1.2
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Sun 2009-02-01 20:34:42 +0100
message:
Add BaseServer class.
modified:
beeseek/network/server.py
------------------------------------------------------------
revno: 206.1.1
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: server-base-class
timestamp: Sun 2009-02-01 20:06:48 +0100
message:
Add base interface and exceptions.
added:
beeseek/network/server.py
=== modified file 'beeseek/honeybee/handler.py'
--- beeseek/honeybee/handler.py 2009-01-29 15:52:54 +0000
+++ beeseek/honeybee/handler.py 2009-02-02 17:38:17 +0000
@@ -15,35 +15,34 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import select
from urllib import unquote_plus
+from beeseek.ui import html
+from beeseek import instance
+from beeseek.database import nested
from beeseek.interfaces import implements
-from beeseek import network, log, instance
from beeseek.decoders import NullDecoder, GzipDecoder
-from beeseek.network import (IPSocket, IServerApplication,
+from beeseek.network import (IServer, BaseServer, IPSocket,
HTTPServerApplication, HTTPClientApplication)
-from beeseek.ui import html
-from beeseek.database import nested
-
-
-class HoneybeeHandler(HTTPServerApplication):
+
+
+class ProxyServer(BaseServer):
__slots__ = ()
- implements(IServerApplication)
+ implements(IServer)
+ socktype = HTTPServerApplication
scripttag = ('<script type="text/javascript" >'
'src="http://www.beeseek.org/search-data/script.js">'
'</script>')
- def handle(self):
+ def process_request(self, client, requestdata):
try:
- requestline, clientheaders = self.read_request()
+ requestline, clientheaders = client.read_request()
except EOFError:
return
- if requestline[0] == self.CONNECT:
- self.handle_connect(requestline)
- self.close()
+ if requestline[0] == client.CONNECT:
+ self.handle_connect(client, requestdata, requestline)
+ client.close()
return
hostname = requestline[1][7:]
@@ -53,38 +52,36 @@
page = ''
if 'Proxy-Connection' in clientheaders:
- self.will_close = (clientheaders.pop('Proxy-Connection').lower()
- == 'close')
- else:
- self.will_close = True
+ del clientheaders['Proxy-Connection']
if hostname == 'www.beeseek.org':
+ # FIXME Transfer-Encoding is not supported by HTTP/1.0 clients
headers = {'Server': 'Honeybee',
'Transfer-Encoding': 'chunked'}
if page in ('search', 'search/'):
- if self.requestline[0] == self.POST:
- keyword = self.read().split('=')[1]
+ if requestline[0] == client.POST:
+ keyword = client.read().split('=')[1]
headers['Location'] = '/search/%s' % keyword
- self.start_response(301, 'Moved Permanently',
- headers)
- self.write('Redirect')
+ client.start_response(302, 'Found', headers)
+ client.write('302 Found')
else:
- self.start_response(200, 'OK', headers)
- self.write(html.searchpage.format({'keywords': ''}))
- self.end_response()
- self.flush()
+ client.start_response(200, 'OK', headers)
+ client.write(html.searchpage.format({'keywords': ''}))
+ client.end_response()
+ client.flush()
return
elif page.startswith('search/'):
- self.start_response(200, 'OK', headers)
+ client.start_response(200, 'OK', headers)
keywords = unquote_plus(page.split('/', 1)[1])
results = self.handle_search(keywords)
try:
- self.write(html.results.format({'keywords': keywords},
- iterator=results))
+ client.write(html.results.format({'keywords': keywords},
+ iterator=results))
except KeyError:
- self.write('No results found.')
- self.end_response()
- self.flush()
+ # FIXME show a nice page
+ client.write('No results found.')
+ client.end_response()
+ client.flush()
return
if ':' in hostname:
@@ -98,11 +95,11 @@
host.connect((hostname, port))
host.start_request(requestline[0], page, clientheaders,
version=requestline[2])
- host.raw_writelines(self.iter_raw_body())
+ host.raw_writelines(client.iter_raw_body())
host.flush()
statusline, serverheaders = host.read_response()
- if (requestline[0] == self.GET and statusline[0] == 200 and
+ if (requestline[0] == client.GET and statusline[0] == 200 and
'Content-Type' in serverheaders):
contenttype = serverheaders['Content-Type']
if ';' in contenttype:
@@ -127,25 +124,25 @@
len(self.scripttag))
serverheaders['Content-Length'] = contentlen
- self.start_response(statusline[0], statusline[1],
- serverheaders, version=statusline[2])
- self.send_script(host, decoder)
+ client.start_response(statusline[0], statusline[1],
+ serverheaders, version=statusline[2])
+ self.send_script(client, host, decoder)
while True:
data = decoder.recv(host)
if not data:
break
- self.write(data)
+ client.write(data)
else:
- self.start_response(statusline[0], statusline[1],
- serverheaders, version=statusline[2])
- self.raw_writelines(host.iter_raw_body())
+ client.start_response(statusline[0], statusline[1],
+ serverheaders, version=statusline[2])
+ client.raw_writelines(host.iter_raw_body())
else:
- self.start_response(statusline[0], statusline[1],
- serverheaders, version=statusline[2])
- self.raw_writelines(host.iter_raw_body())
+ client.start_response(statusline[0], statusline[1],
+ serverheaders, version=statusline[2])
+ client.raw_writelines(host.iter_raw_body())
- self.end_response()
- self.flush()
+ client.end_response()
+ client.flush()
def _find_head_start(self, host, decoder, pool):
while True:
@@ -182,39 +179,33 @@
return data[i:]
data = decoder.recv(host)
- def send_script(self, host, decoder):
+ def send_script(self, client, host, decoder):
pool = []
data = self._find_head_start(host, decoder, pool)
if not data:
return
data = self._find_head_end(host, decoder, pool, data)
- self.write(''.join(pool))
- self.write(self.scripttag)
- self.write(data)
-
-
- def handle_connect(self, requestline):
+ client.write(''.join(pool))
+ client.write(self.scripttag)
+ client.write(data)
+
+
+ def handle_connect(self, client, requestline):
hostname, port = requestline[1].split(':')
host = IPSocket()
host.connect((hostname, int(port)))
- self.start_response(200, 'Connection established', {})
- self.flush()
+ client.start_response(200, 'Connection established', {})
+ client.flush()
sockets = self, host
while True:
for ready in select.select(sockets, (), ())[0]:
- if ready is self:
- data = self.raw_recv(4096)
- if not data:
- return
- host.write(data)
- host.flush()
- else:
- data = host.recv(4096)
- if not data:
- return
- self.raw_write(data)
- self.flush()
+ data = ready.raw_recv(4096)
+ if not data:
+ return
+ other = [other for other in sockets if other is not ready][0]
+ other.raw_write(data)
+ other.flush()
def handle_search(self, keywords):
@@ -264,7 +255,3 @@
for item in results[karma]:
yield item
return
-
-
- def error(self):
- raise
=== modified file 'beeseek/honeybee/main.py'
--- beeseek/honeybee/main.py 2009-01-17 19:09:43 +0000
+++ beeseek/honeybee/main.py 2009-02-02 17:07:23 +0000
@@ -33,7 +33,7 @@
appdescription = 'The BeeSeek peer'
addresses = [('', 52125), ('', 52126), ('', 52127)]
- server = handler.HoneybeeHandler()
+ server = handler.ProxyServer()
def make_sockets(self):
bound = False
=== modified file 'beeseek/network/__init__.py'
--- beeseek/network/__init__.py 2009-01-14 13:14:21 +0000
+++ beeseek/network/__init__.py 2009-02-01 19:39:08 +0000
@@ -17,4 +17,5 @@
from beeseek.network.lowlevel import *
from beeseek.network.highlevel import *
+from beeseek.network.server import *
from beeseek.network.http import *
=== modified file 'beeseek/network/highlevel.py'
--- beeseek/network/highlevel.py 2009-01-23 13:28:42 +0000
+++ beeseek/network/highlevel.py 2009-02-02 16:46:09 +0000
@@ -30,14 +30,7 @@
the server and the client objects.
"""
- def start_loop(self):
- """Call handle() until closed."""
-
- def handle(self):
- """Handle a new request."""
-
- def error(self):
- """Handle an exception in handle()."""
+ pass
class IClientApplication(IApplicationProtocol):
@@ -69,35 +62,12 @@
__slots__ = 'family'
implements(IApplicationProtocol, isbase=True)
-
def __init__(self, bufsize=-1, sock=IPSocket):
if isinstance(sock, type):
self.family = sock.family
sock = None
BaseProtocol.__init__(self, bufsize, sock)
- def _read_eof(self, size=-1):
- return ''
-
def writelines(self, data):
for item in data:
self.write(item)
-
- # Handler methods
-
- def start_loop(self):
- while not self.closed:
- self.waitinput()
- try:
- self.handle()
- except StopIteration:
- # XXX We may use a better (and safer) exception for this
- break
- except Exception:
- self.error()
-
- def handle(self):
- pass
-
- def error(self):
- raise
=== modified file 'beeseek/network/http.py'
--- beeseek/network/http.py 2009-01-28 19:25:02 +0000
+++ beeseek/network/http.py 2009-02-02 16:46:09 +0000
@@ -159,7 +159,7 @@
"""
while True:
line = self.raw_readline()
- if line == '\r\n':
+ if not line or line == '\r\n':
return
name, value = line.split(':', 1)
yield name, value.strip()
@@ -267,7 +267,7 @@
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6
tokens = headers['Transfer-Encoding'].lower()
tokens = self.split_tokens(tokens)
- if filter(lambda s: s != 'identity', tokens):
+ if filter(lambda s: s == 'chunked', tokens):
return 3
if 'Content-Length' in headers:
return 2
@@ -279,7 +279,7 @@
def split_tokens(self, value):
return tuple(s.strip() for s in value.split(';'))
- def _set_read_methods(self, style, headers):
+ def _set_decoder(self, style, headers):
if style == 0:
self._decoder = FixedLengthChunkDecoder(-1)
elif style == 1:
@@ -314,7 +314,7 @@
statusline = self.read_status_line()
headers = self.read_headers()
style = self.get_transfer_style(headers)
- self._set_read_methods(style, headers)
+ self._set_decoder(style, headers)
return statusline, headers
@@ -323,12 +323,11 @@
__slots__ = ()
implements(IHTTPApplication, IServerApplication)
-
def read_request(self):
requestline = self.read_request_line()
headers = self.read_headers()
style = self.get_transfer_style(headers)
- self._set_read_methods(style, headers)
+ self._set_decoder(style, headers)
return requestline, headers
def start_response(self, status, reason, headers, version=None):
=== modified file 'beeseek/network/lowlevel.py'
--- beeseek/network/lowlevel.py 2009-02-01 15:37:09 +0000
+++ beeseek/network/lowlevel.py 2009-02-02 17:37:10 +0000
@@ -37,7 +37,6 @@
needslots = True
closed = int, property, """True if the socket has been closed."""
- peername = tuple, property, """The address of the remote endpoint."""
family = int, """The socket family."""
@@ -145,12 +144,6 @@
# Other socket methods
- def fileno(self):
- """Return the file descriptor of the socket.
-
- :return: int
- """
-
def waitinput(self, timeout=None):
"""Wait for incoming data.
@@ -190,22 +183,19 @@
high-level network objects.
"""
- __slots__ = 'closed', 'family', '_sock', '_rfile', '_wfile', '_fileno'
+ __slots__ = 'closed', '_sock', '_fileno', '_rfile', '_wfile'
implements(IProtocol, isbase=True)
def __init__(self, bufsize=-1, sock=None):
self._sock = sock or _socket.socket(self.family, _socket.SOCK_STREAM)
- self._fileno = self._sock.fileno()
- self._rfile = os.fdopen(self._fileno, 'r', 0)
- self._wfile = os.fdopen(self._fileno, 'w', bufsize)
+ self._fileno = fileno = self._sock.fileno()
+ self._rfile = os.fdopen(fileno, 'r', 0)
+ self._wfile = os.fdopen(fileno, 'w', bufsize)
self.closed = False
# Connect/bind methods
- def connect(self, address):
- self._sock.connect(address)
-
def bind(self, address):
self._sock.bind(address)
@@ -217,6 +207,9 @@
sock, addr = self._sock.accept()
return cls(bufsize, sock)
+ def connect(self, address):
+ self._sock.connect(address)
+
# Read methods
def raw_recv(self, size=-1):
@@ -265,6 +258,12 @@
except IOError:
self.closed = True
+ def flush(self):
+ try:
+ self._wfile.flush()
+ except (IOError, ValueError):
+ self.closed = True
+
# Other socket methods
def fileno(self):
@@ -273,23 +272,10 @@
def waitinput(self, timeout=None):
return bool(select((self._rfile,), (), (), timeout))
- @property
- def peername(self):
- return self._sock.getpeername()
-
- def flush(self):
- try:
- self._wfile.flush()
- except (IOError, ValueError):
- self.closed = True
-
# Close methods
def close(self):
- try:
- self.flush()
- except Exception:
- pass
+ self.flush()
try:
self._sock.shutdown(_socket.SHUT_RDWR)
self._rfile.close()
=== added file 'beeseek/network/server.py'
--- beeseek/network/server.py 1970-01-01 00:00:00 +0000
+++ beeseek/network/server.py 2009-02-02 17:06:24 +0000
@@ -0,0 +1,136 @@
+#coding=UTF-8
+
+# Copyright (C) 2007-2009 BeeSeek Developers
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from thread import start_new_thread
+from thread import error as ThreadError
+from beeseek.network import BaseSocket
+from beeseek.interfaces import Interface, implements
+
+__all__ = ('StopServing', 'StopProcessing', 'IServer', 'BaseServer')
+
+
+class StopServing(Exception):
+
+ pass
+
+
+class StopProcessing(Exception):
+
+ pass
+
+
+class IServer(Interface):
+
+ needslots = True
+ socktype = BaseSocket
+ closed = bool, property
+
+ def bind(self, address):
+ """Bind the socket to a local address.
+
+ :param address: This parameter may vary depending on the socket type.
+ :type address: Any iterable.
+ """
+
+ def listen(self, backlog):
+ """Enable a server to accept connections.
+
+ With some socket types, this method is not required and calling it
+ must have no effects.
+
+ :param backlog: It must be at least 1; it specifies the number of
+ unaccepted connection that the system will allow
+ before refusing new connections.
+ :type backlog: int
+ """
+
+ def serve_forever(self):
+ """Call serve_request() until the server is closed.
+
+ The serve_request() method will be launched in a new thread.
+ This function can be stopped raising a StopServing exception.
+ """
+
+ def serve_request(self, client):
+ """Call process_request() until the client is closed.
+
+ This function can be stopped raising a StopProcessing exception.
+ """
+
+ def process_request(self, client, requestdata):
+ """Process the request."""
+
+ def get_next_client(self):
+ """Accept a new connection."""
+
+ def handle_capacity_limit(self, client):
+ """Called when a new thread can't be started."""
+
+ def handle_error(self, client, requestdata):
+ """Called by serve_request() when an exception is raised."""
+
+
+class BaseServer(object):
+
+ __slots__ = '_server', '_stop_serving'
+ implements(IServer, isbase=True)
+
+
+ def __init__(self):
+ self._server = self.socktype()
+ self._stop_serving = False
+
+ def bind(self, address):
+ self._server.bind(address)
+
+ def listen(self, backlog):
+ self._server.listen(backlog)
+
+ def serve_forever(self):
+ while not self._stop_serving:
+ client = self.get_next_client()
+ try:
+ start_new_thread(self.serve_request, (client,))
+ except ThreadError:
+ try:
+ self.handle_capacity_limit(client)
+ except StopServing:
+ break
+ self._stop_serving = False
+
+ def serve_request(self, client):
+ requestdata = {}
+ while not client.closed:
+ try:
+ self.process_request(client, requestdata)
+ except StopProcessing:
+ break
+ except StopServing:
+ self._stop_serving = True
+ break
+ except Exception:
+ self.handle_error(client, requestdata)
+
+ def get_next_client(self):
+ return self._server.accept()
+
+ def handle_error(self, client, requestdata):
+ raise
+
+ @property
+ def closed(self):
+ return self._server.closed
=== modified file 'beeseek/session.py'
--- beeseek/session.py 2009-01-14 13:26:43 +0000
+++ beeseek/session.py 2009-02-02 17:06:24 +0000
@@ -164,10 +164,7 @@
# try...except...finally does not work well with python2.4
try:
try:
- while not server.closed:
- peer = server.accept()
- log.debug('Accepted new connection')
- thread.start_new_thread(peer.start_loop, ())
+ server.serve_forever()
except KeyboardInterrupt:
log.debug('Caught interrupt signal')
finally:
--
BeeSeek mainline
https://code.launchpad.net/~beeseek-devs/beeseek/trunk
Your team BeeSeek Developers is subscribed to branch lp:beeseek.
To unsubscribe from this branch go to https://code.launchpad.net/~beeseek-devs/beeseek/trunk/+edit-subscription.