← Back to team overview

beeseek-devs team mailing list archive

[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.