Statistics
| Branch: | Tag: | Revision:

root / lib / http / server.py @ c81f452f

History | View | Annotate | Download (17.6 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
  # Error message settings
269
  error_message_format = DEFAULT_ERROR_MESSAGE
270
  error_content_type = DEFAULT_ERROR_CONTENT_TYPE
271

    
272
  responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
273

    
274
  # Timeouts in seconds for socket layer
275
  WRITE_TIMEOUT = 10
276
  READ_TIMEOUT = 10
277
  CLOSE_TIMEOUT = 1
278

    
279
  def __init__(self, server, sock, client_addr):
280
    """Initializes this class.
281

282
    """
283
    self.server = server
284
    self.sock = sock
285
    self.client_addr = client_addr
286

    
287
    self.request_msg = http.HttpMessage()
288
    self.response_msg = http.HttpMessage()
289

    
290
    self.response_msg.start_line = \
291
      http.HttpServerToClientStartLine(version=self.default_request_version,
292
                                       code=None, reason=None)
293

    
294
    # Disable Python's timeout
295
    self.sock.settimeout(None)
296

    
297
    # Operate in non-blocking mode
298
    self.sock.setblocking(0)
299

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

    
314
        try:
315
          try:
316
            request_msg_reader = self._ReadRequest()
317

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

    
325
            (self.response_msg.start_line.code, self.response_msg.headers,
326
             self.response_msg.body) = \
327
              HandleServerRequest(self.server, self.request_msg)
328

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

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

    
345
  def _ReadRequest(self):
346
    """Reads a request sent by client.
347

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

    
358
    self.response_msg.start_line.version = self.request_msg.start_line.version
359

    
360
    return request_msg_reader
361

    
362
  def _SendResponse(self):
363
    """Sends the response to the client.
364

365
    """
366
    if self.response_msg.start_line.code is None:
367
      return
368

    
369
    if not self.response_msg.headers:
370
      self.response_msg.headers = {}
371

    
372
    self.response_msg.headers.update({
373
      # TODO: Keep-alive is not supported
374
      http.HTTP_CONNECTION: "close",
375
      http.HTTP_DATE: _DateTimeHeader(),
376
      http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
377
      })
378

    
379
    # Get response reason based on code
380
    response_code = self.response_msg.start_line.code
381
    if response_code in self.responses:
382
      response_reason = self.responses[response_code][0]
383
    else:
384
      response_reason = ""
385
    self.response_msg.start_line.reason = response_reason
386

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

    
390
    try:
391
      _HttpServerToClientMessageWriter(self.sock, self.request_msg,
392
                                       self.response_msg, self.WRITE_TIMEOUT)
393
    except http.HttpSocketTimeout:
394
      raise http.HttpError("Timeout while sending response")
395
    except socket.error, err:
396
      raise http.HttpError("Error sending response: %s" % err)
397

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

401
    @type err: HttpException
402
    @param err: Exception instance
403

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

    
410
    if err.message:
411
      message = err.message
412
    else:
413
      message = shortmsg
414

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

    
421
    self.response_msg.start_line.code = err.code
422

    
423
    headers = {}
424
    if err.headers:
425
      headers.update(err.headers)
426
    headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
427
    self.response_msg.headers = headers
428

    
429
    self.response_msg.body = self._FormatErrorMessage(values)
430

    
431
  def _FormatErrorMessage(self, values):
432
    """Formats the body of an error message.
433

434
    @type values: dict
435
    @param values: dictionary with keys code, message and explain.
436
    @rtype: string
437
    @return: the body of the message
438

439
    """
440
    return self.error_message_format % values
441

    
442

    
443
class HttpServer(http.HttpBase, asyncore.dispatcher):
444
  """Generic HTTP server class
445

446
  Users of this class must subclass it and override the HandleRequest function.
447

448
  """
449
  MAX_CHILDREN = 20
450

    
451
  def __init__(self, mainloop, local_address, port,
452
               ssl_params=None, ssl_verify_peer=False,
453
               request_executor_class=None):
454
    """Initializes the HTTP server
455

456
    @type mainloop: ganeti.daemon.Mainloop
457
    @param mainloop: Mainloop used to poll for I/O events
458
    @type local_address: string
459
    @param local_address: Local IP address to bind to
460
    @type port: int
461
    @param port: TCP port to listen on
462
    @type ssl_params: HttpSslParams
463
    @param ssl_params: SSL key and certificate
464
    @type ssl_verify_peer: bool
465
    @param ssl_verify_peer: Whether to require client certificate
466
        and compare it with our certificate
467
    @type request_executor_class: class
468
    @param request_executor_class: an class derived from the
469
        HttpServerRequestExecutor class
470

471
    """
472
    http.HttpBase.__init__(self)
473
    asyncore.dispatcher.__init__(self)
474

    
475
    if request_executor_class is None:
476
      self.request_executor = HttpServerRequestExecutor
477
    else:
478
      self.request_executor = request_executor_class
479

    
480
    self.mainloop = mainloop
481
    self.local_address = local_address
482
    self.port = port
483
    family = netutils.IPAddress.GetAddressFamily(local_address)
484
    self.socket = self._CreateSocket(ssl_params, ssl_verify_peer, family)
485

    
486
    # Allow port to be reused
487
    self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
488

    
489
    self._children = []
490
    self.set_socket(self.socket)
491
    self.accepting = True
492
    mainloop.RegisterSignal(self)
493

    
494
  def Start(self):
495
    self.socket.bind((self.local_address, self.port))
496
    self.socket.listen(1024)
497

    
498
  def Stop(self):
499
    self.socket.close()
500

    
501
  def handle_accept(self):
502
    self._IncomingConnection()
503

    
504
  def OnSignal(self, signum):
505
    if signum == signal.SIGCHLD:
506
      self._CollectChildren(True)
507

    
508
  def _CollectChildren(self, quick):
509
    """Checks whether any child processes are done
510

511
    @type quick: bool
512
    @param quick: Whether to only use non-blocking functions
513

514
    """
515
    if not quick:
516
      # Don't wait for other processes if it should be a quick check
517
      while len(self._children) > self.MAX_CHILDREN:
518
        try:
519
          # Waiting without a timeout brings us into a potential DoS situation.
520
          # As soon as too many children run, we'll not respond to new
521
          # requests. The real solution would be to add a timeout for children
522
          # and killing them after some time.
523
          pid, _ = os.waitpid(0, 0)
524
        except os.error:
525
          pid = None
526
        if pid and pid in self._children:
527
          self._children.remove(pid)
528

    
529
    for child in self._children:
530
      try:
531
        pid, _ = os.waitpid(child, os.WNOHANG)
532
      except os.error:
533
        pid = None
534
      if pid and pid in self._children:
535
        self._children.remove(pid)
536

    
537
  def _IncomingConnection(self):
538
    """Called for each incoming connection
539

540
    """
541
    # pylint: disable=W0212
542
    (connection, client_addr) = self.socket.accept()
543

    
544
    self._CollectChildren(False)
545

    
546
    pid = os.fork()
547
    if pid == 0:
548
      # Child process
549
      try:
550
        # The client shouldn't keep the listening socket open. If the parent
551
        # process is restarted, it would fail when there's already something
552
        # listening (in this case its own child from a previous run) on the
553
        # same port.
554
        try:
555
          self.socket.close()
556
        except socket.error:
557
          pass
558
        self.socket = None
559

    
560
        # In case the handler code uses temporary files
561
        utils.ResetTempfileModule()
562

    
563
        self.request_executor(self, connection, client_addr)
564
      except Exception: # pylint: disable=W0703
565
        logging.exception("Error while handling request from %s:%s",
566
                          client_addr[0], client_addr[1])
567
        os._exit(1)
568
      os._exit(0)
569
    else:
570
      self._children.append(pid)
571

    
572
  def PreHandleRequest(self, req):
573
    """Called before handling a request.
574

575
    Can be overridden by a subclass.
576

577
    """
578

    
579
  def HandleRequest(self, req):
580
    """Handles a request.
581

582
    Must be overridden by subclass.
583

584
    """
585
    raise NotImplementedError()