summaryrefslogtreecommitdiff
path: root/http_client.py
diff options
context:
space:
mode:
Diffstat (limited to 'http_client.py')
-rw-r--r--http_client.py78
1 files changed, 78 insertions, 0 deletions
diff --git a/http_client.py b/http_client.py
new file mode 100644
index 0000000..a51ce31
--- /dev/null
+++ b/http_client.py
@@ -0,0 +1,78 @@
+"""Crummy HTTP client.
+
+This is not meant as an example of how to write a good client.
+"""
+
+# Stdlib.
+import re
+import time
+
+# Local.
+import sockets
+
+
+def urlfetch(host, port=None, method='GET', path='/',
+ body=None, hdrs=None, encoding='utf-8', ssl=None, af=0):
+ """COROUTINE: Make an HTTP 1.0 request."""
+ t0 = time.time()
+ if port is None:
+ if ssl:
+ port = 443
+ else:
+ port = 80
+ trans = yield from sockets.create_transport(host, port, ssl=ssl, af=af)
+ yield from trans.send(method.encode(encoding) + b' ' +
+ path.encode(encoding) + b' HTTP/1.0\r\n')
+ if hdrs:
+ kwds = dict(hdrs)
+ else:
+ kwds = {}
+ if 'host' not in kwds:
+ kwds['host'] = host
+ if body is not None:
+ kwds['content_length'] = len(body)
+ for header, value in kwds.items():
+ yield from trans.send(header.replace('_', '-').encode(encoding) +
+ b': ' + value.encode(encoding) + b'\r\n')
+
+ yield from trans.send(b'\r\n')
+ if body is not None:
+ yield from trans.send(body)
+
+ # Read HTTP response line.
+ rdr = sockets.BufferedReader(trans)
+ resp = yield from rdr.readline()
+ m = re.match(br'(?ix) http/(\d\.\d) \s+ (\d\d\d) \s+ ([^\r]*)\r?\n\Z',
+ resp)
+ if not m:
+ trans.close()
+ raise IOError('No valid HTTP response: %r' % resp)
+ http_version, status, message = m.groups()
+
+ # Read HTTP headers.
+ headers = []
+ hdict = {}
+ while True:
+ line = yield from rdr.readline()
+ if not line.strip():
+ break
+ m = re.match(br'([^\s:]+):\s*([^\r]*)\r?\n\Z', line)
+ if not m:
+ raise IOError('Invalid header: %r' % line)
+ header, value = m.groups()
+ headers.append((header, value))
+ hdict[header.decode(encoding).lower()] = value.decode(encoding)
+
+ # Read response body.
+ content_length = hdict.get('content-length')
+ if content_length is not None:
+ size = int(content_length) # TODO: Catch errors.
+ assert size >= 0, size
+ else:
+ size = 2**20 # Protective limit (1 MB).
+ data = yield from rdr.readexactly(size)
+ trans.close() # Can this block?
+ t1 = time.time()
+ result = (host, port, path, int(status), len(data), round(t1-t0, 3))
+## print(result)
+ return result