From 5e0fa4065f928af5e44f136d46c18c37558262ad Mon Sep 17 00:00:00 2001 From: Greg Stein Date: Mon, 26 Jun 2000 08:28:01 +0000 Subject: initial commit of a new HTTP library, supporting HTTP/1.1 and persistent connections. --- Lib/httplib.py | 962 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 734 insertions(+), 228 deletions(-) diff --git a/Lib/httplib.py b/Lib/httplib.py index feb97b3..5c0dacd 100644 --- a/Lib/httplib.py +++ b/Lib/httplib.py @@ -1,235 +1,746 @@ -"""HTTP client class - -See the following URL for a description of the HTTP/1.0 protocol: -http://www.w3.org/hypertext/WWW/Protocols/ -(I actually implemented it from a much earlier draft.) - -Example: - ->>> from httplib import HTTP ->>> h = HTTP('www.python.org') ->>> h.putrequest('GET', '/index.html') ->>> h.putheader('Host', 'www.python.org') ->>> h.putheader('Accept', 'text/html') ->>> h.putheader('Accept', 'text/plain') ->>> h.endheaders() ->>> errcode, errmsg, headers = h.getreply() ->>> if errcode == 200: -... f = h.getfile() -... print f.read() # Print the raw HTML -... - -Python Language Home Page -[...many more lines...] ->>> - -Note that an HTTP object is used for a single request -- to issue a -second request to the same server, you create a new HTTP object. -(This is in accordance with the protocol, which uses a new TCP -connection for each request.) +# +# HTTP/1.1 client library +# + +# ### this may as well go into a doc string... +"""HTTP/1.1 client library + + + + +HTTPConnection go through a number of "states", which defines when a client +may legally make another request or fetch the response for a particular +request. This diagram details these state transitions: + + (null) + | + | HTTPConnection() + v + Idle + | + | putrequest() + v + Request-started + | + | ( putheader() )* endheaders() + v + Request-sent + | + | response = getresponse() + v + Unread-response [Response-headers-read] + |\____________________ + | \ + | response.read() | putrequest() + v v + Idle Req-started-unread-response + _______/| + / | + response.read() | | ( putheader() )* endheaders() + v v + Request-started Req-sent-unread-response + | + | response.read() + v + Request-sent + +This diagram presents the following rules: + -- a second request may not be started until {response-headers-read} + -- a response [object] cannot be retrieved until {request-sent} + -- there is no differentiation between an unread response body and a + partially read response body + +Note: this enforcement is applied by the HTTPConnection class. The + HTTPResponse class does not enforce this state machine, which + implies sophisticated clients may accelerate the request/response + pipeline. Caution should be taken, though: accelerating the states + beyond the above pattern may imply knowledge of the server's + connection-close behavior for certain requests. For example, it + is impossible to tell whether the server will close the connection + UNTIL the response headers have been read; this means that further + requests cannot be placed into the pipeline until it is known that + the server will NOT be closing the connection. + +Logical State __state __response +------------- ------- ---------- +Idle _CS_IDLE None +Request-started _CS_REQ_STARTED None +Request-sent _CS_REQ_SENT None +Unread-response _CS_IDLE +Req-started-unread-response _CS_REQ_STARTED +Req-sent-unread-response _CS_REQ_SENT """ -import os + import socket import string import mimetools try: - from cStringIO import StringIO -except: - from StringIO import StringIO + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO -HTTP_VERSION = 'HTTP/1.0' HTTP_PORT = 80 HTTPS_PORT = 443 +_UNKNOWN = 'UNKNOWN' + +# connection states +_CS_IDLE = 'Idle' +_CS_REQ_STARTED = 'Request-started' +_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 + + # 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 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'. + """ + + # 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 + + class FakeSocket: - def __init__(self, sock, ssl): - self.__sock = sock - self.__ssl = ssl - return - - def makefile(self, mode): # hopefully, never have to write - 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 recv(self, len = 1024, flags = 0): - return self.__ssl.read(len) - - def __getattr__(self, attr): - return getattr(self.__sock, attr) - -class HTTP: - """This class manages a connection to an HTTP server.""" - - def __init__(self, host = '', port = 0, **x509): - """Initialize a new instance. - - If specified, `host' is the name of the remote host to which - to connect. If specified, `port' specifies the port to which - to connect. By default, httplib.HTTP_PORT is used. - - """ - self.key_file = x509.get('key_file') - self.cert_file = x509.get('cert_file') - self.debuglevel = 0 - self.file = None - if host: self.connect(host, port) - - def set_debuglevel(self, debuglevel): - """Set the debug output level. - - A non-false value results in debug messages for connection and - for all messages sent to and received from the server. - - """ - self.debuglevel = debuglevel - - def connect(self, host, port = 0): - """Connect to a host on a given port. - - Note: This method is automatically invoked by __init__, - if a host is specified during instantiation. - - """ - if not port: - i = string.find(host, ':') - if i >= 0: - host, port = host[:i], host[i+1:] - try: port = string.atoi(port) - except string.atoi_error: - raise socket.error, "nonnumeric port" - if not port: port = HTTP_PORT - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if self.debuglevel > 0: print 'connect:', (host, port) - self.sock.connect((host, port)) - - def send(self, str): - """Send `str' to the server.""" - if self.debuglevel > 0: print 'send:', `str` - self.sock.send(str) - - def putrequest(self, request, selector): - """Send a request to the server. - - `request' specifies an HTTP request method, e.g. 'GET'. - `selector' specifies the object being requested, e.g. - '/index.html'. - - """ - if not selector: selector = '/' - str = '%s %s %s\r\n' % (request, selector, HTTP_VERSION) - self.send(str) - - def putheader(self, header, *args): - """Send a request header line to the server. - - For example: h.putheader('Accept', 'text/html') - - """ - str = '%s: %s\r\n' % (header, string.joinfields(args,'\r\n\t')) - self.send(str) - - def endheaders(self): - """Indicate that the last header line has been sent to the server.""" - self.send('\r\n') - - def getreply(self): - """Get a reply from the server. - - Returns a tuple consisting of: - - server response code (e.g. '200' if all goes well) - - server response string corresponding to response code - - any RFC822 headers in the response from the server - - """ - self.file = self.sock.makefile('rb') - line = self.file.readline() - if self.debuglevel > 0: print 'reply:', `line` - try: - [ver, code, msg] = string.split(line, None, 2) - except ValueError: - try: - [ver, code] = string.split(line, None, 1) - msg = "" - except ValueError: - self.headers = None - return -1, line, self.headers - if ver[:5] != 'HTTP/': - self.headers = None - return -1, line, self.headers - errcode = string.atoi(code) - errmsg = string.strip(msg) - self.headers = mimetools.Message(self.file, 0) - return errcode, errmsg, self.headers - - def getfile(self): - """Get a file object from which to receive data from the HTTP server. - - NOTE: This method must not be invoked until getreplies - has been invoked. - - """ - return self.file - - def close(self): - """Close the connection to the HTTP server.""" - if self.file: - self.file.close() - self.file = None - if self.sock: - self.sock.close() - self.sock = None - -if hasattr(socket, "ssl"): - class HTTPS(HTTP): - """This class allows communication via SSL.""" - - def connect(self, host, port = 0): - """Connect to a host on a given port. - - Note: This method is automatically invoked by __init__, - if a host is specified during instantiation. - - """ - if not port: - i = string.find(host, ':') - if i >= 0: - host, port = host[:i], host[i+1:] - try: port = string.atoi(port) - except string.atoi_error: - raise socket.error, "nonnumeric port" - if not port: port = HTTPS_PORT - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if self.debuglevel > 0: print 'connect:', (host, port) - sock.connect((host, port)) - ssl = socket.ssl(sock, self.key_file, self.cert_file) - self.sock = FakeSocket(sock, 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() + + 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 recv(self, len = 1024, flags = 0): + return self.__ssl.read(len) + + 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) + + +class HTTP(HTTPConnection): + "Compatibility class with httplib.py from 1.5." + + _http_vsn = 10 + _http_vsn_str = 'HTTP/1.0' + + debuglevel = 0 + + 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 + + # 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') + + self.file = None + + 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) + 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 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. + + 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) + + # close our socket -- we want to restart after any protocol error + self.close() + + self.headers = None + return -1, e.line, None + + self.headers = response.msg + self.file = response.fp + return response.status, response.reason, response.msg + + 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 + + +class HTTPException(Exception): + pass + +class NotConnected(HTTPException): + pass + +class UnknownProtocol(HTTPException): + def __init__(self, version): + self.version = version + +class UnknownTransferEncoding(HTTPException): + pass + +class IllegalKeywordArgument(HTTPException): + pass + +class UnimplementedFileMode(HTTPException): + pass + +class IncompleteRead(HTTPException): + def __init__(self, partial): + self.partial = partial + +class ImproperConnectionState(HTTPException): + pass + +class CannotSendRequest(ImproperConnectionState): + pass + +class CannotSendHeader(ImproperConnectionState): + pass + +class ResponseNotReady(ImproperConnectionState): + pass + +class BadStatusLine(HTTPException): + def __init__(self, line): + self.line = line + +# for backwards compatibility +error = HTTPException + + +# +# snarfed from httplib.py for now... +# def test(): """Test this module. The test consists of retrieving and displaying the Python home page, along with the error code and error string returned by the www.python.org server. - """ + import sys import getopt opts, args = getopt.getopt(sys.argv[1:], 'd') dl = 0 for o, a in opts: if o == '-d': dl = dl + 1 - print "testing HTTP..." host = 'www.python.org' selector = '/' if args[0:]: host = args[0] @@ -239,34 +750,29 @@ def test(): h.connect(host) h.putrequest('GET', selector) h.endheaders() - errcode, errmsg, headers = h.getreply() - print 'errcode =', errcode - print 'errmsg =', errmsg + status, reason, headers = h.getreply() + print 'status =', status + print 'reason =', reason print if headers: for header in headers.headers: print string.strip(header) print print h.getfile().read() - if hasattr(socket, "ssl"): - print "-"*40 - print "testing HTTPS..." - host = 'synergy.as.cmu.edu' - selector = '/~geek/' - if args[0:]: host = args[0] - if args[1:]: selector = args[1] - h = HTTPS() - h.set_debuglevel(dl) - h.connect(host) - h.putrequest('GET', selector) - h.endheaders() - errcode, errmsg, headers = h.getreply() - print 'errcode =', errcode - print 'errmsg =', errmsg - print - if headers: - for header in headers.headers: print string.strip(header) - print - 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() if __name__ == '__main__': -- cgit v0.12