1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """HTTP server module.
22
23 """
24
25 import BaseHTTPServer
26 import cgi
27 import logging
28 import os
29 import select
30 import socket
31 import time
32 import signal
33
34 from ganeti import http
35
36
37 WEEKDAYNAME = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
38 MONTHNAME = [None,
39 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
40 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
41
42
43 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
44 DEFAULT_ERROR_MESSAGE = """\
45 <html>
46 <head>
47 <title>Error response</title>
48 </head>
49 <body>
50 <h1>Error response</h1>
51 <p>Error code %(code)d.
52 <p>Message: %(message)s.
53 <p>Error code explanation: %(code)s = %(explain)s.
54 </body>
55 </html>
56 """
57
58
60 """Return the current date and time formatted for a message header.
61
62 The time MUST be in the GMT timezone.
63
64 """
65 if gmnow is None:
66 gmnow = time.gmtime()
67 (year, month, day, hh, mm, ss, wd, _, _) = gmnow
68 return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %
69 (WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss))
70
71
73 """Data structure for HTTP request on server side.
74
75 """
77
78 self.request_method = request_msg.start_line.method
79 self.request_path = request_msg.start_line.path
80 self.request_headers = request_msg.headers
81 self.request_body = request_msg.decoded_body
82
83
84 self.resp_headers = {}
85
86
87
88 self.private = None
89
90
92 """Writes an HTTP response to client.
93
94 """
95 - def __init__(self, sock, request_msg, response_msg, write_timeout):
96 """Writes the response to the client.
97
98 @type sock: socket
99 @param sock: Target socket
100 @type request_msg: http.HttpMessage
101 @param request_msg: Request message, required to determine whether
102 response may have a message body
103 @type response_msg: http.HttpMessage
104 @param response_msg: Response message
105 @type write_timeout: float
106 @param write_timeout: Write timeout for socket
107
108 """
109 self._request_msg = request_msg
110 self._response_msg = response_msg
111 http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
112
113 - def HasMessageBody(self):
114 """Logic to detect whether response should contain a message body.
115
116 """
117 if self._request_msg.start_line:
118 request_method = self._request_msg.start_line.method
119 else:
120 request_method = None
121
122 response_code = self._response_msg.start_line.code
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 return (http.HttpMessageWriter.HasMessageBody(self) and
138 (request_method is not None and
139 request_method != http.HTTP_HEAD) and
140 response_code >= http.HTTP_OK and
141 response_code not in (http.HTTP_NO_CONTENT,
142 http.HTTP_NOT_MODIFIED))
143
144
146 """Reads an HTTP request sent by client.
147
148 """
149
150 START_LINE_LENGTH_MAX = 4096
151 HEADER_LENGTH_MAX = 4096
152
154 """Parses the start line sent by client.
155
156 Example: "GET /index.html HTTP/1.1"
157
158 @type start_line: string
159 @param start_line: Start line
160
161 """
162
163 assert start_line
164
165 logging.debug("HTTP request: %s", start_line)
166
167 words = start_line.split()
168
169 if len(words) == 3:
170 [method, path, version] = words
171 if version[:5] != 'HTTP/':
172 raise http.HttpBadRequest("Bad request version (%r)" % version)
173
174 try:
175 base_version_number = version.split("/", 1)[1]
176 version_number = base_version_number.split(".")
177
178
179
180
181
182
183
184 if len(version_number) != 2:
185 raise http.HttpBadRequest("Bad request version (%r)" % version)
186
187 version_number = (int(version_number[0]), int(version_number[1]))
188 except (ValueError, IndexError):
189 raise http.HttpBadRequest("Bad request version (%r)" % version)
190
191 if version_number >= (2, 0):
192 raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
193 base_version_number)
194
195 elif len(words) == 2:
196 version = http.HTTP_0_9
197 [method, path] = words
198 if method != http.HTTP_GET:
199 raise http.HttpBadRequest("Bad HTTP/0.9 request type (%r)" % method)
200
201 else:
202 raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
203
204 return http.HttpClientToServerStartLine(method, path, version)
205
206
208 """Implements server side of HTTP.
209
210 This class implements the server side of HTTP. It's based on code of
211 Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
212 support non-ASCII character encodings. Keep-alive connections are
213 not supported.
214
215 """
216
217
218
219
220 default_request_version = http.HTTP_0_9
221
222
223 error_message_format = DEFAULT_ERROR_MESSAGE
224 error_content_type = DEFAULT_ERROR_CONTENT_TYPE
225
226 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
227
228
229 WRITE_TIMEOUT = 10
230 READ_TIMEOUT = 10
231 CLOSE_TIMEOUT = 1
232
233 - def __init__(self, server, sock, client_addr):
234 """Initializes this class.
235
236 """
237 self.server = server
238 self.sock = sock
239 self.client_addr = client_addr
240
241 self.request_msg = http.HttpMessage()
242 self.response_msg = http.HttpMessage()
243
244 self.response_msg.start_line = \
245 http.HttpServerToClientStartLine(version=self.default_request_version,
246 code=None, reason=None)
247
248
249 self.sock.settimeout(None)
250
251
252 self.sock.setblocking(0)
253
254 logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
255 try:
256 request_msg_reader = None
257 force_close = True
258 try:
259
260 if self.server.using_ssl:
261 self.sock.set_accept_state()
262 try:
263 http.Handshake(self.sock, self.WRITE_TIMEOUT)
264 except http.HttpSessionHandshakeUnexpectedEOF:
265
266 return
267
268 try:
269 try:
270 request_msg_reader = self._ReadRequest()
271 self._HandleRequest()
272
273
274 force_close = False
275 except http.HttpException, err:
276 self._SetErrorStatus(err)
277 finally:
278
279 self._SendResponse()
280 finally:
281 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
282 request_msg_reader, force_close)
283
284 self.sock.close()
285 self.sock = None
286 finally:
287 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
288
290 """Reads a request sent by client.
291
292 """
293 try:
294 request_msg_reader = \
295 _HttpClientToServerMessageReader(self.sock, self.request_msg,
296 self.READ_TIMEOUT)
297 except http.HttpSocketTimeout:
298 raise http.HttpError("Timeout while reading request")
299 except socket.error, err:
300 raise http.HttpError("Error reading request: %s" % err)
301
302 self.response_msg.start_line.version = self.request_msg.start_line.version
303
304 return request_msg_reader
305
337
339 """Sends the response to the client.
340
341 """
342 if self.response_msg.start_line.code is None:
343 return
344
345 if not self.response_msg.headers:
346 self.response_msg.headers = {}
347
348 self.response_msg.headers.update({
349
350 http.HTTP_CONNECTION: "close",
351 http.HTTP_DATE: _DateTimeHeader(),
352 http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
353 })
354
355
356 response_code = self.response_msg.start_line.code
357 if response_code in self.responses:
358 response_reason = self.responses[response_code][0]
359 else:
360 response_reason = ""
361 self.response_msg.start_line.reason = response_reason
362
363 logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1],
364 self.request_msg.start_line, response_code)
365
366 try:
367 _HttpServerToClientMessageWriter(self.sock, self.request_msg,
368 self.response_msg, self.WRITE_TIMEOUT)
369 except http.HttpSocketTimeout:
370 raise http.HttpError("Timeout while sending response")
371 except socket.error, err:
372 raise http.HttpError("Error sending response: %s" % err)
373
375 """Sets the response code and body from a HttpException.
376
377 @type err: HttpException
378 @param err: Exception instance
379
380 """
381 try:
382 (shortmsg, longmsg) = self.responses[err.code]
383 except KeyError:
384 shortmsg = longmsg = "Unknown"
385
386 if err.message:
387 message = err.message
388 else:
389 message = shortmsg
390
391 values = {
392 "code": err.code,
393 "message": cgi.escape(message),
394 "explain": longmsg,
395 }
396
397 self.response_msg.start_line.code = err.code
398
399 headers = {}
400 if err.headers:
401 headers.update(err.headers)
402 headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
403 self.response_msg.headers = headers
404
405 self.response_msg.body = self._FormatErrorMessage(values)
406
417
419 """Generic HTTP server class
420
421 Users of this class must subclass it and override the HandleRequest function.
422
423 """
424 MAX_CHILDREN = 20
425
426 - def __init__(self, mainloop, local_address, port,
427 ssl_params=None, ssl_verify_peer=False,
428 request_executor_class=None):
429 """Initializes the HTTP server
430
431 @type mainloop: ganeti.daemon.Mainloop
432 @param mainloop: Mainloop used to poll for I/O events
433 @type local_address: string
434 @param local_address: Local IP address to bind to
435 @type port: int
436 @param port: TCP port to listen on
437 @type ssl_params: HttpSslParams
438 @param ssl_params: SSL key and certificate
439 @type ssl_verify_peer: bool
440 @param ssl_verify_peer: Whether to require client certificate
441 and compare it with our certificate
442 @type request_executor_class: class
443 @param request_executor_class: an class derived from the
444 HttpServerRequestExecutor class
445
446 """
447 http.HttpBase.__init__(self)
448
449 if request_executor_class is None:
450 self.request_executor = HttpServerRequestExecutor
451 else:
452 self.request_executor = request_executor_class
453
454 self.mainloop = mainloop
455 self.local_address = local_address
456 self.port = port
457
458 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer)
459
460
461 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
462
463 self._children = []
464
465 mainloop.RegisterIO(self, self.socket.fileno(), select.POLLIN)
466 mainloop.RegisterSignal(self)
467
469 self.socket.bind((self.local_address, self.port))
470 self.socket.listen(1024)
471
474
475 - def OnIO(self, fd, condition):
478
482
484 """Checks whether any child processes are done
485
486 @type quick: bool
487 @param quick: Whether to only use non-blocking functions
488
489 """
490 if not quick:
491
492 while len(self._children) > self.MAX_CHILDREN:
493 try:
494
495
496
497
498 pid, _ = os.waitpid(0, 0)
499 except os.error:
500 pid = None
501 if pid and pid in self._children:
502 self._children.remove(pid)
503
504 for child in self._children:
505 try:
506 pid, status = os.waitpid(child, os.WNOHANG)
507 except os.error:
508 pid = None
509 if pid and pid in self._children:
510 self._children.remove(pid)
511
513 """Called for each incoming connection
514
515 """
516 (connection, client_addr) = self.socket.accept()
517
518 self._CollectChildren(False)
519
520 pid = os.fork()
521 if pid == 0:
522
523 try:
524 self.request_executor(self, connection, client_addr)
525 except Exception:
526 logging.exception("Error while handling request from %s:%s",
527 client_addr[0], client_addr[1])
528 os._exit(1)
529 os._exit(0)
530 else:
531 self._children.append(pid)
532
534 """Called before handling a request.
535
536 Can be overridden by a subclass.
537
538 """
539
541 """Handles a request.
542
543 Must be overridden by subclass.
544
545 """
546 raise NotImplementedError()
547