Statistics
| Branch: | Tag: | Revision:

root / lib / http / server.py @ d27458ce

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 = 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)