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