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 = 8192
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 # Instance of 'HttpClientToServerStartLine' has no 'code' member
423 # pylint: disable=E1103,E1101
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,
429 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
430 request_msg_reader, force_close)
434 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
437 def _ReadRequest(sock, timeout):
438 """Reads a request sent by client.
441 msg = http.HttpMessage()
444 reader = _HttpClientToServerMessageReader(sock, msg, timeout)
445 except http.HttpSocketTimeout:
446 raise http.HttpError("Timeout while reading request")
447 except socket.error, err:
448 raise http.HttpError("Error reading request: %s" % err)
453 def _SendResponse(sock, req_msg, msg, timeout):
454 """Sends the response to the client.
458 _HttpServerToClientMessageWriter(sock, req_msg, msg, timeout)
459 except http.HttpSocketTimeout:
460 raise http.HttpError("Timeout while sending response")
461 except socket.error, err:
462 raise http.HttpError("Error sending response: %s" % err)
465 class HttpServer(http.HttpBase, asyncore.dispatcher):
466 """Generic HTTP server class
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
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
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
492 http.HttpBase.__init__(self)
493 asyncore.dispatcher.__init__(self)
495 if request_executor_class is None:
496 self.request_executor = HttpServerRequestExecutor
498 self.request_executor = request_executor_class
500 self.mainloop = mainloop
501 self.local_address = local_address
503 self.handler = handler
504 family = netutils.IPAddress.GetAddressFamily(local_address)
505 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
507 # Allow port to be reused
508 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
511 self.set_socket(self.socket)
512 self.accepting = True
513 mainloop.RegisterSignal(self)
516 self.socket.bind((self.local_address, self.port))
517 self.socket.listen(1024)
522 def handle_accept(self):
523 self._IncomingConnection()
525 def OnSignal(self, signum):
526 if signum == signal.SIGCHLD:
527 self._CollectChildren(True)
529 def _CollectChildren(self, quick):
530 """Checks whether any child processes are done
533 @param quick: Whether to only use non-blocking functions
537 # Don't wait for other processes if it should be a quick check
538 while len(self._children) > self.MAX_CHILDREN:
540 # Waiting without a timeout brings us into a potential DoS situation.
541 # As soon as too many children run, we'll not respond to new
542 # requests. The real solution would be to add a timeout for children
543 # and killing them after some time.
544 pid, _ = os.waitpid(0, 0)
547 if pid and pid in self._children:
548 self._children.remove(pid)
550 for child in self._children:
552 pid, _ = os.waitpid(child, os.WNOHANG)
555 if pid and pid in self._children:
556 self._children.remove(pid)
558 def _IncomingConnection(self):
559 """Called for each incoming connection
562 # pylint: disable=W0212
563 (connection, client_addr) = self.socket.accept()
565 self._CollectChildren(False)
571 # The client shouldn't keep the listening socket open. If the parent
572 # process is restarted, it would fail when there's already something
573 # listening (in this case its own child from a previous run) on the
581 # In case the handler code uses temporary files
582 utils.ResetTempfileModule()
584 self.request_executor(self, self.handler, connection, client_addr)
585 except Exception: # pylint: disable=W0703
586 logging.exception("Error while handling request from %s:%s",
587 client_addr[0], client_addr[1])
591 self._children.append(pid)
594 class HttpServerHandler(object):
595 """Base class for handling HTTP server requests.
597 Users of this class must subclass it and override the L{HandleRequest}
601 def PreHandleRequest(self, req):
602 """Called before handling a request.
604 Can be overridden by a subclass.
608 def HandleRequest(self, req):
609 """Handles a request.
611 Must be overridden by subclass.
614 raise NotImplementedError()
617 def FormatErrorMessage(values):
618 """Formats the body of an error message.
621 @param values: dictionary with keys C{code}, C{message} and C{explain}.
622 @rtype: tuple; (string, string)
623 @return: Content-type and response body
626 return (DEFAULT_ERROR_CONTENT_TYPE, DEFAULT_ERROR_MESSAGE % values)