{"action":"created","comment":{"url":"https://api.github.com/repos/aosabook/500lines/pulls/comments/56810306","id":56810306,"diff_hunk":"@@ -0,0 +1,872 @@\n+title: An Event-driven Web Framework\n+author: Leo Zovic\n+\n+In 2013, I decided to write a [web-based game prototyping tool](https://github.com/Inaimathi/deal) for card and board games called _House_. In these types of games, it is common for one player to wait for another player to make a move; however, when the other player finally does take action, we would like for the waiting player to be notified of the move quickly thereafter.\n+\n+This is a problem that turns out to be more complicated than it first seems. In this chapter, we'll explore the issues with using HTTP to build this sort of interaction, and then we'll build a _web framework_ in Common Lisp that allows us to solve similar problems in the future. \n+\n+## The Basics of HTTP Servers\n+\n+At the simplest level, an HTTP exchange is a single request followed by a single response. A _client_ sends a request, which includes a resource identifier, an HTTP version tag, some headers and some parameters. The _server_ parses that request, figures out what to do about it, and sends a response which includes the same HTTP version tag, a response code, some headers and a request body. (For more on this, see the FIXME web server chapter.)\n+\n+Notice that, in this description, the server responds to a request from a specific client. In our case, we want each player to be updated about _any_ moves as soon as they happen, rather than only getting notifications when their own move is made. This means we need the server to _push_ messages to clients without first receiving a request for the information [^polling]. \n+\n+[^polling]: One solution to this problem is to force the clients to _poll_ the server. That is, each client would periodically send the server a request asking if anything has changed. This can work for simple applications, but in this chapter we're going to focus on the solutions available to you when this model stops working.\n+\n+There are several standard approaches to enabling server push over HTTP. \n+\n+### Comet/Longpoll\n+\n+The \"longpoll\" technique has the client send the server a new request as soon as it receives a response. Instead of fulfilling that request right away, the server waits on a subsequent event to respond. This is a bit of a semantic distinction, since the client is still taking action on the users' behalf on every update.\n+\n+### Server-sent Events (SSE)\n+\n+Server-sent events require that the client initiates a connection and then keeps it open. The server periodically writes new data to the connection without closing it, and the client interprets incoming new messages as they arrive rather than waiting for the response connection to terminate. This is a bit more efficient than the Comet/Longpoll approach because each message doesn't have to incur the overhead of new HTTP headers.\n+\n+### Websockets\n+\n+Websockets are a communication protocol built on top of HTTP. The server and client open up an HTTP conversation, then perform a handshake and protocol escalation. The end result is that they're still communicating over TCP/IP, but they're not using HTTP to do it at all. The advantage this has over SSEs is that you can customize the protocol for efficiency.\n+\n+### Long-Lived Connections\n+\n+These three approaches are quite different from one another, but they all share an important characteristic: They all depend on long-lived connections. Longpolling depends on the server keeping requests around until new data is available, SSEs keep an open stream between client and server to which data is periodically written, and Websockets change the protocol a particular connection is speaking, but leave it open. \n+\n+To see why this might cause problems for your average HTTP server, let's consider how the underlying implementation might work.\n+\n+### Traditional HTTP Server Architecture\n+\n+A single HTTP server processes many requests concurrently. Historically, many HTTP servers have used a _thread-per-request_ architecture. That is, for each incoming request, the server creates a thread to do the work necessary to respond. \n+\n+Since each of these connections is intended to be short-lived, we don't need many threads executing in parallel to handle them all. This model also simplifies the _implementation_ of the server by enabling the server programmer to write code as if there were only one connection being handled at at any given time. It also gives us the freedom to clean up failed or \"zombie\" connections and their associated resources by killing the corresponding thread and letting the garbage collector do its job. \n+\n+The key observation is that an HTTP server hosting a \"traditional\" web application that has $N$ concurrent users might only need to handle a very small fraction of $N$ requests _in parallel_ to succeed. For the type of interactive application that we are trying to build in our initial problem statement, $N$ users of our application will almost certainly require the application to maintain at least $N$ connections in parallel at once.\n+\n+The consequence of keeping long-lived connections around is that we're going to want either:\n+\n+- A platform where threads are \"cheap\" enough that we can use large numbers of them at once\n+- A server architecture that can handle many connections with a single thread\n+\n+There are programming environments such as [Racket](http://racket-lang.org/), [Erlang](http://www.erlang.org/), and [Haskell](http://hackage.haskell.org/package/base-4.7.0.1/docs/Control-Concurrent.html) that do provide thread-like constructs that are \"lightweight\" enough to consider the first option. This approach requires the programmer to explicitly deal with synchronization issues, which are going to be much more prevalent in a system where connections are open for a long time and likely all competing for similar resources. Specifically, if we have some sort of central data shared by several users simltaneously, we will need to coordinate reads and writes of that data in some way. \n+\n+If we don't have cheap threads at our disposal or we are unwilling to work with explicit synchronization, we must consider having a single thread handle many connections[^mn]. In this model, our single thread is going to be handling tiny \"slices\" of many requests all at once, switching between them as efficiently as it possibly can. This system architecture pattern is most commonly referred to as _event-driven_ or _event-based_[^eventbased]. \n+\n+[^mn]: We could consider a more general system that handles $N$ concurrent users with $M$ threads for some configurable value of $M$; in this model, the $N$ connections are said to be _multiplexed_ across the $M$ threads. In this chapter, we are going to focus on writing a program where $M$ is fixed at 1; however, the lessons learned here should be partially applicable to the more general model. \n+\n+[^eventbased]: This nomenclature is a bit confusing, and has its origin in early operating-systems research. It refers to how communication is done between multiple concurrent processes. In a thread-based system, communication is done through a synchronized resource such as shared memory. In an event-based system, processes generally communicate through a queue where they post items that describe what they have done or what they want done, which is maintained by our single thread of execution. Since these items generally describe desired or past actions, they are referred to as 'events'. \n+\n+Since we are only managing a single thread, we don't have to worry as much about protecting shared resources from simultaneous access. However, we do have a unique problem of our own in this model. Since our single thread is working on all in-flight requests at once, we must make sure that it __never blocks__. Blocking on any connection blocks the entire server from making progress on any other request. We have to be able to move on to another client if the current one can't be serviced further, and we need to be able to do so in a manner that doesn't throw out all of the work done so far[^crawler]. \n+\n+[^crawler]: See the FIXME web crawler chapter for another take on this problem. \n+\n+While it is uncommon for a programmer to explicitly tell a thread to stop working, many common operations carry a risk of blocking. Because threads are so prevalent and reasoning about asychronousity is a heavy burden on the programmer, many languages and their frameworks assume that blocking on IO is a desirable property. This makes it very easy to block somewhere _by accident_. Luckily, Common Lisp does provide us with a minimal set of asynchronous IO primitives which we can build on top of.\n+\n+### Architectural decisions\n+\n+Now that we've studied the background of this problem, we've arrived at the point where we need to make informed decisions about _what_ we are building.\n+\n+At the time I started thinking about this project, Common Lisp didn't have a complete green-thread implementation, and the [standard portable threading library](http://common-lisp.net/project/bordeaux-threads/) doesn't qualify as \"really REALLY cheap\". The options amounted to either picking a different language, or building an event-driven web server for my purpose. I chose the latter.\n+\n+In addition to the server architecture, we also need to choose which of the 3 server-push approaches to use. The use-case we are considering (an interactive multiplayer board game) requires frequent updates to each client, but relatively sparse requests _from_ each client, which fits the SSE approach to pushing updates, so we'll go with this.\n+\n+Now that we've motivated our architectural decision and decided on a mechanism for simulating bidirectional communication between clients and server, let's get started on building our web framework. We'll start by building a relatively \"dumb\" server first, and then we'll extend it into a web-application framework that lets us focus on _what_ our heavily-interactive program needs to do, and not _how_ it is doing it.\n+\n+## Building an Event-Driven Web Server\n+\n+### The Event Loop\n+\n+The core of every event-driven program is the _event loop_, which looks something like this: \n+\n+```lisp\n+\t(defmethod start ((port integer))\n+\t  (let ((server (socket-listen usocket:*wildcard-host* port :reuse-address t :element-type 'octet))\n+\t\t    (conns (make-hash-table)))\n+\t    (unwind-protect\n+\t\t    (loop (loop for ready in (wait-for-input (cons server (alexandria:hash-table-keys conns)) :ready-only t)\n+\t\t\t            do (process-ready ready conns)))\n+\t     (loop for c being the hash-keys of conns\n+\t\t     do (loop while (socket-close c)))\n+\t     (loop while (socket-close server)))))\n+```\n+\n+In this loop, we have:\n+\n+- a server socket that listens for incoming connections;\n+- a structure to store connections/buffers;\n+- an infinite loop waiting for new handshakes or incoming data on an existing connection;\n+- cleanup clauses to prevent dangling sockets that are unexpectedly killed (e.g. by an interrupt)\n+\n+If you haven't written a Common Lisp program before, this code block requires some explanation. What we have written here is a _method definition_. While Lisp is popularly known as a functional language, it also has its own system for object-oriented programming called \"The Common Lisp Object System\", which is usually abbreviated as \"CLOS\"[^CLOSpronounce]. \n+\n+[^CLOSpronounce]: Pronounced \"kloss\", \"see-loss\" or \"see-lows\", depending on who you talk to.\n+\n+### CLOS and Generic Functions\n+\n+In CLOS, instead of focusing on classes and methods, we instead write _generic functions_[^juliachap] that are implemented as collections of _methods_. In this model, methods don't _belong_ to classes, they _specialize on_ types. The `start` method we just wrote is a unary method where the argument `port` is _specialized on_ the type `integer`. This means that we could have several implementations of `start` where `port` varies in type, and the runtime will select which implementation to use depending on the type of `port` when `start` is called.\n+\n+[^juliachap]: The Julia programming language takes a similar approach to object-oriented programming; you can learn more about it in FIXME.\n+\n+More generally, methods can specialize on more than one argument. When a `method` is called, the runtime:\n+\n+- dispatches on the type of its arguments to figure out which method body should be run, and\n+- runs the appropriate function.\n+\n+You can think about it as a giant table with \"Type Name\" down the first column and \"Operation\" across the first row, as in Table FIXME.\n+\n+```\n+            | Addition | Subtraction | Multiplication |\n+    ----------------------------------------------------------\n+    Integer |          |             |                |\n+\t-----------------------------------------------------\n+\tReal    |          |             |                |\n+\t-----------------------------------------------\n+\tComplex |          |             |\n+```\n+\n+NOTE: Is this a misleading analogy? If we consider the specific case of these being binary operators (let's not handle things like (+ x y z w)) isn't it more like an N-d table, or for simplicity:\n+\n+```\n+\n+------------------------------------------------------------------------\n+Operation | Type of x | Type of y | Method to call \n+------------------------------------------------------------------------\n+ +        | Int       | Int       | (defmethod + ((x int) (y int)) ... )\n+------------------------------------------------------------------------\n+ +        | Int       | Float     | (defmethod + ((x int) (y float)) ... )\n+------------------------------------------------------------------------\n+ *        | Int       | Int       | (defmethod * ((x int) (y int)) ... )\n+------------------------------------------------------------------------\n+```\n+\n+From a theoretical perspective, the class-focused approach and the function-focused approach can be seen as orthogonal approaches to the same problem. Class-based systems tend to organize methods with the class they \"belong\" to, whereas in a generic-function-based system each method has all of its implementations grouped in one place.\n+\n+This means that in a class-based system, it's difficult to know what classes implement method `foo`; in a generic-function-based system, it is difficult to know all the methods that specialize on type `bar`. Depending on the nature of your system and your problem, you may want one or the other. (FIXME this probably needs more depth. Or just say how to make this decision is outside the scope of this chapter, we'll just give you a taste of what it's like to work in this type of system here.)\n+\n+### Processing sockets\n+\n+We'll see another generic function at work in `process-ready`, which was called earlier from our event loop. It processes a ready socket with one of two methods, depending on the type of socket we are handling.\n+\n+FIXME: What precisely is the difference between `stream-usocket` and `stream-server-usocket`? What coerces our sockets to one of these two types in the first place?\n+\n+If a `stream-server-socket` is `ready`, that means there's a new client socket waiting to start a conversation. We call `socket-accept` to accept the connection, and then put the result in our connection table so that our event loop can begin processing it with the others.\n+\n+```lisp\n+\t(defmethod process-ready ((ready stream-server-usocket) (conns hash-table))\n+\t  (setf (gethash (socket-accept ready :element-type 'octet) conns) nil))\n+```\n+\n+When a `stream-usocket` is `ready`, that means that it has some bytes ready for us to read. (It's also possible that the other party has terminated the connection). \n+\n+```lisp\n+\t(defmethod process-ready ((ready stream-usocket) (conns hash-table))\n+\t  (let ((buf (or (gethash ready conns)\n+\t\t\t (setf (gethash ready conns) (make-instance 'buffer :bi-stream (flex-stream ready))))))\n+\t    (if (eq :eof (buffer! buf))\n+\t\t(ignore-errors \n+\t\t  (remhash ready conns)\n+\t\t  (socket-close ready))\n+\t\t(let ((too-big? (> (total-buffered buf) +max-request-size+))\n+\t\t      (too-old? (> (- (get-universal-time) (started buf)) +max-request-age+))\n+\t\t      (too-needy? (> (tries buf) +max-buffer-tries+)))\n+\t\t  (cond (too-big?\n+\t\t\t (error! +413+ ready)\n+\t\t\t (remhash ready conns))\n+\t\t\t((or too-old? too-needy?)\n+\t\t\t (error! +400+ ready)\n+\t\t\t (remhash ready conns))\n+\t\t\t((and (request buf) (zerop (expecting buf)))\n+\t\t\t (remhash ready conns)\n+\t\t\t (when (contents buf)\n+\t\t\t   (setf (parameters (request buf))\n+\t\t\t\t (nconc (parse buf) (parameters (request buf)))))\t     \n+\t\t\t (handler-case\n+\t\t\t     (handle-request ready (request buf))\n+\t\t\t   (http-assertion-error () (error! +400+ ready))\n+\t\t\t   ((and (not warning)\n+\t\t\t     (not simple-error)) (e)\n+\t\t\t     (error! +500+ ready e))))\n+\t\t\t(t\n+\t\t\t (setf (contents buf) nil)))))))\n+```\n+\n+This is more involved than the first case. We: \n+\n+1. Get the buffer associated with this socket, or create it if it doesn't exist yet;\n+2. Read output into that buffer, which happens in the call to `buffer!`;\n+3. If that read got us an `:eof`, it means the other side hung up, so we discard the socket _and_ its buffer;\n+4. Otherwise, we check if the buffer is one of `complete?`, `too-big?`, `too-old?` or `too-needy?`. If it's any of them, we remove it from the connections table and return the appropriate HTTP response.\n+\n+This is the first time we're seeing I/O in our event loop. In our discussion in FIXME SECTIONREF, we mentioned that we have to be very careful about I/O in an event-driven system, because we could accidentally block our single thread. So, what do we do here to ensure that this doesn't happen? We have to explore our implementation of `buffer!` to find out exactly how this works.\n+\n+### Processing Connections Without Blocking\n+\n+The basis of our approach to processing connections without blocking is the library function [`read-char-no-hang`](http://clhs.lisp.se/Body/f_rd_c_1.htm), which immediately returns `nil` when called on a stream that has no available data. Where there is data to be read, we use a buffer to store intermediate input for this connection.\n+\n+```lisp\n+    (defmethod buffer! ((buffer buffer))\n+      (handler-case\n+          (let ((stream (bi-stream buffer)))\n+    \t(incf (tries buffer))\n+    \t(loop for char = (read-char-no-hang stream) until (null char)\n+    \t   do (push char (contents buffer))\n+    \t   do (incf (total-buffered buffer))\n+    \t   when (request buffer) do (decf (expecting buffer))\n+    \t   when (line-terminated? (contents buffer))\n+    \t   do (multiple-value-bind (parsed expecting) (parse buffer)\n+    \t\t(setf (request buffer) parsed\n+    \t\t      (expecting buffer) expecting)\n+    \t\t(return char))\n+    \t   when (> (total-buffered buffer) +max-request-size+) return char\n+    \t   finally (return char)))\n+        (error () :eof)))\n+```\n+\n+When `buffer!` is called on a `buffer`, it:\n+- increments the `tries` count, so that we can evict \"needy\" buffers in `process-ready`;\n+- loops to read characters from the input stream, and \n+- returns the last character it read if it has read all of the available input. \n+- It also tracks any `\\r\\n\\r\\n` sequences so that we can later detect complete requests. \n+- Finally, any error results it returns an `:eof` to signal that `process-ready` should discard this particular connection.\n+\n+The `buffer` type is a CLOS _class_. Classes in CLOS let us define a type with fields called `slots`. We don't see the behaviours associated with `buffer` on the class definition, because (as we've already learned), we do that using generic functions like `buffer!`.\n+\n+`defclass` does allow us to specify getters/setters (`reader`s/`accessor`s), and slot initializers; `:initform` specifies a default value, while `:initarg` identifies a hook that the caller of `make-instance` can use to provide a default value. \n+\n+NOTE: Generic functions can specialize on types that aren't classes, right? The example of `port` from the first method definition didn't seem like it was a class. I get the impression that classes are just giving us some syntactic sugar over common operations on structs.\n+\n+```lisp\n+\t(defclass buffer ()\n+\t  ((tries :accessor tries :initform 0)\n+\t   (contents :accessor contents :initform nil)\n+\t   (bi-stream :reader bi-stream :initarg :bi-stream)\n+\t   (total-buffered :accessor total-buffered :initform 0)\n+\t   (started :reader started :initform (get-universal-time))\n+\t   (request :accessor request :initform nil)\n+\t   (expecting :accessor expecting :initform 0)))\n+```\n+\n+Our `buffer` class has six slots; seen in FIXME TABLE:\n+\n+TABLE THIS\n+- `tries`, which keeps count of how many times we've tried reading into this buffer\n+- `contents`, which contains what we've read so far\n+- `bi-stream`, which a hack around some of those Common Lisp-specific, non-blocking-IO annoyances I mentioned earlier\n+- `total-buffered`, which is a count of chars we've read so far\n+- `started`, which is a timestamp that tells us when we created this buffer\n+- `request`, which will eventually contain the request we construct from buffered data\n+- `expecting`, which will signal how many more chars we're expecting (if any) after we buffer the request headers\n+\n+### Interpreting Requests\n+\n+Now that we've seen how we incrementally assemble full requests from bits of data that are pooled into our buffers, what happens when we have a full request ready for handling? This happens in the method `handle-request`.\n+\n+```lisp\n+\t(defmethod handle-request ((socket usocket) (req request))\n+\t  (aif (lookup (resource req) *handlers*)\n+\t       (funcall it socket (parameters req))\n+\t       (error! +404+ socket)))\n+```\n+\n+This method adds another layer of error handling so that if the request is old, big, or needy, we can send a `400` response to indicate that the client provided us with some bad or slow data. However, if any _other_ error happens here, it's because the programer made a mistake defining a _handler_, which should be treated as a `500` error. This will inform the client that something went wrong on the server a result of their legitimate request.\n+\n+If the request is well-formed, we do the tiny and obvious job of looking up the requested resource in the `*handlers*` table. If we find one, we `funcall` `it`, passing along the client `socket` as well as the parsed request parameters. If there's no matching handler in the `*handlers*` table, we instead send along a `404` error. The handler system will be part of our full-fledged _web framework_, which we'll discuss in a later section.\n+\n+We still haven't seen how requests are parsed and interpreted from one of our buffers, though. Let's look at that next:\n+\n+```lisp\n+\t(defmethod parse ((buf buffer))\n+\t  (let ((str (coerce (reverse (contents buf)) 'string)))\n+\t    (if (request buf)\n+\t\t    (parse-params str)\n+\t\t    (parse str))))\n+```\n+\n+This high-level method delegates to a specialization of `parse` that works with plain strings or to `parse-params` that interprets the buffer contents as HTTP parameters. These are called depending on how much of the request we've already processed; the final `parse` happens when we already have a partial `request` saved in the given `buffer`, at which point we're only looking to parse the request body. \n+\n+\n+```lisp\n+\t(defmethod parse ((str string))\n+\t  (let ((lines (split \"\\\\r?\\\\n\" str)))\n+\t    (destructuring-bind (req-type path http-version) (split \" \" (pop lines))\n+\t      (declare (ignore req-type))\n+\t\t  (assert-http (string= http-version \"HTTP/1.1\"))\n+\t      (let* ((path-pieces (split \"\\\\?\" path))\n+\t\t         (resource (first path-pieces))\n+\t\t         (parameters (second path-pieces))\n+\t\t         (req (make-instance 'request :resource resource)))\n+\t\t    (loop for header = (pop lines) for (name value) = (split \": \" header)\n+\t\t          until (null name) do (push (cons (->keyword name) value) (headers req)))\n+\t\t    (setf (parameters req) (parse-params parameters))\n+\t\t    req))))\n+\n+\t(defmethod parse-params ((params null)) nil)\n+\n+\t(defmethod parse-params ((params string))\n+\t  (loop for pair in (split \"&\" params)\n+\t        for (name val) = (split \"=\" pair)\n+\t        collect (cons (->keyword name) (or val \"\"))))\n+```\n+\n+In the `parse` method specializing on `string`, we transform the content into usable pieces. We do this on strings instead of working directly with buffers because this makes it easier to test the actual parsing code in an environment like an interpreter or REPL. \n+\n+The parsing process is:\n+\n+1. split on `\"\\\\r?\\\\n\"`\n+2. split the first line of that on `\" \"` to get the request type (`POST`, `GET`, etc)/URI path/http-version\n+3. assert that we're dealing with an `HTTP/1.1` request\n+4. split the URI path on `\"?\"`, which gives us plain resource separate from any potential `GET` parameters\n+5. make a new `request` instance with the resource in place\n+6. populate that `request` instance with each split header line\n+7. set that `request`s parameters to the result of parsing our `GET` parameters\n+\n+As you might expect by now, `request` is an instance of a CLOS class:\n+\n+```lisp\n+\t(defclass request ()\n+\t  ((resource :accessor resource :initarg :resource)\n+\t   (headers :accessor headers :initarg :headers :initform nil)\n+\t   (parameters :accessor parameters :initarg :parameters :initform nil)))\n+```\n+\n+We've now seen how our clients can send requests and have them interpreted and handled by our server. The last thing we have to implement as part of our core server interface is the capability to write responses back to the client.\n+\n+### Rendering Responses\n+\n+Before we discuss rendering responses, we have to consider that there are two kinds of responses that we may be returning to our clients. The first is a \"normal\" HTTP response, complete with HTTP headers and body. We represent these kinds of responses with instances of the `response` class:\n+\n+```lisp\n+\t(defclass response ()\n+\t  ((content-type :accessor content-type :initform \"text/html\" :initarg :content-type)\n+\t   (charset :accessor charset :initform \"utf-8\")\n+\t   (response-code :accessor response-code :initform \"200 OK\" :initarg :response-code)\n+\t   (keep-alive? :accessor keep-alive? :initform nil :initarg :keep-alive?)\n+\t   (body :accessor body :initform nil :initarg :body)))\n+```\n+\n+The second is an [SSE message](http://www.w3.org/TR/eventsource/), which we will use to send an incremental update to our clients.\n+\n+```lisp\n+\t(defclass sse ()\n+\t  ((id :reader id :initarg :id :initform nil)\n+\t   (event :reader event :initarg :event :initform nil)\n+       (retry :reader retry :initarg :retry :initform nil)\n+\t   (data :reader data :initarg :data)))\n+```\n+\n+We'll send an HTTP response whenever we receive a full HTTP request; however, how do we know when and where to send SSE messages without an originating client request?\n+\n+A simple solution is to register _channels_, to which we'll subscribe `socket`s as necessary.\n+\n+```lisp\n+\t(defparameter *channels* (make-hash-table))\n+\n+\t(defmethod subscribe! ((channel symbol) (sock usocket))\n+\t  (push sock (gethash channel *channels*))\n+\t  nil)\n+```\n+\n+NOTE: What is defparameter?","path":"event-web-framework/draft.markdown","position":367,"original_position":367,"commit_id":"93201bdf5b809aa0a4fe05855e30ad54c13963ec","original_commit_id":"93201bdf5b809aa0a4fe05855e30ad54c13963ec","user":{"login":"MichaelDiBernardo","id":2091754,"avatar_url":"https://avatars.githubusercontent.com/u/2091754?v=3","gravatar_id":"","url":"https://api.github.com/users/MichaelDiBernardo","html_url":"https://github.com/MichaelDiBernardo","followers_url":"https://api.github.com/users/MichaelDiBernardo/followers","following_url":"https://api.github.com/users/MichaelDiBernardo/following{/other_user}","gists_url":"https://api.github.com/users/MichaelDiBernardo/gists{/gist_id}","starred_url":"https://api.github.com/users/MichaelDiBernardo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MichaelDiBernardo/subscriptions","organizations_url":"https://api.github.com/users/MichaelDiBernardo/orgs","repos_url":"https://api.github.com/users/MichaelDiBernardo/repos","events_url":"https://api.github.com/users/MichaelDiBernardo/events{/privacy}","received_events_url":"https://api.github.com/users/MichaelDiBernardo/received_events","type":"User","site_admin":false},"body":"Cool, thanks. ","created_at":"2016-03-21T11:48:48Z","updated_at":"2016-03-21T11:48:48Z","html_url":"https://github.com/aosabook/500lines/pull/195#discussion_r56810306","pull_request_url":"https://api.github.com/repos/aosabook/500lines/pulls/195","_links":{"self":{"href":"https://api.github.com/repos/aosabook/500lines/pulls/comments/56810306"},"html":{"href":"https://github.com/aosabook/500lines/pull/195#discussion_r56810306"},"pull_request":{"href":"https://api.github.com/repos/aosabook/500lines/pulls/195"}}},"pull_request":{"url":"https://api.github.com/repos/aosabook/500lines/pulls/195","id":61006647,"html_url":"https://github.com/aosabook/500lines/pull/195","diff_url":"https://github.com/aosabook/500lines/pull/195.diff","patch_url":"https://github.com/aosabook/500lines/pull/195.patch","issue_url":"https://api.github.com/repos/aosabook/500lines/issues/195","number":195,"state":"open","locked":false,"title":"Event-driven web framework edits","user":{"login":"MichaelDiBernardo","id":2091754,"avatar_url":"https://avatars.githubusercontent.com/u/2091754?v=3","gravatar_id":"","url":"https://api.github.com/users/MichaelDiBernardo","html_url":"https://github.com/MichaelDiBernardo","followers_url":"https://api.github.com/users/MichaelDiBernardo/followers","following_url":"https://api.github.com/users/MichaelDiBernardo/following{/other_user}","gists_url":"https://api.github.com/users/MichaelDiBernardo/gists{/gist_id}","starred_url":"https://api.github.com/users/MichaelDiBernardo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MichaelDiBernardo/subscriptions","organizations_url":"https://api.github.com/users/MichaelDiBernardo/orgs","repos_url":"https://api.github.com/users/MichaelDiBernardo/repos","events_url":"https://api.github.com/users/MichaelDiBernardo/events{/privacy}","received_events_url":"https://api.github.com/users/MichaelDiBernardo/received_events","type":"User","site_admin":false},"body":"@Inaimathi: I've reworked this chapter so that it's split into two major sections -- the first focuses on building a webserver, and the second focuses on expanding that server into a framework that makes it easier for application programmers to build their own apps. You already had this distinction in your previous draft; my goal here was to use that as a skeleton + outline to guide the development of the chapter.\r\n\r\nI moved a lot of things around, and probably removed several things. The biggest change structurally was that I inverted the macro explanations so that we see definitions before expansions.\r\n\r\nThere are a few things I need help with:\r\n\r\n1. I had some questions that I couldn't (or didn't!) answer when doing this -- can you respond to those? (I think I can probably do the defparameter one on my own.)\r\n\r\n2. It's likely that some of my changes introduced errors -- by which I mean I may have tried to clarify or move something, and in doing so, written something that is untrue or incorrect. Can you keep an eye out for this?\r\n\r\n3. All Lisp code (including macro expansions!) needs to be formatted so that it's no more than 85 columns wide (this includes the leading whitespace.) I'm not 100% sure what tasteful wrapping conventions look like in Lisp, so I was loathe to do this. My results when I pasted into Emacs and force-formatted weren't pretty.\r\n\r\nLet's get the first two points figured out on this PR. Once that's done, I can merge, and you can open another PR with the code formatting changes for point 3. \r\n\r\nI think that's it :)","created_at":"2016-02-28T21:03:11Z","updated_at":"2016-03-21T11:48:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"925b374bb7cc11edb0e01b1e8f9a8c0666911414","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/aosabook/500lines/pulls/195/commits","review_comments_url":"https://api.github.com/repos/aosabook/500lines/pulls/195/comments","review_comment_url":"https://api.github.com/repos/aosabook/500lines/pulls/comments{/number}","comments_url":"https://api.github.com/repos/aosabook/500lines/issues/195/comments","statuses_url":"https://api.github.com/repos/aosabook/500lines/statuses/93201bdf5b809aa0a4fe05855e30ad54c13963ec","head":{"label":"aosabook:eventsweb-edits","ref":"eventsweb-edits","sha":"93201bdf5b809aa0a4fe05855e30ad54c13963ec","user":{"login":"aosabook","id":5631751,"avatar_url":"https://avatars.githubusercontent.com/u/5631751?v=3","gravatar_id":"","url":"https://api.github.com/users/aosabook","html_url":"https://github.com/aosabook","followers_url":"https://api.github.com/users/aosabook/followers","following_url":"https://api.github.com/users/aosabook/following{/other_user}","gists_url":"https://api.github.com/users/aosabook/gists{/gist_id}","starred_url":"https://api.github.com/users/aosabook/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aosabook/subscriptions","organizations_url":"https://api.github.com/users/aosabook/orgs","repos_url":"https://api.github.com/users/aosabook/repos","events_url":"https://api.github.com/users/aosabook/events{/privacy}","received_events_url":"https://api.github.com/users/aosabook/received_events","type":"User","site_admin":false},"repo":{"id":14098121,"name":"500lines","full_name":"aosabook/500lines","owner":{"login":"aosabook","id":5631751,"avatar_url":"https://avatars.githubusercontent.com/u/5631751?v=3","gravatar_id":"","url":"https://api.github.com/users/aosabook","html_url":"https://github.com/aosabook","followers_url":"https://api.github.com/users/aosabook/followers","following_url":"https://api.github.com/users/aosabook/following{/other_user}","gists_url":"https://api.github.com/users/aosabook/gists{/gist_id}","starred_url":"https://api.github.com/users/aosabook/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aosabook/subscriptions","organizations_url":"https://api.github.com/users/aosabook/orgs","repos_url":"https://api.github.com/users/aosabook/repos","events_url":"https://api.github.com/users/aosabook/events{/privacy}","received_events_url":"https://api.github.com/users/aosabook/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/aosabook/500lines","description":"500 Lines or Less","fork":false,"url":"https://api.github.com/repos/aosabook/500lines","forks_url":"https://api.github.com/repos/aosabook/500lines/forks","keys_url":"https://api.github.com/repos/aosabook/500lines/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aosabook/500lines/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aosabook/500lines/teams","hooks_url":"https://api.github.com/repos/aosabook/500lines/hooks","issue_events_url":"https://api.github.com/repos/aosabook/500lines/issues/events{/number}","events_url":"https://api.github.com/repos/aosabook/500lines/events","assignees_url":"https://api.github.com/repos/aosabook/500lines/assignees{/user}","branches_url":"https://api.github.com/repos/aosabook/500lines/branches{/branch}","tags_url":"https://api.github.com/repos/aosabook/500lines/tags","blobs_url":"https://api.github.com/repos/aosabook/500lines/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aosabook/500lines/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aosabook/500lines/git/refs{/sha}","trees_url":"https://api.github.com/repos/aosabook/500lines/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aosabook/500lines/statuses/{sha}","languages_url":"https://api.github.com/repos/aosabook/500lines/languages","stargazers_url":"https://api.github.com/repos/aosabook/500lines/stargazers","contributors_url":"https://api.github.com/repos/aosabook/500lines/contributors","subscribers_url":"https://api.github.com/repos/aosabook/500lines/subscribers","subscription_url":"https://api.github.com/repos/aosabook/500lines/subscription","commits_url":"https://api.github.com/repos/aosabook/500lines/commits{/sha}","git_commits_url":"https://api.github.com/repos/aosabook/500lines/git/commits{/sha}","comments_url":"https://api.github.com/repos/aosabook/500lines/comments{/number}","issue_comment_url":"https://api.github.com/repos/aosabook/500lines/issues/comments{/number}","contents_url":"https://api.github.com/repos/aosabook/500lines/contents/{+path}","compare_url":"https://api.github.com/repos/aosabook/500lines/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aosabook/500lines/merges","archive_url":"https://api.github.com/repos/aosabook/500lines/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aosabook/500lines/downloads","issues_url":"https://api.github.com/repos/aosabook/500lines/issues{/number}","pulls_url":"https://api.github.com/repos/aosabook/500lines/pulls{/number}","milestones_url":"https://api.github.com/repos/aosabook/500lines/milestones{/number}","notifications_url":"https://api.github.com/repos/aosabook/500lines/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aosabook/500lines/labels{/name}","releases_url":"https://api.github.com/repos/aosabook/500lines/releases{/id}","deployments_url":"https://api.github.com/repos/aosabook/500lines/deployments","created_at":"2013-11-04T02:02:53Z","updated_at":"2016-03-21T08:44:12Z","pushed_at":"2016-03-21T11:35:24Z","git_url":"git://github.com/aosabook/500lines.git","ssh_url":"git@github.com:aosabook/500lines.git","clone_url":"https://github.com/aosabook/500lines.git","svn_url":"https://github.com/aosabook/500lines","homepage":null,"size":61097,"stargazers_count":6781,"watchers_count":6781,"language":"TeX","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1386,"mirror_url":null,"open_issues_count":16,"forks":1386,"open_issues":16,"watchers":6781,"default_branch":"master"}},"base":{"label":"aosabook:master","ref":"master","sha":"f87b3d6c44981d552dd0b4e260c8d8f7b1642fdc","user":{"login":"aosabook","id":5631751,"avatar_url":"https://avatars.githubusercontent.com/u/5631751?v=3","gravatar_id":"","url":"https://api.github.com/users/aosabook","html_url":"https://github.com/aosabook","followers_url":"https://api.github.com/users/aosabook/followers","following_url":"https://api.github.com/users/aosabook/following{/other_user}","gists_url":"https://api.github.com/users/aosabook/gists{/gist_id}","starred_url":"https://api.github.com/users/aosabook/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aosabook/subscriptions","organizations_url":"https://api.github.com/users/aosabook/orgs","repos_url":"https://api.github.com/users/aosabook/repos","events_url":"https://api.github.com/users/aosabook/events{/privacy}","received_events_url":"https://api.github.com/users/aosabook/received_events","type":"User","site_admin":false},"repo":{"id":14098121,"name":"500lines","full_name":"aosabook/500lines","owner":{"login":"aosabook","id":5631751,"avatar_url":"https://avatars.githubusercontent.com/u/5631751?v=3","gravatar_id":"","url":"https://api.github.com/users/aosabook","html_url":"https://github.com/aosabook","followers_url":"https://api.github.com/users/aosabook/followers","following_url":"https://api.github.com/users/aosabook/following{/other_user}","gists_url":"https://api.github.com/users/aosabook/gists{/gist_id}","starred_url":"https://api.github.com/users/aosabook/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aosabook/subscriptions","organizations_url":"https://api.github.com/users/aosabook/orgs","repos_url":"https://api.github.com/users/aosabook/repos","events_url":"https://api.github.com/users/aosabook/events{/privacy}","received_events_url":"https://api.github.com/users/aosabook/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/aosabook/500lines","description":"500 Lines or Less","fork":false,"url":"https://api.github.com/repos/aosabook/500lines","forks_url":"https://api.github.com/repos/aosabook/500lines/forks","keys_url":"https://api.github.com/repos/aosabook/500lines/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aosabook/500lines/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aosabook/500lines/teams","hooks_url":"https://api.github.com/repos/aosabook/500lines/hooks","issue_events_url":"https://api.github.com/repos/aosabook/500lines/issues/events{/number}","events_url":"https://api.github.com/repos/aosabook/500lines/events","assignees_url":"https://api.github.com/repos/aosabook/500lines/assignees{/user}","branches_url":"https://api.github.com/repos/aosabook/500lines/branches{/branch}","tags_url":"https://api.github.com/repos/aosabook/500lines/tags","blobs_url":"https://api.github.com/repos/aosabook/500lines/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aosabook/500lines/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aosabook/500lines/git/refs{/sha}","trees_url":"https://api.github.com/repos/aosabook/500lines/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aosabook/500lines/statuses/{sha}","languages_url":"https://api.github.com/repos/aosabook/500lines/languages","stargazers_url":"https://api.github.com/repos/aosabook/500lines/stargazers","contributors_url":"https://api.github.com/repos/aosabook/500lines/contributors","subscribers_url":"https://api.github.com/repos/aosabook/500lines/subscribers","subscription_url":"https://api.github.com/repos/aosabook/500lines/subscription","commits_url":"https://api.github.com/repos/aosabook/500lines/commits{/sha}","git_commits_url":"https://api.github.com/repos/aosabook/500lines/git/commits{/sha}","comments_url":"https://api.github.com/repos/aosabook/500lines/comments{/number}","issue_comment_url":"https://api.github.com/repos/aosabook/500lines/issues/comments{/number}","contents_url":"https://api.github.com/repos/aosabook/500lines/contents/{+path}","compare_url":"https://api.github.com/repos/aosabook/500lines/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aosabook/500lines/merges","archive_url":"https://api.github.com/repos/aosabook/500lines/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aosabook/500lines/downloads","issues_url":"https://api.github.com/repos/aosabook/500lines/issues{/number}","pulls_url":"https://api.github.com/repos/aosabook/500lines/pulls{/number}","milestones_url":"https://api.github.com/repos/aosabook/500lines/milestones{/number}","notifications_url":"https://api.github.com/repos/aosabook/500lines/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aosabook/500lines/labels{/name}","releases_url":"https://api.github.com/repos/aosabook/500lines/releases{/id}","deployments_url":"https://api.github.com/repos/aosabook/500lines/deployments","created_at":"2013-11-04T02:02:53Z","updated_at":"2016-03-21T08:44:12Z","pushed_at":"2016-03-21T11:35:24Z","git_url":"git://github.com/aosabook/500lines.git","ssh_url":"git@github.com:aosabook/500lines.git","clone_url":"https://github.com/aosabook/500lines.git","svn_url":"https://github.com/aosabook/500lines","homepage":null,"size":61097,"stargazers_count":6781,"watchers_count":6781,"language":"TeX","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1386,"mirror_url":null,"open_issues_count":16,"forks":1386,"open_issues":16,"watchers":6781,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/aosabook/500lines/pulls/195"},"html":{"href":"https://github.com/aosabook/500lines/pull/195"},"issue":{"href":"https://api.github.com/repos/aosabook/500lines/issues/195"},"comments":{"href":"https://api.github.com/repos/aosabook/500lines/issues/195/comments"},"review_comments":{"href":"https://api.github.com/repos/aosabook/500lines/pulls/195/comments"},"review_comment":{"href":"https://api.github.com/repos/aosabook/500lines/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/aosabook/500lines/pulls/195/commits"},"statuses":{"href":"https://api.github.com/repos/aosabook/500lines/statuses/93201bdf5b809aa0a4fe05855e30ad54c13963ec"}}}}