Commits

Robert Brewer committed faaaef5

Fixed some error handling and made a log sink for HTTPServer.

Comments (0)

Files changed (6)

cherrypy/_cpwsgi_server.py

         
         self.stats['Enabled'] = getattr(self.server_adapter, 'statistics', False)
 
+    def log(self, msg="", level=20, traceback=False):
+        cherrypy.engine.log(msg, level, traceback)
+

cherrypy/test/test_http.py

 """Tests for managing HTTP issues (malformed requests, etc)."""
 
+import errno
 import mimetypes
+import socket
+import sys
 
 import cherrypy
 from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, ntob, py3k
         self.assertEqual(response.status, 400)
         self.assertEqual(response.fp.read(22), ntob("Malformed Request-Line"))
         c.close()
-    
+
     def test_malformed_header(self):
         if self.scheme == 'https':
             c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
         conn.putheader("Host", self.HOST)
         conn.endheaders()
         response = conn.response_class(conn.sock, method="GET")
-        response.begin()
-        self.assertEqual(response.status, 400)
-        self.body = response.read()
-        self.assertBody("The client sent a plain HTTP request, but this "
-                        "server only speaks HTTPS on this port.")
+        try:
+            response.begin()
+            self.assertEqual(response.status, 400)
+            self.body = response.read()
+            self.assertBody("The client sent a plain HTTP request, but this "
+                            "server only speaks HTTPS on this port.")
+        except socket.error:
+            e = sys.exc_info()[1]
+            # "Connection reset by peer" is also acceptable.
+            if e.errno != errno.ECONNRESET:
+                raise
 
+    def test_garbage_in(self):
+        # Connect without SSL regardless of server.scheme
+        c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
+        c._output(ntob('gjkgjklsgjklsgjkljklsg'))
+        c._send_output()
+        response = c.response_class(c.sock, method="GET")
+        try:
+            response.begin()
+        except socket.error:
+            e = sys.exc_info()[1]
+            # "Connection reset by peer" is also acceptable.
+            if e.errno != errno.ECONNRESET:
+                raise
+        else:
+            raise AssertionError("Server did not reset connection.")
+

cherrypy/wsgiserver/__init__.py

            'WorkerThread', 'ThreadPool', 'SSLAdapter',
            'CherryPyWSGIServer',
            'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
-           'WSGIPathInfoDispatcher']
+           'WSGIPathInfoDispatcher', 'get_ssl_adapter_class']
 
 import sys
 if sys.version_info < (3, 0):

cherrypy/wsgiserver/ssl_builtin.py

                 if e.args[1].endswith('http request'):
                     # The client is speaking HTTP to an HTTPS server.
                     raise wsgiserver.NoSSLError
+                elif e.args[1].endswith('unknown protocol'):
+                    # The client is speaking some non-HTTP protocol.
+                    # Drop the conn.
+                    return None, {}
             raise
         return s, self.get_environ(s)
     

cherrypy/wsgiserver/wsgiserver2.py

            'WorkerThread', 'ThreadPool', 'SSLAdapter',
            'CherryPyWSGIServer',
            'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
-           'WSGIPathInfoDispatcher']
+           'WSGIPathInfoDispatcher', 'get_ssl_adapter_class']
 
 import os
 try:
         self.rfile = SizeCheckWrapper(self.conn.rfile,
                                       self.server.max_request_header_size)
         try:
-            self.read_request_line()
+            success = self.read_request_line()
         except MaxSizeExceeded:
             self.simple_response("414 Request-URI Too Long",
                 "The Request-URI sent with the request exceeds the maximum "
                 "allowed bytes.")
             return
+        else:
+            if not success:
+                return
         
         try:
             success = self.read_request_headers()
         # from here on out.
         self.started_request = True
         if not request_line:
-            # Force self.ready = False so the connection will close.
-            self.ready = False
-            return
+            return False
         
         if request_line == CRLF:
             # RFC 2616 sec 4.1: "...if the server is reading the protocol
             # But only ignore one leading line! else we enable a DoS.
             request_line = self.rfile.readline()
             if not request_line:
-                self.ready = False
-                return
+                return False
         
         if not request_line.endswith(CRLF):
             self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
-            return
+            return False
         
         try:
             method, uri, req_protocol = request_line.strip().split(SPACE, 2)
             rp = int(req_protocol[5]), int(req_protocol[7])
         except (ValueError, IndexError):
             self.simple_response("400 Bad Request", "Malformed Request-Line")
-            return
+            return False
         
         self.uri = uri
         self.method = method
         if NUMBER_SIGN in path:
             self.simple_response("400 Bad Request",
                                  "Illegal #fragment in Request-URI.")
-            return
+            return False
         
         if scheme:
             self.scheme = scheme
         except ValueError:
             ex = sys.exc_info()[1]
             self.simple_response("400 Bad Request", ex.args[0])
-            return
+            return False
         path = "%2F".join(atoms)
         self.path = path
         
         
         if sp[0] != rp[0]:
             self.simple_response("505 HTTP Version Not Supported")
-            return
+            return False
+
         self.request_protocol = req_protocol
         self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
-    
+
+        return True
+
     def read_request_headers(self):
         """Read self.rfile into self.inheaders. Return success."""
         
                             # Close the connection.
                             return
             elif errnum not in socket_errors_to_ignore:
+                self.server.error_log("socket.error %s" % repr(errnum),
+                                      level=logging.WARNING, traceback=True)
                 if req and not req.sent_headers:
                     try:
-                        req.simple_response("500 Internal Server Error",
-                                            format_exc())
+                        req.simple_response("500 Internal Server Error")
                     except FatalSSLAlert:
                         # Close the connection.
                         return
                     "this server only speaks HTTPS on this port.")
                 self.linger = True
         except Exception:
+            e = sys.exc_info()[1]
+            self.server.error_log(repr(e), level=logging.ERROR, traceback=True)
             if req and not req.sent_headers:
                 try:
-                    req.simple_response("500 Internal Server Error", format_exc())
+                    req.simple_response("500 Internal Server Error")
                 except FatalSSLAlert:
                     # Close the connection.
                     return
         self.ready = True
         self._start_time = time.time()
         while self.ready:
-            self.tick()
+            try:
+                self.tick()
+            except:
+                self.error_log("Error in HTTPServer.tick", level-logging.ERROR,
+                               traceback=True)
+            
             if self.interrupt:
                 while self.interrupt is True:
                     # Wait for self.stop() to complete. See _set_interrupt.
                     time.sleep(0.1)
                 if self.interrupt:
                     raise self.interrupt
-    
+
+    def error_log(self, msg="", level=20, traceback=False):
+        # Override this in subclasses as desired
+        sys.stderr.write(msg + '\n')
+        sys.stderr.flush()
+        if traceback:
+            tblines = format_exc()
+            sys.stderr.write(tblines)
+            sys.stderr.flush()
+
     def bind(self, family, type, proto=0):
         """Create (or recreate) the actual socket object."""
         self.socket = socket.socket(family, type, proto)

cherrypy/wsgiserver/wsgiserver3.py

            'WorkerThread', 'ThreadPool', 'SSLAdapter',
            'CherryPyWSGIServer',
            'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
-           'WSGIPathInfoDispatcher']
+           'WSGIPathInfoDispatcher', 'get_ssl_adapter_class']
 
 import os
 try:
         self.rfile = SizeCheckWrapper(self.conn.rfile,
                                       self.server.max_request_header_size)
         try:
-            self.read_request_line()
+            success = self.read_request_line()
         except MaxSizeExceeded:
             self.simple_response("414 Request-URI Too Long",
                 "The Request-URI sent with the request exceeds the maximum "
                 "allowed bytes.")
             return
+        else:
+            if not success:
+                return
         
         try:
             success = self.read_request_headers()
         # from here on out.
         self.started_request = True
         if not request_line:
-            # Force self.ready = False so the connection will close.
-            self.ready = False
-            return
+            return False
         
         if request_line == CRLF:
             # RFC 2616 sec 4.1: "...if the server is reading the protocol
             # But only ignore one leading line! else we enable a DoS.
             request_line = self.rfile.readline()
             if not request_line:
-                self.ready = False
-                return
+                return False
         
         if not request_line.endswith(CRLF):
             self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
-            return
+            return False
         
         try:
             method, uri, req_protocol = request_line.strip().split(SPACE, 2)
             rp = int(req_protocol[5:6]), int(req_protocol[7:8])
         except ValueError:
             self.simple_response("400 Bad Request", "Malformed Request-Line")
-            return
+            return False
         
         self.uri = uri
         self.method = method
         if NUMBER_SIGN in path:
             self.simple_response("400 Bad Request",
                                  "Illegal #fragment in Request-URI.")
-            return
+            return False
         
         if scheme:
             self.scheme = scheme
         except ValueError:
             ex = sys.exc_info()[1]
             self.simple_response("400 Bad Request", ex.args[0])
-            return
+            return False
         path = b"%2F".join(atoms)
         self.path = path
         
         
         if sp[0] != rp[0]:
             self.simple_response("505 HTTP Version Not Supported")
-            return
+            return False
+
         self.request_protocol = req_protocol
         self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
-    
+        return True
+
     def read_request_headers(self):
         """Read self.rfile into self.inheaders. Return success."""
         
                             # Close the connection.
                             return
             elif errnum not in socket_errors_to_ignore:
+                self.server.error_log("socket.error %s" % repr(errnum),
+                                      level=logging.WARNING, traceback=True)
                 if req and not req.sent_headers:
                     try:
-                        req.simple_response("500 Internal Server Error",
-                                            format_exc())
+                        req.simple_response("500 Internal Server Error")
                     except FatalSSLAlert:
                         # Close the connection.
                         return
                     "this server only speaks HTTPS on this port.")
                 self.linger = True
         except Exception:
+            e = sys.exc_info()[1]
+            self.server.error_log(repr(e), level=logging.ERROR, traceback=True)
             if req and not req.sent_headers:
                 try:
-                    req.simple_response("500 Internal Server Error", format_exc())
+                    req.simple_response("500 Internal Server Error")
                 except FatalSSLAlert:
                     # Close the connection.
                     return
         self.ready = True
         self._start_time = time.time()
         while self.ready:
-            self.tick()
+            try:
+                self.tick()
+            except:
+                self.error_log("Error in HTTPServer.tick", level=logging.ERROR,
+                               traceback=True)
             if self.interrupt:
                 while self.interrupt is True:
                     # Wait for self.stop() to complete. See _set_interrupt.
                     time.sleep(0.1)
                 if self.interrupt:
                     raise self.interrupt
-    
+
+    def error_log(self, msg="", level=20, traceback=False):
+        # Override this in subclasses as desired
+        sys.stderr.write(msg + '\n')
+        sys.stderr.flush()
+        if traceback:
+            tblines = format_exc()
+            sys.stderr.write(tblines)
+            sys.stderr.flush()
+
     def bind(self, family, type, proto=0):
         """Create (or recreate) the actual socket object."""
         self.socket = socket.socket(family, type, proto)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.