4 # Copyright (C) 2007, 2008 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
38 WEEKDAYNAME = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
40 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
41 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
43 # Default error message
44 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
45 DEFAULT_ERROR_MESSAGE = """\
48 <title>Error response</title>
51 <h1>Error response</h1>
52 <p>Error code %(code)d.
53 <p>Message: %(message)s.
54 <p>Error code explanation: %(code)s = %(explain)s.
60 def _DateTimeHeader(gmnow=None):
61 """Return the current date and time formatted for a message header.
63 The time MUST be in the GMT timezone.
68 (year, month, day, hh, mm, ss, wd, _, _) = gmnow
69 return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %
70 (WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss))
73 class _HttpServerRequest(object):
74 """Data structure for HTTP request on server side.
77 def __init__(self, method, path, headers, body):
79 self.request_method = method
80 self.request_path = path
81 self.request_headers = headers
82 self.request_body = body
85 self.resp_headers = {}
87 # Private data for request handler (useful in combination with
92 status = ["%s.%s" % (self.__class__.__module__, self.__class__.__name__),
93 self.request_method, self.request_path,
94 "headers=%r" % str(self.request_headers),
95 "body=%r" % (self.request_body, )]
97 return "<%s at %#x>" % (" ".join(status), id(self))
100 class _HttpServerToClientMessageWriter(http.HttpMessageWriter):
101 """Writes an HTTP response to client.
104 def __init__(self, sock, request_msg, response_msg, write_timeout):
105 """Writes the response to the client.
108 @param sock: Target socket
109 @type request_msg: http.HttpMessage
110 @param request_msg: Request message, required to determine whether
111 response may have a message body
112 @type response_msg: http.HttpMessage
113 @param response_msg: Response message
114 @type write_timeout: float
115 @param write_timeout: Write timeout for socket
118 self._request_msg = request_msg
119 self._response_msg = response_msg
120 http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
122 def HasMessageBody(self):
123 """Logic to detect whether response should contain a message body.
126 if self._request_msg.start_line:
127 request_method = self._request_msg.start_line.method
129 request_method = None
131 response_code = self._response_msg.start_line.code
133 # RFC2616, section 4.3: "A message-body MUST NOT be included in a request
134 # if the specification of the request method (section 5.1.1) does not allow
135 # sending an entity-body in requests"
137 # RFC2616, section 9.4: "The HEAD method is identical to GET except that
138 # the server MUST NOT return a message-body in the response."
140 # RFC2616, section 10.2.5: "The 204 response MUST NOT include a
141 # message-body [...]"
143 # RFC2616, section 10.3.5: "The 304 response MUST NOT contain a
144 # message-body, [...]"
146 return (http.HttpMessageWriter.HasMessageBody(self) and
147 (request_method is not None and
148 request_method != http.HTTP_HEAD) and
149 response_code >= http.HTTP_OK and
150 response_code not in (http.HTTP_NO_CONTENT,
151 http.HTTP_NOT_MODIFIED))
154 class _HttpClientToServerMessageReader(http.HttpMessageReader):
155 """Reads an HTTP request sent by client.
159 START_LINE_LENGTH_MAX = 4096
160 HEADER_LENGTH_MAX = 4096
162 def ParseStartLine(self, start_line):
163 """Parses the start line sent by client.
165 Example: "GET /index.html HTTP/1.1"
167 @type start_line: string
168 @param start_line: Start line
171 # Empty lines are skipped when reading
174 logging.debug("HTTP request: %s", start_line)
176 words = start_line.split()
179 [method, path, version] = words
180 if version[:5] != 'HTTP/':
181 raise http.HttpBadRequest("Bad request version (%r)" % version)
184 base_version_number = version.split("/", 1)[1]
185 version_number = base_version_number.split(".")
187 # RFC 2145 section 3.1 says there can be only one "." and
188 # - major and minor numbers MUST be treated as
190 # - HTTP/2.4 is a lower version than HTTP/2.13, which in
191 # turn is lower than HTTP/12.3;
192 # - Leading zeros MUST be ignored by recipients.
193 if len(version_number) != 2:
194 raise http.HttpBadRequest("Bad request version (%r)" % version)
196 version_number = (int(version_number[0]), int(version_number[1]))
197 except (ValueError, IndexError):
198 raise http.HttpBadRequest("Bad request version (%r)" % version)
200 if version_number >= (2, 0):
201 raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
204 elif len(words) == 2:
205 version = http.HTTP_0_9
206 [method, path] = words
207 if method != http.HTTP_GET:
208 raise http.HttpBadRequest("Bad HTTP/0.9 request type (%r)" % method)
211 raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
213 return http.HttpClientToServerStartLine(method, path, version)
216 class HttpServerRequestExecutor(object):
217 """Implements server side of HTTP.
219 This class implements the server side of HTTP. It's based on code of
220 Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
221 support non-ASCII character encodings. Keep-alive connections are
225 # The default request version. This only affects responses up until
226 # the point where the request line is parsed, so it mainly decides what
227 # the client gets back when sending a malformed request line.
228 # Most web servers default to HTTP 0.9, i.e. don't send a status line.
229 default_request_version = http.HTTP_0_9
231 # Error message settings
232 error_message_format = DEFAULT_ERROR_MESSAGE
233 error_content_type = DEFAULT_ERROR_CONTENT_TYPE
235 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
237 # Timeouts in seconds for socket layer
242 def __init__(self, server, sock, client_addr):
243 """Initializes this class.
248 self.client_addr = client_addr
250 self.request_msg = http.HttpMessage()
251 self.response_msg = http.HttpMessage()
253 self.response_msg.start_line = \
254 http.HttpServerToClientStartLine(version=self.default_request_version,
255 code=None, reason=None)
257 # Disable Python's timeout
258 self.sock.settimeout(None)
260 # Operate in non-blocking mode
261 self.sock.setblocking(0)
263 logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
265 request_msg_reader = None
268 # Do the secret SSL handshake
269 if self.server.using_ssl:
270 self.sock.set_accept_state()
272 http.Handshake(self.sock, self.WRITE_TIMEOUT)
273 except http.HttpSessionHandshakeUnexpectedEOF:
279 request_msg_reader = self._ReadRequest()
281 # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond
282 # with a 400 (Bad Request) status code to any HTTP/1.1 request
283 # message which lacks a Host header field.
284 if (self.request_msg.start_line.version == http.HTTP_1_1 and
285 http.HTTP_HOST not in self.request_msg.headers):
286 raise http.HttpBadRequest(message="Missing Host header")
288 self._HandleRequest()
290 # Only wait for client to close if we didn't have any exception.
292 except http.HttpException, err:
293 self._SetErrorStatus(err)
295 # Try to send a response
298 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
299 request_msg_reader, force_close)
304 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
306 def _ReadRequest(self):
307 """Reads a request sent by client.
311 request_msg_reader = \
312 _HttpClientToServerMessageReader(self.sock, self.request_msg,
314 except http.HttpSocketTimeout:
315 raise http.HttpError("Timeout while reading request")
316 except socket.error, err:
317 raise http.HttpError("Error reading request: %s" % err)
319 self.response_msg.start_line.version = self.request_msg.start_line.version
321 return request_msg_reader
323 def _HandleRequest(self):
324 """Calls the handler function for the current request.
327 handler_context = _HttpServerRequest(self.request_msg.start_line.method,
328 self.request_msg.start_line.path,
329 self.request_msg.headers,
330 self.request_msg.body)
332 logging.debug("Handling request %r", handler_context)
336 # Authentication, etc.
337 self.server.PreHandleRequest(handler_context)
339 # Call actual request handler
340 result = self.server.HandleRequest(handler_context)
341 except (http.HttpException, KeyboardInterrupt, SystemExit):
343 except Exception, err:
344 logging.exception("Caught exception")
345 raise http.HttpInternalServerError(message=str(err))
347 logging.exception("Unknown exception")
348 raise http.HttpInternalServerError(message="Unknown error")
350 if not isinstance(result, basestring):
351 raise http.HttpError("Handler function didn't return string type")
353 self.response_msg.start_line.code = http.HTTP_OK
354 self.response_msg.headers = handler_context.resp_headers
355 self.response_msg.body = result
357 # No reason to keep this any longer, even for exceptions
358 handler_context.private = None
360 def _SendResponse(self):
361 """Sends the response to the client.
364 if self.response_msg.start_line.code is None:
367 if not self.response_msg.headers:
368 self.response_msg.headers = {}
370 self.response_msg.headers.update({
371 # TODO: Keep-alive is not supported
372 http.HTTP_CONNECTION: "close",
373 http.HTTP_DATE: _DateTimeHeader(),
374 http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
377 # Get response reason based on code
378 response_code = self.response_msg.start_line.code
379 if response_code in self.responses:
380 response_reason = self.responses[response_code][0]
383 self.response_msg.start_line.reason = response_reason
385 logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1],
386 self.request_msg.start_line, response_code)
389 _HttpServerToClientMessageWriter(self.sock, self.request_msg,
390 self.response_msg, self.WRITE_TIMEOUT)
391 except http.HttpSocketTimeout:
392 raise http.HttpError("Timeout while sending response")
393 except socket.error, err:
394 raise http.HttpError("Error sending response: %s" % err)
396 def _SetErrorStatus(self, err):
397 """Sets the response code and body from a HttpException.
399 @type err: HttpException
400 @param err: Exception instance
404 (shortmsg, longmsg) = self.responses[err.code]
406 shortmsg = longmsg = "Unknown"
409 message = err.message
415 "message": cgi.escape(message),
419 self.response_msg.start_line.code = err.code
423 headers.update(err.headers)
424 headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
425 self.response_msg.headers = headers
427 self.response_msg.body = self._FormatErrorMessage(values)
429 def _FormatErrorMessage(self, values):
430 """Formats the body of an error message.
433 @param values: dictionary with keys code, message and explain.
435 @return: the body of the message
438 return self.error_message_format % values
441 class HttpServer(http.HttpBase, asyncore.dispatcher):
442 """Generic HTTP server class
444 Users of this class must subclass it and override the HandleRequest function.
449 def __init__(self, mainloop, local_address, port,
450 ssl_params=None, ssl_verify_peer=False,
451 request_executor_class=None):
452 """Initializes the HTTP server
454 @type mainloop: ganeti.daemon.Mainloop
455 @param mainloop: Mainloop used to poll for I/O events
456 @type local_address: string
457 @param local_address: Local IP address to bind to
459 @param port: TCP port to listen on
460 @type ssl_params: HttpSslParams
461 @param ssl_params: SSL key and certificate
462 @type ssl_verify_peer: bool
463 @param ssl_verify_peer: Whether to require client certificate
464 and compare it with our certificate
465 @type request_executor_class: class
466 @param request_executor_class: an class derived from the
467 HttpServerRequestExecutor class
470 http.HttpBase.__init__(self)
471 asyncore.dispatcher.__init__(self)
473 if request_executor_class is None:
474 self.request_executor = HttpServerRequestExecutor
476 self.request_executor = request_executor_class
478 self.mainloop = mainloop
479 self.local_address = local_address
482 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer)
484 # Allow port to be reused
485 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
488 self.set_socket(self.socket)
489 self.accepting = True
490 mainloop.RegisterSignal(self)
493 self.socket.bind((self.local_address, self.port))
494 self.socket.listen(1024)
499 def handle_accept(self):
500 self._IncomingConnection()
502 def OnSignal(self, signum):
503 if signum == signal.SIGCHLD:
504 self._CollectChildren(True)
506 def _CollectChildren(self, quick):
507 """Checks whether any child processes are done
510 @param quick: Whether to only use non-blocking functions
514 # Don't wait for other processes if it should be a quick check
515 while len(self._children) > self.MAX_CHILDREN:
517 # Waiting without a timeout brings us into a potential DoS situation.
518 # As soon as too many children run, we'll not respond to new
519 # requests. The real solution would be to add a timeout for children
520 # and killing them after some time.
521 pid, _ = os.waitpid(0, 0)
524 if pid and pid in self._children:
525 self._children.remove(pid)
527 for child in self._children:
529 pid, _ = os.waitpid(child, os.WNOHANG)
532 if pid and pid in self._children:
533 self._children.remove(pid)
535 def _IncomingConnection(self):
536 """Called for each incoming connection
539 # pylint: disable-msg=W0212
540 (connection, client_addr) = self.socket.accept()
542 self._CollectChildren(False)
548 # The client shouldn't keep the listening socket open. If the parent
549 # process is restarted, it would fail when there's already something
550 # listening (in this case its own child from a previous run) on the
558 # In case the handler code uses temporary files
559 utils.ResetTempfileModule()
561 self.request_executor(self, connection, client_addr)
562 except Exception: # pylint: disable-msg=W0703
563 logging.exception("Error while handling request from %s:%s",
564 client_addr[0], client_addr[1])
568 self._children.append(pid)
570 def PreHandleRequest(self, req):
571 """Called before handling a request.
573 Can be overridden by a subclass.
577 def HandleRequest(self, req):
578 """Handles a request.
580 Must be overridden by subclass.
583 raise NotImplementedError()