summaryrefslogtreecommitdiffstats
path: root/Lib/httplib.py
diff options
context:
space:
mode:
authorGreg Stein <gstein@lyra.org>2000-07-18 09:09:48 (GMT)
committerGreg Stein <gstein@lyra.org>2000-07-18 09:09:48 (GMT)
commitdd6eefb3484411969346bab19b642e758a81633c (patch)
tree1a7ad5d6d09f96e4d04d932fd64a55595fb3b4cd /Lib/httplib.py
parent9542f48fd56a238ba56d35c3bf0b88618de1b665 (diff)
downloadcpython-dd6eefb3484411969346bab19b642e758a81633c.zip
cpython-dd6eefb3484411969346bab19b642e758a81633c.tar.gz
cpython-dd6eefb3484411969346bab19b642e758a81633c.tar.bz2
no changes other than indentation level (now 4) and comment reflow.
use "cvs diff -b" to verify.
Diffstat (limited to 'Lib/httplib.py')
-rw-r--r--Lib/httplib.py1150
1 files changed, 577 insertions, 573 deletions
diff --git a/Lib/httplib.py b/Lib/httplib.py
index 5c0dacd..2a9546f 100644
--- a/Lib/httplib.py
+++ b/Lib/httplib.py
@@ -77,9 +77,9 @@ import string
import mimetools
try:
- from cStringIO import StringIO
+ from cStringIO import StringIO
except ImportError:
- from StringIO import StringIO
+ from StringIO import StringIO
HTTP_PORT = 80
HTTPS_PORT = 443
@@ -93,632 +93,636 @@ _CS_REQ_SENT = 'Request-sent'
class HTTPResponse:
- def __init__(self, sock):
- self.fp = sock.makefile('rb', 0)
-
- self.msg = None
-
- # from the Status-Line of the response
- self.version = _UNKNOWN # HTTP-Version
- self.status = _UNKNOWN # Status-Code
- self.reason = _UNKNOWN # Reason-Phrase
-
- self.chunked = _UNKNOWN # is "chunked" being used?
- self.chunk_left = _UNKNOWN # bytes left to read in current chunk
- self.length = _UNKNOWN # number of bytes left in response
- self.will_close = _UNKNOWN # connection will close at end of response
-
- def begin(self):
- if self.msg is not None:
- # we've already started reading the response
- return
-
- line = self.fp.readline()
- try:
- [version, status, reason] = string.split(line, None, 2)
- except ValueError:
- try:
- [version, status] = string.split(line, None, 1)
- reason = ""
- except ValueError:
- self.close()
- raise BadStatusLine(line)
- if version[:5] != 'HTTP/':
- self.close()
- raise BadStatusLine(line)
-
- self.status = status = int(status)
- self.reason = string.strip(reason)
-
- if version == 'HTTP/1.0':
- self.version = 10
- elif version[:7] == 'HTTP/1.':
- self.version = 11 # use HTTP/1.1 code for HTTP/1.x where x>=1
- else:
- raise UnknownProtocol(version)
-
- self.msg = mimetools.Message(self.fp, 0)
-
- # don't let the msg keep an fp
- self.msg.fp = None
-
- # are we using the chunked-style of transfer encoding?
- tr_enc = self.msg.getheader('transfer-encoding')
- if tr_enc:
- if string.lower(tr_enc) != 'chunked':
- raise UnknownTransferEncoding()
- self.chunked = 1
- self.chunk_left = None
- else:
- self.chunked = 0
-
- # will the connection close at the end of the response?
- conn = self.msg.getheader('connection')
- if conn:
- conn = string.lower(conn)
- # a "Connection: close" will always close the connection. if we
- # don't see that and this is not HTTP/1.1, then the connection will
- # close unless we see a Keep-Alive header.
- self.will_close = string.find(conn, 'close') != -1 or \
- ( self.version != 11 and \
- not self.msg.getheader('keep-alive') )
- else:
- # for HTTP/1.1, the connection will always remain open
- # otherwise, it will remain open IFF we see a Keep-Alive header
- self.will_close = self.version != 11 and \
- not self.msg.getheader('keep-alive')
-
- # do we have a Content-Length?
- # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
- length = self.msg.getheader('content-length')
- if length and not self.chunked:
- self.length = int(length)
- else:
- self.length = None
-
- # does the body have a fixed length? (of zero)
- if (status == 204 or # No Content
- status == 304 or # Not Modified
- 100 <= status < 200): # 1xx codes
- self.length = 0
-
- # if the connection remains open, and we aren't using chunked, and
- # a content-length was not provided, then assume that the connection
- # WILL close.
- if not self.will_close and \
- not self.chunked and \
- self.length is None:
- self.will_close = 1
-
- # if there is no body, then close NOW. read() may never be called, thus
- # we will never mark self as closed.
- if self.length == 0:
- self.close()
-
- def close(self):
- if self.fp:
- self.fp.close()
- self.fp = None
-
- def isclosed(self):
- # NOTE: it is possible that we will not ever call self.close(). This
- # case occurs when will_close is TRUE, length is None, and we
- # read up to the last byte, but NOT past it.
- #
- # IMPLIES: if will_close is FALSE, then self.close() will ALWAYS be
- # called, meaning self.isclosed() is meaningful.
- return self.fp is None
-
- def read(self, amt=None):
- if self.fp is None:
- return ''
-
- if self.chunked:
- chunk_left = self.chunk_left
- value = ''
- while 1:
- if chunk_left is None:
- line = self.fp.readline()
- i = string.find(line, ';')
- if i >= 0:
- line = line[:i] # strip chunk-extensions
- chunk_left = string.atoi(line, 16)
- if chunk_left == 0:
- break
- if amt is None:
- value = value + self._safe_read(chunk_left)
- elif amt < chunk_left:
- value = value + self._safe_read(amt)
- self.chunk_left = chunk_left - amt
- return value
- elif amt == chunk_left:
- value = value + self._safe_read(amt)
- self._safe_read(2) # toss the CRLF at the end of the chunk
- self.chunk_left = None
- return value
- else:
- value = value + self._safe_read(chunk_left)
- amt = amt - chunk_left
+ def __init__(self, sock):
+ self.fp = sock.makefile('rb', 0)
+
+ self.msg = None
+
+ # from the Status-Line of the response
+ self.version = _UNKNOWN # HTTP-Version
+ self.status = _UNKNOWN # Status-Code
+ self.reason = _UNKNOWN # Reason-Phrase
- # we read the whole chunk, get another
- self._safe_read(2) # toss the CRLF at the end of the chunk
- chunk_left = None
+ self.chunked = _UNKNOWN # is "chunked" being used?
+ self.chunk_left = _UNKNOWN # bytes left to read in current chunk
+ self.length = _UNKNOWN # number of bytes left in response
+ self.will_close = _UNKNOWN # conn will close at end of response
+
+ def begin(self):
+ if self.msg is not None:
+ # we've already started reading the response
+ return
- # read and discard trailer up to the CRLF terminator
- ### note: we shouldn't have any trailers!
- while 1:
line = self.fp.readline()
- if line == '\r\n':
- break
-
- # we read everything; close the "file"
- self.close()
-
- return value
-
- elif amt is None:
- # unbounded read
- if self.will_close:
- s = self.fp.read()
- else:
- s = self._safe_read(self.length)
- self.close() # we read everything
- return s
-
- if self.length is not None:
- if amt > self.length:
- # clip the read to the "end of response"
- amt = self.length
- self.length = self.length - amt
-
- # we do not use _safe_read() here because this may be a .will_close
- # connection, and the user is reading more bytes than will be provided
- # (for example, reading in 1k chunks)
- s = self.fp.read(amt)
-
- # close our "file" if we know we should
- ### I'm not sure about the len(s) < amt part; we should be safe because
- ### we shouldn't be using non-blocking sockets
- if self.length == 0 or len(s) < amt:
- self.close()
-
- return s
-
- def _safe_read(self, amt):
- """Read the number of bytes requested, compensating for partial reads.
-
- Normally, we have a blocking socket, but a read() can be interrupted
- by a signal (resulting in a partial read).
-
- Note that we cannot distinguish between EOF and an interrupt when zero
- bytes have been read. IncompleteRead() will be raised in this situation.
-
- This function should be used when <amt> bytes "should" be present for
- reading. If the bytes are truly not available (due to EOF), then the
- IncompleteRead exception can be used to detect the problem.
- """
- s = ''
- while amt > 0:
- chunk = self.fp.read(amt)
- if not chunk:
- raise IncompleteRead(s)
- s = s + chunk
- amt = amt - len(chunk)
- return s
+ try:
+ [version, status, reason] = string.split(line, None, 2)
+ except ValueError:
+ try:
+ [version, status] = string.split(line, None, 1)
+ reason = ""
+ except ValueError:
+ self.close()
+ raise BadStatusLine(line)
+ if version[:5] != 'HTTP/':
+ self.close()
+ raise BadStatusLine(line)
+
+ self.status = status = int(status)
+ self.reason = string.strip(reason)
+
+ if version == 'HTTP/1.0':
+ self.version = 10
+ elif version[:7] == 'HTTP/1.':
+ self.version = 11 # use HTTP/1.1 code for HTTP/1.x where x>=1
+ else:
+ raise UnknownProtocol(version)
- def getheader(self, name, default=None):
- if self.msg is None:
- raise ResponseNotReady()
- return self.msg.getheader(name, default)
+ self.msg = mimetools.Message(self.fp, 0)
+
+ # don't let the msg keep an fp
+ self.msg.fp = None
+
+ # are we using the chunked-style of transfer encoding?
+ tr_enc = self.msg.getheader('transfer-encoding')
+ if tr_enc:
+ if string.lower(tr_enc) != 'chunked':
+ raise UnknownTransferEncoding()
+ self.chunked = 1
+ self.chunk_left = None
+ else:
+ self.chunked = 0
+
+ # will the connection close at the end of the response?
+ conn = self.msg.getheader('connection')
+ if conn:
+ conn = string.lower(conn)
+ # a "Connection: close" will always close the connection. if we
+ # don't see that and this is not HTTP/1.1, then the connection will
+ # close unless we see a Keep-Alive header.
+ self.will_close = string.find(conn, 'close') != -1 or \
+ ( self.version != 11 and \
+ not self.msg.getheader('keep-alive') )
+ else:
+ # for HTTP/1.1, the connection will always remain open
+ # otherwise, it will remain open IFF we see a Keep-Alive header
+ self.will_close = self.version != 11 and \
+ not self.msg.getheader('keep-alive')
+
+ # do we have a Content-Length?
+ # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
+ length = self.msg.getheader('content-length')
+ if length and not self.chunked:
+ self.length = int(length)
+ else:
+ self.length = None
+
+ # does the body have a fixed length? (of zero)
+ if (status == 204 or # No Content
+ status == 304 or # Not Modified
+ 100 <= status < 200): # 1xx codes
+ self.length = 0
+
+ # if the connection remains open, and we aren't using chunked, and
+ # a content-length was not provided, then assume that the connection
+ # WILL close.
+ if not self.will_close and \
+ not self.chunked and \
+ self.length is None:
+ self.will_close = 1
+
+ # if there is no body, then close NOW. read() may never be called, thus
+ # we will never mark self as closed.
+ if self.length == 0:
+ self.close()
+
+ def close(self):
+ if self.fp:
+ self.fp.close()
+ self.fp = None
+
+ def isclosed(self):
+ # NOTE: it is possible that we will not ever call self.close(). This
+ # case occurs when will_close is TRUE, length is None, and we
+ # read up to the last byte, but NOT past it.
+ #
+ # IMPLIES: if will_close is FALSE, then self.close() will ALWAYS be
+ # called, meaning self.isclosed() is meaningful.
+ return self.fp is None
+
+ def read(self, amt=None):
+ if self.fp is None:
+ return ''
+
+ if self.chunked:
+ chunk_left = self.chunk_left
+ value = ''
+ while 1:
+ if chunk_left is None:
+ line = self.fp.readline()
+ i = string.find(line, ';')
+ if i >= 0:
+ line = line[:i] # strip chunk-extensions
+ chunk_left = string.atoi(line, 16)
+ if chunk_left == 0:
+ break
+ if amt is None:
+ value = value + self._safe_read(chunk_left)
+ elif amt < chunk_left:
+ value = value + self._safe_read(amt)
+ self.chunk_left = chunk_left - amt
+ return value
+ elif amt == chunk_left:
+ value = value + self._safe_read(amt)
+ self._safe_read(2) # toss the CRLF at the end of the chunk
+ self.chunk_left = None
+ return value
+ else:
+ value = value + self._safe_read(chunk_left)
+ amt = amt - chunk_left
+
+ # we read the whole chunk, get another
+ self._safe_read(2) # toss the CRLF at the end of the chunk
+ chunk_left = None
+
+ # read and discard trailer up to the CRLF terminator
+ ### note: we shouldn't have any trailers!
+ while 1:
+ line = self.fp.readline()
+ if line == '\r\n':
+ break
+
+ # we read everything; close the "file"
+ self.close()
+
+ return value
+
+ elif amt is None:
+ # unbounded read
+ if self.will_close:
+ s = self.fp.read()
+ else:
+ s = self._safe_read(self.length)
+ self.close() # we read everything
+ return s
+
+ if self.length is not None:
+ if amt > self.length:
+ # clip the read to the "end of response"
+ amt = self.length
+ self.length = self.length - amt
+
+ # we do not use _safe_read() here because this may be a .will_close
+ # connection, and the user is reading more bytes than will be provided
+ # (for example, reading in 1k chunks)
+ s = self.fp.read(amt)
+
+ # close our "file" if we know we should
+ ### I'm not sure about the len(s) < amt part; we should be safe because
+ ### we shouldn't be using non-blocking sockets
+ if self.length == 0 or len(s) < amt:
+ self.close()
+
+ return s
+
+ def _safe_read(self, amt):
+ """Read the number of bytes requested, compensating for partial reads.
+
+ Normally, we have a blocking socket, but a read() can be interrupted
+ by a signal (resulting in a partial read).
+
+ Note that we cannot distinguish between EOF and an interrupt when zero
+ bytes have been read. IncompleteRead() will be raised in this
+ situation.
+
+ This function should be used when <amt> bytes "should" be present for
+ reading. If the bytes are truly not available (due to EOF), then the
+ IncompleteRead exception can be used to detect the problem.
+ """
+ s = ''
+ while amt > 0:
+ chunk = self.fp.read(amt)
+ if not chunk:
+ raise IncompleteRead(s)
+ s = s + chunk
+ amt = amt - len(chunk)
+ return s
+
+ def getheader(self, name, default=None):
+ if self.msg is None:
+ raise ResponseNotReady()
+ return self.msg.getheader(name, default)
class HTTPConnection:
- _http_vsn = 11
- _http_vsn_str = 'HTTP/1.1'
-
- response_class = HTTPResponse
- default_port = HTTP_PORT
- auto_open = 1
-
- def __init__(self, host, port=None):
- self.sock = None
- self.__response = None
- self.__state = _CS_IDLE
-
- self._set_hostport(host, port)
-
- def _set_hostport(self, host, port):
- if port is None:
- i = string.find(host, ':')
- if i >= 0:
- port = int(host[i+1:])
- host = host[:i]
- else:
- port = self.default_port
- self.host = host
- self.port = port
-
- def connect(self):
- """Connect to the host and port specified in __init__."""
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.sock.connect((self.host, self.port))
-
- def close(self):
- """Close the connection to the HTTP server."""
- if self.sock:
- self.sock.close() # close it manually... there may be other refs
- self.sock = None
- if self.__response:
- self.__response.close()
- self.__response = None
- self.__state = _CS_IDLE
-
- def send(self, str):
- """Send `str' to the server."""
- if self.sock is None:
- if self.auto_open:
- self.connect()
- else:
- raise NotConnected()
-
- # send the data to the server. if we get a broken pipe, then close
- # the socket. we want to reconnect when somebody tries to send again.
- #
- # NOTE: we DO propagate the error, though, because we cannot simply
- # ignore the error... the caller will know if they can retry.
- try:
- self.sock.send(str)
- except socket.error, v:
- if v[0] == 32: # Broken pipe
- self.close()
- raise
-
- def putrequest(self, method, url):
- """Send a request to the server.
-
- `method' specifies an HTTP request method, e.g. 'GET'.
- `url' specifies the object being requested, e.g. '/index.html'.
- """
+ _http_vsn = 11
+ _http_vsn_str = 'HTTP/1.1'
+
+ response_class = HTTPResponse
+ default_port = HTTP_PORT
+ auto_open = 1
+
+ def __init__(self, host, port=None):
+ self.sock = None
+ self.__response = None
+ self.__state = _CS_IDLE
+
+ self._set_hostport(host, port)
+
+ def _set_hostport(self, host, port):
+ if port is None:
+ i = string.find(host, ':')
+ if i >= 0:
+ port = int(host[i+1:])
+ host = host[:i]
+ else:
+ port = self.default_port
+ self.host = host
+ self.port = port
+
+ def connect(self):
+ """Connect to the host and port specified in __init__."""
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect((self.host, self.port))
+
+ def close(self):
+ """Close the connection to the HTTP server."""
+ if self.sock:
+ self.sock.close() # close it manually... there may be other refs
+ self.sock = None
+ if self.__response:
+ self.__response.close()
+ self.__response = None
+ self.__state = _CS_IDLE
+
+ def send(self, str):
+ """Send `str' to the server."""
+ if self.sock is None:
+ if self.auto_open:
+ self.connect()
+ else:
+ raise NotConnected()
+
+ # send the data to the server. if we get a broken pipe, then close
+ # the socket. we want to reconnect when somebody tries to send again.
+ #
+ # NOTE: we DO propagate the error, though, because we cannot simply
+ # ignore the error... the caller will know if they can retry.
+ try:
+ self.sock.send(str)
+ except socket.error, v:
+ if v[0] == 32: # Broken pipe
+ self.close()
+ raise
+
+ def putrequest(self, method, url):
+ """Send a request to the server.
+
+ `method' specifies an HTTP request method, e.g. 'GET'.
+ `url' specifies the object being requested, e.g. '/index.html'.
+ """
+
+ # check if a prior response has been completed
+ if self.__response and self.__response.isclosed():
+ self.__response = None
+
+ #
+ # in certain cases, we cannot issue another request on this connection.
+ # this occurs when:
+ # 1) we are in the process of sending a request. (_CS_REQ_STARTED)
+ # 2) a response to a previous request has signalled that it is going
+ # to close the connection upon completion.
+ # 3) the headers for the previous response have not been read, thus
+ # we cannot determine whether point (2) is true. (_CS_REQ_SENT)
+ #
+ # if there is no prior response, then we can request at will.
+ #
+ # if point (2) is true, then we will have passed the socket to the
+ # response (effectively meaning, "there is no prior response"), and
+ # will open a new one when a new request is made.
+ #
+ # Note: if a prior response exists, then we *can* start a new request.
+ # We are not allowed to begin fetching the response to this new
+ # request, however, until that prior response is complete.
+ #
+ if self.__state == _CS_IDLE:
+ self.__state = _CS_REQ_STARTED
+ else:
+ raise CannotSendRequest()
+
+ if not url:
+ url = '/'
+ str = '%s %s %s\r\n' % (method, url, self._http_vsn_str)
+
+ try:
+ self.send(str)
+ except socket.error, v:
+ # trap 'Broken pipe' if we're allowed to automatically reconnect
+ if v[0] != 32 or not self.auto_open:
+ raise
+ # try one more time (the socket was closed; this will reopen)
+ self.send(str)
+
+ if self._http_vsn == 11:
+ # Issue some standard headers for better HTTP/1.1 compliance
+
+ # this header is issued *only* for HTTP/1.1 connections. more
+ # specifically, this means it is only issued when the client uses
+ # the new HTTPConnection() class. backwards-compat clients will
+ # be using HTTP/1.0 and those clients may be issuing this header
+ # themselves. we should NOT issue it twice; some web servers (such
+ # as Apache) barf when they see two Host: headers
+ self.putheader('Host', self.host)
+
+ # note: we are assuming that clients will not attempt to set these
+ # headers since *this* library must deal with the
+ # consequences. this also means that when the supporting
+ # libraries are updated to recognize other forms, then this
+ # code should be changed (removed or updated).
+
+ # we only want a Content-Encoding of "identity" since we don't
+ # support encodings such as x-gzip or x-deflate.
+ self.putheader('Accept-Encoding', 'identity')
+
+ # we can accept "chunked" Transfer-Encodings, but no others
+ # NOTE: no TE header implies *only* "chunked"
+ #self.putheader('TE', 'chunked')
+
+ # if TE is supplied in the header, then it must appear in a
+ # Connection header.
+ #self.putheader('Connection', 'TE')
- # check if a prior response has been completed
- if self.__response and self.__response.isclosed():
- self.__response = None
-
- #
- # in certain cases, we cannot issue another request on this connection.
- # this occurs when:
- # 1) we are in the process of sending a request. (_CS_REQ_STARTED)
- # 2) a response to a previous request has signalled that it is going
- # to close the connection upon completion.
- # 3) the headers for the previous response have not been read, thus
- # we cannot determine whether point (2) is true. (_CS_REQ_SENT)
- #
- # if there is no prior response, then we can request at will.
- #
- # if point (2) is true, then we will have passed the socket to the
- # response (effectively meaning, "there is no prior response"), and will
- # open a new one when a new request is made.
- #
- # Note: if a prior response exists, then we *can* start a new request.
- # We are not allowed to begin fetching the response to this new
- # request, however, until that prior response is complete.
- #
- if self.__state == _CS_IDLE:
- self.__state = _CS_REQ_STARTED
- else:
- raise CannotSendRequest()
-
- if not url:
- url = '/'
- str = '%s %s %s\r\n' % (method, url, self._http_vsn_str)
-
- try:
- self.send(str)
- except socket.error, v:
- # trap 'Broken pipe' if we're allowed to automatically reconnect
- if v[0] != 32 or not self.auto_open:
- raise
- # try one more time (the socket was closed; this will reopen)
- self.send(str)
-
- if self._http_vsn == 11:
- # Issue some standard headers for better HTTP/1.1 compliance
-
- # this header is issued *only* for HTTP/1.1 connections. more
- # specifically, this means it is only issued when the client uses
- # the new HTTPConnection() class. backwards-compat clients will
- # be using HTTP/1.0 and those clients may be issuing this header
- # themselves. we should NOT issue it twice; some web servers (such
- # as Apache) barf when they see two Host: headers
- self.putheader('Host', self.host)
-
- # note: we are assuming that clients will not attempt to set these
- # headers since *this* library must deal with the consequences.
- # this also means that when the supporting libraries are
- # updated to recognize other forms, then this code should be
- # changed (removed or updated).
-
- # we only want a Content-Encoding of "identity" since we don't
- # support encodings such as x-gzip or x-deflate.
- self.putheader('Accept-Encoding', 'identity')
-
- # we can accept "chunked" Transfer-Encodings, but no others
- # NOTE: no TE header implies *only* "chunked"
- #self.putheader('TE', 'chunked')
-
- # if TE is supplied in the header, then it must appear in a
- # Connection header.
- #self.putheader('Connection', 'TE')
-
- else:
- # For HTTP/1.0, the server will assume "not chunked"
- pass
-
- def putheader(self, header, value):
- """Send a request header line to the server.
-
- For example: h.putheader('Accept', 'text/html')
- """
- if self.__state != _CS_REQ_STARTED:
- raise CannotSendHeader()
-
- str = '%s: %s\r\n' % (header, value)
- self.send(str)
-
- def endheaders(self):
- """Indicate that the last header line has been sent to the server."""
-
- if self.__state == _CS_REQ_STARTED:
- self.__state = _CS_REQ_SENT
- else:
- raise CannotSendHeader()
-
- self.send('\r\n')
-
- def request(self, method, url, body=None, headers={}):
- """Send a complete request to the server."""
-
- try:
- self._send_request(method, url, body, headers)
- except socket.error, v:
- # trap 'Broken pipe' if we're allowed to automatically reconnect
- if v[0] != 32 or not self.auto_open:
- raise
- # try one more time
- self._send_request(method, url, body, headers)
-
- def _send_request(self, method, url, body, headers):
- self.putrequest(method, url)
-
- if body:
- self.putheader('Content-Length', str(len(body)))
- for hdr, value in headers.items():
- self.putheader(hdr, value)
- self.endheaders()
-
- if body:
- self.send(body)
-
- def getresponse(self):
- "Get the response from the server."
-
- # check if a prior response has been completed
- if self.__response and self.__response.isclosed():
- self.__response = None
-
- #
- # if a prior response exists, then it must be completed (otherwise, we
- # cannot read this response's header to determine the connection-close
- # behavior)
- #
- # note: if a prior response existed, but was connection-close, then the
- # socket and response were made independent of this HTTPConnection object
- # since a new request requires that we open a whole new connection
- #
- # this means the prior response had one of two states:
- # 1) will_close: this connection was reset and the prior socket and
- # response operate independently
- # 2) persistent: the response was retained and we await its isclosed()
- # status to become true.
- #
- if self.__state != _CS_REQ_SENT or self.__response:
- raise ResponseNotReady()
-
- response = self.response_class(self.sock)
-
- response.begin()
- self.__state = _CS_IDLE
-
- if response.will_close:
- # this effectively passes the connection to the response
- self.close()
- else:
- # remember this, so we can tell when it is complete
- self.__response = response
-
- return response
+ else:
+ # For HTTP/1.0, the server will assume "not chunked"
+ pass
+
+ def putheader(self, header, value):
+ """Send a request header line to the server.
+
+ For example: h.putheader('Accept', 'text/html')
+ """
+ if self.__state != _CS_REQ_STARTED:
+ raise CannotSendHeader()
+
+ str = '%s: %s\r\n' % (header, value)
+ self.send(str)
+
+ def endheaders(self):
+ """Indicate that the last header line has been sent to the server."""
+
+ if self.__state == _CS_REQ_STARTED:
+ self.__state = _CS_REQ_SENT
+ else:
+ raise CannotSendHeader()
+
+ self.send('\r\n')
+
+ def request(self, method, url, body=None, headers={}):
+ """Send a complete request to the server."""
+
+ try:
+ self._send_request(method, url, body, headers)
+ except socket.error, v:
+ # trap 'Broken pipe' if we're allowed to automatically reconnect
+ if v[0] != 32 or not self.auto_open:
+ raise
+ # try one more time
+ self._send_request(method, url, body, headers)
+
+ def _send_request(self, method, url, body, headers):
+ self.putrequest(method, url)
+
+ if body:
+ self.putheader('Content-Length', str(len(body)))
+ for hdr, value in headers.items():
+ self.putheader(hdr, value)
+ self.endheaders()
+
+ if body:
+ self.send(body)
+
+ def getresponse(self):
+ "Get the response from the server."
+
+ # check if a prior response has been completed
+ if self.__response and self.__response.isclosed():
+ self.__response = None
+
+ #
+ # if a prior response exists, then it must be completed (otherwise, we
+ # cannot read this response's header to determine the connection-close
+ # behavior)
+ #
+ # note: if a prior response existed, but was connection-close, then the
+ # socket and response were made independent of this HTTPConnection
+ # object since a new request requires that we open a whole new
+ # connection
+ #
+ # this means the prior response had one of two states:
+ # 1) will_close: this connection was reset and the prior socket and
+ # response operate independently
+ # 2) persistent: the response was retained and we await its
+ # isclosed() status to become true.
+ #
+ if self.__state != _CS_REQ_SENT or self.__response:
+ raise ResponseNotReady()
+
+ response = self.response_class(self.sock)
+
+ response.begin()
+ self.__state = _CS_IDLE
+
+ if response.will_close:
+ # this effectively passes the connection to the response
+ self.close()
+ else:
+ # remember this, so we can tell when it is complete
+ self.__response = response
+
+ return response
class FakeSocket:
- def __init__(self, sock, ssl):
- self.__sock = sock
- self.__ssl = ssl
+ def __init__(self, sock, ssl):
+ self.__sock = sock
+ self.__ssl = ssl
- def makefile(self, mode): # hopefully, never have to write
- if mode != 'r' and mode != 'rb':
- raise UnimplementedFileMode()
+ def makefile(self, mode): # hopefully, never have to write
+ if mode != 'r' and mode != 'rb':
+ raise UnimplementedFileMode()
- msgbuf = ""
- while 1:
- try:
- msgbuf = msgbuf + self.__ssl.read()
- except socket.sslerror, msg:
- break
- return StringIO(msgbuf)
+ msgbuf = ""
+ while 1:
+ try:
+ msgbuf = msgbuf + self.__ssl.read()
+ except socket.sslerror, msg:
+ break
+ return StringIO(msgbuf)
- def send(self, stuff, flags = 0):
- return self.__ssl.write(stuff)
+ def send(self, stuff, flags = 0):
+ return self.__ssl.write(stuff)
- def recv(self, len = 1024, flags = 0):
- return self.__ssl.read(len)
+ def recv(self, len = 1024, flags = 0):
+ return self.__ssl.read(len)
- def __getattr__(self, attr):
- return getattr(self.__sock, attr)
+ def __getattr__(self, attr):
+ return getattr(self.__sock, attr)
class HTTPSConnection(HTTPConnection):
- "This class allows communication via SSL."
-
- default_port = HTTPS_PORT
-
- def __init__(self, host, port=None, **x509):
- keys = x509.keys()
- try:
- keys.remove('key_file')
- except ValueError:
- pass
- try:
- keys.remove('cert_file')
- except ValueError:
- pass
- if keys:
- raise IllegalKeywordArgument()
- HTTPConnection.__init__(self, host, port)
- self.key_file = x509.get('key_file')
- self.cert_file = x509.get('cert_file')
-
- def connect(self):
- "Connect to a host on a given (SSL) port."
-
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect((self.host, self.port))
- ssl = socket.ssl(sock, self.key_file, self.cert_file)
- self.sock = FakeSocket(sock, ssl)
+ "This class allows communication via SSL."
+
+ default_port = HTTPS_PORT
+
+ def __init__(self, host, port=None, **x509):
+ keys = x509.keys()
+ try:
+ keys.remove('key_file')
+ except ValueError:
+ pass
+ try:
+ keys.remove('cert_file')
+ except ValueError:
+ pass
+ if keys:
+ raise IllegalKeywordArgument()
+ HTTPConnection.__init__(self, host, port)
+ self.key_file = x509.get('key_file')
+ self.cert_file = x509.get('cert_file')
+
+ def connect(self):
+ "Connect to a host on a given (SSL) port."
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect((self.host, self.port))
+ ssl = socket.ssl(sock, self.key_file, self.cert_file)
+ self.sock = FakeSocket(sock, ssl)
class HTTP(HTTPConnection):
- "Compatibility class with httplib.py from 1.5."
+ "Compatibility class with httplib.py from 1.5."
- _http_vsn = 10
- _http_vsn_str = 'HTTP/1.0'
+ _http_vsn = 10
+ _http_vsn_str = 'HTTP/1.0'
- debuglevel = 0
+ debuglevel = 0
- def __init__(self, host='', port=None, **x509):
- "Provide a default host, since the superclass requires one."
+ def __init__(self, host='', port=None, **x509):
+ "Provide a default host, since the superclass requires one."
- # some joker passed 0 explicitly, meaning default port
- if port == 0:
- port = None
+ # some joker passed 0 explicitly, meaning default port
+ if port == 0:
+ port = None
- # Note that we may pass an empty string as the host; this will throw
- # an error when we attempt to connect. Presumably, the client code
- # will call connect before then, with a proper host.
- HTTPConnection.__init__(self, host, port)
+ # Note that we may pass an empty string as the host; this will throw
+ # an error when we attempt to connect. Presumably, the client code
+ # will call connect before then, with a proper host.
+ HTTPConnection.__init__(self, host, port)
- # we never actually use these for anything, but we keep them here for
- # compatibility with post-1.5.2 CVS.
- self.key_file = x509.get('key_file')
- self.cert_file = x509.get('cert_file')
+ # we never actually use these for anything, but we keep them here for
+ # compatibility with post-1.5.2 CVS.
+ self.key_file = x509.get('key_file')
+ self.cert_file = x509.get('cert_file')
- self.file = None
+ self.file = None
- def connect(self, host=None, port=None):
- "Accept arguments to set the host/port, since the superclass doesn't."
+ def connect(self, host=None, port=None):
+ "Accept arguments to set the host/port, since the superclass doesn't."
- if host is not None:
- self._set_hostport(host, port)
- HTTPConnection.connect(self)
+ if host is not None:
+ self._set_hostport(host, port)
+ HTTPConnection.connect(self)
- def set_debuglevel(self, debuglevel):
- "The class no longer supports the debuglevel."
- pass
+ def set_debuglevel(self, debuglevel):
+ "The class no longer supports the debuglevel."
+ pass
- def getfile(self):
- "Provide a getfile, since the superclass' does not use this concept."
- return self.file
+ def getfile(self):
+ "Provide a getfile, since the superclass' does not use this concept."
+ return self.file
- def putheader(self, header, *values):
- "The superclass allows only one value argument."
- HTTPConnection.putheader(self, header, string.joinfields(values, '\r\n\t'))
+ def putheader(self, header, *values):
+ "The superclass allows only one value argument."
+ HTTPConnection.putheader(self, header,
+ string.joinfields(values, '\r\n\t'))
- def getreply(self):
- """Compat definition since superclass does not define it.
+ def getreply(self):
+ """Compat definition since superclass does not define it.
- Returns a tuple consisting of:
- - server status code (e.g. '200' if all goes well)
- - server "reason" corresponding to status code
- - any RFC822 headers in the response from the server
- """
- try:
- response = self.getresponse()
- except BadStatusLine, e:
- ### hmm. if getresponse() ever closes the socket on a bad request,
- ### then we are going to have problems with self.sock
+ Returns a tuple consisting of:
+ - server status code (e.g. '200' if all goes well)
+ - server "reason" corresponding to status code
+ - any RFC822 headers in the response from the server
+ """
+ try:
+ response = self.getresponse()
+ except BadStatusLine, e:
+ ### hmm. if getresponse() ever closes the socket on a bad request,
+ ### then we are going to have problems with self.sock
- ### should we keep this behavior? do people use it?
- # keep the socket open (as a file), and return it
- self.file = self.sock.makefile('rb', 0)
+ ### should we keep this behavior? do people use it?
+ # keep the socket open (as a file), and return it
+ self.file = self.sock.makefile('rb', 0)
- # close our socket -- we want to restart after any protocol error
- self.close()
+ # close our socket -- we want to restart after any protocol error
+ self.close()
- self.headers = None
- return -1, e.line, None
+ self.headers = None
+ return -1, e.line, None
- self.headers = response.msg
- self.file = response.fp
- return response.status, response.reason, response.msg
+ self.headers = response.msg
+ self.file = response.fp
+ return response.status, response.reason, response.msg
- def close(self):
- HTTPConnection.close(self)
+ def close(self):
+ HTTPConnection.close(self)
- # note that self.file == response.fp, which gets closed by the
- # superclass. just clear the object ref here.
- ### hmm. messy. if status==-1, then self.file is owned by us.
- ### well... we aren't explicitly closing, but losing this ref will do it
- self.file = None
+ # note that self.file == response.fp, which gets closed by the
+ # superclass. just clear the object ref here.
+ ### hmm. messy. if status==-1, then self.file is owned by us.
+ ### well... we aren't explicitly closing, but losing this ref will
+ ### do it
+ self.file = None
class HTTPException(Exception):
- pass
+ pass
class NotConnected(HTTPException):
- pass
+ pass
class UnknownProtocol(HTTPException):
- def __init__(self, version):
- self.version = version
+ def __init__(self, version):
+ self.version = version
class UnknownTransferEncoding(HTTPException):
- pass
+ pass
class IllegalKeywordArgument(HTTPException):
- pass
+ pass
class UnimplementedFileMode(HTTPException):
- pass
+ pass
class IncompleteRead(HTTPException):
- def __init__(self, partial):
- self.partial = partial
+ def __init__(self, partial):
+ self.partial = partial
class ImproperConnectionState(HTTPException):
- pass
+ pass
class CannotSendRequest(ImproperConnectionState):
- pass
+ pass
class CannotSendHeader(ImproperConnectionState):
- pass
+ pass
class ResponseNotReady(ImproperConnectionState):
- pass
+ pass
class BadStatusLine(HTTPException):
- def __init__(self, line):
- self.line = line
+ def __init__(self, line):
+ self.line = line
# for backwards compatibility
error = HTTPException
@@ -760,19 +764,19 @@ def test():
print h.getfile().read()
if hasattr(socket, 'ssl'):
- host = 'www.c2.net'
- hs = HTTPS()
- hs.connect(host)
- hs.putrequest('GET', selector)
- hs.endheaders()
- status, reason, headers = hs.getreply()
- print 'status =', status
- print 'reason =', reason
- print
- if headers:
- for header in headers.headers: print string.strip(header)
- print
- print hs.getfile().read()
+ host = 'www.c2.net'
+ hs = HTTPS()
+ hs.connect(host)
+ hs.putrequest('GET', selector)
+ hs.endheaders()
+ status, reason, headers = hs.getreply()
+ print 'status =', status
+ print 'reason =', reason
+ print
+ if headers:
+ for header in headers.headers: print string.strip(header)
+ print
+ print hs.getfile().read()
if __name__ == '__main__':