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