""" httplib_async.py
Asynchronous HTTP client.
"""
__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
__license__ = """ GNU General Public License
This program is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA. """
import asyncore
import socket
import mimetools
try:
from cStringIO import StringIO
except:
from StringIO import StringIO
import sys
from error import log
class HTTPConnection_async(asyncore.dispatcher_with_send):
# asynchronous http client
def __init__(self, host, ip, port, consumer, sock=None, map=None, proxy=None):
asyncore.dispatcher_with_send.__init__(self)
if ip is None:
ip = host
self.ip = ip
self.host = host
self.port = port
self.consumer = consumer
self.status = None
self.http_version = "HTTP/1.1"
self.proxy = None
self.proxy_port = None
if proxy:
self.proxy = proxy[0]
self.proxy_port = proxy[1]
def set_proxy(self, proxy):
self.proxy = proxy[0]
self.proxy_port = proxy[1]
def request(self, method, path, headers={}, data=None):
self.header = None
self.data = ""
send_data = []
self.method = method or "GET"
self.path = path or "/"
if self.path:
if self.path[0] != "/":
self.path = "/" + self.path
else:
self.path = "/"
if self.proxy and self.proxy_port:
self.path = "http://%s%s" % (self.host, path)
self._initialize_chunked()
send_data.append("%s %s %s\r\n" % (self.method, self.path, self.http_version))
host_found = 0
cl_found = 0
for h, v in headers.items():
h = h.lower()
if h == 'host':
host_found = 1
if h == 'content-length':
cl_found = 1
send_data.append("%s: %s\r\n" % (h, v))
if not host_found:
send_data.append("Host: %s\r\n" % self.host)
if data and not cl_found:
send_data.append("Content-length: %d\r\n" % len(data))
send_data.append("\r\n")
if data:
send_data.append(data)
self.send_data = "".join(send_data)
if self.connected:
self.send(self.send_data)
def execute(self):
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
if self.proxy and self.proxy_port:
self.connect((self.proxy, self.proxy_port))
else:
self.connect((self.ip, self.port))
def handle_connect(self):
# connection succeeded
self.send(self.send_data)
def handle_expt(self):
# This is used for OOB data.
log("OOB data received")
def handle_error(self):
exctype, value = sys.exc_info()[:2]
self.close()
self.consumer.http_failed(value)
def _initialize_chunked(self, chunked=0):
self.chunked = chunked
self.chunk_left = None
self.chunk_data = []
self.chunks_done = 0
def handle_read(self):
data = self.recv(4096)
self.data += data
if not self.header:
# check if we've seen a full header
header = self.data.split("\r\n\r\n", 1)
if len(header) <= 1:
# i guess not everyone can read the spec - congrats go to
# thinkgeek
header = self.data.split("\n\n", 1)
if len(header) <= 1:
return
header, data = header
# parse header
fp = StringIO(header)
line = fp.readline()
try:
self.status = line.split(" ", 2)
self.status[1] = int(self.status[1])
except ValueError:
self.consumer.http_failed(BadStatusException(line))
self.header = mimetools.Message(fp)
self.data = data
if self.status[1] in (301, 302):
self.consumer.http_redirect(self.header.getheader('location'),self.status[1] == 301)
self.close()
return
elif self.header.getheader('transfer-encoding', '').lower() == 'chunked':
self._initialize_chunked(1)
else:
self.consumer.http_header(self.status, self.header)
if not self.connected:
return # channel was closed by consumer
if self.chunked:
# we are reading chunked data, read it all and the possible
# extra headers after that
while not self.chunks_done and len(self.data) > 0:
if self.chunk_left is None or self.chunk_left < 0:
# one chunk over, read the length of the next one
if self.data[:2] == '\r\n':
self.data = self.data[2:]
cdata = self.data.split("\r\n", 1)
if len(cdata) <= 1:
return
line, self.data = cdata
i = line.find(';')
if i >= 0:
line = line[:i]
self.chunk_left = int(line, 16)
if self.chunk_left == 0:
# no more chunks, put back CRLF to help things later
self.data = "\r\n" + self.data
self.chunks_done = 1
break
self.chunk_data.append(self.data[:self.chunk_left])
l = len(self.data)
self.data = self.data[self.chunk_left:]
self.chunk_left -= l
if self.chunks_done:
cdata = self.data.split("\r\n\r\n", 1)
if len(cdata) <= 1:
return
headerlines = cdata[0][2:]
fp = StringIO(headerlines)
header = mimetools.Message(fp)
chunk_data = "".join(self.chunk_data)
for h, v in header.items():
self.header[h] = v
if not self.header.has_key('content-length'):
self.header['content-length'] = str(len(chunk_data))
del self.header['transfer-encoding']
self.consumer.http_header(self.status, self.header)
self.consumer.feed(chunk_data)
return
elif data:
self.consumer.feed(data)
def handle_close(self):
self.close()
self.consumer.http_close()
self.consumer = None
class HTTPException(Exception):
pass
class BadStatusException(HTTPException):
def __init__(self, line):
HTTPException.__init__(self)
self.args = line
self.line = line
syntax highlighted by Code2HTML, v. 0.9.1