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

root/branches/cherrypy-3.0.x/cherrypy/_cpserver.py

Revision 1608 (checked in by fumanchu, 2 years ago)

New engine.release method, which decouples request and engine. Also new server.base method, which simplifies cherrypy.url. Finally, cherrypy._serving is promoted to cherrypy.serving, and has a new "load" method.

  • Property svn:eol-style set to native
Line 
1 """Manage an HTTP server with CherryPy."""
2
3 import socket
4 import threading
5 import time
6
7 import cherrypy
8 from cherrypy.lib import attributes
9
10
11 class Server(object):
12     """Manager for a set of HTTP servers.
13     
14     This is both a container and controller for "HTTP server" objects,
15     which are kept in Server.httpservers, a dictionary of the form:
16     {httpserver: bind_addr} where 'bind_addr' is usually a (host, port)
17     tuple.
18     
19     Most often, you will only be starting a single HTTP server. In this
20     common case, you can set attributes (like socket_host and socket_port)
21     on *this* object (which is probably cherrypy.server), and call
22     quickstart. For example:
23     
24         cherrypy.server.socket_port = 80
25         cherrypy.server.quickstart()
26     
27     But if you need to start more than one HTTP server (to serve on multiple
28     ports, or protocols, etc.), you can manually register each one and then
29     control them all through this object:
30     
31         s1 = MyWSGIServer(host='', port=80)
32         s2 = another.HTTPServer(host='localhost', SSL=True)
33         cherrypy.server.httpservers = {s1: ('', 80), s2: ('localhost', 443)}
34         # Note we do not use quickstart when we define our own httpservers
35         cherrypy.server.start()
36     
37     Whether you use quickstart(), or define your own httpserver entries and
38     use start(), you'll find that the start, wait, restart, and stop methods
39     work the same way, controlling all registered httpserver objects at once.
40     """
41    
42     socket_port = 8080
43     socket_host = ''
44     socket_file = ''
45     socket_queue_size = 5
46     socket_timeout = 10
47     protocol_version = 'HTTP/1.1'
48     reverse_dns = False
49     thread_pool = 10
50     max_request_header_size = 500 * 1024
51     max_request_body_size = 100 * 1024 * 1024
52     instance = None
53     ssl_certificate = None
54     ssl_private_key = None
55    
56     def __init__(self):
57         self.httpservers = {}
58         self.interrupt = None
59    
60     def quickstart(self, server=None):
61         """Start from defaults. MUST be called from the main thread.
62         
63         This function works like CherryPy 2's server.start(). It loads and
64         starts an httpserver based on the given server object (if provided)
65         and attributes of self.
66         """
67         httpserver, bind_addr = self.httpserver_from_self(server)
68         self.httpservers[httpserver] = bind_addr
69         self.start()
70    
71     def httpserver_from_self(self, httpserver=None):
72         """Return a (httpserver, bind_addr) pair based on self attributes."""
73         if httpserver is None:
74             httpserver = self.instance
75         if httpserver is None:
76             from cherrypy import _cpwsgi
77             httpserver = _cpwsgi.CPWSGIServer()
78         if isinstance(httpserver, basestring):
79             httpserver = attributes(httpserver)()
80        
81         if self.socket_file:
82             return httpserver, self.socket_file
83        
84         host = self.socket_host
85         port = self.socket_port
86         return httpserver, (host, port)
87    
88     def start(self):
89         """Start all registered HTTP servers."""
90         self.interrupt = None
91         if not self.httpservers:
92             raise ValueError("No HTTP servers have been created. "
93                              "Try server.quickstart instead.")
94         for httpserver in self.httpservers:
95             self._start_http(httpserver)
96    
97     def _start_http(self, httpserver):
98         """Start the given httpserver in a new thread."""
99         scheme = "http"
100         if getattr(httpserver, "ssl_certificate", None):
101             scheme = "https"
102         bind_addr = self.httpservers[httpserver]
103         if isinstance(bind_addr, tuple):
104             wait_for_free_port(*bind_addr)
105             host, port = bind_addr
106             if not host:
107                 host = '0.0.0.0'
108             on_what = "%s://%s:%s/" % (scheme, host, port)
109         else:
110             on_what = "socket file: %s" % bind_addr
111        
112         t = threading.Thread(target=self._start_http_thread, args=(httpserver,))
113         t.setName("CPHTTPServer " + t.getName())
114         t.start()
115        
116         self.wait(httpserver)
117         cherrypy.log("Serving %s on %s" % (scheme.upper(), on_what), 'HTTP')
118    
119     def _start_http_thread(self, httpserver):
120         """HTTP servers MUST be started in new threads, so that the
121         main thread persists to receive KeyboardInterrupt's. If an
122         exception is raised in the httpserver's thread then it's
123         trapped here, and the httpserver(s) and engine are shut down.
124         """
125         try:
126             httpserver.start()
127         except KeyboardInterrupt, exc:
128             cherrypy.log("<Ctrl-C> hit: shutting down HTTP servers", "SERVER")
129             self.interrupt = exc
130             self.stop()
131             cherrypy.engine.stop()
132         except SystemExit, exc:
133             cherrypy.log("SystemExit raised: shutting down HTTP servers", "SERVER")
134             self.interrupt = exc
135             self.stop()
136             cherrypy.engine.stop()
137             raise
138    
139     def wait(self, httpserver=None):
140         """Wait until the HTTP server is ready to receive requests.
141         
142         If no httpserver is specified, wait for all registered httpservers.
143         """
144         if httpserver is None:
145             httpservers = self.httpservers.items()
146         else:
147             httpservers = [(httpserver, self.httpservers[httpserver])]
148        
149         for httpserver, bind_addr in httpservers:
150             while not (getattr(httpserver, "ready", False) or self.interrupt):
151                 time.sleep(.1)
152             if self.interrupt:
153                 raise self.interrupt
154            
155             # Wait for port to be occupied
156             if isinstance(bind_addr, tuple):
157                 wait_for_occupied_port(*bind_addr)
158    
159     def stop(self):
160         """Stop all HTTP servers."""
161         for httpserver, bind_addr in self.httpservers.items():
162             try:
163                 httpstop = httpserver.stop
164             except AttributeError:
165                 pass
166             else:
167                 # httpstop() MUST block until the server is *truly* stopped.
168                 httpstop()
169                 if isinstance(bind_addr, tuple):
170                     wait_for_free_port(*bind_addr)
171                 cherrypy.log("HTTP Server shut down", "HTTP")
172    
173     def restart(self):
174         """Restart all HTTP servers."""
175         self.stop()
176         self.start()
177    
178     def base(self):
179         """Return the base (scheme://host) for this server manager."""
180         if self.socket_file:
181             return self.socket_file
182        
183         host = self.socket_host
184         if not host:
185             # The empty string signifies INADDR_ANY. Look up the host name,
186             # which should be the safest thing to spit out in a URL.
187             host = socket.gethostname()
188        
189         port = self.socket_port
190        
191         if self.ssl_certificate:
192             scheme = "https"
193             if port != 443:
194                 host += ":%s" % port
195         else:
196             scheme = "http"
197             if port != 80:
198                 host += ":%s" % port
199        
200         return "%s://%s" % (scheme, host)
201
202
203 def check_port(host, port):
204     """Raise an error if the given port is not free on the given host."""
205     if not host:
206         # The empty string signifies INADDR_ANY,
207         # which should respond on localhost.
208         host = 'localhost'
209     port = int(port)
210    
211     # AF_INET or AF_INET6 socket
212     # Get the correct address family for our host (allows IPv6 addresses)
213     for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
214                                   socket.SOCK_STREAM):
215         af, socktype, proto, canonname, sa = res
216         s = None
217         try:
218             s = socket.socket(af, socktype, proto)
219             # See http://groups.google.com/group/cherrypy-users/
220             #        browse_frm/thread/bbfe5eb39c904fe0
221             s.settimeout(1.0)
222             s.connect((host, port))
223             s.close()
224             raise IOError("Port %s is in use on %s; perhaps the previous "
225                           "httpserver did not shut down properly." %
226                           (repr(port), repr(host)))
227         except socket.error:
228             if s:
229                 s.close()
230
231
232 def wait_for_free_port(host, port):
233     """Wait for the specified port to become free (drop requests)."""
234     if not host:
235         # The empty string signifies INADDR_ANY,
236         # which should respond on localhost.
237         host = 'localhost'
238    
239     for trial in xrange(50):
240         try:
241             check_port(host, port)
242         except IOError:
243             # Give the old server thread time to free the port.
244             time.sleep(.1)
245         else:
246             return
247    
248     msg = "Port %s not free on %s" % (repr(port), repr(host))
249     cherrypy.log(msg, 'HTTP')
250     raise IOError(msg)
251
252 def wait_for_occupied_port(host, port):
253     """Wait for the specified port to become active (receive requests)."""
254     if not host:
255         # The empty string signifies INADDR_ANY,
256         # which should respond on localhost.
257         host = 'localhost'
258    
259     for trial in xrange(50):
260         try:
261             check_port(host, port)
262         except IOError:
263             return
264         else:
265             time.sleep(.1)
266    
267     msg = "Port %s not bound on %s" % (repr(port), repr(host))
268     cherrypy.log(msg, 'HTTP')
269     raise IOError(msg)
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets