Revision 42242313 lib/http.py
b/lib/http.py | ||
---|---|---|
19 | 19 |
|
20 | 20 |
""" |
21 | 21 |
|
22 |
import socket |
|
23 | 22 |
import BaseHTTPServer |
23 |
import cgi |
|
24 |
import logging |
|
25 |
import mimetools |
|
24 | 26 |
import OpenSSL |
27 |
import os |
|
28 |
import select |
|
29 |
import socket |
|
30 |
import sys |
|
25 | 31 |
import time |
26 |
import logging
|
|
32 |
import signal
|
|
27 | 33 |
|
34 |
from ganeti import constants |
|
28 | 35 |
from ganeti import logger |
29 | 36 |
from ganeti import serializer |
30 | 37 |
|
31 | 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 |
<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 |
""" |
|
57 |
|
|
58 |
HTTP_OK = 200 |
|
59 |
HTTP_NO_CONTENT = 204 |
|
60 |
HTTP_NOT_MODIFIED = 304 |
|
61 |
|
|
62 |
HTTP_0_9 = "HTTP/0.9" |
|
63 |
HTTP_1_0 = "HTTP/1.0" |
|
64 |
HTTP_1_1 = "HTTP/1.1" |
|
65 |
|
|
66 |
HTTP_GET = "GET" |
|
67 |
HTTP_HEAD = "HEAD" |
|
68 |
|
|
69 |
|
|
70 |
class SocketClosed(socket.error): |
|
71 |
pass |
|
72 |
|
|
73 |
|
|
32 | 74 |
class HTTPException(Exception): |
33 | 75 |
code = None |
34 | 76 |
message = None |
35 | 77 |
|
36 | 78 |
def __init__(self, message=None): |
79 |
Exception.__init__(self) |
|
37 | 80 |
if message is not None: |
38 | 81 |
self.message = message |
39 | 82 |
|
... | ... | |
70 | 113 |
code = 503 |
71 | 114 |
|
72 | 115 |
|
116 |
class HTTPVersionNotSupported(HTTPException): |
|
117 |
code = 505 |
|
118 |
|
|
119 |
|
|
73 | 120 |
class ApacheLogfile: |
74 | 121 |
"""Utility class to write HTTP server log files. |
75 | 122 |
|
... | ... | |
77 | 124 |
http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#examples |
78 | 125 |
|
79 | 126 |
""" |
80 |
MONTHNAME = [None, |
|
81 |
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', |
|
82 |
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] |
|
83 |
|
|
84 | 127 |
def __init__(self, fd): |
85 | 128 |
"""Constructor for ApacheLogfile class. |
86 | 129 |
|
... | ... | |
125 | 168 |
|
126 | 169 |
""" |
127 | 170 |
(_, month, _, _, _, _, _, _, _) = tm = time.gmtime(seconds) |
128 |
format = "%d/" + self.MONTHNAME[month] + "/%Y:%H:%M:%S +0000"
|
|
171 |
format = "%d/" + MONTHNAME[month] + "/%Y:%H:%M:%S +0000" |
|
129 | 172 |
return time.strftime(format, tm) |
130 | 173 |
|
131 | 174 |
|
... | ... | |
275 | 318 |
logging.debug("Handled request: %s", format % args) |
276 | 319 |
if self.server.httplog: |
277 | 320 |
self.server.httplog.LogRequest(self, format, *args) |
321 |
|
|
322 |
|
|
323 |
class _HttpConnectionHandler(object): |
|
324 |
"""Implements server side of HTTP |
|
325 |
|
|
326 |
This class implements the server side of HTTP. It's based on code of Python's |
|
327 |
BaseHTTPServer, from both version 2.4 and 3k. It does not support non-ASCII |
|
328 |
character encodings. Keep-alive connections are not supported. |
|
329 |
|
|
330 |
""" |
|
331 |
# String for "Server" header |
|
332 |
server_version = "Ganeti %s" % constants.RELEASE_VERSION |
|
333 |
|
|
334 |
# The default request version. This only affects responses up until |
|
335 |
# the point where the request line is parsed, so it mainly decides what |
|
336 |
# the client gets back when sending a malformed request line. |
|
337 |
# Most web servers default to HTTP 0.9, i.e. don't send a status line. |
|
338 |
default_request_version = HTTP_0_9 |
|
339 |
|
|
340 |
# Error message settings |
|
341 |
error_message_format = DEFAULT_ERROR_MESSAGE |
|
342 |
error_content_type = DEFAULT_ERROR_CONTENT_TYPE |
|
343 |
|
|
344 |
responses = BaseHTTPServer.BaseHTTPRequestHandler.responses |
|
345 |
|
|
346 |
def __init__(self, server, conn, client_addr, fileio_class): |
|
347 |
"""Initializes this class. |
|
348 |
|
|
349 |
Part of the initialization is reading the request and eventual POST/PUT |
|
350 |
data sent by the client. |
|
351 |
|
|
352 |
""" |
|
353 |
self._server = server |
|
354 |
|
|
355 |
# We default rfile to buffered because otherwise it could be |
|
356 |
# really slow for large data (a getc() call per byte); we make |
|
357 |
# wfile unbuffered because (a) often after a write() we want to |
|
358 |
# read and we need to flush the line; (b) big writes to unbuffered |
|
359 |
# files are typically optimized by stdio even when big reads |
|
360 |
# aren't. |
|
361 |
self.rfile = fileio_class(conn, mode="rb", bufsize=-1) |
|
362 |
self.wfile = fileio_class(conn, mode="wb", bufsize=0) |
|
363 |
|
|
364 |
self.client_addr = client_addr |
|
365 |
|
|
366 |
self.request_headers = None |
|
367 |
self.request_method = None |
|
368 |
self.request_path = None |
|
369 |
self.request_requestline = None |
|
370 |
self.request_version = self.default_request_version |
|
371 |
|
|
372 |
self.response_body = None |
|
373 |
self.response_code = HTTP_OK |
|
374 |
self.response_content_type = None |
|
375 |
|
|
376 |
self.should_fork = False |
|
377 |
|
|
378 |
try: |
|
379 |
self._ReadRequest() |
|
380 |
self._ReadPostData() |
|
381 |
|
|
382 |
self.should_fork = self._server.ForkForRequest(self) |
|
383 |
except HTTPException, err: |
|
384 |
self._SetErrorStatus(err) |
|
385 |
|
|
386 |
def Close(self): |
|
387 |
if not self.wfile.closed: |
|
388 |
self.wfile.flush() |
|
389 |
self.wfile.close() |
|
390 |
self.rfile.close() |
|
391 |
|
|
392 |
def _DateTimeHeader(self): |
|
393 |
"""Return the current date and time formatted for a message header. |
|
394 |
|
|
395 |
""" |
|
396 |
(year, month, day, hh, mm, ss, wd, _, _) = time.gmtime() |
|
397 |
return ("%s, %02d %3s %4d %02d:%02d:%02d GMT" % |
|
398 |
(WEEKDAYNAME[wd], day, MONTHNAME[month], year, hh, mm, ss)) |
|
399 |
|
|
400 |
def _SetErrorStatus(self, err): |
|
401 |
"""Sets the response code and body from a HTTPException. |
|
402 |
|
|
403 |
@type err: HTTPException |
|
404 |
@param err: Exception instance |
|
405 |
|
|
406 |
""" |
|
407 |
try: |
|
408 |
(shortmsg, longmsg) = self.responses[err.code] |
|
409 |
except KeyError: |
|
410 |
shortmsg = longmsg = "Unknown" |
|
411 |
|
|
412 |
if err.message: |
|
413 |
message = err.message |
|
414 |
else: |
|
415 |
message = shortmsg |
|
416 |
|
|
417 |
values = { |
|
418 |
"code": err.code, |
|
419 |
"message": cgi.escape(message), |
|
420 |
"explain": longmsg, |
|
421 |
} |
|
422 |
|
|
423 |
self.response_code = err.code |
|
424 |
self.response_content_type = self.error_content_type |
|
425 |
self.response_body = self.error_message_format % values |
|
426 |
|
|
427 |
def HandleRequest(self): |
|
428 |
"""Handle the actual request. |
|
429 |
|
|
430 |
Calls the actual handler function and converts exceptions into HTTP errors. |
|
431 |
|
|
432 |
""" |
|
433 |
# Don't do anything if there's already been a problem |
|
434 |
if self.response_code != HTTP_OK: |
|
435 |
return |
|
436 |
|
|
437 |
assert self.request_method, "Status code %s requires a method" % HTTP_OK |
|
438 |
|
|
439 |
# Check whether client is still there |
|
440 |
self.rfile.read(0) |
|
441 |
|
|
442 |
try: |
|
443 |
try: |
|
444 |
result = self._server.HandleRequest(self) |
|
445 |
|
|
446 |
# TODO: Content-type |
|
447 |
encoder = HTTPJsonConverter() |
|
448 |
body = encoder.Encode(result) |
|
449 |
|
|
450 |
self.response_content_type = encoder.CONTENT_TYPE |
|
451 |
self.response_body = body |
|
452 |
except (HTTPException, KeyboardInterrupt, SystemExit): |
|
453 |
raise |
|
454 |
except Exception, err: |
|
455 |
logging.exception("Caught exception") |
|
456 |
raise HTTPInternalError(message=str(err)) |
|
457 |
except: |
|
458 |
logging.exception("Unknown exception") |
|
459 |
raise HTTPInternalError(message="Unknown error") |
|
460 |
|
|
461 |
except HTTPException, err: |
|
462 |
self._SetErrorStatus(err) |
|
463 |
|
|
464 |
def SendResponse(self): |
|
465 |
"""Sends response to the client. |
|
466 |
|
|
467 |
""" |
|
468 |
# Check whether client is still there |
|
469 |
self.rfile.read(0) |
|
470 |
|
|
471 |
logging.info("%s:%s %s %s", self.client_addr[0], self.client_addr[1], |
|
472 |
self.request_requestline, self.response_code) |
|
473 |
|
|
474 |
if self.response_code in self.responses: |
|
475 |
response_message = self.responses[self.response_code][0] |
|
476 |
else: |
|
477 |
response_message = "" |
|
478 |
|
|
479 |
if self.request_version != HTTP_0_9: |
|
480 |
self.wfile.write("%s %d %s\r\n" % |
|
481 |
(self.request_version, self.response_code, |
|
482 |
response_message)) |
|
483 |
self._SendHeader("Server", self.server_version) |
|
484 |
self._SendHeader("Date", self._DateTimeHeader()) |
|
485 |
self._SendHeader("Content-Type", self.response_content_type) |
|
486 |
self._SendHeader("Content-Length", str(len(self.response_body))) |
|
487 |
# We don't support keep-alive at this time |
|
488 |
self._SendHeader("Connection", "close") |
|
489 |
self.wfile.write("\r\n") |
|
490 |
|
|
491 |
if (self.request_method != HTTP_HEAD and |
|
492 |
self.response_code >= HTTP_OK and |
|
493 |
self.response_code not in (HTTP_NO_CONTENT, HTTP_NOT_MODIFIED)): |
|
494 |
self.wfile.write(self.response_body) |
|
495 |
|
|
496 |
def _SendHeader(self, name, value): |
|
497 |
if self.request_version != HTTP_0_9: |
|
498 |
self.wfile.write("%s: %s\r\n" % (name, value)) |
|
499 |
|
|
500 |
def _ReadRequest(self): |
|
501 |
"""Reads and parses request line |
|
502 |
|
|
503 |
""" |
|
504 |
raw_requestline = self.rfile.readline() |
|
505 |
|
|
506 |
requestline = raw_requestline |
|
507 |
if requestline[-2:] == '\r\n': |
|
508 |
requestline = requestline[:-2] |
|
509 |
elif requestline[-1:] == '\n': |
|
510 |
requestline = requestline[:-1] |
|
511 |
|
|
512 |
if not requestline: |
|
513 |
raise HTTPBadRequest("Empty request line") |
|
514 |
|
|
515 |
self.request_requestline = requestline |
|
516 |
|
|
517 |
logging.debug("HTTP request: %s", raw_requestline.rstrip("\r\n")) |
|
518 |
|
|
519 |
words = requestline.split() |
|
520 |
|
|
521 |
if len(words) == 3: |
|
522 |
[method, path, version] = words |
|
523 |
if version[:5] != 'HTTP/': |
|
524 |
raise HTTPBadRequest("Bad request version (%r)" % version) |
|
525 |
|
|
526 |
try: |
|
527 |
base_version_number = version.split('/', 1)[1] |
|
528 |
version_number = base_version_number.split(".") |
|
529 |
|
|
530 |
# RFC 2145 section 3.1 says there can be only one "." and |
|
531 |
# - major and minor numbers MUST be treated as |
|
532 |
# separate integers; |
|
533 |
# - HTTP/2.4 is a lower version than HTTP/2.13, which in |
|
534 |
# turn is lower than HTTP/12.3; |
|
535 |
# - Leading zeros MUST be ignored by recipients. |
|
536 |
if len(version_number) != 2: |
|
537 |
raise HTTPBadRequest("Bad request version (%r)" % version) |
|
538 |
|
|
539 |
version_number = int(version_number[0]), int(version_number[1]) |
|
540 |
except (ValueError, IndexError): |
|
541 |
raise HTTPBadRequest("Bad request version (%r)" % version) |
|
542 |
|
|
543 |
if version_number >= (2, 0): |
|
544 |
raise HTTPVersionNotSupported("Invalid HTTP Version (%s)" % |
|
545 |
base_version_number) |
|
546 |
|
|
547 |
elif len(words) == 2: |
|
548 |
version = HTTP_0_9 |
|
549 |
[method, path] = words |
|
550 |
if method != HTTP_GET: |
|
551 |
raise HTTPBadRequest("Bad HTTP/0.9 request type (%r)" % method) |
|
552 |
|
|
553 |
else: |
|
554 |
raise HTTPBadRequest("Bad request syntax (%r)" % requestline) |
|
555 |
|
|
556 |
# Examine the headers and look for a Connection directive |
|
557 |
headers = mimetools.Message(self.rfile, 0) |
|
558 |
|
|
559 |
self.request_method = method |
|
560 |
self.request_path = path |
|
561 |
self.request_version = version |
|
562 |
self.request_headers = headers |
|
563 |
|
|
564 |
def _ReadPostData(self): |
|
565 |
"""Reads POST/PUT data |
|
566 |
|
|
567 |
""" |
|
568 |
if not self.request_method or self.request_method.upper() not in ("POST", "PUT"): |
|
569 |
self.request_post_data = None |
|
570 |
return |
|
571 |
|
|
572 |
# TODO: Decide what to do when Content-Length header was not sent |
|
573 |
try: |
|
574 |
content_length = int(self.request_headers.get('Content-Length', 0)) |
|
575 |
except ValueError: |
|
576 |
raise HTTPBadRequest("No Content-Length header or invalid format") |
|
577 |
|
|
578 |
data = self.rfile.read(content_length) |
|
579 |
|
|
580 |
# TODO: Content-type, error handling |
|
581 |
self.request_post_data = HTTPJsonConverter().Decode(data) |
|
582 |
|
|
583 |
logging.debug("HTTP POST data: %s", self.request_post_data) |
|
584 |
|
|
585 |
|
|
586 |
class HttpServer(object): |
|
587 |
"""Generic HTTP server class |
|
588 |
|
|
589 |
Users of this class must subclass it and override the HandleRequest function. |
|
590 |
Optionally, the ForkForRequest function can be overriden. |
|
591 |
|
|
592 |
""" |
|
593 |
MAX_CHILDREN = 20 |
|
594 |
|
|
595 |
def __init__(self, mainloop, server_address): |
|
596 |
self.mainloop = mainloop |
|
597 |
self.server_address = server_address |
|
598 |
|
|
599 |
# TODO: SSL support |
|
600 |
self.ssl_cert = None |
|
601 |
self.ssl_key = self.ssl_cert |
|
602 |
|
|
603 |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
604 |
|
|
605 |
if self.ssl_cert and self.ssl_key: |
|
606 |
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) |
|
607 |
ctx.set_options(OpenSSL.SSL.OP_NO_SSLv2) |
|
608 |
|
|
609 |
ctx.use_certificate_file(self.ssl_cert) |
|
610 |
ctx.use_privatekey_file(self.ssl_key) |
|
611 |
|
|
612 |
self.socket = OpenSSL.SSL.Connection(ctx, sock) |
|
613 |
self._fileio_class = _SSLFileObject |
|
614 |
else: |
|
615 |
self.socket = sock |
|
616 |
self._fileio_class = socket._fileobject |
|
617 |
|
|
618 |
# Allow port to be reused |
|
619 |
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
|
620 |
|
|
621 |
self._children = [] |
|
622 |
|
|
623 |
mainloop.RegisterIO(self, self.socket.fileno(), select.POLLIN) |
|
624 |
mainloop.RegisterSignal(self) |
|
625 |
|
|
626 |
def Start(self): |
|
627 |
self.socket.bind(self.server_address) |
|
628 |
self.socket.listen(5) |
|
629 |
|
|
630 |
def Stop(self): |
|
631 |
self.socket.close() |
|
632 |
|
|
633 |
def OnIO(self, fd, condition): |
|
634 |
if condition & select.POLLIN: |
|
635 |
self._IncomingConnection() |
|
636 |
|
|
637 |
def OnSignal(self, signum): |
|
638 |
if signum == signal.SIGCHLD: |
|
639 |
self._CollectChildren(True) |
|
640 |
|
|
641 |
def _CollectChildren(self, quick): |
|
642 |
"""Checks whether any child processes are done |
|
643 |
|
|
644 |
@type quick: bool |
|
645 |
@param quick: Whether to only use non-blocking functions |
|
646 |
|
|
647 |
""" |
|
648 |
if not quick: |
|
649 |
# Don't wait for other processes if it should be a quick check |
|
650 |
while len(self._children) > self.MAX_CHILDREN: |
|
651 |
try: |
|
652 |
pid, status = os.waitpid(0, 0) |
|
653 |
except os.error: |
|
654 |
pid = None |
|
655 |
if pid and pid in self._children: |
|
656 |
self._children.remove(pid) |
|
657 |
|
|
658 |
for child in self._children: |
|
659 |
try: |
|
660 |
pid, status = os.waitpid(child, os.WNOHANG) |
|
661 |
except os.error: |
|
662 |
pid = None |
|
663 |
if pid and pid in self._children: |
|
664 |
self._children.remove(pid) |
|
665 |
|
|
666 |
def _IncomingConnection(self): |
|
667 |
connection, client_addr = self.socket.accept() |
|
668 |
logging.info("Connection from %s:%s", client_addr[0], client_addr[1]) |
|
669 |
try: |
|
670 |
handler = _HttpConnectionHandler(self, connection, client_addr, self._fileio_class) |
|
671 |
except (socket.error, SocketClosed): |
|
672 |
return |
|
673 |
|
|
674 |
def FinishRequest(): |
|
675 |
try: |
|
676 |
try: |
|
677 |
try: |
|
678 |
handler.HandleRequest() |
|
679 |
finally: |
|
680 |
# Try to send a response |
|
681 |
handler.SendResponse() |
|
682 |
handler.Close() |
|
683 |
except SocketClosed: |
|
684 |
pass |
|
685 |
finally: |
|
686 |
logging.info("Disconnected %s:%s", client_addr[0], client_addr[1]) |
|
687 |
|
|
688 |
# Check whether we should fork or not |
|
689 |
if not handler.should_fork: |
|
690 |
FinishRequest() |
|
691 |
return |
|
692 |
|
|
693 |
self._CollectChildren(False) |
|
694 |
|
|
695 |
pid = os.fork() |
|
696 |
if pid == 0: |
|
697 |
# Child process |
|
698 |
try: |
|
699 |
FinishRequest() |
|
700 |
except: |
|
701 |
logging.exception("Error while handling request from %s:%s", |
|
702 |
client_addr[0], client_addr[1]) |
|
703 |
os._exit(1) |
|
704 |
os._exit(0) |
|
705 |
else: |
|
706 |
self._children.append(pid) |
|
707 |
|
|
708 |
def HandleRequest(self, req): |
|
709 |
raise NotImplementedError() |
|
710 |
|
|
711 |
def ForkForRequest(self, req): |
|
712 |
return True |
|
713 |
|
|
714 |
|
|
715 |
class _SSLFileObject(object): |
|
716 |
"""Wrapper around socket._fileobject |
|
717 |
|
|
718 |
This wrapper is required to handle OpenSSL exceptions. |
|
719 |
|
|
720 |
""" |
|
721 |
def _RequireOpenSocket(fn): |
|
722 |
def wrapper(self, *args, **kwargs): |
|
723 |
if self.closed: |
|
724 |
raise SocketClosed("Socket is closed") |
|
725 |
return fn(self, *args, **kwargs) |
|
726 |
return wrapper |
|
727 |
|
|
728 |
def __init__(self, sock, mode='rb', bufsize=-1): |
|
729 |
self._base = socket._fileobject(sock, mode=mode, bufsize=bufsize) |
|
730 |
|
|
731 |
def _ConnectionLost(self): |
|
732 |
self._base = None |
|
733 |
|
|
734 |
def _getclosed(self): |
|
735 |
return self._base is None or self._base.closed |
|
736 |
closed = property(_getclosed, doc="True if the file is closed") |
|
737 |
|
|
738 |
@_RequireOpenSocket |
|
739 |
def close(self): |
|
740 |
return self._base.close() |
|
741 |
|
|
742 |
@_RequireOpenSocket |
|
743 |
def flush(self): |
|
744 |
return self._base.flush() |
|
745 |
|
|
746 |
@_RequireOpenSocket |
|
747 |
def fileno(self): |
|
748 |
return self._base.fileno() |
|
749 |
|
|
750 |
@_RequireOpenSocket |
|
751 |
def read(self, size=-1): |
|
752 |
return self._ReadWrapper(self._base.read, size=size) |
|
753 |
|
|
754 |
@_RequireOpenSocket |
|
755 |
def readline(self, size=-1): |
|
756 |
return self._ReadWrapper(self._base.readline, size=size) |
|
757 |
|
|
758 |
def _ReadWrapper(self, fn, *args, **kwargs): |
|
759 |
while True: |
|
760 |
try: |
|
761 |
return fn(*args, **kwargs) |
|
762 |
|
|
763 |
except OpenSSL.SSL.ZeroReturnError, err: |
|
764 |
self._ConnectionLost() |
|
765 |
return "" |
|
766 |
|
|
767 |
except OpenSSL.SSL.WantReadError: |
|
768 |
continue |
|
769 |
|
|
770 |
#except OpenSSL.SSL.WantWriteError: |
|
771 |
# TODO |
|
772 |
|
|
773 |
except OpenSSL.SSL.SysCallError, (retval, desc): |
|
774 |
if ((retval == -1 and desc == "Unexpected EOF") |
|
775 |
or retval > 0): |
|
776 |
self._ConnectionLost() |
|
777 |
return "" |
|
778 |
|
|
779 |
logging.exception("Error in OpenSSL") |
|
780 |
self._ConnectionLost() |
|
781 |
raise socket.error(err.args) |
|
782 |
|
|
783 |
except OpenSSL.SSL.Error, err: |
|
784 |
self._ConnectionLost() |
|
785 |
raise socket.error(err.args) |
|
786 |
|
|
787 |
@_RequireOpenSocket |
|
788 |
def write(self, data): |
|
789 |
return self._WriteWrapper(self._base.write, data) |
|
790 |
|
|
791 |
def _WriteWrapper(self, fn, *args, **kwargs): |
|
792 |
while True: |
|
793 |
try: |
|
794 |
return fn(*args, **kwargs) |
|
795 |
except OpenSSL.SSL.ZeroReturnError, err: |
|
796 |
self._ConnectionLost() |
|
797 |
return 0 |
|
798 |
|
|
799 |
except OpenSSL.SSL.WantWriteError: |
|
800 |
continue |
|
801 |
|
|
802 |
#except OpenSSL.SSL.WantReadError: |
|
803 |
# TODO |
|
804 |
|
|
805 |
except OpenSSL.SSL.SysCallError, err: |
|
806 |
if err.args[0] == -1 and data == "": |
|
807 |
# errors when writing empty strings are expected |
|
808 |
# and can be ignored |
|
809 |
return 0 |
|
810 |
|
|
811 |
self._ConnectionLost() |
|
812 |
raise socket.error(err.args) |
|
813 |
|
|
814 |
except OpenSSL.SSL.Error, err: |
|
815 |
self._ConnectionLost() |
|
816 |
raise socket.error(err.args) |
Also available in: Unified diff