Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 843

Show
Ignore:
Timestamp:
11/27/05 21:08:38
Author:
fumanchu
Message:

Fix for #388 (IPv6 support). While I was up to my elbows in the HTTP servers, I went ahead and:

  1. Implemented MaxSize? limits in _cphttpserver
  2. Made PooledThreadServer? a subclass of CherryHTTPServer, which consolidated a lot of features that each had which the other didn't, including AF_UNIX support.
  3. Implemented AF_UNIX support for _cpwsgiserver.
  4. Pulled the MaxSize? logic out of _cpwsgiserver completely; the upshot is that both server interfaces can now use the same wrapper (now in lib/httptools), and 413 errors in the headers are now logged by CP.
  5. Implemented server.socketQueueSize for _cpwsgiserver.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/_cphttpserver.py

    r838 r843  
    11"""A native HTTP server for CherryPy.""" 
    22 
    3 import threading, os, socket, time 
    4 import SocketServer, BaseHTTPServer, Queue 
     3from BaseHTTPServer import BaseHTTPRequestHandler 
     4import os 
     5import Queue 
     6import socket 
     7import SocketServer 
     8import threading 
     9import time 
     10 
    511import cherrypy 
    612from cherrypy import _cputil 
    7  
    8  
    9 class CherryHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 
    10      
    11     """CherryPy HTTP request handler with the following commands: 
    12          
    13         o  GET 
    14         o  HEAD 
    15         o  POST 
    16         o  HOTRELOAD 
    17          
    18     """ 
     13from cherrypy.lib import httptools 
     14 
     15 
     16class CherryHTTPRequestHandler(BaseHTTPRequestHandler): 
     17    """CherryPy HTTP request handler""" 
    1918     
    2019    def address_string(self): 
    21         """ Try to do a reverse DNS based on [server]reverseDNS in the config file """ 
     20        """ Try to do a reverse DNS based on server.reverseDNS in the config file """ 
    2221        if cherrypy.config.get('server.reverseDNS'): 
    23             return BaseHTTPServer.BaseHTTPRequestHandler.address_string(self) 
     22            return BaseHTTPRequestHandler.address_string(self) 
    2423        else: 
    2524            return self.client_address[0] 
    2625     
    2726    def _headerlist(self): 
    28         list = [] 
     27        hlist = [] 
    2928        hit = 0 
    3029        for line in self.headers.headers: 
     
    3231                if line[0] in ' \t': 
    3332                    # Continuation line. Add to previous entry. 
    34                     if list: 
    35                         list[-1][1] += " " + line.lstrip() 
     33                    if hlist: 
     34                        hlist[-1][1] += " " + line.lstrip() 
    3635                else: 
    3736                    # New header. Add a new entry. We don't catch 
    3837                    # ValueError here because we trust rfc822.py. 
    3938                    name, value = line.split(":", 1) 
    40                     list.append((name.strip(), value.strip())) 
    41         return list 
     39                    hlist.append((name.strip(), value.strip())) 
     40        return hlist 
     41     
     42    def parse_request(self): 
     43        # Extended to provide header and body length limits. 
     44        mhs = int(cherrypy.config.get('server.maxRequestHeaderSize', 
     45                                      500 * 1024)) 
     46        self.rfile = httptools.SizeCheckWrapper(self.rfile, mhs) 
     47        try: 
     48            presult = BaseHTTPRequestHandler.parse_request(self) 
     49        except httptools.MaxSizeExceeded: 
     50            self.send_error(413, "Request Entity Too Large") 
     51            tb = _cputil.formatExc() 
     52            cherrypy.log(tb) 
     53            return False 
     54        else: 
     55            if presult: 
     56                # Request header is parsed 
     57                # We prepare the SizeCheckWrapper for the request body 
     58                self.rfile.bytes_read = 0 
     59                path = self.path 
     60                if path == "*": 
     61                    path = "global" 
     62                mbs = int(cherrypy.config.get('server.maxRequestBodySize', 
     63                                              100 * 1024 * 1024, path=path)) 
     64                self.rfile.maxlen = mbs 
     65        return presult 
    4266     
    4367    def handle_one_request(self): 
     
    107131 
    108132 
    109 class CherryHTTPServer(SocketServer.TCPServer): 
    110     # Subclass TCPServer (instead of BaseHTTPServer.HTTPServer), because 
     133class CherryHTTPServer(SocketServer.BaseServer): 
     134    # Subclass BaseServer (instead of BaseHTTPServer.HTTPServer), because 
    111135    # getfqdn call was timing out on localhost when calling gethostbyaddr. 
    112136     
    113137    ready = False 
    114138    interrupt = None 
    115      
     139    RequestHandlerClass = CherryHTTPRequestHandler 
    116140    allow_reuse_address = True 
    117141     
    118142    def __init__(self): 
     143        # SocketServer __init__'s all say "do not override", 
     144        # but we have to in order to implement SSL and IPv6 support! 
     145         
    119146        # Set protocol_version 
    120         proto = cherrypy.config.get('server.protocolVersion') or "HTTP/1.0" 
    121         CherryHTTPRequestHandler.protocol_version = proto 
     147        httpproto = cherrypy.config.get('server.protocolVersion') or "HTTP/1.0" 
     148        self.RequestHandlerClass.protocol_version = httpproto 
     149         
     150        self.request_queue_size = cherrypy.config.get('server.socketQueueSize') 
    122151         
    123152        # Select the appropriate server based on config options 
     
    135164            except: pass 
    136165             
    137             server_address = sockFile 
     166            self.server_address = sockFile 
     167            self.socket = socket.socket(self.address_family, self.socket_type) 
     168            self.server_bind() 
    138169        else: 
    139             # AF_INET socket 
    140             server_address = (cherrypy.config.get('server.socketHost'), 
    141                               cherrypy.config.get('server.socketPort')) 
    142          
    143         self.request_queue_size = cherrypy.config.get('server.socketQueueSize') 
    144          
    145         SocketServer.TCPServer.__init__(self, server_address, CherryHTTPRequestHandler) 
     170            # AF_INET or AF_INET6 socket 
     171            host = cherrypy.config.get('server.socketHost') 
     172            port = cherrypy.config.get('server.socketPort') 
     173            self.server_address = (host, port) 
     174             
     175            # Get the correct address family for our host (allows IPv6 addresses) 
     176            for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, 
     177                                          socket.SOCK_STREAM): 
     178                af, socktype, proto, canonname, sa = res 
     179                self.address_family = af 
     180                self.socket_type = socktype 
     181                try: 
     182                    self.socket = socket.socket(af, socktype, proto) 
     183                    self.server_bind() 
     184                except socket.error, msg: 
     185                    if self.socket: 
     186                        self.socket.close() 
     187                    self.socket = None 
     188                    continue 
     189                break 
     190             
     191            if not self.socket: 
     192                raise socket.error, msg 
     193         
     194        self.server_activate() 
    146195     
    147196    def server_activate(self): 
    148197        """Override server_activate to set timeout on our listener socket""" 
    149198        self.socket.settimeout(1) 
    150         SocketServer.TCPServer.server_activate(self) 
     199        self.socket.listen(self.request_queue_size) 
     200     
     201    def server_bind(self): 
     202        """Called by constructor to bind the socket.""" 
     203        if self.allow_reuse_address: 
     204            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
     205        self.socket.bind(self.server_address) 
     206     
     207    def close_request(self, request): 
     208        """Called to clean up an individual request.""" 
     209        request.close() 
    151210     
    152211    def get_request(self): 
     
    156215        # (where wfile and rfile are set in SocketServer.py) we explicitly 
    157216        # set the request socket to blocking 
    158          
    159217        request, client_address = self.socket.accept() 
    160         request.setblocking(1) 
     218        if hasattr(request, 'setblocking'): 
     219            request.setblocking(1) 
    161220        return request, client_address 
    162221     
     
    166225        try: 
    167226            request, client_address = self.get_request() 
    168         except socket.error: 
    169             return 
    170         except socket.timeout: 
     227        except (socket.error, socket.timeout): 
    171228            # The only reason for the timeout is so we can notice keyboard 
    172229            # interrupts on Win32, which don't interrupt accept() by default 
    173             return 1 
    174          
    175         if self.verify_request(request, client_address): 
    176             try: 
    177                 self.process_request(request, client_address) 
    178             except (KeyboardInterrupt, SystemExit): 
    179                 self.close_request(request) 
    180                 raise 
    181             except: 
    182                 self.handle_error(request, client_address) 
    183                 self.close_request(request) 
     230            return 
     231         
     232        try: 
     233            self.process_request(request, client_address) 
     234        except (KeyboardInterrupt, SystemExit): 
     235            self.close_request(request) 
     236            raise 
     237        except: 
     238            self.handle_error(request, client_address) 
     239            self.close_request(request) 
    184240     
    185241    def handle_error(self, request, client_address): 
    186         errorBody = _cputil.formatExc() 
    187         cherrypy.log(errorBody) 
     242        cherrypy.log(_cputil.formatExc()) 
    188243     
    189244    def serve_forever(self): 
     
    191246        self.ready = True 
    192247        while self.ready: 
    193             self.handle_request() 
    194248            if self.interrupt: 
    195249                raise self.interrupt 
     250            self.handle_request() 
     251        self.server_close() 
    196252    start = serve_forever 
    197253     
    198     def shutdown(self): 
     254    def server_close(self): 
    199255        self.ready = False 
    200         # Close the socket 
    201         self.server_close() 
    202     stop = shutdown 
    203  
    204  
    205 _SHUTDOWNREQUEST = (0,0) 
    206  
    207 class ServerThread(threading.Thread): 
    208      
    209     def __init__(self, RequestHandlerClass, requestQueue, server): 
    210         self.server = server 
    211         self.ready = False 
    212         threading.Thread.__init__(self) 
    213         self._RequestHandlerClass = RequestHandlerClass 
    214         self._requestQueue = requestQueue 
    215      
    216     def run(self): 
    217         try: 
    218             self.ready = True 
    219             while 1: 
    220                 request, client_address = self._requestQueue.get() 
    221                 if (request, client_address) == _SHUTDOWNREQUEST: 
    222                     return 
    223                 if self.verify_request(request, client_address): 
    224                     try: 
    225                         self.process_request(request, client_address) 
    226                     except (KeyboardInterrupt, SystemExit): 
    227                         self.close_request(request) 
    228                         raise 
    229                     except: 
    230                         self.handle_error(request, client_address) 
    231                         self.close_request(request) 
    232                 else: 
    233                     self.close_request(request) 
    234         except (KeyboardInterrupt, SystemExit), exc: 
    235             self.server.interrupt = exc 
    236      
    237     def verify_request(self, request, client_address): 
    238         """ Verify the request.  May be overridden. 
    239             Return 1 if we should proceed with this request. """ 
    240         return 1 
    241      
    242     def process_request(self, request, client_address): 
    243         self._RequestHandlerClass(request, client_address, self) 
    244         self.close_request(request) 
    245      
    246     def close_request(self, request): 
    247         """ Called to clean up an individual request. """ 
    248         request.close() 
    249      
    250     def handle_error(self, request, client_address): 
    251         """Handle an error gracefully.  May be overridden.""" 
    252         errorBody = _cputil.formatExc() 
    253         cherrypy.log(errorBody) 
    254  
    255  
    256 class PooledThreadServer(SocketServer.TCPServer): 
     256        self.socket.close() 
     257    stop = shutdown = server_close 
     258 
     259 
     260class PooledThreadServer(CherryHTTPServer): 
    257261    """A TCP Server using a pool of worker threads. This is superior to the 
    258262       alternatives provided by the Python standard library, which only offer 
     
    264268       requests (i.e. you don't have to bother with Deferreds).""" 
    265269     
    266     allow_reuse_address = 1 
    267     ready = False 
    268     interrupt = None 
    269      
    270270    def __init__(self): 
    271         # Set protocol_version 
    272         proto = cherrypy.config.get('server.protocolVersion') or "HTTP/1.0" 
    273         CherryHTTPRequestHandler.protocol_version = proto 
    274          
    275         # Select the appropriate server based on config options 
    276         threadPool = cherrypy.config.get('server.threadPool') 
    277         server_address = (cherrypy.config.get('server.socketHost'), 
    278                           cherrypy.config.get('server.socketPort')) 
    279         self.request_queue_size = cherrypy.config.get('server.socketQueueSize') 
    280          
    281         # I know it says "do not override", 
    282         # but I have to in order to implement SSL support ! 
    283         SocketServer.BaseServer.__init__(self, server_address, CherryHTTPRequestHandler) 
    284         self.socket = socket.socket(self.address_family, self.socket_type) 
    285         self.server_bind() 
    286         self.server_activate() 
    287          
    288         self._numThreads = threadPool 
    289         self._RequestHandlerClass = CherryHTTPRequestHandler 
    290         self._ThreadClass = ServerThread 
    291         self._requestQueue = Queue.Queue() 
    292         self._workerThreads = [] 
     271        self.numThreads = cherrypy.config.get('server.threadPool') 
     272        self.ThreadClass = ServerThread 
     273        self.requestQueue = Queue.Queue() 
     274        self.workerThreads = [] 
     275        CherryHTTPServer.__init__(self) 
     276     
     277    def process_request(self, request, client_address): 
     278        """Call finish_request.""" 
     279        self.finish_request(request, client_address) 
     280        # Let the ServerThread close the request when it's finished. 
     281##      NO!  self.close_request(request) 
     282     
     283    def finish_request(self, request, client_address): 
     284        """Finish one request by passing it to the Queue.""" 
     285        self.requestQueue.put((request, client_address)) 
    293286     
    294287    def createThread(self): 
    295         return self._ThreadClass(self._RequestHandlerClass, self._requestQueue, self) 
    296      
    297     def server_activate(self): 
    298         """Override server_activate to set timeout on our listener socket""" 
    299         self.socket.settimeout(1) 
    300         SocketServer.TCPServer.server_activate(self) 
    301      
    302     def shutdown(self): 
     288        return self.ThreadClass(self.RequestHandlerClass, self.requestQueue, self) 
     289     
     290    def serve_forever(self): 
     291        """Handle one request at a time until doomsday (or shutdown is called).""" 
     292        if self.workerThreads == []: 
     293            for i in xrange(self.numThreads): 
     294                self.workerThreads.append(self.createThread()) 
     295            for worker in self.workerThreads: 
     296                worker.start() 
     297         
     298        for worker in self.workerThreads: 
     299            while not worker.ready: 
     300                time.sleep(.1) 
     301         
     302        CherryHTTPServer.serve_forever(self) 
     303    start = serve_forever 
     304     
     305    def server_close(self): 
    303306        """Gracefully shutdown a server that is serve_forever()ing.""" 
    304         self.ready = False 
    305         # Close the socket so restarts work. 
    306         self.server_close() 
     307        CherryHTTPServer.server_close(self) 
    307308         
    308309        # Must shut down threads here so the code that calls 
    309310        # this method can know when all threads are stopped. 
    310         for worker in self._workerThreads: 
    311             self._requestQueue.put(_SHUTDOWNREQUEST) 
     311        for worker in self.workerThreads: 
     312            self.requestQueue.put(_SHUTDOWNREQUEST) 
    312313        current = threading.currentThread() 
    313         for worker in self._workerThreads: 
     314        for worker in self.workerThreads: 
    314315            if worker is not current and worker.isAlive: 
    315316                worker.join() 
    316         self._workerThreads = [] 
    317     stop = shutdown 
    318      
    319     def serve_forever(self): 
    320         """Handle one request at a time until doomsday (or shutdown is called).""" 
    321         if self._workerThreads == []: 
    322             for i in xrange(self._numThreads): 
    323                 self._workerThreads.append(self.createThread()) 
    324             for worker in self._workerThreads: 
    325                 worker.start() 
    326          
    327         for worker in self._workerThreads: 
    328             while not worker.ready: 
    329                 time.sleep(.1) 
    330          
    331         self.ready = True 
    332         while self.ready: 
    333             if self.interrupt: 
    334                 raise self.interrupt 
    335             if not self.handle_request(): 
    336                 break 
    337         self.server_close() 
    338     start = serve_forever 
    339      
    340     def handle_request(self): 
    341         """Override handle_request to enqueue requests rather than handle 
    342            them synchronously. Return 1 by default, 0 to shutdown the 
    343            server.""" 
    344         try: 
    345             request, client_address = self.get_request() 
    346         except socket.error, e: 
    347             return 1 
    348         self._requestQueue.put((request, client_address)) 
    349         return 1 
    350      
    351     def get_request(self): 
    352         # With Python 2.3 it seems that an accept socket in timeout 
    353         # (nonblocking) mode results in request sockets that are also set 
    354         # in nonblocking mode. Since that doesn't play well with makefile() 
    355         # (where wfile and rfile are set in SocketServer.py) we explicitly 
    356         # set the request socket to blocking 
    357          
    358         request, client_address = self.socket.accept() 
    359         if hasattr(request,'setblocking'): 
    360             request.setblocking(1) 
    361         return request, client_address 
     317        self.workerThreads = [] 
     318    stop = shutdown = server_close 
     319 
     320 
     321_SHUTDOWNREQUEST = (0,0) 
     322 
     323class ServerThread(threading.Thread): 
     324     
     325    def __init__(self, RequestHandlerClass, requestQueue, server): 
     326        self.server = server 
     327        self.ready = False 
     328        threading.Thread.__init__(self) 
     329        self.RequestHandlerClass = RequestHandlerClass 
     330        self.requestQueue = requestQueue 
     331     
     332    def run(self): 
     333        try: 
     334            self.ready = True 
     335            while 1: 
     336                request, client_address = self.requestQueue.get() 
     337                if (request, client_address) == _SHUTDOWNREQUEST: 
     338                    return 
     339                try: 
     340                    try: 
     341                        self.RequestHandlerClass(request, client_address, self) 
     342                    except (KeyboardInterrupt, SystemExit): 
     343                        raise 
     344                    except: 
     345                        cherrypy.log(_cputil.formatExc()) 
     346                finally: 
     347                    request.close() 
     348        except (KeyboardInterrupt, SystemExit), exc: 
     349            self.server.interrupt = exc 
    362350 
    363351 
     
    366354     
    367355    # Select the appropriate server based on config options 
    368     sockFile = cherrypy.config.get('server.socketFile') 
    369     threadPool = cherrypy.config.get('server.threadPool') 
    370     if threadPool > 1 and not sockFile: 
     356    if cherrypy.config.get('server.threadPool', 1) > 1: 
    371357        ServerClass = PooledThreadServer 
    372358    else: 
  • trunk/cherrypy/_cphttptools.py

    r838 r843  
    77 
    88import cherrypy 
    9 from cherrypy import _cputil, _cpcgifs, _cpwsgiserver 
     9from cherrypy import _cputil, _cpcgifs 
    1010from cherrypy.filters import applyFilters 
    1111from cherrypy.lib import cptools, httptools 
     
    219219                                          environ=methenv, 
    220220                                          keep_blank_values=1) 
    221         except _cpwsgiserver.MaxSizeExceeded: 
     221        except httptools.MaxSizeExceeded: 
    222222            # Post data is too big 
    223223            raise cherrypy.HTTPError(413) 
  • trunk/cherrypy/_cpwsgi.py

    r838 r843  
    33import sys 
    44import cherrypy 
    5 from cherrypy import _cputil 
    6 from cherrypy._cpwsgiserver import CherryPyWSGIServer as server 
     5from cherrypy import _cputil, _cpwsgiserver 
     6from cherrypy.lib import httptools 
    77 
    88 
     
    108108 
    109109 
    110  
    111110# Server components. 
    112111 
    113 class WSGIServer(server): 
     112 
     113class CPHTTPRequest(_cpwsgiserver.HTTPRequest): 
     114     
     115    def __init__(self, socket, addr, server): 
     116        _cpwsgiserver.HTTPRequest.__init__(self, socket, addr, server) 
     117        mhs = int(cherrypy.config.get('server.maxRequestHeaderSize', 
     118                                      500 * 1024)) 
     119        self.rfile = httptools.SizeCheckWrapper(self.rfile, mhs) 
     120     
     121    def parse_request(self): 
     122        try: 
     123            _cpwsgiserver.HTTPRequest.parse_request(self) 
     124        except httptools.MaxSizeExceeded: 
     125            msg = "Request Entity Too Large" 
     126            proto = self.environ.get("SERVER_PROTOCOL", "HTTP/1.0") 
     127            self.wfile.write("%s 413 %s\r\n" % (proto, msg)) 
     128            self.wfile.write("Content-Length: %s\r\n\r\n" % len(msg)) 
     129            self.wfile.write(msg) 
     130            self.wfile.flush() 
     131            self.ready = False 
     132             
     133            tb = _cputil.formatExc() 
     134            cherrypy.log(tb) 
     135        else: 
     136            if self.ready: 
     137                # Request header is parsed 
     138                # We prepare the SizeCheckWrapper for the request body 
     139                self.rfile.bytes_read = 0 
     140                path = self.environ["SCRIPT_NAME"] 
     141                if path == "*": 
     142                    path = "global" 
     143                else: 
     144                    path = "/" + path 
     145                mbs = int(cherrypy.config.get('server.maxRequestBodySize', 
     146                                              100 * 1024 * 1024, path=path)) 
     147                self.rfile.maxlen = mbs 
     148 
     149 
     150class WSGIServer(_cpwsgiserver.CherryPyWSGIServer): 
    114151     
    115152    """Wrapper for _cpwsgiserver.CherryPyWSGIServer. 
     
    121158    """ 
    122159     
     160    RequestHandlerClass = CPHTTPRequest 
     161     
    123162    def __init__(self): 
    124163        conf = cherrypy.config.get 
    125         server.__init__(self, 
    126                         (conf("server.socketHost"), conf("server.socketPort")), 
    127                         wsgiApp, 
    128                         conf("server.threadPool"), 
    129                         conf("server.socketHost"), 
    130                         config = cherrypy.config 
    131                         ) 
     164         
     165        sockFile = cherrypy.config.get('server.socketFile') 
     166        if sockFile: 
     167            bind_addr = sockFile 
     168        else: 
     169            bind_addr = (conf("server.socketHost"), conf("server.socketPort")) 
     170         
     171        s = _cpwsgiserver.CherryPyWSGIServer 
     172        s.__init__(self, bind_addr, wsgiApp, 
     173                   conf("server.threadPool"), 
     174                   conf("server.socketHost"), 
     175                   request_queue_size = conf('server.socketQueueSize'), 
     176                   ) 
     177 
  • trunk/cherrypy/_cpwsgiserver.py

    r825 r843  
    1313                   'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 
    1414 
    15 class MaxSizeExceeded(Exception): 
    16     pass 
    17  
    18 class SizeCheckWrapper(object): 
    19     """ Wrapper around the rfile object. For each data reading method, 
    20         it reads the data but it checks that the size of the data doesn't 
    21         exceed a certain limit 
    22     """ 
    23     def __init__(self, rfile, maxlen): 
    24         self.rfile = rfile 
    25         self.maxlen = maxlen 
    26         self.bytes_read = 0 
    27     def _check_length(self): 
    28         if self.maxlen and self.bytes_read > self.maxlen: 
    29             raise MaxSizeExceeded() 
    30     def read(self, size = None): 
    31         data = self.rfile.read(size) 
    32         self.bytes_read += len(data) 
    33         self._check_length() 
    34         return data 
    35     def readline(self, size = None): 
    36         if size is not None: 
    37             data = self.rfile.readline(size) 
    38             self.bytes_read += len(data) 
    39             self._check_length() 
    40             return data 
    41  
    42         # User didn't specify a size ... 
    43         # We read the line in chunks to make sure it's not a 100MB line ! 
    44         res = [] 
    45         while True: 
    46             data = self.rfile.readline(256) 
    47             self.bytes_read += len(data) 
    48             self._check_length() 
    49             res.append(data) 
    50             if len(data) < 256: 
    51                 return ''.join(res) 
    52     def close(self): 
    53         self.rfile.close() 
    54  
    55     def __iter__(self): 
    56         return self.rfile 
    57  
    58     def next(self): 
    59         data = self.rfile.next() 
    60         self.bytes_read += len(data) 
    61         self._check_length() 
    62 ##      Normally the next method must raise StopIteration when it 
    63 ##      fails but CP expects MaxSizeExceeded  
    64 ##        try: 
    65 ##            self._check_length() 
    66 ##        except: 
    67 ##            raise StopIteration() 
    68         return data 
    6915 
    7016class HTTPRequest(object): 
     17     
     18    stderr = sys.stderr 
     19    bufsize = -1 
     20     
    7121    def __init__(self, socket, addr, server): 
    7222        self.socket = socket 
     
    7929        self.outheaders = [] 
    8030        self.outheaderkeys = [] 
    81         self.rfile = self.socket.makefile("r", self.server.bufsize) 
    82         if self.server.config: 
    83             mhs = self.server.config.get( 
    84                 'server.maxRequestHeaderSize', 
    85                 500 * 1024) # 500KB by default 
    86             self.rfile = SizeCheckWrapper(self.rfile, mhs) 
    87         self.wfile = self.socket.makefile("w", self.server.bufsize) 
     31        self.rfile = self.socket.makefile("r", self.bufsize) 
     32        self.wfile = self.socket.makefile("w", self.bufsize) 
    8833        self.sent_headers = False 
    8934     
     
    9439        self.environ["wsgi.url_scheme"] = "http" 
    9540        self.environ["wsgi.input"] = self.rfile 
    96         self.environ["wsgi.errors"] = self.server.stderr 
     41        self.environ["wsgi.errors"] = self.stderr 
    9742        self.environ["wsgi.multithread"] = True 
    9843        self.environ["wsgi.multiprocess"] = False 
     
    11661        self.environ["SERVER_PROTOCOL"] = version 
    11762        self.environ["SERVER_NAME"] = self.server.server_name 
    118         self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) 
    119         # optional values 
    120         self.environ["REMOTE_HOST"] = self.addr[0] 
    121         self.environ["REMOTE_ADDR"] = self.addr[0] 
    122         self.environ["REMOTE_PORT"] = str(self.addr[1]) 
     63        if isinstance(self.server.bind_addr, basestring): 
     64            # AF_UNIX. This isn't really allowed by WSGI, which doesn't 
     65            # address unix domain sockets. But it's better than nothing. 
     66            self.environ["SERVER_PORT"] = "" 
     67        else: 
     68            self.environ["SERVER_PORT"] = str(self.server.bind_addr[1]) 
     69            # optional values 
     70            self.environ["REMOTE_HOST"] = self.addr[0] 
     71            self.environ["REMOTE_ADDR"] = self.addr[0] 
     72            self.environ["REMOTE_PORT"] = str(self.addr[1]) 
    12373        # then all the http headers 
    12474        headers = mimetools.Message(self.rfile) 
     
    12979            self.environ[envname] = v 
    13080        self.ready = True 
    131  
    132         # Request header is parsed 
    133         # We prepare the SizeCheckWrapper for the request body 
    134         if self.server.config: 
    135             mbs = self.server.config.get( 
    136                 'server.maxRequestBodySize', 
    137                 100 * 1024 * 1024, # 100MB by default 
    138                 path = path) 
    139             self.rfile.bytes_read = 0 
    140             self.rfile.maxlen = mbs 
    14181     
    14282    def start_response(self, status, headers, exc_info = None): 
     
    224164                        else: 
    225165                            raise 
    226                     except MaxSizeExceeded: 
    227                         str = "Request Entity Too Large" 
    228                         proto = request.environ.get("SERVER_PROTOCOL", "HTTP/1.0") 
    229                         request.wfile.write("%s 413 %s\r\n" % (proto, str)) 
    230                         request.wfile.write("Content-Length: %s\r\n\r\n" % len(str)) 
    231                         request.wfile.write(str) 
    232                         request.wfile.flush() 
    233166                    except (KeyboardInterrupt, SystemExit), exc: 
    234167                        self.server.interrupt = exc 
     
    246179    ready = False 
    247180    interrupt = None 
     181    RequestHandlerClass = HTTPRequest 
    248182     
    249183    def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, 
    250                  stderr=sys.stderr, bufsize=-1, max=-1, 
    251                  config = None): 
     184                 max=-1, request_queue_size=5): 
    252185        ''' 
    253186        be careful w/ max 
     
    257190        self.bind_addr = bind_addr 
    258191        self.numthreads = numthreads or 1 
    259         self.config = config 
    260         if server_name: 
    261             self.server_name = server_name 
    262         else: 
    263             self.server_name = socket.gethostname() 
    264         self.stderr = stderr 
    265         self.bufsize = bufsize 
     192        if not server_name: 
     193            server_name = socket.gethostname() 
     194        self.server_name = server_name 
     195        self.request_queue_size = request_queue_size 
    266196        self._workerThreads = [] 
    267197     
    268198    def start(self): 
    269         ''' 
    270         run the server forever 
    271         ''' 
     199        """Run the server forever.""" 
    272200        # We don't have to trap KeyboardInterrupt or SystemExit here, 
    273201        # because cherrpy.server already does so, calling self.stop() for us. 
    274202        # If you're using this server with another framework, you should 
    275203        # trap those exceptions in whatever code block calls start(). 
    276         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) 
    277         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    278         self.socket.bind(self.bind_addr) 
     204         
     205        # Select the appropriate socket 
     206        if isinstance(self.bind_addr, basestring): 
     207            # AF_UNIX socket 
     208             
     209            # So we can reuse the socket... 
     210            try: os.unlink(self.bind_addr) 
     211            except: pass 
     212             
     213            # So everyone can access the socket... 
     214            try: os.chmod(self.bind_addr, 0777) 
     215            except: pass 
     216             
     217            self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 
     218            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
     219            self.socket.bind(self.bind_addr) 
     220        else: 
     221            # AF_INET or AF_INET6 socket 
     222            # Get the correct address family for our host (allows IPv6 addresses) 
     223            host, port = self.bind_addr 
     224            for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, 
     225                                          socket.SOCK_STREAM): 
     226                af, socktype, proto, canonname, sa = res 
     227                try: 
     228                    self.socket = socket.socket(af, socktype, proto) 
     229                    self.socket.setsockopt(socket.SOL_SOCKET, 
     230                                           socket.SO_REUSEADDR, 1) 
     231                    self.socket.bind(self.bind_addr) 
     232                except socket.error, msg: 
     233                    if self.socket: 
     234                        self.socket.close() 
     235                    self.socket = None 
     236                    continue 
     237                break 
     238             
     239            if not self.socket: 
     240                raise socket.error, msg 
     241         
    279242        # Timeout so KeyboardInterrupt can be caught on Win32 
    280243        self.socket.settimeout(1) 
    281         self.socket.listen(5
     244        self.socket.listen(self.request_queue_size
    282245         
    283246        # Create worker threads 
     
    301264            if hasattr(s, 'setblocking'): 
    302265                s.setblocking(1) 
    303             request = HTTPRequest(s, addr, self) 
     266            request = self.RequestHandlerClass(s, addr, self) 
    304267            self.requests.put(request) 
    305             # optimized version follows 
    306             #self.requests.put(HTTPRequest(*self.socket.accept())) 
    307268        except socket.timeout: 
    308269            # The only reason for the timeout in start() is so we can 
  • trunk/cherrypy/lib/httptools.py

    r826 r843  
    410410            return None 
    411411        return header_elements(key, h) 
     412 
     413 
     414class MaxSizeExceeded(Exception): 
     415    pass 
     416 
     417class SizeCheckWrapper(object): 
     418    """ Wrapper around an rfile object. For each data reading method, 
     419        it reads the data but it checks that the size of the data doesn't 
     420        exceed a certain limit 
     421    """ 
     422    def __init__(self, rfile, maxlen): 
     423        self.rfile = rfile 
     424        self.maxlen = maxlen 
     425        self.bytes_read = 0 
     426     
     427    def _check_length(self): 
     428        if self.maxlen and self.bytes_read > self.maxlen: 
     429            raise MaxSizeExceeded() 
     430     
     431    def read(self, size = None): 
     432        data = self.rfile.read(size) 
     433        self.bytes_read += len(data) 
     434        self._check_length() 
     435        return data 
     436     
     437    def readline(self, size = None): 
     438        if size is not None: 
     439            data = self.rfile.readline(size) 
     440            self.bytes_read += len(data) 
     441            self._check_length() 
     442            return data 
     443 
     444        # User didn't specify a size ... 
     445        # We read the line in chunks to make sure it's not a 100MB line ! 
     446        res = [] 
     447        while True: 
     448            data = self.rfile.readline(256) 
     449            self.bytes_read += len(data) 
     450            self._check_length() 
     451            res.append(data) 
     452            if len(data) < 256: 
     453                return ''.join(res) 
     454     
     455    def close(self): 
     456        self.rfile.close() 
     457     
     458    def __iter__(self): 
     459        return self.rfile 
     460     
     461    def next(self): 
     462        data = self.rfile.next() 
     463        self.bytes_read += len(data) 
     464        self._check_length() 
     465##      Normally the next method must raise StopIteration when it 
     466##      fails but CP expects MaxSizeExceeded  
     467##        try: 
     468##            self._check_length() 
     469##        except: 
     470##            raise StopIteration() 
     471        return data 
  • trunk/cherrypy/test/test_core.py

    r838 r843  
    834834         
    835835        httpcls = cherrypy.server.httpserverclass 
    836         if httpcls and httpcls.__name__ == "WSGIServer"
     836        if httpcls
    837837            cherrypy.config.update({'server.maxRequestHeaderSize': 10}) 
    838838            self.getPage("/maxrequestsize/index") 
    839839            self.assertStatus("413 Request Entity Too Large") 
    840             self.assertBody("Request Entity Too Large") 
     840            self.assertInBody("Request Entity Too Large") 
    841841            cherrypy.config.update({'server.maxRequestHeaderSize': 0}) 
    842842         
     
    855855         
    856856        httpcls = cherrypy.server.httpserverclass 
    857         if httpcls and httpcls.__name__ == "WSGIServer"
     857        if httpcls
    858858            cherrypy.config.update({ 
    859859                '%s/maxrequestsize' % helper.vroot: {'server.maxRequestBodySize': 3}}) 

Hosted by WebFaction

Log in as guest/cpguest to create tickets