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
39 WEEKDAYNAME = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
41 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
42 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
44 # Default error message
45 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
46 DEFAULT_ERROR_MESSAGE = """\
49 <title>Error response</title>
52 <h1>Error response</h1>
53 <p>Error code %(code)d.
54 <p>Message: %(message)s.
55 <p>Error code explanation: %(code)s = %(explain)s.
61 def _DateTimeHeader(gmnow=None):
62 """Return the current date and time formatted for a message header.
64 The time MUST be in the GMT timezone.
69 (year, month, day, hh, mm, ss, wd, _, _) = gmnow
70 return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %
71 (WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss))
74 class _HttpServerRequest(object):
75 """Data structure for HTTP request on server side.
78 def __init__(self, method, path, headers, body):
80 self.request_method = method
81 self.request_path = path
82 self.request_headers = headers
83 self.request_body = body
86 self.resp_headers = {}
88 # Private data for request handler (useful in combination with
93 status = ["%s.%s" % (self.__class__.__module__, self.__class__.__name__),
94 self.request_method, self.request_path,
95 "headers=%r" % str(self.request_headers),
96 "body=%r" % (self.request_body, )]
98 return "<%s at %#x>" % (" ".join(status), id(self))
101 class _HttpServerToClientMessageWriter(http.HttpMessageWriter):
102 """Writes an HTTP response to client.
105 def __init__(self, sock, request_msg, response_msg, write_timeout):
106 """Writes the response to the client.
109 @param sock: Target socket
110 @type request_msg: http.HttpMessage
111 @param request_msg: Request message, required to determine whether
112 response may have a message body
113 @type response_msg: http.HttpMessage
114 @param response_msg: Response message
115 @type write_timeout: float
116 @param write_timeout: Write timeout for socket
119 self._request_msg = request_msg
120 self._response_msg = response_msg
121 http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
123 def HasMessageBody(self):
124 """Logic to detect whether response should contain a message body.
127 if self._request_msg.start_line:
128 request_method = self._request_msg.start_line.method
130 request_method = None
132 response_code = self._response_msg.start_line.code
134 # RFC2616, section 4.3: "A message-body MUST NOT be included in a request
135 # if the specification of the request method (section 5.1.1) does not allow
136 # sending an entity-body in requests"
138 # RFC2616, section 9.4: "The HEAD method is identical to GET except that
139 # the server MUST NOT return a message-body in the response."
141 # RFC2616, section 10.2.5: "The 204 response MUST NOT include a
142 # message-body [...]"
144 # RFC2616, section 10.3.5: "The 304 response MUST NOT contain a
145 # message-body, [...]"
147 return (http.HttpMessageWriter.HasMessageBody(self) and
148 (request_method is not None and
149 request_method != http.HTTP_HEAD) and
150 response_code >= http.HTTP_OK and
151 response_code not in (http.HTTP_NO_CONTENT,
152 http.HTTP_NOT_MODIFIED))
155 class _HttpClientToServerMessageReader(http.HttpMessageReader):
156 """Reads an HTTP request sent by client.
160 START_LINE_LENGTH_MAX = 4096
161 HEADER_LENGTH_MAX = 4096
163 def ParseStartLine(self, start_line):
164 """Parses the start line sent by client.
166 Example: "GET /index.html HTTP/1.1"
168 @type start_line: string
169 @param start_line: Start line
172 # Empty lines are skipped when reading
175 logging.debug("HTTP request: %s", start_line)
177 words = start_line.split()
180 [method, path, version] = words
181 if version[:5] != "HTTP/":
182 raise http.HttpBadRequest("Bad request version (%r)" % version)
185 base_version_number = version.split("/", 1)[1]
186 version_number = base_version_number.split(".")
188 # RFC 2145 section 3.1 says there can be only one "." and
189 # - major and minor numbers MUST be treated as
191 # - HTTP/2.4 is a lower version than HTTP/2.13, which in
192 # turn is lower than HTTP/12.3;
193 # - Leading zeros MUST be ignored by recipients.
194 if len(version_number) != 2:
195 raise http.HttpBadRequest("Bad request version (%r)" % version)
197 version_number = (int(version_number[0]), int(version_number[1]))
198 except (ValueError, IndexError):
199 raise http.HttpBadRequest("Bad request version (%r)" % version)
201 if version_number >= (2, 0):
202 raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
205 elif len(words) == 2:
206 version = http.HTTP_0_9
207 [method, path] = words
208 if method != http.HTTP_GET:
209 raise http.HttpBadRequest("Bad HTTP/0.9 request type (%r)" % method)
212 raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
214 return http.HttpClientToServerStartLine(method, path, version)
217 def HandleServerRequest(handler, req_msg):
218 """Calls the handler function for the current request.
221 handler_context = _HttpServerRequest(req_msg.start_line.method,
222 req_msg.start_line.path,
226 logging.debug("Handling request %r", handler_context)
230 # Authentication, etc.
231 handler.PreHandleRequest(handler_context)
233 # Call actual request handler
234 result = handler.HandleRequest(handler_context)
235 except (http.HttpException, KeyboardInterrupt, SystemExit):
237 except Exception, err:
238 logging.exception("Caught exception")
239 raise http.HttpInternalServerError(message=str(err))
241 logging.exception("Unknown exception")
242 raise http.HttpInternalServerError(message="Unknown error")
244 if not isinstance(result, basestring):
245 raise http.HttpError("Handler function didn't return string type")
247 return (http.HTTP_OK, handler_context.resp_headers, result)
249 # No reason to keep this any longer, even for exceptions
250 handler_context.private = None
253 class HttpServerRequestExecutor(object):
254 """Implements server side of HTTP.
256 This class implements the server side of HTTP. It's based on code of
257 Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
258 support non-ASCII character encodings. Keep-alive connections are
262 # The default request version. This only affects responses up until
263 # the point where the request line is parsed, so it mainly decides what
264 # the client gets back when sending a malformed request line.
265 # Most web servers default to HTTP 0.9, i.e. don't send a status line.
266 default_request_version = http.HTTP_0_9
268 # Error message settings
269 error_message_format = DEFAULT_ERROR_MESSAGE
270 error_content_type = DEFAULT_ERROR_CONTENT_TYPE
272 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
274 # Timeouts in seconds for socket layer
279 def __init__(self, server, handler, sock, client_addr):
280 """Initializes this class.
284 self.handler = handler
286 self.client_addr = client_addr
288 self.request_msg = http.HttpMessage()
289 self.response_msg = http.HttpMessage()
291 self.response_msg.start_line = \
292 http.HttpServerToClientStartLine(version=self.default_request_version,
293 code=None, reason=None)
295 # Disable Python's timeout
296 self.sock.settimeout(None)
298 # Operate in non-blocking mode
299 self.sock.setblocking(0)
301 logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
303 request_msg_reader = None
306 # Do the secret SSL handshake
307 if self.server.using_ssl:
308 self.sock.set_accept_state()
310 http.Handshake(self.sock, self.WRITE_TIMEOUT)
311 except http.HttpSessionHandshakeUnexpectedEOF:
317 request_msg_reader = self._ReadRequest()
319 # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond
320 # with a 400 (Bad Request) status code to any HTTP/1.1 request
321 # message which lacks a Host header field.
322 if (self.request_msg.start_line.version == http.HTTP_1_1 and
323 http.HTTP_HOST not in self.request_msg.headers):
324 raise http.HttpBadRequest(message="Missing Host header")
326 (self.response_msg.start_line.code, self.response_msg.headers,
327 self.response_msg.body) = \
328 HandleServerRequest(self.handler, self.request_msg)
330 # Only wait for client to close if we didn't have any exception.
332 except http.HttpException, err:
333 self._SetErrorStatus(err)
335 # Try to send a response
338 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
339 request_msg_reader, force_close)
344 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
346 def _ReadRequest(self):
347 """Reads a request sent by client.
351 request_msg_reader = \
352 _HttpClientToServerMessageReader(self.sock, self.request_msg,
354 except http.HttpSocketTimeout:
355 raise http.HttpError("Timeout while reading request")
356 except socket.error, err:
357 raise http.HttpError("Error reading request: %s" % err)
359 self.response_msg.start_line.version = self.request_msg.start_line.version
361 return request_msg_reader
363 def _SendResponse(self):
364 """Sends the response to the client.
367 # HttpMessage.start_line can be of different types, pylint: disable=E1103
368 if self.response_msg.start_line.code is None:
371 if not self.response_msg.headers:
372 self.response_msg.headers = {}
374 self.response_msg.headers.update({
375 # TODO: Keep-alive is not supported
376 http.HTTP_CONNECTION: "close",
377 http.HTTP_DATE: _DateTimeHeader(),
378 http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
381 # Get response reason based on code
382 response_code = self.response_msg.start_line.code
383 if response_code in self.responses:
384 response_reason = self.responses[response_code][0]
387 self.response_msg.start_line.reason = response_reason
389 logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1],
390 self.request_msg.start_line, response_code)
393 _HttpServerToClientMessageWriter(self.sock, self.request_msg,
394 self.response_msg, self.WRITE_TIMEOUT)
395 except http.HttpSocketTimeout:
396 raise http.HttpError("Timeout while sending response")
397 except socket.error, err:
398 raise http.HttpError("Error sending response: %s" % err)
400 def _SetErrorStatus(self, err):
401 """Sets the response code and body from a HttpException.
403 @type err: HttpException
404 @param err: Exception instance
408 (shortmsg, longmsg) = self.responses[err.code]
410 shortmsg = longmsg = "Unknown"
413 message = err.message
419 "message": cgi.escape(message),
423 self.response_msg.start_line.code = err.code
427 headers.update(err.headers)
428 headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
429 self.response_msg.headers = headers
431 self.response_msg.body = self._FormatErrorMessage(values)
433 def _FormatErrorMessage(self, values):
434 """Formats the body of an error message.
437 @param values: dictionary with keys code, message and explain.
439 @return: the body of the message
442 return self.error_message_format % values
445 class HttpServer(http.HttpBase, asyncore.dispatcher):
446 """Generic HTTP server class
451 def __init__(self, mainloop, local_address, port, handler,
452 ssl_params=None, ssl_verify_peer=False,
453 request_executor_class=None):
454 """Initializes the HTTP server
456 @type mainloop: ganeti.daemon.Mainloop
457 @param mainloop: Mainloop used to poll for I/O events
458 @type local_address: string
459 @param local_address: Local IP address to bind to
461 @param port: TCP port to listen on
462 @type ssl_params: HttpSslParams
463 @param ssl_params: SSL key and certificate
464 @type ssl_verify_peer: bool
465 @param ssl_verify_peer: Whether to require client certificate
466 and compare it with our certificate
467 @type request_executor_class: class
468 @param request_executor_class: an class derived from the
469 HttpServerRequestExecutor class
472 http.HttpBase.__init__(self)
473 asyncore.dispatcher.__init__(self)
475 if request_executor_class is None:
476 self.request_executor = HttpServerRequestExecutor
478 self.request_executor = request_executor_class
480 self.mainloop = mainloop
481 self.local_address = local_address
483 self.handler = handler
484 family = netutils.IPAddress.GetAddressFamily(local_address)
485 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
487 # Allow port to be reused
488 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
491 self.set_socket(self.socket)
492 self.accepting = True
493 mainloop.RegisterSignal(self)
496 self.socket.bind((self.local_address, self.port))
497 self.socket.listen(1024)
502 def handle_accept(self):
503 self._IncomingConnection()
505 def OnSignal(self, signum):
506 if signum == signal.SIGCHLD:
507 self._CollectChildren(True)
509 def _CollectChildren(self, quick):
510 """Checks whether any child processes are done
513 @param quick: Whether to only use non-blocking functions
517 # Don't wait for other processes if it should be a quick check
518 while len(self._children) > self.MAX_CHILDREN:
520 # Waiting without a timeout brings us into a potential DoS situation.
521 # As soon as too many children run, we'll not respond to new
522 # requests. The real solution would be to add a timeout for children
523 # and killing them after some time.
524 pid, _ = os.waitpid(0, 0)
527 if pid and pid in self._children:
528 self._children.remove(pid)
530 for child in self._children:
532 pid, _ = os.waitpid(child, os.WNOHANG)
535 if pid and pid in self._children:
536 self._children.remove(pid)
538 def _IncomingConnection(self):
539 """Called for each incoming connection
542 # pylint: disable=W0212
543 (connection, client_addr) = self.socket.accept()
545 self._CollectChildren(False)
551 # The client shouldn't keep the listening socket open. If the parent
552 # process is restarted, it would fail when there's already something
553 # listening (in this case its own child from a previous run) on the
561 # In case the handler code uses temporary files
562 utils.ResetTempfileModule()
564 self.request_executor(self, self.handler, connection, client_addr)
565 except Exception: # pylint: disable=W0703
566 logging.exception("Error while handling request from %s:%s",
567 client_addr[0], client_addr[1])
571 self._children.append(pid)
574 class HttpServerHandler(object):
575 """Base class for handling HTTP server requests.
577 Users of this class must subclass it and override the L{HandleRequest}
581 def PreHandleRequest(self, req):
582 """Called before handling a request.
584 Can be overridden by a subclass.
588 def HandleRequest(self, req):
589 """Handles a request.
591 Must be overridden by subclass.
594 raise NotImplementedError()