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
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
57 self.httpservers = {}
58 self.interrupt = None
59
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
87
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
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
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
156 if isinstance(bind_addr, tuple):
157 wait_for_occupied_port(*bind_addr)
158
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
168 httpstop()
169 if isinstance(bind_addr, tuple):
170 wait_for_free_port(*bind_addr)
171 cherrypy.log("HTTP Server shut down", "HTTP")
172
174 """Restart all HTTP servers."""
175 self.stop()
176 self.start()
177
201
202
204 """Raise an error if the given port is not free on the given host."""
205 if not host:
206
207
208 host = 'localhost'
209 port = int(port)
210
211
212
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
220
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
233 """Wait for the specified port to become free (drop requests)."""
234 if not host:
235
236
237 host = 'localhost'
238
239 for trial in xrange(50):
240 try:
241 check_port(host, port)
242 except IOError:
243
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
253 """Wait for the specified port to become active (receive requests)."""
254 if not host:
255
256
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)
270