Allow link local IPv6 gateways
[ganeti-local] / lib / http / server.py
1 #
2 #
3
4 # Copyright (C) 2007, 2008, 2010, 2012 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21 """HTTP server module.
22
23 """
24
25 import BaseHTTPServer
26 import cgi
27 import logging
28 import os
29 import socket
30 import time
31 import signal
32 import asyncore
33
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
39
40
41 WEEKDAYNAME = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
42 MONTHNAME = [None,
43              "Jan", "Feb", "Mar", "Apr", "May", "Jun",
44              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
45
46 # Default error message
47 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
48 DEFAULT_ERROR_MESSAGE = """\
49 <html>
50 <head>
51 <title>Error response</title>
52 </head>
53 <body>
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.
58 </body>
59 </html>
60 """
61
62
63 def _DateTimeHeader(gmnow=None):
64   """Return the current date and time formatted for a message header.
65
66   The time MUST be in the GMT timezone.
67
68   """
69   if gmnow is None:
70     gmnow = time.gmtime()
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))
74
75
76 class _HttpServerRequest(object):
77   """Data structure for HTTP request on server side.
78
79   """
80   def __init__(self, method, path, headers, body):
81     # Request attributes
82     self.request_method = method
83     self.request_path = path
84     self.request_headers = headers
85     self.request_body = body
86
87     # Response attributes
88     self.resp_headers = {}
89
90     # Private data for request handler (useful in combination with
91     # authentication)
92     self.private = None
93
94   def __repr__(self):
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, )]
99
100     return "<%s at %#x>" % (" ".join(status), id(self))
101
102
103 class _HttpServerToClientMessageWriter(http.HttpMessageWriter):
104   """Writes an HTTP response to client.
105
106   """
107   def __init__(self, sock, request_msg, response_msg, write_timeout):
108     """Writes the response to the client.
109
110     @type sock: socket
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
119
120     """
121     self._request_msg = request_msg
122     self._response_msg = response_msg
123     http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
124
125   def HasMessageBody(self):
126     """Logic to detect whether response should contain a message body.
127
128     """
129     if self._request_msg.start_line:
130       request_method = self._request_msg.start_line.method
131     else:
132       request_method = None
133
134     response_code = self._response_msg.start_line.code
135
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"
139     #
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."
142     #
143     # RFC2616, section 10.2.5: "The 204 response MUST NOT include a
144     # message-body [...]"
145     #
146     # RFC2616, section 10.3.5: "The 304 response MUST NOT contain a
147     # message-body, [...]"
148
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))
155
156
157 class _HttpClientToServerMessageReader(http.HttpMessageReader):
158   """Reads an HTTP request sent by client.
159
160   """
161   # Length limits
162   START_LINE_LENGTH_MAX = 8192
163   HEADER_LENGTH_MAX = 4096
164
165   def ParseStartLine(self, start_line):
166     """Parses the start line sent by client.
167
168     Example: "GET /index.html HTTP/1.1"
169
170     @type start_line: string
171     @param start_line: Start line
172
173     """
174     # Empty lines are skipped when reading
175     assert start_line
176
177     logging.debug("HTTP request: %s", start_line)
178
179     words = start_line.split()
180
181     if len(words) == 3:
182       [method, path, version] = words
183       if version[:5] != "HTTP/":
184         raise http.HttpBadRequest("Bad request version (%r)" % version)
185
186       try:
187         base_version_number = version.split("/", 1)[1]
188         version_number = base_version_number.split(".")
189
190         # RFC 2145 section 3.1 says there can be only one "." and
191         #   - major and minor numbers MUST be treated as
192         #      separate integers;
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)
198
199         version_number = (int(version_number[0]), int(version_number[1]))
200       except (ValueError, IndexError):
201         raise http.HttpBadRequest("Bad request version (%r)" % version)
202
203       if version_number >= (2, 0):
204         raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
205                                            base_version_number)
206
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)
212
213     else:
214       raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
215
216     return http.HttpClientToServerStartLine(method, path, version)
217
218
219 def _HandleServerRequestInner(handler, req_msg):
220   """Calls the handler function for the current request.
221
222   """
223   handler_context = _HttpServerRequest(req_msg.start_line.method,
224                                        req_msg.start_line.path,
225                                        req_msg.headers,
226                                        req_msg.body)
227
228   logging.debug("Handling request %r", handler_context)
229
230   try:
231     try:
232       # Authentication, etc.
233       handler.PreHandleRequest(handler_context)
234
235       # Call actual request handler
236       result = handler.HandleRequest(handler_context)
237     except (http.HttpException, errors.RapiTestResult,
238             KeyboardInterrupt, SystemExit):
239       raise
240     except Exception, err:
241       logging.exception("Caught exception")
242       raise http.HttpInternalServerError(message=str(err))
243     except:
244       logging.exception("Unknown exception")
245       raise http.HttpInternalServerError(message="Unknown error")
246
247     if not isinstance(result, basestring):
248       raise http.HttpError("Handler function didn't return string type")
249
250     return (http.HTTP_OK, handler_context.resp_headers, result)
251   finally:
252     # No reason to keep this any longer, even for exceptions
253     handler_context.private = None
254
255
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
262
263   responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
264
265   def __init__(self, handler):
266     """Initializes this class.
267
268     """
269     self._handler = handler
270
271   def __call__(self, fn):
272     """Handles a request.
273
274     @type fn: callable
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})
278
279     """
280     response_msg = http.HttpMessage()
281     response_msg.start_line = \
282       http.HttpServerToClientStartLine(version=self.default_request_version,
283                                        code=None, reason=None)
284
285     force_close = True
286
287     try:
288       (request_msg, req_msg_reader) = fn()
289
290       response_msg.start_line.version = request_msg.start_line.version
291
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")
299
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)
305     else:
306       # Only wait for client to close if we didn't have any exception.
307       force_close = False
308
309     return (request_msg, req_msg_reader, force_close,
310             self._Finalize(self.responses, response_msg))
311
312   @staticmethod
313   def _SetError(responses, handler, response_msg, err):
314     """Sets the response code and body from a HttpException.
315
316     @type err: HttpException
317     @param err: Exception instance
318
319     """
320     try:
321       (shortmsg, longmsg) = responses[err.code]
322     except KeyError:
323       shortmsg = longmsg = "Unknown"
324
325     if err.message:
326       message = err.message
327     else:
328       message = shortmsg
329
330     values = {
331       "code": err.code,
332       "message": cgi.escape(message),
333       "explain": longmsg,
334       }
335
336     (content_type, body) = handler.FormatErrorMessage(values)
337
338     headers = {
339       http.HTTP_CONTENT_TYPE: content_type,
340       }
341
342     if err.headers:
343       headers.update(err.headers)
344
345     response_msg.start_line.code = err.code
346     response_msg.headers = headers
347     response_msg.body = body
348
349   @staticmethod
350   def _Finalize(responses, msg):
351     assert msg.start_line.reason is None
352
353     if not msg.headers:
354       msg.headers = {}
355
356     msg.headers.update({
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,
361       })
362
363     # Get response reason based on code
364     try:
365       code_desc = responses[msg.start_line.code]
366     except KeyError:
367       reason = ""
368     else:
369       (reason, _) = code_desc
370
371     msg.start_line.reason = reason
372
373     return msg
374
375
376 class HttpServerRequestExecutor(object):
377   """Implements server side of HTTP.
378
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
382   not supported.
383
384   """
385   # Timeouts in seconds for socket layer
386   WRITE_TIMEOUT = 10
387   READ_TIMEOUT = 10
388   CLOSE_TIMEOUT = 1
389
390   def __init__(self, server, handler, sock, client_addr):
391     """Initializes this class.
392
393     """
394     responder = HttpResponder(handler)
395
396     # Disable Python's timeout
397     sock.settimeout(None)
398
399     # Operate in non-blocking mode
400     sock.setblocking(0)
401
402     request_msg_reader = None
403     force_close = True
404
405     logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
406     try:
407       # Block for closing connection
408       try:
409         # Do the secret SSL handshake
410         if server.using_ssl:
411           sock.set_accept_state()
412           try:
413             http.Handshake(sock, self.WRITE_TIMEOUT)
414           except http.HttpSessionHandshakeUnexpectedEOF:
415             # Ignore rest
416             return
417
418         (request_msg, request_msg_reader, force_close, response_msg) = \
419           responder(compat.partial(self._ReadRequest, sock, self.READ_TIMEOUT))
420         if response_msg:
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,
427                              self.WRITE_TIMEOUT)
428       finally:
429         http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
430                                 request_msg_reader, force_close)
431
432       sock.close()
433     finally:
434       logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
435
436   @staticmethod
437   def _ReadRequest(sock, timeout):
438     """Reads a request sent by client.
439
440     """
441     msg = http.HttpMessage()
442
443     try:
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)
449
450     return (msg, reader)
451
452   @staticmethod
453   def _SendResponse(sock, req_msg, msg, timeout):
454     """Sends the response to the client.
455
456     """
457     try:
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)
463
464
465 class HttpServer(http.HttpBase, asyncore.dispatcher):
466   """Generic HTTP server class
467
468   """
469   MAX_CHILDREN = 20
470
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
475
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
480     @type port: int
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
490
491     """
492     http.HttpBase.__init__(self)
493     asyncore.dispatcher.__init__(self)
494
495     if request_executor_class is None:
496       self.request_executor = HttpServerRequestExecutor
497     else:
498       self.request_executor = request_executor_class
499
500     self.mainloop = mainloop
501     self.local_address = local_address
502     self.port = port
503     self.handler = handler
504     family = netutils.IPAddress.GetAddressFamily(local_address)
505     self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
506
507     # Allow port to be reused
508     self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
509
510     self._children = []
511     self.set_socket(self.socket)
512     self.accepting = True
513     mainloop.RegisterSignal(self)
514
515   def Start(self):
516     self.socket.bind((self.local_address, self.port))
517     self.socket.listen(1024)
518
519   def Stop(self):
520     self.socket.close()
521
522   def handle_accept(self):
523     self._IncomingConnection()
524
525   def OnSignal(self, signum):
526     if signum == signal.SIGCHLD:
527       self._CollectChildren(True)
528
529   def _CollectChildren(self, quick):
530     """Checks whether any child processes are done
531
532     @type quick: bool
533     @param quick: Whether to only use non-blocking functions
534
535     """
536     if not quick:
537       # Don't wait for other processes if it should be a quick check
538       while len(self._children) > self.MAX_CHILDREN:
539         try:
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)
545         except os.error:
546           pid = None
547         if pid and pid in self._children:
548           self._children.remove(pid)
549
550     for child in self._children:
551       try:
552         pid, _ = os.waitpid(child, os.WNOHANG)
553       except os.error:
554         pid = None
555       if pid and pid in self._children:
556         self._children.remove(pid)
557
558   def _IncomingConnection(self):
559     """Called for each incoming connection
560
561     """
562     # pylint: disable=W0212
563     (connection, client_addr) = self.socket.accept()
564
565     self._CollectChildren(False)
566
567     pid = os.fork()
568     if pid == 0:
569       # Child process
570       try:
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
574         # same port.
575         try:
576           self.socket.close()
577         except socket.error:
578           pass
579         self.socket = None
580
581         # In case the handler code uses temporary files
582         utils.ResetTempfileModule()
583
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])
588         os._exit(1)
589       os._exit(0)
590     else:
591       self._children.append(pid)
592
593
594 class HttpServerHandler(object):
595   """Base class for handling HTTP server requests.
596
597   Users of this class must subclass it and override the L{HandleRequest}
598   function.
599
600   """
601   def PreHandleRequest(self, req):
602     """Called before handling a request.
603
604     Can be overridden by a subclass.
605
606     """
607
608   def HandleRequest(self, req):
609     """Handles a request.
610
611     Must be overridden by subclass.
612
613     """
614     raise NotImplementedError()
615
616   @staticmethod
617   def FormatErrorMessage(values):
618     """Formats the body of an error message.
619
620     @type values: dict
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
624
625     """
626     return (DEFAULT_ERROR_CONTENT_TYPE, DEFAULT_ERROR_MESSAGE % values)