From 438808939ae22aeca05b0010392f99e38bee04a8 Mon Sep 17 00:00:00 2001 From: Marc Teale Date: Mon, 18 Jan 2016 19:52:42 -0800 Subject: [PATCH 1/3] It works. --- resources/session02/http_server.py | 92 ++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/resources/session02/http_server.py b/resources/session02/http_server.py index d5aaf480..ca945a9c 100644 --- a/resources/session02/http_server.py +++ b/resources/session02/http_server.py @@ -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) @@ -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() From f40f4e308efe7fb5f970f396dbf77320b880ed69 Mon Sep 17 00:00:00 2001 From: marcteale Date: Tue, 26 Jan 2016 10:21:41 -0800 Subject: [PATCH 2/3] Assignment 3 --- resources/session03/wsgi/calculator.py | 136 +++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 resources/session03/wsgi/calculator.py diff --git a/resources/session03/wsgi/calculator.py b/resources/session03/wsgi/calculator.py new file mode 100644 index 00000000..c2f00936 --- /dev/null +++ b/resources/session03/wsgi/calculator.py @@ -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 = """ +

Instructions for use:

+

Separate all arguments with slashes.

+

Multiply

+

Append 'multiply' to the URL string followed by the multiplicands.

+ {0}multiply/3/4 = 12 +

Divide

+

Append 'divide', then the dividend and divisor.

+ Example: {0}divide/12/4 = 3 +

Add

+

Append 'add', followed by any number of addends.

+ Example: {0}add/23/52 = 75 +

Subtract

+

Append 'subtract', followed by the minuend and the subtrahend. (I had no idea those were words before today.)

+ Example: {0}subtract/36/4 = 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 = '

{}

Referer was {}'.format(status_str, referer, referer) + return status_str, content + + +def application(environ, start_response): + header = """ + + WSGI Calculator + + """ + footer = """ + + """ + + # 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 = '

Examples'.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() From 62f6b2cdfc52dda75d8228731f57c776ad994477 Mon Sep 17 00:00:00 2001 From: Marc Teale Date: Tue, 12 Jul 2016 13:18:47 -0700 Subject: [PATCH 3/3] Remove 'How I Explained REST to My Wife' The creator took down the content. --- source/readings.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/readings.rst b/source/readings.rst index 4a9ef294..ba8bb062 100644 --- a/source/readings.rst +++ b/source/readings.rst @@ -145,8 +145,6 @@ Session 4 - APIs and Mashups * `xmlrpc spec (short) `_ * `the SOAP specification `_ * `json overview and spec (short) `_ -* `How I Explained REST to My Wife (Tomayko 2004) - `_ * `A Brief Introduction to REST (Tilkov 2007) `_ * `Wikipedia on REST