Revision d859a2cf

b/lib/http/server.py
34 34
from ganeti import http
35 35
from ganeti import utils
36 36
from ganeti import netutils
37
from ganeti import compat
37 38

  
38 39

  
39 40
WEEKDAYNAME = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
......
214 215
    return http.HttpClientToServerStartLine(method, path, version)
215 216

  
216 217

  
217
def HandleServerRequest(handler, req_msg):
218
def _HandleServerRequestInner(handler, req_msg):
218 219
  """Calls the handler function for the current request.
219 220

  
220 221
  """
......
250 251
    handler_context.private = None
251 252

  
252 253

  
254
class HttpResponder(object):
255
  # The default request version.  This only affects responses up until
256
  # the point where the request line is parsed, so it mainly decides what
257
  # the client gets back when sending a malformed request line.
258
  # Most web servers default to HTTP 0.9, i.e. don't send a status line.
259
  default_request_version = http.HTTP_0_9
260

  
261
  responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
262

  
263
  def __init__(self, handler):
264
    """Initializes this class.
265

  
266
    """
267
    self._handler = handler
268

  
269
  def __call__(self, fn):
270
    """Handles a request.
271

  
272
    @type fn: callable
273
    @param fn: Callback for retrieving HTTP request, must return a tuple
274
      containing request message (L{http.HttpMessage}) and C{None} or the
275
      message reader (L{_HttpClientToServerMessageReader})
276

  
277
    """
278
    response_msg = http.HttpMessage()
279
    response_msg.start_line = \
280
      http.HttpServerToClientStartLine(version=self.default_request_version,
281
                                       code=None, reason=None)
282

  
283
    force_close = True
284

  
285
    try:
286
      (request_msg, req_msg_reader) = fn()
287

  
288
      response_msg.start_line.version = request_msg.start_line.version
289

  
290
      # RFC2616, 14.23: All Internet-based HTTP/1.1 servers MUST respond
291
      # with a 400 (Bad Request) status code to any HTTP/1.1 request
292
      # message which lacks a Host header field.
293
      if (request_msg.start_line.version == http.HTTP_1_1 and
294
          not (request_msg.headers and
295
               http.HTTP_HOST in request_msg.headers)):
296
        raise http.HttpBadRequest(message="Missing Host header")
297

  
298
      (response_msg.start_line.code, response_msg.headers,
299
       response_msg.body) = \
300
        _HandleServerRequestInner(self._handler, request_msg)
301
    except http.HttpException, err:
302
      self._SetError(self.responses, self._handler, response_msg, err)
303
    else:
304
      # Only wait for client to close if we didn't have any exception.
305
      force_close = False
306

  
307
    return (request_msg, req_msg_reader, force_close,
308
            self._Finalize(self.responses, response_msg))
309

  
310
  @staticmethod
311
  def _SetError(responses, handler, response_msg, err):
312
    """Sets the response code and body from a HttpException.
313

  
314
    @type err: HttpException
315
    @param err: Exception instance
316

  
317
    """
318
    try:
319
      (shortmsg, longmsg) = responses[err.code]
320
    except KeyError:
321
      shortmsg = longmsg = "Unknown"
322

  
323
    if err.message:
324
      message = err.message
325
    else:
326
      message = shortmsg
327

  
328
    values = {
329
      "code": err.code,
330
      "message": cgi.escape(message),
331
      "explain": longmsg,
332
      }
333

  
334
    (content_type, body) = handler.FormatErrorMessage(values)
335

  
336
    headers = {
337
      http.HTTP_CONTENT_TYPE: content_type,
338
      }
339

  
340
    if err.headers:
341
      headers.update(err.headers)
342

  
343
    response_msg.start_line.code = err.code
344
    response_msg.headers = headers
345
    response_msg.body = body
346

  
347
  @staticmethod
348
  def _Finalize(responses, msg):
349
    assert msg.start_line.reason is None
350

  
351
    if not msg.headers:
352
      msg.headers = {}
353

  
354
    msg.headers.update({
355
      # TODO: Keep-alive is not supported
356
      http.HTTP_CONNECTION: "close",
357
      http.HTTP_DATE: _DateTimeHeader(),
358
      http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
359
      })
360

  
361
    # Get response reason based on code
362
    try:
363
      code_desc = responses[msg.start_line.code]
364
    except KeyError:
365
      reason = ""
366
    else:
367
      (reason, _) = code_desc
368

  
369
    msg.start_line.reason = reason
370

  
371
    return msg
372

  
373

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

  
......
259 380
  not supported.
260 381

  
261 382
  """
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 383
  # Timeouts in seconds for socket layer
271 384
  WRITE_TIMEOUT = 10
272 385
  READ_TIMEOUT = 10
......
276 389
    """Initializes this class.
277 390

  
278 391
    """
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)
392
    responder = HttpResponder(handler)
290 393

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

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

  
400
    request_msg_reader = None
401
    force_close = True
296 402

  
297 403
    logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
298 404
    try:
299
      request_msg_reader = None
300
      force_close = True
405
      # Block for closing connection
301 406
      try:
302 407
        # Do the secret SSL handshake
303
        if self.server.using_ssl:
304
          self.sock.set_accept_state()
408
        if server.using_ssl:
409
          sock.set_accept_state()
305 410
          try:
306
            http.Handshake(self.sock, self.WRITE_TIMEOUT)
411
            http.Handshake(sock, self.WRITE_TIMEOUT)
307 412
          except http.HttpSessionHandshakeUnexpectedEOF:
308 413
            # Ignore rest
309 414
            return
310 415

  
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()
416
        (request_msg, request_msg_reader, force_close, response_msg) = \
417
          responder(compat.partial(self._ReadRequest, sock, self.READ_TIMEOUT))
418
        if response_msg:
419
          # HttpMessage.start_line can be of different types
420
          # pylint: disable=E1103
421
          logging.info("%s:%s %s %s", client_addr[0], client_addr[1],
422
                       request_msg.start_line, response_msg.start_line.code)
423
          self._SendResponse(sock, request_msg, response_msg,
424
                             self.WRITE_TIMEOUT)
333 425
      finally:
334 426
        http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
335 427
                                request_msg_reader, force_close)
336 428

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

  
342
  def _ReadRequest(self):
433
  @staticmethod
434
  def _ReadRequest(sock, timeout):
343 435
    """Reads a request sent by client.
344 436

  
345 437
    """
438
    msg = http.HttpMessage()
439

  
346 440
    try:
347
      request_msg_reader = \
348
        _HttpClientToServerMessageReader(self.sock, self.request_msg,
349
                                         self.READ_TIMEOUT)
441
      reader = _HttpClientToServerMessageReader(sock, msg, timeout)
350 442
    except http.HttpSocketTimeout:
351 443
      raise http.HttpError("Timeout while reading request")
352 444
    except socket.error, err:
353 445
      raise http.HttpError("Error reading request: %s" % err)
354 446

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

  
357
    return request_msg_reader
447
    return (msg, reader)
358 448

  
359
  def _SendResponse(self):
449
  @staticmethod
450
  def _SendResponse(sock, req_msg, msg, timeout):
360 451
    """Sends the response to the client.
361 452

  
362 453
    """
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 454
    try:
389
      _HttpServerToClientMessageWriter(self.sock, self.request_msg,
390
                                       self.response_msg, self.WRITE_TIMEOUT)
455
      _HttpServerToClientMessageWriter(sock, req_msg, msg, timeout)
391 456
    except http.HttpSocketTimeout:
392 457
      raise http.HttpError("Timeout while sending response")
393 458
    except socket.error, err:
394 459
      raise http.HttpError("Error sending response: %s" % err)
395 460

  
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 461

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

Also available in: Unified diff