4 # Copyright (C) 2007, 2008, 2010 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """HTTP server module.
34 from ganeti import http
35 from ganeti import utils
36 from ganeti import netutils
37 from ganeti import compat
40 WEEKDAYNAME = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
42 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
43 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
45 # Default error message
46 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
47 DEFAULT_ERROR_MESSAGE = """\
50 <title>Error response</title>
53 <h1>Error response</h1>
54 <p>Error code %(code)d.
55 <p>Message: %(message)s.
56 <p>Error code explanation: %(code)s = %(explain)s.
62 def _DateTimeHeader(gmnow=None):
63 """Return the current date and time formatted for a message header.
65 The time MUST be in the GMT timezone.
70 (year, month, day, hh, mm, ss, wd, _, _) = gmnow
71 return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %
72 (WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss))
75 class _HttpServerRequest(object):
76 """Data structure for HTTP request on server side.
79 def __init__(self, method, path, headers, body):
81 self.request_method = method
82 self.request_path = path
83 self.request_headers = headers
84 self.request_body = body
87 self.resp_headers = {}
89 # Private data for request handler (useful in combination with
94 status = ["%s.%s" % (self.__class__.__module__, self.__class__.__name__),
95 self.request_method, self.request_path,
96 "headers=%r" % str(self.request_headers),
97 "body=%r" % (self.request_body, )]
99 return "<%s at %#x>" % (" ".join(status), id(self))
102 class _HttpServerToClientMessageWriter(http.HttpMessageWriter):
103 """Writes an HTTP response to client.
106 def __init__(self, sock, request_msg, response_msg, write_timeout):
107 """Writes the response to the client.
110 @param sock: Target socket
111 @type request_msg: http.HttpMessage
112 @param request_msg: Request message, required to determine whether
113 response may have a message body
114 @type response_msg: http.HttpMessage
115 @param response_msg: Response message
116 @type write_timeout: float
117 @param write_timeout: Write timeout for socket
120 self._request_msg = request_msg
121 self._response_msg = response_msg
122 http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
124 def HasMessageBody(self):
125 """Logic to detect whether response should contain a message body.
128 if self._request_msg.start_line:
129 request_method = self._request_msg.start_line.method
131 request_method = None
133 response_code = self._response_msg.start_line.code
135 # RFC2616, section 4.3: "A message-body MUST NOT be included in a request
136 # if the specification of the request method (section 5.1.1) does not allow
137 # sending an entity-body in requests"
139 # RFC2616, section 9.4: "The HEAD method is identical to GET except that
140 # the server MUST NOT return a message-body in the response."
142 # RFC2616, section 10.2.5: "The 204 response MUST NOT include a
143 # message-body [...]"
145 # RFC2616, section 10.3.5: "The 304 response MUST NOT contain a
146 # message-body, [...]"
148 return (http.HttpMessageWriter.HasMessageBody(self) and
149 (request_method is not None and
150 request_method != http.HTTP_HEAD) and
151 response_code >= http.HTTP_OK and
152 response_code not in (http.HTTP_NO_CONTENT,
153 http.HTTP_NOT_MODIFIED))
156 class _HttpClientToServerMessageReader(http.HttpMessageReader):
157 """Reads an HTTP request sent by client.
161 START_LINE_LENGTH_MAX = 4096
162 HEADER_LENGTH_MAX = 4096
164 def ParseStartLine(self, start_line):
165 """Parses the start line sent by client.
167 Example: "GET /index.html HTTP/1.1"
169 @type start_line: string
170 @param start_line: Start line
173 # Empty lines are skipped when reading
176 logging.debug("HTTP request: %s", start_line)
178 words = start_line.split()
181 [method, path, version] = words
182 if version[:5] != "HTTP/":
183 raise http.HttpBadRequest("Bad request version (%r)" % version)
186 base_version_number = version.split("/", 1)[1]
187 version_number = base_version_number.split(".")
189 # RFC 2145 section 3.1 says there can be only one "." and
190 # - major and minor numbers MUST be treated as
192 # - HTTP/2.4 is a lower version than HTTP/2.13, which in
193 # turn is lower than HTTP/12.3;
194 # - Leading zeros MUST be ignored by recipients.
195 if len(version_number) != 2:
196 raise http.HttpBadRequest("Bad request version (%r)" % version)
198 version_number = (int(version_number[0]), int(version_number[1]))
199 except (ValueError, IndexError):
200 raise http.HttpBadRequest("Bad request version (%r)" % version)
202 if version_number >= (2, 0):
203 raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
206 elif len(words) == 2:
207 version = http.HTTP_0_9
208 [method, path] = words
209 if method != http.HTTP_GET:
210 raise http.HttpBadRequest("Bad HTTP/0.9 request type (%r)" % method)
213 raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
215 return http.HttpClientToServerStartLine(method, path, version)
218 def _HandleServerRequestInner(handler, req_msg):
219 """Calls the handler function for the current request.
222 handler_context = _HttpServerRequest(req_msg.start_line.method,
223 req_msg.start_line.path,
227 logging.debug("Handling request %r", handler_context)
231 # Authentication, etc.
232 handler.PreHandleRequest(handler_context)
234 # Call actual request handler
235 result = handler.HandleRequest(handler_context)
236 except (http.HttpException, KeyboardInterrupt, SystemExit):
238 except Exception, err:
239 logging.exception("Caught exception")
240 raise http.HttpInternalServerError(message=str(err))
242 logging.exception("Unknown exception")
243 raise http.HttpInternalServerError(message="Unknown error")
245 if not isinstance(result, basestring):
246 raise http.HttpError("Handler function didn't return string type")
248 return (http.HTTP_OK, handler_context.resp_headers, result)
250 # No reason to keep this any longer, even for exceptions
251 handler_context.private = None
254 class HttpResponder(object):
255 # The default request version. This only affects responses up until
256 # the point where the request line is parsed, so it mainly decides what
257 # the client gets back when sending a malformed request line.
258 # Most web servers default to HTTP 0.9, i.e. don't send a status line.
259 default_request_version = http.HTTP_0_9
261 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
263 def __init__(self, handler):
264 """Initializes this class.
267 self._handler = handler
269 def __call__(self, fn):
270 """Handles a request.
273 @param fn: Callback for retrieving HTTP request, must return a tuple
274 containing request message (L{http.HttpMessage}) and C{None} or the
275 message reader (L{_HttpClientToServerMessageReader})
278 response_msg = http.HttpMessage()
279 response_msg.start_line = \
280 http.HttpServerToClientStartLine(version=self.default_request_version,
281 code=None, reason=None)
286 (request_msg, req_msg_reader) = fn()
288 response_msg.start_line.version = request_msg.start_line.version
290 # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond
291 # with a 400 (Bad Request) status code to any HTTP/1.1 request
292 # message which lacks a Host header field.
293 if (request_msg.start_line.version == http.HTTP_1_1 and
294 not (request_msg.headers and
295 http.HTTP_HOST in request_msg.headers)):
296 raise http.HttpBadRequest(message="Missing Host header")
298 (response_msg.start_line.code, response_msg.headers,
299 response_msg.body) = \
300 _HandleServerRequestInner(self._handler, request_msg)
301 except http.HttpException, err:
302 self._SetError(self.responses, self._handler, response_msg, err)
304 # Only wait for client to close if we didn't have any exception.
307 return (request_msg, req_msg_reader, force_close,
308 self._Finalize(self.responses, response_msg))
311 def _SetError(responses, handler, response_msg, err):
312 """Sets the response code and body from a HttpException.
314 @type err: HttpException
315 @param err: Exception instance
319 (shortmsg, longmsg) = responses[err.code]
321 shortmsg = longmsg = "Unknown"
324 message = err.message
330 "message": cgi.escape(message),
334 (content_type, body) = handler.FormatErrorMessage(values)
337 http.HTTP_CONTENT_TYPE: content_type,
341 headers.update(err.headers)
343 response_msg.start_line.code = err.code
344 response_msg.headers = headers
345 response_msg.body = body
348 def _Finalize(responses, msg):
349 assert msg.start_line.reason is None
355 # TODO: Keep-alive is not supported
356 http.HTTP_CONNECTION: "close",
357 http.HTTP_DATE: _DateTimeHeader(),
358 http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
361 # Get response reason based on code
363 code_desc = responses[msg.start_line.code]
367 (reason, _) = code_desc
369 msg.start_line.reason = reason
374 class HttpServerRequestExecutor(object):
375 """Implements server side of HTTP.
377 This class implements the server side of HTTP. It's based on code of
378 Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
379 support non-ASCII character encodings. Keep-alive connections are
383 # Timeouts in seconds for socket layer
388 def __init__(self, server, handler, sock, client_addr):
389 """Initializes this class.
392 responder = HttpResponder(handler)
394 # Disable Python's timeout
395 sock.settimeout(None)
397 # Operate in non-blocking mode
400 request_msg_reader = None
403 logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
405 # Block for closing connection
407 # Do the secret SSL handshake
409 sock.set_accept_state()
411 http.Handshake(sock, self.WRITE_TIMEOUT)
412 except http.HttpSessionHandshakeUnexpectedEOF:
416 (request_msg, request_msg_reader, force_close, response_msg) = \
417 responder(compat.partial(self._ReadRequest, sock, self.READ_TIMEOUT))
419 # HttpMessage.start_line can be of different types
420 # pylint: disable=E1103
421 logging.info("%s:%s %s %s", client_addr[0], client_addr[1],
422 request_msg.start_line, response_msg.start_line.code)
423 self._SendResponse(sock, request_msg, response_msg,
426 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
427 request_msg_reader, force_close)
431 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
434 def _ReadRequest(sock, timeout):
435 """Reads a request sent by client.
438 msg = http.HttpMessage()
441 reader = _HttpClientToServerMessageReader(sock, msg, timeout)
442 except http.HttpSocketTimeout:
443 raise http.HttpError("Timeout while reading request")
444 except socket.error, err:
445 raise http.HttpError("Error reading request: %s" % err)
450 def _SendResponse(sock, req_msg, msg, timeout):
451 """Sends the response to the client.
455 _HttpServerToClientMessageWriter(sock, req_msg, msg, timeout)
456 except http.HttpSocketTimeout:
457 raise http.HttpError("Timeout while sending response")
458 except socket.error, err:
459 raise http.HttpError("Error sending response: %s" % err)
462 class HttpServer(http.HttpBase, asyncore.dispatcher):
463 """Generic HTTP server class
468 def __init__(self, mainloop, local_address, port, handler,
469 ssl_params=None, ssl_verify_peer=False,
470 request_executor_class=None):
471 """Initializes the HTTP server
473 @type mainloop: ganeti.daemon.Mainloop
474 @param mainloop: Mainloop used to poll for I/O events
475 @type local_address: string
476 @param local_address: Local IP address to bind to
478 @param port: TCP port to listen on
479 @type ssl_params: HttpSslParams
480 @param ssl_params: SSL key and certificate
481 @type ssl_verify_peer: bool
482 @param ssl_verify_peer: Whether to require client certificate
483 and compare it with our certificate
484 @type request_executor_class: class
485 @param request_executor_class: an class derived from the
486 HttpServerRequestExecutor class
489 http.HttpBase.__init__(self)
490 asyncore.dispatcher.__init__(self)
492 if request_executor_class is None:
493 self.request_executor = HttpServerRequestExecutor
495 self.request_executor = request_executor_class
497 self.mainloop = mainloop
498 self.local_address = local_address
500 self.handler = handler
501 family = netutils.IPAddress.GetAddressFamily(local_address)
502 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
504 # Allow port to be reused
505 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
508 self.set_socket(self.socket)
509 self.accepting = True
510 mainloop.RegisterSignal(self)
513 self.socket.bind((self.local_address, self.port))
514 self.socket.listen(1024)
519 def handle_accept(self):
520 self._IncomingConnection()
522 def OnSignal(self, signum):
523 if signum == signal.SIGCHLD:
524 self._CollectChildren(True)
526 def _CollectChildren(self, quick):
527 """Checks whether any child processes are done
530 @param quick: Whether to only use non-blocking functions
534 # Don't wait for other processes if it should be a quick check
535 while len(self._children) > self.MAX_CHILDREN:
537 # Waiting without a timeout brings us into a potential DoS situation.
538 # As soon as too many children run, we'll not respond to new
539 # requests. The real solution would be to add a timeout for children
540 # and killing them after some time.
541 pid, _ = os.waitpid(0, 0)
544 if pid and pid in self._children:
545 self._children.remove(pid)
547 for child in self._children:
549 pid, _ = os.waitpid(child, os.WNOHANG)
552 if pid and pid in self._children:
553 self._children.remove(pid)
555 def _IncomingConnection(self):
556 """Called for each incoming connection
559 # pylint: disable=W0212
560 (connection, client_addr) = self.socket.accept()
562 self._CollectChildren(False)
568 # The client shouldn't keep the listening socket open. If the parent
569 # process is restarted, it would fail when there's already something
570 # listening (in this case its own child from a previous run) on the
578 # In case the handler code uses temporary files
579 utils.ResetTempfileModule()
581 self.request_executor(self, self.handler, connection, client_addr)
582 except Exception: # pylint: disable=W0703
583 logging.exception("Error while handling request from %s:%s",
584 client_addr[0], client_addr[1])
588 self._children.append(pid)
591 class HttpServerHandler(object):
592 """Base class for handling HTTP server requests.
594 Users of this class must subclass it and override the L{HandleRequest}
598 def PreHandleRequest(self, req):
599 """Called before handling a request.
601 Can be overridden by a subclass.
605 def HandleRequest(self, req):
606 """Handles a request.
608 Must be overridden by subclass.
611 raise NotImplementedError()
614 def FormatErrorMessage(values):
615 """Formats the body of an error message.
618 @param values: dictionary with keys C{code}, C{message} and C{explain}.
619 @rtype: tuple; (string, string)
620 @return: Content-type and response body
623 return (DEFAULT_ERROR_CONTENT_TYPE, DEFAULT_ERROR_MESSAGE % values)