Statistics
| Branch: | Tag: | Revision:

root / lib / http.py @ 686d7433

History | View | Annotate | Download (21.7 kB)

1
#
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11
# General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16
# 02110-1301, USA.
17

    
18
"""HTTP server module.
19

20
"""
21

    
22
import BaseHTTPServer
23
import cgi
24
import logging
25
import mimetools
26
import OpenSSL
27
import os
28
import select
29
import socket
30
import sys
31
import time
32
import signal
33

    
34
from ganeti import constants
35
from ganeti import logger
36
from ganeti import serializer
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
<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

    
74
class HTTPException(Exception):
75
  code = None
76
  message = None
77

    
78
  def __init__(self, message=None):
79
    Exception.__init__(self)
80
    if message is not None:
81
      self.message = message
82

    
83

    
84
class HTTPBadRequest(HTTPException):
85
  code = 400
86

    
87

    
88
class HTTPForbidden(HTTPException):
89
  code = 403
90

    
91

    
92
class HTTPNotFound(HTTPException):
93
  code = 404
94

    
95

    
96
class HTTPGone(HTTPException):
97
  code = 410
98

    
99

    
100
class HTTPLengthRequired(HTTPException):
101
  code = 411
102

    
103

    
104
class HTTPInternalError(HTTPException):
105
  code = 500
106

    
107

    
108
class HTTPNotImplemented(HTTPException):
109
  code = 501
110

    
111

    
112
class HTTPServiceUnavailable(HTTPException):
113
  code = 503
114

    
115

    
116
class HTTPVersionNotSupported(HTTPException):
117
  code = 505
118

    
119

    
120
class ApacheLogfile:
121
  """Utility class to write HTTP server log files.
122

123
  The written format is the "Common Log Format" as defined by Apache:
124
  http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#examples
125

126
  """
127
  def __init__(self, fd):
128
    """Constructor for ApacheLogfile class.
129

130
    Args:
131
    - fd: Open file object
132

133
    """
134
    self._fd = fd
135

    
136
  def LogRequest(self, request, format, *args):
137
    self._fd.write("%s %s %s [%s] %s\n" % (
138
      # Remote host address
139
      request.address_string(),
140

    
141
      # RFC1413 identity (identd)
142
      "-",
143

    
144
      # Remote user
145
      "-",
146

    
147
      # Request time
148
      self._FormatCurrentTime(),
149

    
150
      # Message
151
      format % args,
152
      ))
153
    self._fd.flush()
154

    
155
  def _FormatCurrentTime(self):
156
    """Formats current time in Common Log Format.
157

158
    """
159
    return self._FormatLogTime(time.time())
160

    
161
  def _FormatLogTime(self, seconds):
162
    """Formats time for Common Log Format.
163

164
    All timestamps are logged in the UTC timezone.
165

166
    Args:
167
    - seconds: Time in seconds since the epoch
168

169
    """
170
    (_, month, _, _, _, _, _, _, _) = tm = time.gmtime(seconds)
171
    format = "%d/" + MONTHNAME[month] + "/%Y:%H:%M:%S +0000"
172
    return time.strftime(format, tm)
173

    
174

    
175
class HTTPServer(BaseHTTPServer.HTTPServer, object):
176
  """Class to provide an HTTP/HTTPS server.
177

178
  """
179
  allow_reuse_address = True
180

    
181
  def __init__(self, server_address, HandlerClass, httplog=None,
182
               enable_ssl=False, ssl_key=None, ssl_cert=None):
183
    """Server constructor.
184

185
    Args:
186
      server_address: a touple containing:
187
        ip: a string with IP address, localhost if empty string
188
        port: port number, integer
189
      HandlerClass: HTTPRequestHandler object
190
      httplog: Access log object
191
      enable_ssl: Whether to enable SSL
192
      ssl_key: SSL key file
193
      ssl_cert: SSL certificate key
194

195
    """
196
    BaseHTTPServer.HTTPServer.__init__(self, server_address, HandlerClass)
197

    
198
    self.httplog = httplog
199

    
200
    if enable_ssl:
201
      # Set up SSL
202
      context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
203
      context.use_privatekey_file(ssl_key)
204
      context.use_certificate_file(ssl_cert)
205
      self.socket = OpenSSL.SSL.Connection(context,
206
                                           socket.socket(self.address_family,
207
                                           self.socket_type))
208
    else:
209
      self.socket = socket.socket(self.address_family, self.socket_type)
210

    
211
    self.server_bind()
212
    self.server_activate()
213

    
214

    
215
class HTTPJsonConverter:
216
  CONTENT_TYPE = "application/json"
217

    
218
  def Encode(self, data):
219
    return serializer.DumpJson(data)
220

    
221
  def Decode(self, data):
222
    return serializer.LoadJson(data)
223

    
224

    
225
class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
226
  """Request handler class.
227

228
  """
229
  def setup(self):
230
    """Setup secure read and write file objects.
231

232
    """
233
    self.connection = self.request
234
    self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
235
    self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
236

    
237
  def handle_one_request(self):
238
    """Parses a request and calls the handler function.
239

240
    """
241
    self.raw_requestline = None
242
    try:
243
      self.raw_requestline = self.rfile.readline()
244
    except OpenSSL.SSL.Error, ex:
245
      logger.Error("Error in SSL: %s" % str(ex))
246
    if not self.raw_requestline:
247
      self.close_connection = 1
248
      return
249
    if not self.parse_request(): # An error code has been sent, just exit
250
      return
251
    logging.debug("HTTP request: %s", self.raw_requestline.rstrip("\r\n"))
252

    
253
    try:
254
      self._ReadPostData()
255

    
256
      result = self.HandleRequest()
257

    
258
      # TODO: Content-type
259
      encoder = HTTPJsonConverter()
260
      encoded_result = encoder.Encode(result)
261

    
262
      self.send_response(200)
263
      self.send_header("Content-Type", encoder.CONTENT_TYPE)
264
      self.send_header("Content-Length", str(len(encoded_result)))
265
      self.end_headers()
266

    
267
      self.wfile.write(encoded_result)
268

    
269
    except HTTPException, err:
270
      self.send_error(err.code, message=err.message)
271

    
272
    except Exception, err:
273
      self.send_error(HTTPInternalError.code, message=str(err))
274

    
275
    except:
276
      self.send_error(HTTPInternalError.code, message="Unknown error")
277

    
278
  def _ReadPostData(self):
279
    if self.command.upper() not in ("POST", "PUT"):
280
      self.post_data = None
281
      return
282

    
283
    # TODO: Decide what to do when Content-Length header was not sent
284
    try:
285
      content_length = int(self.headers.get('Content-Length', 0))
286
    except ValueError:
287
      raise HTTPBadRequest("No Content-Length header or invalid format")
288

    
289
    try:
290
      data = self.rfile.read(content_length)
291
    except socket.error, err:
292
      logger.Error("Socket error while reading: %s" % str(err))
293
      return
294

    
295
    # TODO: Content-type, error handling
296
    self.post_data = HTTPJsonConverter().Decode(data)
297

    
298
    logging.debug("HTTP POST data: %s", self.post_data)
299

    
300
  def HandleRequest(self):
301
    """Handles a request.
302

303
    """
304
    raise NotImplementedError()
305

    
306
  def log_message(self, format, *args):
307
    """Log an arbitrary message.
308

309
    This is used by all other logging functions.
310

311
    The first argument, FORMAT, is a format string for the
312
    message to be logged.  If the format string contains
313
    any % escapes requiring parameters, they should be
314
    specified as subsequent arguments (it's just like
315
    printf!).
316

317
    """
318
    logging.debug("Handled request: %s", format % args)
319
    if self.server.httplog:
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)