Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 82 additions & 10 deletions resources/session02/http_server.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,73 @@
import socket
import sys
import mimetypes
import os.path


def response_ok(body=b"this is a pretty minimal response", mimetype=b"text/plain"):
"""returns a basic HTTP response"""
mimetype = b"content-type:" + mimetype
resp = []
resp.append(b"HTTP/1.1 200 OK")
resp.append(mimetype)
resp.append(b"")
resp.append(body)
return b"\r\n".join(resp)


def response_method_not_allowed():
"""returns a 405 Method Not Allowed response"""
resp = []
resp.append("HTTP/1.1 405 Method Not Allowed")
resp.append("")
return "\r\n".join(resp).encode('utf8')


def response_not_found():
"""returns a 404 Not Found response"""
return b"HTTP/1.1 404 Not Found"


def parse_request(request):
first_line = request.split("\r\n", 1)[0]
try:
method, uri, protocol = first_line.split()
# Chrome seems to be sending a zero-length request that breaks the split.
except ValueError:
print('The client tried to break me. Request was: \'{}\''.format(request))
method = ''
if method != "GET":
raise NotImplementedError("We only accept GET")
return uri


def resolve_uri(uri):
"""Return appropriate content and a mime type"""
# Return the contents of a file if it exists
uri = os.getcwd() + uri
if os.path.isfile(uri):
mime_type, _ = mimetypes.guess_type(uri)
mime_type = mime_type.encode('utf8')
with open(uri, "rb") as content_file:
f = content_file.read()
content = bytearray(f)
# Return an index if a directory is requested
elif os.path.isdir(uri):
mime_type = b'text/plain'
filelist = os.listdir(uri)
filelist.sort
content = '\r\n'.join(filelist)
content = content.encode('utf8')
else:
raise NameError
return content, mime_type


def server(log_buffer=sys.stderr):
address = ('127.0.0.1', 10000)
address = ('0.0.0.0', 10000) # I'm running this from a remote VM
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
print("making a server on {0}:{1}".format(*address), file=log_buffer)
print("making a server on {0}:{1}".format(*address), file=log_buffer) # NOQA
sock.bind(address)
sock.listen(1)

Expand All @@ -16,16 +77,27 @@ def server(log_buffer=sys.stderr):
conn, addr = sock.accept() # blocking
try:
print('connection - {0}:{1}'.format(*addr), file=log_buffer)
request = ''
while True:
data = conn.recv(16)
print('received "{0}"'.format(data), file=log_buffer)
if data:
print('sending data back to client', file=log_buffer)
conn.sendall(data)
else:
msg = 'no more data from {0}:{1}'.format(*addr)
print(msg, log_buffer)
data = conn.recv(1024)
request += data.decode('utf8')
if len(data) < 1024:
break

try:
uri = parse_request(request)
except NotImplementedError:
response = response_method_not_allowed()
else:
try:
content, mime_type = resolve_uri(uri)
except NameError:
response = response_not_found()
else:
response = response_ok(content, mime_type)

print('sending response', file=log_buffer)
conn.sendall(response)
finally:
conn.close()

Expand Down
136 changes: 136 additions & 0 deletions resources/session03/wsgi/calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/python
# import cgitb
from urllib.parse import urlparse

# cgitb.enable()


def parse_path(referer):
'''
Parse the path provided and respond with appropriate content. Returns the
status of the request and content of response.
'''
response = ''
status = '200 OK'

scheme, netloc, path, params, query, fragment = urlparse(referer)
baseurl = "{}://{}/".format(scheme, netloc)

args = path.split('/')
args.pop(0) # First element is always a null string, get rid of it

# We only support paths, return a 501 if we get anything else
if params or query or fragment != '':
raise NotImplementedError
# Send the examples
if path == '/' or '':
response = """
<h1>Instructions for use:</h1>
<h2>Separate all arguments with slashes.</h2>
<h3>Multiply</h3>
<p>Append 'multiply' to the URL string followed by the multiplicands.</p>
<a href="{0}multiply/3/4">{0}multiply/3/4</a> = 12
<h3>Divide</h3>
<p>Append 'divide', then the dividend and divisor.</p>
Example: <a href="{0}divide/12/4">{0}divide/12/4</a> = 3
<h3>Add</h3>
<p>Append 'add', followed by any number of addends.</p>
Example: <a href="{0}add/23/52">{0}add/23/52</a> = 75
<h3>Subtract</h3>
<p>Append 'subtract', followed by the minuend and the subtrahend. (I had no idea those were words before today.)</p>
Example: <a href="{0}subtract/36/4">{0}subtract/36/4</a> = 12
""".format(baseurl)
# Do the math
elif args[0].lower() == 'multiply' or 'divide' or 'add' or 'subtract':
response = do_some_math(args)
# This is supposed to return a NameError when the path doesn't match, but it
# returns a 500 instead. I can't figure out why.
else:
raise NameError
return status, response


def do_some_math(args):
operation = args[0]
args.pop(0)
if len(args) > 2:
raise SyntaxError
first = float(args[0])
second = float(args[1])
if operation == 'subtract':
operator = '-'
result = first - second
elif operation == 'divide':
if float(second) == 0.0:
raise ValueError
operator = '/'
result = first / second
elif operation == 'multiply':
operator = '*'
result = first * second
else:
operator = '+'
result = first + second
content = "{} {} {} = {}".format(first, operator, second, result)

return content


def error_content(status_code, referer):
'''
Creates error pages based on an intentionally limited number of HTTP error
codes. Returns the HTTP error code string and content of the error message.
'''
error_codes = {400: '400 Bad request',
404: '404 Not Found',
500: '500 Internal Server Error',
501: '501 Not Implemented'
}
scheme, netloc, path, params, query, fragment = urlparse(referer)
status_str = error_codes[status_code]
content = '<h1>{}</h1>Referer was <a href="{}">{}</a>'.format(status_str, referer, referer)
return status_str, content


def application(environ, start_response):
header = """<html>
<head>
<title>WSGI Calculator</title>
</head>
<body>"""
footer = """
</body>
</html>"""

# I'm sure there was an easier way to get the full request, but I couldn't find it.
http_host = environ.get('HTTP_HOST', None)
path = environ.get('PATH_INFO', None)
query_string = environ.get('QUERY_STRING', None)
examples = '<p><a href="http://{}/">Examples</a>'.format(http_host)
if query_string == '':
request = "http://{}{}".format(http_host, path)
else:
request = "http://{}{}?{}".format(http_host, path, query_string)

try:
status, content = parse_path(request)
except SyntaxError or ValueError:
status, content = error_content(400, request)
except NameError:
status, content = error_content(404, request)
except NotImplementedError:
status, content = error_content(501, request)
except Exception:
status, content = error_content(500, request)

content = header + content + examples + footer
response_headers = [('Content-Type', 'text/html'),
('Content-Length', str(len(content)))]
start_response(status, response_headers)
return[content.encode('utf8')]


if __name__ == '__main__':
from wsgiref.simple_server import make_server
srv = make_server('0.0.0.0', 8080, application)
srv.serve_forever()
2 changes: 0 additions & 2 deletions source/readings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,6 @@ Session 4 - APIs and Mashups
* `xmlrpc spec (short) <http://www.xmlrpc.com/spec>`_
* `the SOAP specification <http://www.w3.org/TR/soap/>`_
* `json overview and spec (short) <http://www.json.org/>`_
* `How I Explained REST to My Wife (Tomayko 2004)
<http://tomayko.com/writings/rest-to-my-wife>`_
* `A Brief Introduction to REST (Tilkov 2007)
<http://www.infoq.com/articles/rest-introduction>`_
* `Wikipedia on REST
Expand Down