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 self._HandleRequest()
281
282
283 force_close = False
284 except http.HttpException, err:
285 self._SetErrorStatus(err)
286 finally:
287
288 self._SendResponse()
289 finally:
290 http.ShutdownConnection(sock, self.CLOSE_TIMEOUT, self.WRITE_TIMEOUT,
291 request_msg_reader, force_close)
292
293 self.sock.close()
294 self.sock = None
295 finally:
296 logging.debug("Disconnected %s:%s", client_addr[0], client_addr[1])
297
299 """Reads a request sent by client.
300
301 """
302 try:
303 request_msg_reader = \
304 _HttpClientToServerMessageReader(self.sock, self.request_msg,
305 self.READ_TIMEOUT)
306 except http.HttpSocketTimeout:
307 raise http.HttpError("Timeout while reading request")
308 except socket.error, err:
309 raise http.HttpError("Error reading request: %s" % err)
310
311 self.response_msg.start_line.version = self.request_msg.start_line.version
312
313 return request_msg_reader
314
316 """Calls the handler function for the current request.
317
318 """
319 handler_context = _HttpServerRequest(self.request_msg.start_line.method,
320 self.request_msg.start_line.path,
321 self.request_msg.headers,
322 self.request_msg.decoded_body)
323
324 logging.debug("Handling request %r", handler_context)
325
326 try:
327 try:
328
329 self.server.PreHandleRequest(handler_context)
330
331
332 result = self.server.HandleRequest(handler_context)
333 except (http.HttpException, KeyboardInterrupt, SystemExit):
334 raise
335 except Exception, err:
336 logging.exception("Caught exception")
337 raise http.HttpInternalServerError(message=str(err))
338 except:
339 logging.exception("Unknown exception")
340 raise http.HttpInternalServerError(message="Unknown error")
341
342
343 encoder = http.HttpJsonConverter()
344 self.response_msg.start_line.code = http.HTTP_OK
345 self.response_msg.body = encoder.Encode(result)
346 self.response_msg.headers = handler_context.resp_headers
347 self.response_msg.headers[http.HTTP_CONTENT_TYPE] = encoder.CONTENT_TYPE
348 finally:
349
350 handler_context.private = None
351
353 """Sends the response to the client.
354
355 """
356 if self.response_msg.start_line.code is None:
357 return
358
359 if not self.response_msg.headers:
360 self.response_msg.headers = {}
361
362 self.response_msg.headers.update({
363
364 http.HTTP_CONNECTION: "close",
365 http.HTTP_DATE: _DateTimeHeader(),
366 http.HTTP_SERVER: http.HTTP_GANETI_VERSION,
367 })
368
369
370 response_code = self.response_msg.start_line.code
371 if response_code in self.responses:
372 response_reason = self.responses[response_code][0]
373 else:
374 response_reason = ""
375 self.response_msg.start_line.reason = response_reason
376
377 logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1],
378 self.request_msg.start_line, response_code)
379
380 try:
381 _HttpServerToClientMessageWriter(self.sock, self.request_msg,
382 self.response_msg, self.WRITE_TIMEOUT)
383 except http.HttpSocketTimeout:
384 raise http.HttpError("Timeout while sending response")
385 except socket.error, err:
386 raise http.HttpError("Error sending response: %s" % err)
387
389 """Sets the response code and body from a HttpException.
390
391 @type err: HttpException
392 @param err: Exception instance
393
394 """
395 try:
396 (shortmsg, longmsg) = self.responses[err.code]
397 except KeyError:
398 shortmsg = longmsg = "Unknown"
399
400 if err.message:
401 message = err.message
402 else:
403 message = shortmsg
404
405 values = {
406 "code": err.code,
407 "message": cgi.escape(message),
408 "explain": longmsg,
409 }
410
411 self.response_msg.start_line.code = err.code
412
413 headers = {}
414 if err.headers:
415 headers.update(err.headers)
416 headers[http.HTTP_CONTENT_TYPE] = self.error_content_type
417 self.response_msg.headers = headers
418
419 self.response_msg.body = self._FormatErrorMessage(values)
420
431
432 -class HttpServer(http.HttpBase, asyncore.dispatcher):
433 """Generic HTTP server class
434
435 Users of this class must subclass it and override the HandleRequest function.
436
437 """
438 MAX_CHILDREN = 20
439
440 - def __init__(self, mainloop, local_address, port,
441 ssl_params=None, ssl_verify_peer=False,
442 request_executor_class=None):
443 """Initializes the HTTP server
444
445 @type mainloop: ganeti.daemon.Mainloop
446 @param mainloop: Mainloop used to poll for I/O events
447 @type local_address: string
448 @param local_address: Local IP address to bind to
449 @type port: int
450 @param port: TCP port to listen on
451 @type ssl_params: HttpSslParams
452 @param ssl_params: SSL key and certificate
453 @type ssl_verify_peer: bool
454 @param ssl_verify_peer: Whether to require client certificate
455 and compare it with our certificate
456 @type request_executor_class: class
457 @param request_executor_class: an class derived from the
458 HttpServerRequestExecutor class
459
460 """
461 http.HttpBase.__init__(self)
462 asyncore.dispatcher.__init__(self)
463
464 if request_executor_class is None:
465 self.request_executor = HttpServerRequestExecutor
466 else:
467 self.request_executor = request_executor_class
468
469 self.mainloop = mainloop
470 self.local_address = local_address
471 self.port = port
472
473 self.socket = self._CreateSocket(ssl_params, ssl_verify_peer)
474
475
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
484 self.socket.bind((self.local_address, self.port))
485 self.socket.listen(1024)
486
489
492
496
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
506 while len(self._children) > self.MAX_CHILDREN:
507 try:
508
509
510
511
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
527 """Called for each incoming connection
528
529 """
530
531 (connection, client_addr) = self.socket.accept()
532
533 self._CollectChildren(False)
534
535 pid = os.fork()
536 if pid == 0:
537
538 try:
539
540
541
542
543 try:
544 self.socket.close()
545 except socket.error:
546 pass
547 self.socket = None
548
549
550 utils.ResetTempfileModule()
551
552 self.request_executor(self, connection, client_addr)
553 except Exception:
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
562 """Called before handling a request.
563
564 Can be overridden by a subclass.
565
566 """
567
569 """Handles a request.
570
571 Must be overridden by subclass.
572
573 """
574 raise NotImplementedError()
575