1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
37
38 WEEKDAYNAME = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
39 MONTHNAME = [None,
40 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
41 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
42
43
44 DEFAULT_ERROR_CONTENT_TYPE = "text/html"
45 DEFAULT_ERROR_MESSAGE = """\
46 <html>
47 <head>
48 <title>Error response</title>
49 </head>
50 <body>
51 <h1>Error response</h1>
52 <p>Error code %(code)d.
53 <p>Message: %(message)s.
54 <p>Error code explanation: %(code)s = %(explain)s.
55 </body>
56 </html>
57 """
58
59
61 """Return the current date and time formatted for a message header.
62
63 The time MUST be in the GMT timezone.
64
65 """
66 if gmnow is None:
67 gmnow = time.gmtime()
68 (year, month, day, hh, mm, ss, wd, _, _) = gmnow
69 return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" %
70 (WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss))
71
72
74 """Data structure for HTTP request on server side.
75
76 """
77 - def __init__(self, method, path, headers, body):
78
79 self.request_method = method
80 self.request_path = path
81 self.request_headers = headers
82 self.request_body = body
83
84
85 self.resp_headers = {}
86
87
88
89 self.private = None
90
92 status = ["%s.%s" % (self.__class__.__module__, self.__class__.__name__),
93 self.request_method, self.request_path,
94 "headers=%r" % str(self.request_headers),
95 "body=%r" % (self.request_body, )]
96
97 return "<%s at %#x>" % (" ".join(status), id(self))
98
99
101 """Writes an HTTP response to client.
102
103 """
104 - def __init__(self, sock, request_msg, response_msg, write_timeout):
105 """Writes the response to the client.
106
107 @type sock: socket
108 @param sock: Target socket
109 @type request_msg: http.HttpMessage
110 @param request_msg: Request message, required to determine whether
111 response may have a message body
112 @type response_msg: http.HttpMessage
113 @param response_msg: Response message
114 @type write_timeout: float
115 @param write_timeout: Write timeout for socket
116
117 """
118 self._request_msg = request_msg
119 self._response_msg = response_msg
120 http.HttpMessageWriter.__init__(self, sock, response_msg, write_timeout)
121
122 - def HasMessageBody(self):
123 """Logic to detect whether response should contain a message body.
124
125 """
126 if self._request_msg.start_line:
127 request_method = self._request_msg.start_line.method
128 else:
129 request_method = None
130
131 response_code = self._response_msg.start_line.code
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 return (http.HttpMessageWriter.HasMessageBody(self) and
147 (request_method is not None and
148 request_method != http.HTTP_HEAD) and
149 response_code >= http.HTTP_OK and
150 response_code not in (http.HTTP_NO_CONTENT,
151 http.HTTP_NOT_MODIFIED))
152
153
155 """Reads an HTTP request sent by client.
156
157 """
158
159 START_LINE_LENGTH_MAX = 4096
160 HEADER_LENGTH_MAX = 4096
161
163 """Parses the start line sent by client.
164
165 Example: "GET /index.html HTTP/1.1"
166
167 @type start_line: string
168 @param start_line: Start line
169
170 """
171
172 assert start_line
173
174 logging.debug("HTTP request: %s", start_line)
175
176 words = start_line.split()
177
178 if len(words) == 3:
179 [method, path, version] = words
180 if version[:5] != 'HTTP/':
181 raise http.HttpBadRequest("Bad request version (%r)" % version)
182
183 try:
184 base_version_number = version.split("/", 1)[1]
185 version_number = base_version_number.split(".")
186
187
188
189
190
191
192
193 if len(version_number) != 2:
194 raise http.HttpBadRequest("Bad request version (%r)" % version)
195
196 version_number = (int(version_number[0]), int(version_number[1]))
197 except (ValueError, IndexError):
198 raise http.HttpBadRequest("Bad request version (%r)" % version)
199
200 if version_number >= (2, 0):
201 raise http.HttpVersionNotSupported("Invalid HTTP Version (%s)" %
202 base_version_number)
203
204 elif len(words) == 2:
205 version = http.HTTP_0_9
206 [method, path] = words
207 if method != http.HTTP_GET:
208 raise http.HttpBadRequest("Bad HTTP/0.9 request type (%r)" % method)
209
210 else:
211 raise http.HttpBadRequest("Bad request syntax (%r)" % start_line)
212
213 return http.HttpClientToServerStartLine(method, path, version)
214
215
217 """Implements server side of HTTP.
218
219 This class implements the server side of HTTP. It's based on code of
220 Python's BaseHTTPServer, from both version 2.4 and 3k. It does not
221 support non-ASCII character encodings. Keep-alive connections are
222 not supported.
223
224 """
225
226
227
228
229 default_request_version = http.HTTP_0_9
230
231
232 error_message_format = DEFAULT_ERROR_MESSAGE
233 error_content_type = DEFAULT_ERROR_CONTENT_TYPE
234
235 responses = BaseHTTPServer.BaseHTTPRequestHandler.responses
236
237
238 WRITE_TIMEOUT = 10
239 READ_TIMEOUT = 10
240 CLOSE_TIMEOUT = 1
241
242 - def __init__(self, server, sock, client_addr):
243 """Initializes this class.
244
245 """
246 self.server = server
247 self.sock = sock
248 self.client_addr = client_addr
249
250 self.request_msg = http.HttpMessage()
251 self.response_msg = http.HttpMessage()
252
253 self.response_msg.start_line = \
254 http.HttpServerToClientStartLine(version=self.default_request_version,
255 code=None, reason=None)
256
257
258 self.sock.settimeout(None)
259
260
261 self.sock.setblocking(0)
262
263 logging.debug("Connection from %s:%s", client_addr[0], client_addr[1])
264 try:
265 request_msg_reader = None
266 force_close = True
267 try:
268
269 if self.server.using_ssl:
270 self.sock.set_accept_state()
271 try:
272 http.Handshake(self.sock, self.WRITE_TIMEOUT)
273 except http.HttpSessionHandshakeUnexpectedEOF:
274
275 return
276
277 try:
278 try:
279 request_msg_reader = self._ReadRequest()
280
281
282
283
284 if (self.request_msg.start_line.version == http.HTTP_1_1 and
285 http.HTTP_HOST not in self.request_msg.headers):
286 raise http.HttpBadRequest(message="Missing Host header")
287
288 self._HandleRequest()
289
290
291 force_close = False
292 except http.HttpException, err:
293 self._SetErrorStatus(err)
294 finally:
295
296 self._SendResponse()
297 finally:
298 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
299 request_msg_reader, force_close)
300
301 self.sock.close()
302 self.sock = None
303 finally:
304 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
305
307 """Reads a request sent by client.
308
309 """
310 try:
311 request_msg_reader = \
312 _HttpClientToServerMessageReader(self.sock, self.request_msg,
313 self.READ_TIMEOUT)
314 except http.HttpSocketTimeout:
315 raise http.HttpError("Timeout while reading request")
316 except socket.error, err:
317 raise http.HttpError("Error reading request: %s" % err)
318
319 self.response_msg.start_line.version = self.request_msg.start_line.version
320
321 return request_msg_reader
322
324 """Calls the handler function for the current request.
325
326 """
327 handler_context = _HttpServerRequest(self.request_msg.start_line.method,
328 self.request_msg.start_line.path,
329 self.request_msg.headers,
330 self.request_msg.body)
331
332 logging.debug("Handling request %r", handler_context)
333
334 try:
335 try:
336
337 self.server.PreHandleRequest(handler_context)
338
339
340 result = self.server.HandleRequest(handler_context)
341 except (http.HttpException, KeyboardInterrupt, SystemExit):
342 raise
343 except Exception, err:
344 logging.exception("Caught exception")
345 raise http.HttpInternalServerError(message=str(err))
346 except:
347 logging.exception("Unknown exception")
348 raise http.HttpInternalServerError(message="Unknown error")
349
350 if not isinstance(result, basestring):
351 raise http.HttpError("Handler function didn't return string type")
352
353 self.response_msg.start_line.code = http.HTTP_OK
354 self.response_msg.headers = handler_context.resp_headers
355 self.response_msg.body = result
356 finally:
357
358 handler_context.private = None
359
361 """Sends the response to the client.
362
363 """
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
372 http.HTTP_CONNECTION: "close",
373 http.HTTP_DATE: _DateTimeHeader(),
374 http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
375 })
376
377
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
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 self.response_msg.start_line.code = err.code
420
421 headers = {}
422 if err.headers:
423 headers.update(err.headers)
424 headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
425 self.response_msg.headers = headers
426
427 self.response_msg.body = self._FormatErrorMessage(values)
428
439
440
441 -class HttpServer(http.HttpBase, asyncore.dispatcher):
442 """Generic HTTP server class
443
444 Users of this class must subclass it and override the HandleRequest function.
445
446 """
447 MAX_CHILDREN = 20
448
449 - def __init__(self, mainloop, local_address, port,
450 ssl_params=None, ssl_verify_peer=False,
451 request_executor_class=None):
452 """Initializes the HTTP server
453
454 @type mainloop: ganeti.daemon.Mainloop
455 @param mainloop: Mainloop used to poll for I/O events
456 @type local_address: string
457 @param local_address: Local IP address to bind to
458 @type port: int
459 @param port: TCP port to listen on
460 @type ssl_params: HttpSslParams
461 @param ssl_params: SSL key and certificate
462 @type ssl_verify_peer: bool
463 @param ssl_verify_peer: Whether to require client certificate
464 and compare it with our certificate
465 @type request_executor_class: class
466 @param request_executor_class: an class derived from the
467 HttpServerRequestExecutor class
468
469 """
470 http.HttpBase.__init__(self)
471 asyncore.dispatcher.__init__(self)
472
473 if request_executor_class is None:
474 self.request_executor = HttpServerRequestExecutor
475 else:
476 self.request_executor = request_executor_class
477
478 self.mainloop = mainloop
479 self.local_address = local_address
480 self.port = port
481
482 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer)
483
484
485 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
486
487 self._children = []
488 self.set_socket(self.socket)
489 self.accepting = True
490 mainloop.RegisterSignal(self)
491
493 self.socket.bind((self.local_address, self.port))
494 self.socket.listen(1024)
495
498
501
505
507 """Checks whether any child processes are done
508
509 @type quick: bool
510 @param quick: Whether to only use non-blocking functions
511
512 """
513 if not quick:
514
515 while len(self._children) > self.MAX_CHILDREN:
516 try:
517
518
519
520
521 pid, _ = os.waitpid(0, 0)
522 except os.error:
523 pid = None
524 if pid and pid in self._children:
525 self._children.remove(pid)
526
527 for child in self._children:
528 try:
529 pid, _ = os.waitpid(child, os.WNOHANG)
530 except os.error:
531 pid = None
532 if pid and pid in self._children:
533 self._children.remove(pid)
534
536 """Called for each incoming connection
537
538 """
539
540 (connection, client_addr) = self.socket.accept()
541
542 self._CollectChildren(False)
543
544 pid = os.fork()
545 if pid == 0:
546
547 try:
548
549
550
551
552 try:
553 self.socket.close()
554 except socket.error:
555 pass
556 self.socket = None
557
558
559 utils.ResetTempfileModule()
560
561 self.request_executor(self, connection, client_addr)
562 except Exception:
563 logging.exception("Error while handling request from %s:%s",
564 client_addr[0], client_addr[1])
565 os._exit(1)
566 os._exit(0)
567 else:
568 self._children.append(pid)
569
571 """Called before handling a request.
572
573 Can be overridden by a subclass.
574
575 """
576
578 """Handles a request.
579
580 Must be overridden by subclass.
581
582 """
583 raise NotImplementedError()
584