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 socket
30 import time
31 import signal
32 import asyncore
33
34 from ganeti import http
35 from ganeti import utils
36 from ganeti import netutils
37
38
39 WEEKDAYNAME = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
40 MONTHNAME = [None,
41 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
42 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
43
44
45 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
46 DEFAULT_ERROR_MESSAGE = """\
47 <html>
48 <head>
49 <title>Error response</title>
50 </head>
51 <body>
52 <h1>Error response</h1>
53 <p>Error code %(code)d.
54 <p>Message: %(message)s.
55 <p>Error code explanation: %(code)s = %(explain)s.
56 </body>
57 </html>
58 """
59
60
62 """Return the current date and time formatted for a message header.
63
64 The time MUST be in the GMT timezone.
65
66 """
67 if gmnow is None:
68 gmnow = time.gmtime()
69 (year, month, day, hh, mm, ss, wd, _, _) = gmnow
70 return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %
71 (WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss))
72
73
75 """Data structure for HTTP request on server side.
76
77 """
78 - def __init__(self, method, path, headers, body):
79
80 self.request_method = method
81 self.request_path = path
82 self.request_headers = headers
83 self.request_body = body
84
85
86 self.resp_headers = {}
87
88
89
90 self.private = None
91
93 status = ["%s.%s" % (self.__class__.__module__, self.__class__.__name__),
94 self.request_method, self.request_path,
95 "headers=%r" % str(self.request_headers),
96 "body=%r" % (self.request_body, )]
97
98 return "<%s at %#x>" % (" ".join(status), id(self))
99
100
102 """Writes an HTTP response to client.
103
104 """
105 - def __init__(self, sock, request_msg, response_msg, write_timeout):
106 """Writes the response to the client.
107
108 @type sock: socket
109 @param sock: Target socket
110 @type request_msg: http.HttpMessage
111 @param request_msg: Request message, required to determine whether
112 response may have a message body
113 @type response_msg: http.HttpMessage
114 @param response_msg: Response message
115 @type write_timeout: float
116 @param write_timeout: Write timeout for socket
117
118 """
119 self._request_msg = request_msg
120 self._response_msg = response_msg
121 http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
122
123 - def HasMessageBody(self):
124 """Logic to detect whether response should contain a message body.
125
126 """
127 if self._request_msg.start_line:
128 request_method = self._request_msg.start_line.method
129 else:
130 request_method = None
131
132 response_code = self._response_msg.start_line.code
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 return (http.HttpMessageWriter.HasMessageBody(self) and
148 (request_method is not None and
149 request_method != http.HTTP_HEAD) and
150 response_code >= http.HTTP_OK and
151 response_code not in (http.HTTP_NO_CONTENT,
152 http.HTTP_NOT_MODIFIED))
153
154
156 """Reads an HTTP request sent by client.
157
158 """
159
160 START_LINE_LENGTH_MAX = 4096
161 HEADER_LENGTH_MAX = 4096
162
164 """Parses the start line sent by client.
165
166 Example: "GET /index.html HTTP/1.1"
167
168 @type start_line: string
169 @param start_line: Start line
170
171 """
172
173 assert start_line
174
175 logging.debug("HTTP request: %s", start_line)
176
177 words = start_line.split()
178
179 if len(words) == 3:
180 [method, path, version] = words
181 if version[:5] != 'HTTP/':
182 raise http.HttpBadRequest("Bad request version (%r)" % version)
183
184 try:
185 base_version_number = version.split("/", 1)[1]
186 version_number = base_version_number.split(".")
187
188
189
190
191
192
193
194 if len(version_number) != 2:
195 raise http.HttpBadRequest("Bad request version (%r)" % version)
196
197 version_number = (int(version_number[0]), int(version_number[1]))
198 except (ValueError, IndexError):
199 raise http.HttpBadRequest("Bad request version (%r)" % version)
200
201 if version_number >= (2, 0):
202 raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
203 base_version_number)
204
205 elif len(words) == 2:
206 version = http.HTTP_0_9
207 [method, path] = words
208 if method != http.HTTP_GET:
209 raise http.HttpBadRequest("Bad HTTP/0.9 request type (%r)" % method)
210
211 else:
212 raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
213
214 return http.HttpClientToServerStartLine(method, path, version)
215
216
218 """Implements server side of HTTP.
219
220 This class implements the server side of HTTP. It's based on code of
221 Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
222 support non-ASCII character encodings. Keep-alive connections are
223 not supported.
224
225 """
226
227
228
229
230 default_request_version = http.HTTP_0_9
231
232
233 error_message_format = DEFAULT_ERROR_MESSAGE
234 error_content_type = DEFAULT_ERROR_CONTENT_TYPE
235
236 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
237
238
239 WRITE_TIMEOUT = 10
240 READ_TIMEOUT = 10
241 CLOSE_TIMEOUT = 1
242
243 - def __init__(self, server, sock, client_addr):
244 """Initializes this class.
245
246 """
247 self.server = server
248 self.sock = sock
249 self.client_addr = client_addr
250
251 self.request_msg = http.HttpMessage()
252 self.response_msg = http.HttpMessage()
253
254 self.response_msg.start_line = \
255 http.HttpServerToClientStartLine(version=self.default_request_version,
256 code=None, reason=None)
257
258
259 self.sock.settimeout(None)
260
261
262 self.sock.setblocking(0)
263
264 logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
265 try:
266 request_msg_reader = None
267 force_close = True
268 try:
269
270 if self.server.using_ssl:
271 self.sock.set_accept_state()
272 try:
273 http.Handshake(self.sock, self.WRITE_TIMEOUT)
274 except http.HttpSessionHandshakeUnexpectedEOF:
275
276 return
277
278 try:
279 try:
280 request_msg_reader = self._ReadRequest()
281
282
283
284
285 if (self.request_msg.start_line.version == http.HTTP_1_1 and
286 http.HTTP_HOST not in self.request_msg.headers):
287 raise http.HttpBadRequest(message="Missing Host header")
288
289 self._HandleRequest()
290
291
292 force_close = False
293 except http.HttpException, err:
294 self._SetErrorStatus(err)
295 finally:
296
297 self._SendResponse()
298 finally:
299 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
300 request_msg_reader, force_close)
301
302 self.sock.close()
303 self.sock = None
304 finally:
305 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
306
308 """Reads a request sent by client.
309
310 """
311 try:
312 request_msg_reader = \
313 _HttpClientToServerMessageReader(self.sock, self.request_msg,
314 self.READ_TIMEOUT)
315 except http.HttpSocketTimeout:
316 raise http.HttpError("Timeout while reading request")
317 except socket.error, err:
318 raise http.HttpError("Error reading request: %s" % err)
319
320 self.response_msg.start_line.version = self.request_msg.start_line.version
321
322 return request_msg_reader
323
325 """Calls the handler function for the current request.
326
327 """
328 handler_context = _HttpServerRequest(self.request_msg.start_line.method,
329 self.request_msg.start_line.path,
330 self.request_msg.headers,
331 self.request_msg.body)
332
333 logging.debug("Handling request %r", handler_context)
334
335 try:
336 try:
337
338 self.server.PreHandleRequest(handler_context)
339
340
341 result = self.server.HandleRequest(handler_context)
342 except (http.HttpException, KeyboardInterrupt, SystemExit):
343 raise
344 except Exception, err:
345 logging.exception("Caught exception")
346 raise http.HttpInternalServerError(message=str(err))
347 except:
348 logging.exception("Unknown exception")
349 raise http.HttpInternalServerError(message="Unknown error")
350
351 if not isinstance(result, basestring):
352 raise http.HttpError("Handler function didn't return string type")
353
354 self.response_msg.start_line.code = http.HTTP_OK
355 self.response_msg.headers = handler_context.resp_headers
356 self.response_msg.body = result
357 finally:
358
359 handler_context.private = None
360
362 """Sends the response to the client.
363
364 """
365 if self.response_msg.start_line.code is None:
366 return
367
368 if not self.response_msg.headers:
369 self.response_msg.headers = {}
370
371 self.response_msg.headers.update({
372
373 http.HTTP_CONNECTION: "close",
374 http.HTTP_DATE: _DateTimeHeader(),
375 http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
376 })
377
378
379 response_code = self.response_msg.start_line.code
380 if response_code in self.responses:
381 response_reason = self.responses[response_code][0]
382 else:
383 response_reason = ""
384 self.response_msg.start_line.reason = response_reason
385
386 logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1],
387 self.request_msg.start_line, response_code)
388
389 try:
390 _HttpServerToClientMessageWriter(self.sock, self.request_msg,
391 self.response_msg, self.WRITE_TIMEOUT)
392 except http.HttpSocketTimeout:
393 raise http.HttpError("Timeout while sending response")
394 except socket.error, err:
395 raise http.HttpError("Error sending response: %s" % err)
396
398 """Sets the response code and body from a HttpException.
399
400 @type err: HttpException
401 @param err: Exception instance
402
403 """
404 try:
405 (shortmsg, longmsg) = self.responses[err.code]
406 except KeyError:
407 shortmsg = longmsg = "Unknown"
408
409 if err.message:
410 message = err.message
411 else:
412 message = shortmsg
413
414 values = {
415 "code": err.code,
416 "message": cgi.escape(message),
417 "explain": longmsg,
418 }
419
420 self.response_msg.start_line.code = err.code
421
422 headers = {}
423 if err.headers:
424 headers.update(err.headers)
425 headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
426 self.response_msg.headers = headers
427
428 self.response_msg.body = self._FormatErrorMessage(values)
429
440
441
442 -class HttpServer(http.HttpBase, asyncore.dispatcher):
443 """Generic HTTP server class
444
445 Users of this class must subclass it and override the HandleRequest function.
446
447 """
448 MAX_CHILDREN = 20
449
450 - def __init__(self, mainloop, local_address, port,
451 ssl_params=None, ssl_verify_peer=False,
452 request_executor_class=None):
453 """Initializes the HTTP server
454
455 @type mainloop: ganeti.daemon.Mainloop
456 @param mainloop: Mainloop used to poll for I/O events
457 @type local_address: string
458 @param local_address: Local IP address to bind to
459 @type port: int
460 @param port: TCP port to listen on
461 @type ssl_params: HttpSslParams
462 @param ssl_params: SSL key and certificate
463 @type ssl_verify_peer: bool
464 @param ssl_verify_peer: Whether to require client certificate
465 and compare it with our certificate
466 @type request_executor_class: class
467 @param request_executor_class: an class derived from the
468 HttpServerRequestExecutor class
469
470 """
471 http.HttpBase.__init__(self)
472 asyncore.dispatcher.__init__(self)
473
474 if request_executor_class is None:
475 self.request_executor = HttpServerRequestExecutor
476 else:
477 self.request_executor = request_executor_class
478
479 self.mainloop = mainloop
480 self.local_address = local_address
481 self.port = port
482 family = netutils.IPAddress.GetAddressFamily(local_address)
483 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
484
485
486 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
487
488 self._children = []
489 self.set_socket(self.socket)
490 self.accepting = True
491 mainloop.RegisterSignal(self)
492
494 self.socket.bind((self.local_address, self.port))
495 self.socket.listen(1024)
496
499
502
506
508 """Checks whether any child processes are done
509
510 @type quick: bool
511 @param quick: Whether to only use non-blocking functions
512
513 """
514 if not quick:
515
516 while len(self._children) > self.MAX_CHILDREN:
517 try:
518
519
520
521
522 pid, _ = os.waitpid(0, 0)
523 except os.error:
524 pid = None
525 if pid and pid in self._children:
526 self._children.remove(pid)
527
528 for child in self._children:
529 try:
530 pid, _ = os.waitpid(child, os.WNOHANG)
531 except os.error:
532 pid = None
533 if pid and pid in self._children:
534 self._children.remove(pid)
535
537 """Called for each incoming connection
538
539 """
540
541 (connection, client_addr) = self.socket.accept()
542
543 self._CollectChildren(False)
544
545 pid = os.fork()
546 if pid == 0:
547
548 try:
549
550
551
552
553 try:
554 self.socket.close()
555 except socket.error:
556 pass
557 self.socket = None
558
559
560 utils.ResetTempfileModule()
561
562 self.request_executor(self, connection, client_addr)
563 except Exception:
564 logging.exception("Error while handling request from %s:%s",
565 client_addr[0], client_addr[1])
566 os._exit(1)
567 os._exit(0)
568 else:
569 self._children.append(pid)
570
572 """Called before handling a request.
573
574 Can be overridden by a subclass.
575
576 """
577
579 """Handles a request.
580
581 Must be overridden by subclass.
582
583 """
584 raise NotImplementedError()
585