Statistics
| Branch: | Tag: | Revision:

root / lib / http / server.py @ 377ae13e

History | View | Annotate | Download (17.8 kB)

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
def HandleServerRequest(handler, req_msg):
218
  """Calls the handler function for the current request.
219

220
  """
221
  handler_context = _HttpServerRequest(req_msg.start_line.method,
222
                                       req_msg.start_line.path,
223
                                       req_msg.headers,
224
                                       req_msg.body)
225

    
226
  logging.debug("Handling request %r", handler_context)
227

    
228
  try:
229
    try:
230
      # Authentication, etc.
231
      handler.PreHandleRequest(handler_context)
232

    
233
      # Call actual request handler
234
      result = handler.HandleRequest(handler_context)
235
    except (http.HttpException, KeyboardInterrupt, SystemExit):
236
      raise
237
    except Exception, err:
238
      logging.exception("Caught exception")
239
      raise http.HttpInternalServerError(message=str(err))
240
    except:
241
      logging.exception("Unknown exception")
242
      raise http.HttpInternalServerError(message="Unknown error")
243

    
244
    if not isinstance(result, basestring):
245
      raise http.HttpError("Handler function didn't return string type")
246

    
247
    return (http.HTTP_OK, handler_context.resp_headers, result)
248
  finally:
249
    # No reason to keep this any longer, even for exceptions
250
    handler_context.private = None
251

    
252

    
253
class HttpServerRequestExecutor(object):
254
  """Implements server side of HTTP.
255

256
  This class implements the server side of HTTP. It's based on code of
257
  Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
258
  support non-ASCII character encodings. Keep-alive connections are
259
  not supported.
260

261
  """
262
  # The default request version.  This only affects responses up until
263
  # the point where the request line is parsed, so it mainly decides what
264
  # the client gets back when sending a malformed request line.
265
  # Most web servers default to HTTP 0.9, i.e. don't send a status line.
266
  default_request_version = http.HTTP_0_9
267

    
268
  responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
269

    
270
  # Timeouts in seconds for socket layer
271
  WRITE_TIMEOUT = 10
272
  READ_TIMEOUT = 10
273
  CLOSE_TIMEOUT = 1
274

    
275
  def __init__(self, server, handler, sock, client_addr):
276
    """Initializes this class.
277

278
    """
279
    self.server = server
280
    self.handler = handler
281
    self.sock = sock
282
    self.client_addr = client_addr
283

    
284
    self.request_msg = http.HttpMessage()
285
    self.response_msg = http.HttpMessage()
286

    
287
    self.response_msg.start_line = \
288
      http.HttpServerToClientStartLine(version=self.default_request_version,
289
                                       code=None, reason=None)
290

    
291
    # Disable Python's timeout
292
    self.sock.settimeout(None)
293

    
294
    # Operate in non-blocking mode
295
    self.sock.setblocking(0)
296

    
297
    logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
298
    try:
299
      request_msg_reader = None
300
      force_close = True
301
      try:
302
        # Do the secret SSL handshake
303
        if self.server.using_ssl:
304
          self.sock.set_accept_state()
305
          try:
306
            http.Handshake(self.sock, self.WRITE_TIMEOUT)
307
          except http.HttpSessionHandshakeUnexpectedEOF:
308
            # Ignore rest
309
            return
310

    
311
        try:
312
          try:
313
            request_msg_reader = self._ReadRequest()
314

    
315
            # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond
316
            # with a 400 (Bad Request) status code to any HTTP/1.1 request
317
            # message which lacks a Host header field.
318
            if (self.request_msg.start_line.version == http.HTTP_1_1 and
319
                http.HTTP_HOST not in self.request_msg.headers):
320
              raise http.HttpBadRequest(message="Missing Host header")
321

    
322
            (self.response_msg.start_line.code, self.response_msg.headers,
323
             self.response_msg.body) = \
324
              HandleServerRequest(self.handler, self.request_msg)
325

    
326
            # Only wait for client to close if we didn't have any exception.
327
            force_close = False
328
          except http.HttpException, err:
329
            self._SetErrorStatus(err)
330
        finally:
331
          # Try to send a response
332
          self._SendResponse()
333
      finally:
334
        http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
335
                                request_msg_reader, force_close)
336

    
337
      self.sock.close()
338
      self.sock = None
339
    finally:
340
      logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
341

    
342
  def _ReadRequest(self):
343
    """Reads a request sent by client.
344

345
    """
346
    try:
347
      request_msg_reader = \
348
        _HttpClientToServerMessageReader(self.sock, self.request_msg,
349
                                         self.READ_TIMEOUT)
350
    except http.HttpSocketTimeout:
351
      raise http.HttpError("Timeout while reading request")
352
    except socket.error, err:
353
      raise http.HttpError("Error reading request: %s" % err)
354

    
355
    self.response_msg.start_line.version = self.request_msg.start_line.version
356

    
357
    return request_msg_reader
358

    
359
  def _SendResponse(self):
360
    """Sends the response to the client.
361

362
    """
363
    # HttpMessage.start_line can be of different types, pylint: disable=E1103
364
    if self.response_msg.start_line.code is None:
365
      return
366

    
367
    if not self.response_msg.headers:
368
      self.response_msg.headers = {}
369

    
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,
375
      })
376

    
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]
381
    else:
382
      response_reason = ""
383
    self.response_msg.start_line.reason = response_reason
384

    
385
    logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1],
386
                 self.request_msg.start_line, response_code)
387

    
388
    try:
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)
395

    
396
  def _SetErrorStatus(self, err):
397
    """Sets the response code and body from a HttpException.
398

399
    @type err: HttpException
400
    @param err: Exception instance
401

402
    """
403
    try:
404
      (shortmsg, longmsg) = self.responses[err.code]
405
    except KeyError:
406
      shortmsg = longmsg = "Unknown"
407

    
408
    if err.message:
409
      message = err.message
410
    else:
411
      message = shortmsg
412

    
413
    values = {
414
      "code": err.code,
415
      "message": cgi.escape(message),
416
      "explain": longmsg,
417
      }
418

    
419
    (content_type, body) = self.handler.FormatErrorMessage(values)
420

    
421
    headers = {
422
      http.HTTP_CONTENT_TYPE: content_type,
423
      }
424

    
425
    if err.headers:
426
      headers.update(err.headers)
427

    
428
    self.response_msg.start_line.code = err.code
429
    self.response_msg.headers = headers
430
    self.response_msg.body = body
431

    
432

    
433
class HttpServer(http.HttpBase, asyncore.dispatcher):
434
  """Generic HTTP server class
435

436
  """
437
  MAX_CHILDREN = 20
438

    
439
  def __init__(self, mainloop, local_address, port, handler,
440
               ssl_params=None, ssl_verify_peer=False,
441
               request_executor_class=None):
442
    """Initializes the HTTP server
443

444
    @type mainloop: ganeti.daemon.Mainloop
445
    @param mainloop: Mainloop used to poll for I/O events
446
    @type local_address: string
447
    @param local_address: Local IP address to bind to
448
    @type port: int
449
    @param port: TCP port to listen on
450
    @type ssl_params: HttpSslParams
451
    @param ssl_params: SSL key and certificate
452
    @type ssl_verify_peer: bool
453
    @param ssl_verify_peer: Whether to require client certificate
454
        and compare it with our certificate
455
    @type request_executor_class: class
456
    @param request_executor_class: an class derived from the
457
        HttpServerRequestExecutor class
458

459
    """
460
    http.HttpBase.__init__(self)
461
    asyncore.dispatcher.__init__(self)
462

    
463
    if request_executor_class is None:
464
      self.request_executor = HttpServerRequestExecutor
465
    else:
466
      self.request_executor = request_executor_class
467

    
468
    self.mainloop = mainloop
469
    self.local_address = local_address
470
    self.port = port
471
    self.handler = handler
472
    family = netutils.IPAddress.GetAddressFamily(local_address)
473
    self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
474

    
475
    # Allow port to be reused
476
    self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
477

    
478
    self._children = []
479
    self.set_socket(self.socket)
480
    self.accepting = True
481
    mainloop.RegisterSignal(self)
482

    
483
  def Start(self):
484
    self.socket.bind((self.local_address, self.port))
485
    self.socket.listen(1024)
486

    
487
  def Stop(self):
488
    self.socket.close()
489

    
490
  def handle_accept(self):
491
    self._IncomingConnection()
492

    
493
  def OnSignal(self, signum):
494
    if signum == signal.SIGCHLD:
495
      self._CollectChildren(True)
496

    
497
  def _CollectChildren(self, quick):
498
    """Checks whether any child processes are done
499

500
    @type quick: bool
501
    @param quick: Whether to only use non-blocking functions
502

503
    """
504
    if not quick:
505
      # Don't wait for other processes if it should be a quick check
506
      while len(self._children) > self.MAX_CHILDREN:
507
        try:
508
          # Waiting without a timeout brings us into a potential DoS situation.
509
          # As soon as too many children run, we'll not respond to new
510
          # requests. The real solution would be to add a timeout for children
511
          # and killing them after some time.
512
          pid, _ = os.waitpid(0, 0)
513
        except os.error:
514
          pid = None
515
        if pid and pid in self._children:
516
          self._children.remove(pid)
517

    
518
    for child in self._children:
519
      try:
520
        pid, _ = os.waitpid(child, os.WNOHANG)
521
      except os.error:
522
        pid = None
523
      if pid and pid in self._children:
524
        self._children.remove(pid)
525

    
526
  def _IncomingConnection(self):
527
    """Called for each incoming connection
528

529
    """
530
    # pylint: disable=W0212
531
    (connection, client_addr) = self.socket.accept()
532

    
533
    self._CollectChildren(False)
534

    
535
    pid = os.fork()
536
    if pid == 0:
537
      # Child process
538
      try:
539
        # The client shouldn't keep the listening socket open. If the parent
540
        # process is restarted, it would fail when there's already something
541
        # listening (in this case its own child from a previous run) on the
542
        # same port.
543
        try:
544
          self.socket.close()
545
        except socket.error:
546
          pass
547
        self.socket = None
548

    
549
        # In case the handler code uses temporary files
550
        utils.ResetTempfileModule()
551

    
552
        self.request_executor(self, self.handler, connection, client_addr)
553
      except Exception: # pylint: disable=W0703
554
        logging.exception("Error while handling request from %s:%s",
555
                          client_addr[0], client_addr[1])
556
        os._exit(1)
557
      os._exit(0)
558
    else:
559
      self._children.append(pid)
560

    
561

    
562
class HttpServerHandler(object):
563
  """Base class for handling HTTP server requests.
564

565
  Users of this class must subclass it and override the L{HandleRequest}
566
  function.
567

568
  """
569
  def PreHandleRequest(self, req):
570
    """Called before handling a request.
571

572
    Can be overridden by a subclass.
573

574
    """
575

    
576
  def HandleRequest(self, req):
577
    """Handles a request.
578

579
    Must be overridden by subclass.
580

581
    """
582
    raise NotImplementedError()
583

    
584
  @staticmethod
585
  def FormatErrorMessage(values):
586
    """Formats the body of an error message.
587

588
    @type values: dict
589
    @param values: dictionary with keys C{code}, C{message} and C{explain}.
590
    @rtype: tuple; (string, string)
591
    @return: Content-type and response body
592

593
    """
594
    return (DEFAULT_ERROR_CONTENT_TYPE, DEFAULT_ERROR_MESSAGE % values)