Support IPv6 in lib/http/server.py
[ganeti-local] / lib / http / server.py
1 #
2 #
3
4 # Copyright (C) 2007, 2008, 2010 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
38
39 WEEKDAYNAME = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
40 MONTHNAME = [None,
41              'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
42              'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
43
44 # Default error message
45 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
46 DEFAULT_ERROR_MESSAGE = """\
47 <html>
48 <head>
49 <title>Error response</title>
50 </head>
51 <body>
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.
56 </body>
57 </html>
58 """
59
60
61 def _DateTimeHeader(gmnow=None):
62   """Return the current date and time formatted for a message header.
63
64   The time MUST be in the GMT timezone.
65
66   """
67   if gmnow is None:
68     gmnow = time.gmtime()
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))
72
73
74 class _HttpServerRequest(object):
75   """Data structure for HTTP request on server side.
76
77   """
78   def __init__(self, method, path, headers, body):
79     # Request attributes
80     self.request_method = method
81     self.request_path = path
82     self.request_headers = headers
83     self.request_body = body
84
85     # Response attributes
86     self.resp_headers = {}
87
88     # Private data for request handler (useful in combination with
89     # authentication)
90     self.private = None
91
92   def __repr__(self):
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, )]
97
98     return "<%s at %#x>" % (" ".join(status), id(self))
99
100
101 class _HttpServerToClientMessageWriter(http.HttpMessageWriter):
102   """Writes an HTTP response to client.
103
104   """
105   def __init__(self, sock, request_msg, response_msg, write_timeout):
106     """Writes the response to the client.
107
108     @type sock: socket
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
117
118     """
119     self._request_msg = request_msg
120     self._response_msg = response_msg
121     http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
122
123   def HasMessageBody(self):
124     """Logic to detect whether response should contain a message body.
125
126     """
127     if self._request_msg.start_line:
128       request_method = self._request_msg.start_line.method
129     else:
130       request_method = None
131
132     response_code = self._response_msg.start_line.code
133
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"
137     #
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."
140     #
141     # RFC2616, section 10.2.5: "The 204 response MUST NOT include a
142     # message-body [...]"
143     #
144     # RFC2616, section 10.3.5: "The 304 response MUST NOT contain a
145     # message-body, [...]"
146
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))
153
154
155 class _HttpClientToServerMessageReader(http.HttpMessageReader):
156   """Reads an HTTP request sent by client.
157
158   """
159   # Length limits
160   START_LINE_LENGTH_MAX = 4096
161   HEADER_LENGTH_MAX = 4096
162
163   def ParseStartLine(self, start_line):
164     """Parses the start line sent by client.
165
166     Example: "GET /index.html HTTP/1.1"
167
168     @type start_line: string
169     @param start_line: Start line
170
171     """
172     # Empty lines are skipped when reading
173     assert start_line
174
175     logging.debug("HTTP request: %s", start_line)
176
177     words = start_line.split()
178
179     if len(words) == 3:
180       [method, path, version] = words
181       if version[:5] != 'HTTP/':
182         raise http.HttpBadRequest("Bad request version (%r)" % version)
183
184       try:
185         base_version_number = version.split("/", 1)[1]
186         version_number = base_version_number.split(".")
187
188         # RFC 2145 section 3.1 says there can be only one "." and
189         #   - major and minor numbers MUST be treated as
190         #      separate integers;
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)
196
197         version_number = (int(version_number[0]), int(version_number[1]))
198       except (ValueError, IndexError):
199         raise http.HttpBadRequest("Bad request version (%r)" % version)
200
201       if version_number >= (2, 0):
202         raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
203                                       base_version_number)
204
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)
210
211     else:
212       raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
213
214     return http.HttpClientToServerStartLine(method, path, version)
215
216
217 class HttpServerRequestExecutor(object):
218   """Implements server side of HTTP.
219
220   This class implements the server side of HTTP. It's based on code of
221   Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
222   support non-ASCII character encodings. Keep-alive connections are
223   not supported.
224
225   """
226   # The default request version.  This only affects responses up until
227   # the point where the request line is parsed, so it mainly decides what
228   # the client gets back when sending a malformed request line.
229   # Most web servers default to HTTP 0.9, i.e. don't send a status line.
230   default_request_version = http.HTTP_0_9
231
232   # Error message settings
233   error_message_format = DEFAULT_ERROR_MESSAGE
234   error_content_type = DEFAULT_ERROR_CONTENT_TYPE
235
236   responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
237
238   # Timeouts in seconds for socket layer
239   WRITE_TIMEOUT = 10
240   READ_TIMEOUT = 10
241   CLOSE_TIMEOUT = 1
242
243   def __init__(self, server, sock, client_addr):
244     """Initializes this class.
245
246     """
247     self.server = server
248     self.sock = sock
249     self.client_addr = client_addr
250
251     self.request_msg = http.HttpMessage()
252     self.response_msg = http.HttpMessage()
253
254     self.response_msg.start_line = \
255       http.HttpServerToClientStartLine(version=self.default_request_version,
256                                        code=None, reason=None)
257
258     # Disable Python's timeout
259     self.sock.settimeout(None)
260
261     # Operate in non-blocking mode
262     self.sock.setblocking(0)
263
264     logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
265     try:
266       request_msg_reader = None
267       force_close = True
268       try:
269         # Do the secret SSL handshake
270         if self.server.using_ssl:
271           self.sock.set_accept_state()
272           try:
273             http.Handshake(self.sock, self.WRITE_TIMEOUT)
274           except http.HttpSessionHandshakeUnexpectedEOF:
275             # Ignore rest
276             return
277
278         try:
279           try:
280             request_msg_reader = self._ReadRequest()
281
282             # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond
283             # with a 400 (Bad Request) status code to any HTTP/1.1 request
284             # message which lacks a Host header field.
285             if (self.request_msg.start_line.version == http.HTTP_1_1 and
286                 http.HTTP_HOST not in self.request_msg.headers):
287               raise http.HttpBadRequest(message="Missing Host header")
288
289             self._HandleRequest()
290
291             # Only wait for client to close if we didn't have any exception.
292             force_close = False
293           except http.HttpException, err:
294             self._SetErrorStatus(err)
295         finally:
296           # Try to send a response
297           self._SendResponse()
298       finally:
299         http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
300                                 request_msg_reader, force_close)
301
302       self.sock.close()
303       self.sock = None
304     finally:
305       logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
306
307   def _ReadRequest(self):
308     """Reads a request sent by client.
309
310     """
311     try:
312       request_msg_reader = \
313         _HttpClientToServerMessageReader(self.sock, self.request_msg,
314                                          self.READ_TIMEOUT)
315     except http.HttpSocketTimeout:
316       raise http.HttpError("Timeout while reading request")
317     except socket.error, err:
318       raise http.HttpError("Error reading request: %s" % err)
319
320     self.response_msg.start_line.version = self.request_msg.start_line.version
321
322     return request_msg_reader
323
324   def _HandleRequest(self):
325     """Calls the handler function for the current request.
326
327     """
328     handler_context = _HttpServerRequest(self.request_msg.start_line.method,
329                                          self.request_msg.start_line.path,
330                                          self.request_msg.headers,
331                                          self.request_msg.body)
332
333     logging.debug("Handling request %r", handler_context)
334
335     try:
336       try:
337         # Authentication, etc.
338         self.server.PreHandleRequest(handler_context)
339
340         # Call actual request handler
341         result = self.server.HandleRequest(handler_context)
342       except (http.HttpException, KeyboardInterrupt, SystemExit):
343         raise
344       except Exception, err:
345         logging.exception("Caught exception")
346         raise http.HttpInternalServerError(message=str(err))
347       except:
348         logging.exception("Unknown exception")
349         raise http.HttpInternalServerError(message="Unknown error")
350
351       if not isinstance(result, basestring):
352         raise http.HttpError("Handler function didn't return string type")
353
354       self.response_msg.start_line.code = http.HTTP_OK
355       self.response_msg.headers = handler_context.resp_headers
356       self.response_msg.body = result
357     finally:
358       # No reason to keep this any longer, even for exceptions
359       handler_context.private = None
360
361   def _SendResponse(self):
362     """Sends the response to the client.
363
364     """
365     if self.response_msg.start_line.code is None:
366       return
367
368     if not self.response_msg.headers:
369       self.response_msg.headers = {}
370
371     self.response_msg.headers.update({
372       # TODO: Keep-alive is not supported
373       http.HTTP_CONNECTION: "close",
374       http.HTTP_DATE: _DateTimeHeader(),
375       http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
376       })
377
378     # Get response reason based on code
379     response_code = self.response_msg.start_line.code
380     if response_code in self.responses:
381       response_reason = self.responses[response_code][0]
382     else:
383       response_reason = ""
384     self.response_msg.start_line.reason = response_reason
385
386     logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1],
387                  self.request_msg.start_line, response_code)
388
389     try:
390       _HttpServerToClientMessageWriter(self.sock, self.request_msg,
391                                        self.response_msg, self.WRITE_TIMEOUT)
392     except http.HttpSocketTimeout:
393       raise http.HttpError("Timeout while sending response")
394     except socket.error, err:
395       raise http.HttpError("Error sending response: %s" % err)
396
397   def _SetErrorStatus(self, err):
398     """Sets the response code and body from a HttpException.
399
400     @type err: HttpException
401     @param err: Exception instance
402
403     """
404     try:
405       (shortmsg, longmsg) = self.responses[err.code]
406     except KeyError:
407       shortmsg = longmsg = "Unknown"
408
409     if err.message:
410       message = err.message
411     else:
412       message = shortmsg
413
414     values = {
415       "code": err.code,
416       "message": cgi.escape(message),
417       "explain": longmsg,
418       }
419
420     self.response_msg.start_line.code = err.code
421
422     headers = {}
423     if err.headers:
424       headers.update(err.headers)
425     headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
426     self.response_msg.headers = headers
427
428     self.response_msg.body = self._FormatErrorMessage(values)
429
430   def _FormatErrorMessage(self, values):
431     """Formats the body of an error message.
432
433     @type values: dict
434     @param values: dictionary with keys code, message and explain.
435     @rtype: string
436     @return: the body of the message
437
438     """
439     return self.error_message_format % values
440
441
442 class HttpServer(http.HttpBase, asyncore.dispatcher):
443   """Generic HTTP server class
444
445   Users of this class must subclass it and override the HandleRequest function.
446
447   """
448   MAX_CHILDREN = 20
449
450   def __init__(self, mainloop, local_address, port,
451                ssl_params=None, ssl_verify_peer=False,
452                request_executor_class=None):
453     """Initializes the HTTP server
454
455     @type mainloop: ganeti.daemon.Mainloop
456     @param mainloop: Mainloop used to poll for I/O events
457     @type local_address: string
458     @param local_address: Local IP address to bind to
459     @type port: int
460     @param port: TCP port to listen on
461     @type ssl_params: HttpSslParams
462     @param ssl_params: SSL key and certificate
463     @type ssl_verify_peer: bool
464     @param ssl_verify_peer: Whether to require client certificate
465         and compare it with our certificate
466     @type request_executor_class: class
467     @param request_executor_class: an class derived from the
468         HttpServerRequestExecutor class
469
470     """
471     http.HttpBase.__init__(self)
472     asyncore.dispatcher.__init__(self)
473
474     if request_executor_class is None:
475       self.request_executor = HttpServerRequestExecutor
476     else:
477       self.request_executor = request_executor_class
478
479     self.mainloop = mainloop
480     self.local_address = local_address
481     self.port = port
482     family = netutils.IPAddress.GetAddressFamily(local_address)
483     self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
484
485     # Allow port to be reused
486     self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
487
488     self._children = []
489     self.set_socket(self.socket)
490     self.accepting = True
491     mainloop.RegisterSignal(self)
492
493   def Start(self):
494     self.socket.bind((self.local_address, self.port))
495     self.socket.listen(1024)
496
497   def Stop(self):
498     self.socket.close()
499
500   def handle_accept(self):
501     self._IncomingConnection()
502
503   def OnSignal(self, signum):
504     if signum == signal.SIGCHLD:
505       self._CollectChildren(True)
506
507   def _CollectChildren(self, quick):
508     """Checks whether any child processes are done
509
510     @type quick: bool
511     @param quick: Whether to only use non-blocking functions
512
513     """
514     if not quick:
515       # Don't wait for other processes if it should be a quick check
516       while len(self._children) > self.MAX_CHILDREN:
517         try:
518           # Waiting without a timeout brings us into a potential DoS situation.
519           # As soon as too many children run, we'll not respond to new
520           # requests. The real solution would be to add a timeout for children
521           # and killing them after some time.
522           pid, _ = os.waitpid(0, 0)
523         except os.error:
524           pid = None
525         if pid and pid in self._children:
526           self._children.remove(pid)
527
528     for child in self._children:
529       try:
530         pid, _ = os.waitpid(child, os.WNOHANG)
531       except os.error:
532         pid = None
533       if pid and pid in self._children:
534         self._children.remove(pid)
535
536   def _IncomingConnection(self):
537     """Called for each incoming connection
538
539     """
540     # pylint: disable-msg=W0212
541     (connection, client_addr) = self.socket.accept()
542
543     self._CollectChildren(False)
544
545     pid = os.fork()
546     if pid == 0:
547       # Child process
548       try:
549         # The client shouldn't keep the listening socket open. If the parent
550         # process is restarted, it would fail when there's already something
551         # listening (in this case its own child from a previous run) on the
552         # same port.
553         try:
554           self.socket.close()
555         except socket.error:
556           pass
557         self.socket = None
558
559         # In case the handler code uses temporary files
560         utils.ResetTempfileModule()
561
562         self.request_executor(self, connection, client_addr)
563       except Exception: # pylint: disable-msg=W0703
564         logging.exception("Error while handling request from %s:%s",
565                           client_addr[0], client_addr[1])
566         os._exit(1)
567       os._exit(0)
568     else:
569       self._children.append(pid)
570
571   def PreHandleRequest(self, req):
572     """Called before handling a request.
573
574     Can be overridden by a subclass.
575
576     """
577
578   def HandleRequest(self, req):
579     """Handles a request.
580
581     Must be overridden by subclass.
582
583     """
584     raise NotImplementedError()