273 lines
7.9 KiB
Python
273 lines
7.9 KiB
Python
"""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('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
|
|
...
|
|
<HEAD>
|
|
<TITLE>Python Language Home Page</TITLE>
|
|
[...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.)
|
|
"""
|
|
|
|
import os
|
|
import socket
|
|
import string
|
|
import mimetools
|
|
|
|
try:
|
|
from cStringIO import StringIO
|
|
except:
|
|
from StringIO import StringIO
|
|
|
|
HTTP_VERSION = 'HTTP/1.0'
|
|
HTTP_PORT = 80
|
|
HTTPS_PORT = 443
|
|
|
|
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 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]
|
|
if args[1:]: selector = args[1]
|
|
h = HTTP()
|
|
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"):
|
|
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 __name__ == '__main__':
|
|
test()
|