Statistics
| Branch: | Tag: | Revision:

root / lib / http / server.py @ 5ae4945a

History | View | Annotate | Download (18.4 kB)

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 = 4096
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
          # pylint: disable=E1103
423
          logging.info("%s:%s %s %s", client_addr[0], client_addr[1],
424
                       request_msg.start_line, response_msg.start_line.code)
425
          self._SendResponse(sock, request_msg, response_msg,
426
                             self.WRITE_TIMEOUT)
427
      finally:
428
        http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
429
                                request_msg_reader, force_close)
430

    
431
      sock.close()
432
    finally:
433
      logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
434

    
435
  @staticmethod
436
  def _ReadRequest(sock, timeout):
437
    """Reads a request sent by client.
438

439
    """
440
    msg = http.HttpMessage()
441

    
442
    try:
443
      reader = _HttpClientToServerMessageReader(sock, msg, timeout)
444
    except http.HttpSocketTimeout:
445
      raise http.HttpError("Timeout while reading request")
446
    except socket.error, err:
447
      raise http.HttpError("Error reading request: %s" % err)
448

    
449
    return (msg, reader)
450

    
451
  @staticmethod
452
  def _SendResponse(sock, req_msg, msg, timeout):
453
    """Sends the response to the client.
454

455
    """
456
    try:
457
      _HttpServerToClientMessageWriter(sock, req_msg, msg, timeout)
458
    except http.HttpSocketTimeout:
459
      raise http.HttpError("Timeout while sending response")
460
    except socket.error, err:
461
      raise http.HttpError("Error sending response: %s" % err)
462

    
463

    
464
class HttpServer(http.HttpBase, asyncore.dispatcher):
465
  """Generic HTTP server class
466

467
  """
468
  MAX_CHILDREN = 20
469

    
470
  def __init__(self, mainloop, local_address, port, handler,
471
               ssl_params=None, ssl_verify_peer=False,
472
               request_executor_class=None):
473
    """Initializes the HTTP server
474

475
    @type mainloop: ganeti.daemon.Mainloop
476
    @param mainloop: Mainloop used to poll for I/O events
477
    @type local_address: string
478
    @param local_address: Local IP address to bind to
479
    @type port: int
480
    @param port: TCP port to listen on
481
    @type ssl_params: HttpSslParams
482
    @param ssl_params: SSL key and certificate
483
    @type ssl_verify_peer: bool
484
    @param ssl_verify_peer: Whether to require client certificate
485
        and compare it with our certificate
486
    @type request_executor_class: class
487
    @param request_executor_class: an class derived from the
488
        HttpServerRequestExecutor class
489

490
    """
491
    http.HttpBase.__init__(self)
492
    asyncore.dispatcher.__init__(self)
493

    
494
    if request_executor_class is None:
495
      self.request_executor = HttpServerRequestExecutor
496
    else:
497
      self.request_executor = request_executor_class
498

    
499
    self.mainloop = mainloop
500
    self.local_address = local_address
501
    self.port = port
502
    self.handler = handler
503
    family = netutils.IPAddress.GetAddressFamily(local_address)
504
    self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
505

    
506
    # Allow port to be reused
507
    self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
508

    
509
    self._children = []
510
    self.set_socket(self.socket)
511
    self.accepting = True
512
    mainloop.RegisterSignal(self)
513

    
514
  def Start(self):
515
    self.socket.bind((self.local_address, self.port))
516
    self.socket.listen(1024)
517

    
518
  def Stop(self):
519
    self.socket.close()
520

    
521
  def handle_accept(self):
522
    self._IncomingConnection()
523

    
524
  def OnSignal(self, signum):
525
    if signum == signal.SIGCHLD:
526
      self._CollectChildren(True)
527

    
528
  def _CollectChildren(self, quick):
529
    """Checks whether any child processes are done
530

531
    @type quick: bool
532
    @param quick: Whether to only use non-blocking functions
533

534
    """
535
    if not quick:
536
      # Don't wait for other processes if it should be a quick check
537
      while len(self._children) > self.MAX_CHILDREN:
538
        try:
539
          # Waiting without a timeout brings us into a potential DoS situation.
540
          # As soon as too many children run, we'll not respond to new
541
          # requests. The real solution would be to add a timeout for children
542
          # and killing them after some time.
543
          pid, _ = os.waitpid(0, 0)
544
        except os.error:
545
          pid = None
546
        if pid and pid in self._children:
547
          self._children.remove(pid)
548

    
549
    for child in self._children:
550
      try:
551
        pid, _ = os.waitpid(child, os.WNOHANG)
552
      except os.error:
553
        pid = None
554
      if pid and pid in self._children:
555
        self._children.remove(pid)
556

    
557
  def _IncomingConnection(self):
558
    """Called for each incoming connection
559

560
    """
561
    # pylint: disable=W0212
562
    (connection, client_addr) = self.socket.accept()
563

    
564
    self._CollectChildren(False)
565

    
566
    pid = os.fork()
567
    if pid == 0:
568
      # Child process
569
      try:
570
        # The client shouldn't keep the listening socket open. If the parent
571
        # process is restarted, it would fail when there's already something
572
        # listening (in this case its own child from a previous run) on the
573
        # same port.
574
        try:
575
          self.socket.close()
576
        except socket.error:
577
          pass
578
        self.socket = None
579

    
580
        # In case the handler code uses temporary files
581
        utils.ResetTempfileModule()
582

    
583
        self.request_executor(self, self.handler, connection, client_addr)
584
      except Exception: # pylint: disable=W0703
585
        logging.exception("Error while handling request from %s:%s",
586
                          client_addr[0], client_addr[1])
587
        os._exit(1)
588
      os._exit(0)
589
    else:
590
      self._children.append(pid)
591

    
592

    
593
class HttpServerHandler(object):
594
  """Base class for handling HTTP server requests.
595

596
  Users of this class must subclass it and override the L{HandleRequest}
597
  function.
598

599
  """
600
  def PreHandleRequest(self, req):
601
    """Called before handling a request.
602

603
    Can be overridden by a subclass.
604

605
    """
606

    
607
  def HandleRequest(self, req):
608
    """Handles a request.
609

610
    Must be overridden by subclass.
611

612
    """
613
    raise NotImplementedError()
614

    
615
  @staticmethod
616
  def FormatErrorMessage(values):
617
    """Formats the body of an error message.
618

619
    @type values: dict
620
    @param values: dictionary with keys C{code}, C{message} and C{explain}.
621
    @rtype: tuple; (string, string)
622
    @return: Content-type and response body
623

624
    """
625
    return (DEFAULT_ERROR_CONTENT_TYPE, DEFAULT_ERROR_MESSAGE % values)