4 # Copyright (C) 2007, 2008, 2010, 2012 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
38 from ganeti import errors
41 WEEKDAYNAME = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
43 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
44 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
46 # Default error message
47 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
48 DEFAULT_ERROR_MESSAGE = """\
51 <title>Error response</title>
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.
63 def _DateTimeHeader(gmnow=None):
64 """Return the current date and time formatted for a message header.
66 The time MUST be in the GMT timezone.
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))
76 class _HttpServerRequest(object):
77 """Data structure for HTTP request on server side.
80 def __init__(self, method, path, headers, body):
82 self.request_method = method
83 self.request_path = path
84 self.request_headers = headers
85 self.request_body = body
88 self.resp_headers = {}
90 # Private data for request handler (useful in combination with
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, )]
100 return "<%s at %#x>" % (" ".join(status), id(self))
103 class _HttpServerToClientMessageWriter(http.HttpMessageWriter):
104 """Writes an HTTP response to client.
107 def __init__(self, sock, request_msg, response_msg, write_timeout):
108 """Writes the response to the client.
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
121 self._request_msg = request_msg
122 self._response_msg = response_msg
123 http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
125 def HasMessageBody(self):
126 """Logic to detect whether response should contain a message body.
129 if self._request_msg.start_line:
130 request_method = self._request_msg.start_line.method
132 request_method = None
134 response_code = self._response_msg.start_line.code
136 # RFC2616, section 4.3: "A message-body MUST NOT be included in a request
137 # if the specification of the request method (section 5.1.1) does not allow
138 # sending an entity-body in requests"
140 # RFC2616, section 9.4: "The HEAD method is identical to GET except that
141 # the server MUST NOT return a message-body in the response."
143 # RFC2616, section 10.2.5: "The 204 response MUST NOT include a
144 # message-body [...]"
146 # RFC2616, section 10.3.5: "The 304 response MUST NOT contain a
147 # message-body, [...]"
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))
157 class _HttpClientToServerMessageReader(http.HttpMessageReader):
158 """Reads an HTTP request sent by client.
162 START_LINE_LENGTH_MAX = 4096
163 HEADER_LENGTH_MAX = 4096
165 def ParseStartLine(self, start_line):
166 """Parses the start line sent by client.
168 Example: "GET /index.html HTTP/1.1"
170 @type start_line: string
171 @param start_line: Start line
174 # Empty lines are skipped when reading
177 logging.debug("HTTP request: %s", start_line)
179 words = start_line.split()
182 [method, path, version] = words
183 if version[:5] != "HTTP/":
184 raise http.HttpBadRequest("Bad request version (%r)" % version)
187 base_version_number = version.split("/", 1)[1]
188 version_number = base_version_number.split(".")
190 # RFC 2145 section 3.1 says there can be only one "." and
191 # - major and minor numbers MUST be treated as
193 # - HTTP/2.4 is a lower version than HTTP/2.13, which in
194 # turn is lower than HTTP/12.3;
195 # - Leading zeros MUST be ignored by recipients.
196 if len(version_number) != 2:
197 raise http.HttpBadRequest("Bad request version (%r)" % version)
199 version_number = (int(version_number[0]), int(version_number[1]))
200 except (ValueError, IndexError):
201 raise http.HttpBadRequest("Bad request version (%r)" % version)
203 if version_number >= (2, 0):
204 raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
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)
214 raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
216 return http.HttpClientToServerStartLine(method, path, version)
219 def _HandleServerRequestInner(handler, req_msg):
220 """Calls the handler function for the current request.
223 handler_context = _HttpServerRequest(req_msg.start_line.method,
224 req_msg.start_line.path,
228 logging.debug("Handling request %r", handler_context)
232 # Authentication, etc.
233 handler.PreHandleRequest(handler_context)
235 # Call actual request handler
236 result = handler.HandleRequest(handler_context)
237 except (http.HttpException, errors.RapiTestResult,
238 KeyboardInterrupt, SystemExit):
240 except Exception, err:
241 logging.exception("Caught exception")
242 raise http.HttpInternalServerError(message=str(err))
244 logging.exception("Unknown exception")
245 raise http.HttpInternalServerError(message="Unknown error")
247 if not isinstance(result, basestring):
248 raise http.HttpError("Handler function didn't return string type")
250 return (http.HTTP_OK, handler_context.resp_headers, result)
252 # No reason to keep this any longer, even for exceptions
253 handler_context.private = None
256 class HttpResponder(object):
257 # The default request version. This only affects responses up until
258 # the point where the request line is parsed, so it mainly decides what
259 # the client gets back when sending a malformed request line.
260 # Most web servers default to HTTP 0.9, i.e. don't send a status line.
261 default_request_version = http.HTTP_0_9
263 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
265 def __init__(self, handler):
266 """Initializes this class.
269 self._handler = handler
271 def __call__(self, fn):
272 """Handles a request.
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})
280 response_msg = http.HttpMessage()
281 response_msg.start_line = \
282 http.HttpServerToClientStartLine(version=self.default_request_version,
283 code=None, reason=None)
288 (request_msg, req_msg_reader) = fn()
290 response_msg.start_line.version = request_msg.start_line.version
292 # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond
293 # with a 400 (Bad Request) status code to any HTTP/1.1 request
294 # message which lacks a Host header field.
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")
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)
306 # Only wait for client to close if we didn't have any exception.
309 return (request_msg, req_msg_reader, force_close,
310 self._Finalize(self.responses, response_msg))
313 def _SetError(responses, handler, response_msg, err):
314 """Sets the response code and body from a HttpException.
316 @type err: HttpException
317 @param err: Exception instance
321 (shortmsg, longmsg) = responses[err.code]
323 shortmsg = longmsg = "Unknown"
326 message = err.message
332 "message": cgi.escape(message),
336 (content_type, body) = handler.FormatErrorMessage(values)
339 http.HTTP_CONTENT_TYPE: content_type,
343 headers.update(err.headers)
345 response_msg.start_line.code = err.code
346 response_msg.headers = headers
347 response_msg.body = body
350 def _Finalize(responses, msg):
351 assert msg.start_line.reason is None
357 # TODO: Keep-alive is not supported
358 http.HTTP_CONNECTION: "close",
359 http.HTTP_DATE: _DateTimeHeader(),
360 http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
363 # Get response reason based on code
365 code_desc = responses[msg.start_line.code]
369 (reason, _) = code_desc
371 msg.start_line.reason = reason
376 class HttpServerRequestExecutor(object):
377 """Implements server side of HTTP.
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
385 # Timeouts in seconds for socket layer
390 def __init__(self, server, handler, sock, client_addr):
391 """Initializes this class.
394 responder = HttpResponder(handler)
396 # Disable Python's timeout
397 sock.settimeout(None)
399 # Operate in non-blocking mode
402 request_msg_reader = None
405 logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
407 # Block for closing connection
409 # Do the secret SSL handshake
411 sock.set_accept_state()
413 http.Handshake(sock, self.WRITE_TIMEOUT)
414 except http.HttpSessionHandshakeUnexpectedEOF:
418 (request_msg, request_msg_reader, force_close, response_msg) = \
419 responder(compat.partial(self._ReadRequest, sock, self.READ_TIMEOUT))
421 # HttpMessage.start_line can be of different types
422 # pylint: disable=E1103
423 logging.info("%s:%s %s %s", client_addr[0], client_addr[1],
424 request_msg.start_line, response_msg.start_line.code)
425 self._SendResponse(sock, request_msg, response_msg,
428 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
429 request_msg_reader, force_close)
433 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
436 def _ReadRequest(sock, timeout):
437 """Reads a request sent by client.
440 msg = http.HttpMessage()
443 reader = _HttpClientToServerMessageReader(sock, msg, timeout)
444 except http.HttpSocketTimeout:
445 raise http.HttpError("Timeout while reading request")
446 except socket.error, err:
447 raise http.HttpError("Error reading request: %s" % err)
452 def _SendResponse(sock, req_msg, msg, timeout):
453 """Sends the response to the client.
457 _HttpServerToClientMessageWriter(sock, req_msg, msg, timeout)
458 except http.HttpSocketTimeout:
459 raise http.HttpError("Timeout while sending response")
460 except socket.error, err:
461 raise http.HttpError("Error sending response: %s" % err)
464 class HttpServer(http.HttpBase, asyncore.dispatcher):
465 """Generic HTTP server class
470 def __init__(self, mainloop, local_address, port, handler,
471 ssl_params=None, ssl_verify_peer=False,
472 request_executor_class=None):
473 """Initializes the HTTP server
475 @type mainloop: ganeti.daemon.Mainloop
476 @param mainloop: Mainloop used to poll for I/O events
477 @type local_address: string
478 @param local_address: Local IP address to bind to
480 @param port: TCP port to listen on
481 @type ssl_params: HttpSslParams
482 @param ssl_params: SSL key and certificate
483 @type ssl_verify_peer: bool
484 @param ssl_verify_peer: Whether to require client certificate
485 and compare it with our certificate
486 @type request_executor_class: class
487 @param request_executor_class: an class derived from the
488 HttpServerRequestExecutor class
491 http.HttpBase.__init__(self)
492 asyncore.dispatcher.__init__(self)
494 if request_executor_class is None:
495 self.request_executor = HttpServerRequestExecutor
497 self.request_executor = request_executor_class
499 self.mainloop = mainloop
500 self.local_address = local_address
502 self.handler = handler
503 family = netutils.IPAddress.GetAddressFamily(local_address)
504 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
506 # Allow port to be reused
507 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
510 self.set_socket(self.socket)
511 self.accepting = True
512 mainloop.RegisterSignal(self)
515 self.socket.bind((self.local_address, self.port))
516 self.socket.listen(1024)
521 def handle_accept(self):
522 self._IncomingConnection()
524 def OnSignal(self, signum):
525 if signum == signal.SIGCHLD:
526 self._CollectChildren(True)
528 def _CollectChildren(self, quick):
529 """Checks whether any child processes are done
532 @param quick: Whether to only use non-blocking functions
536 # Don't wait for other processes if it should be a quick check
537 while len(self._children) > self.MAX_CHILDREN:
539 # Waiting without a timeout brings us into a potential DoS situation.
540 # As soon as too many children run, we'll not respond to new
541 # requests. The real solution would be to add a timeout for children
542 # and killing them after some time.
543 pid, _ = os.waitpid(0, 0)
546 if pid and pid in self._children:
547 self._children.remove(pid)
549 for child in self._children:
551 pid, _ = os.waitpid(child, os.WNOHANG)
554 if pid and pid in self._children:
555 self._children.remove(pid)
557 def _IncomingConnection(self):
558 """Called for each incoming connection
561 # pylint: disable=W0212
562 (connection, client_addr) = self.socket.accept()
564 self._CollectChildren(False)
570 # The client shouldn't keep the listening socket open. If the parent
571 # process is restarted, it would fail when there's already something
572 # listening (in this case its own child from a previous run) on the
580 # In case the handler code uses temporary files
581 utils.ResetTempfileModule()
583 self.request_executor(self, self.handler, connection, client_addr)
584 except Exception: # pylint: disable=W0703
585 logging.exception("Error while handling request from %s:%s",
586 client_addr[0], client_addr[1])
590 self._children.append(pid)
593 class HttpServerHandler(object):
594 """Base class for handling HTTP server requests.
596 Users of this class must subclass it and override the L{HandleRequest}
600 def PreHandleRequest(self, req):
601 """Called before handling a request.
603 Can be overridden by a subclass.
607 def HandleRequest(self, req):
608 """Handles a request.
610 Must be overridden by subclass.
613 raise NotImplementedError()
616 def FormatErrorMessage(values):
617 """Formats the body of an error message.
620 @param values: dictionary with keys C{code}, C{message} and C{explain}.
621 @rtype: tuple; (string, string)
622 @return: Content-type and response body
625 return (DEFAULT_ERROR_CONTENT_TYPE, DEFAULT_ERROR_MESSAGE % values)