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