Assignment 7, CSC430, Winter 2012
1 Goal
The goal of this assignment is to write a simple web server that can run programs, and can inform users about what’s going on.
More specifically, your servers must evaluate programs written in the Froggy language–no, there’s no good reason for the name that I can think of–which consists of the following forms:
sleep: sleep for n seconds (where n is a real number greater than zero).
set-mood: set the program’s "mood" string to the given string s.
goto: transfer the running program to the server indicated by the given address addr, using an http request as described below. Stop running the program locally.
loop: run the body expression exactly n times.
seq: evaluate each of the expressions in the list exps once.
You’ll notice that none of these expressions evaluate to a value; that’s
okay. Also, it’s a very very simple language; it can’t run forever, and
it can’t really compute anything. In fact, adding those
features—
2 Server Requests
Your servers must listen on a particular port—
status: returns a web page indicating the names of the programs running on the server, and what each of their moods are. This page is to be viewed in a web browser, and you may use your discretion in applying visual frou-frou. This request uses the URL path /status.html. If my server were running on example.com, on port 8209, then the URL for the status page would be http://example.com:8209/status.html This request should be an ordinary "GET" request, since no additional data is being passed.
JSON status: returns the status of the server as a JSON object, so that you (and I) can run tests on it without parsing your web page. This request uses the URL path /json-status.html The result should be encoded as JSON, in the format given below.
run: runs the given program. This request need not respond. This request uses the URL path /run.html, and the continuation to be run is encoded as a POST request. The encoding of the POST request is given below.
3 Encoding
3.1 Encoding running programs as continuations
Passing a continuation/program to a new server requires encoding it as a stream of bytes in a POST request.
We’ll be using JSON to encode these values–it’s a *very* common exchange format, and you should be able to find a JSON package for nearly any environment.
In a run request, the byte string associated with the POST request should be a single JSON-encoded string, represented using utf-8, with this format:
{name:<programname>,mood:<programmood>,kont:<programkont>}
... where <programname> and <programmood> are strings, and <programkont> is another JSON-encoded value, following this template:
<programkont> = {ty:"base-k",args:[]} |
| {ty:"loop-k",args:[<number>,<exp>,<programkont>]} |
| {ty:"seq-k",args:[[<exp>,...],<programkont>]} |
... where <number> is a number, [<exp>,...] is a comma-separated array of <exp>s, and an exp is another JSON-encoded value, following this template:
<exp> = {ty:"sleep",args:[<number>]} |
| {ty:"set-mood",args:[<string>]} |
| {ty:"loop",args:[<number>,<exp>]} |
| {ty:"goto",args:[<string>,<number>]} |
| {ty:"seq",args:[<exp>,...]} |
... with the same conventions as above, and where <string> is a string.
As an example, Here’s an example of a POST bytestring:
{"name": "Wilhelm", "kont": {"args": [14, {"args": [{"args": ["confused!"], "ty": "set-mood"}, {"args": [3.2], "ty": "sleep"}], "ty": "seq"}, {"args": [], "ty": "base-k"}], "ty": "loop-k"}, "mood": "AIIEEE!"}
Note that standard JSON rules apply: whitespace is unimportant outside of strings, and the order of fields within curly braces is also unimportant.
This byte string represents a program called "Wilhelm" that will loop 14 more times, each time changing its mood to "Confused!" and then sleeping for 3.2 seconds.
Here’s another one:
{"name": "Columbus", "kont": {"args": [[{"args": [1492], "ty": "sleep"}, {"args": ["new-world.org", 8829], "ty": "goto"}], {"args": [], "ty": "base-k"}], "ty": "seq-k"}, "mood": "leaving for America"}
This one represents a program called "Columbus" that will wait 1492 seconds, then move itself to the server running on port 8829 at "new-world.org".
4 Encoding status
In response to a json-status.html request, your server must respond with a list of the programs running on the server, and their moods. This should be encoded as a list of JSON objects where each one has a name and mood field. The response should be given the application/json MIME type. So, a status request to a server might result in this:
[{"name": "MyProgram","mood": "sulky"},{"name":"Dr. Chipper","mood":"relentlessly optimistic"}]
5 Threading
Your life will be much simpler if you choose an environment with support for threading, and if you use it to run each program on its own thread.
6 HANDIN
Handin has two parts: you must leave your server running over the weekend, and you must hand in a bundle (.tar or .zip) containing your code.
Submit your code by visiting the URL
https://brinckerhoff.org:7980
... and signing in.
Note that this site uses a self-signed certificate with SHA1 fingerprint
20:F1:42:B4:5E:6B:B9:5A:7D:AC:1A:F0:67:E3:D0:4C:E7:63:88:8E
... so you’ll probably have to confirm a security exception.
Include in your bundle all of the code that you wrote, but don’t include any library or framework code; my goal is to read your code, not to run it. Be sure to include a TEAM file that tells whom you worked with. A README file indicating what each file contains would also be helpful.
7 Partners
On this assignment, you may work with any partner that you like (in the class, of course).
8 Suggested Development Steps
Trying to tackle this project all at once will probably get you in trouble. I would suggest following these incremental steps:
Develop an interpreter for the language. "Cheat" on the interesting parts like moving from server to server.
Transform your interpreter into continuation passing style.
Add threading, so that two programs can run at the same time.
Model "servers" within a single process, by explicitly representing the server state. Update your interpreter so that it can queue continuations on another server. Keep in mind that the "servers" here are just separate collections of running threads. Add a query function that can check what programs are running on a server. Write lots of tests.
Without changing the model of servers, develop serialization and deserialization so that you can pass the continuations between servers as text strings.
Separately, develop a small server that accepts actual http connections.
Merge the two!
9 Sample Code
Here’s a small Racket program that starts a server on a given port that just echoes the text of the POST request back:
#lang racket (require web-server/servlet-env web-server/http/xexpr web-server/http) ; the function that handles requests: (define (start request) (define post-bytes (request-post-data/raw request)) (response/xexpr `(html (p "raw POST bytes:") (pre ,(format "~a" post-bytes))))) ; start serving the 'start' function on port 8054: (serve/servlet start #:servlet-regexp #px".*" #:port 8054 #:launch-browser? #f #:listen-ip #f)
Here’s a small racket program that sends a POST request to a server, and gathers the response bytes:
#lang racket (require net/url (planet dherman/json)) ; ping the server ; the URL to send the request to: (define local-url (string->url "http://localhost:8054/ping.html")) ; the byte-string to attach as the POST bytes: (define post-bytes (string->bytes/utf-8 (jsexpr->json (hash "a" 134 "z" (list 4 5))))) ; make the request, receiving the port for the response: (define response-port (post-pure-port local-url post-bytes)) ; suck all of the characters out of the port: (regexp-match #px".*" response-port)