Created
January 29, 2011 00:26
-
-
Save dpk/801314 to your computer and use it in GitHub Desktop.
A sneak-peek at a new web framework written in Arc, Paul Graham's "hundred year language," or whatever. To go: (load "albert.arc") (srv)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(load "lib/re.arc") | |
(load "albert/httpd.arc") | |
(load "albert/routes.arc") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(load "lib/re.arc") | |
(load "albert/httpdata.arc") | |
(= quit-server?* nil ; set (= quit?* t) and reload page to quit | |
max-thread-life* 30 ; max allowed time in seconds for a single page to load | |
http-handler nil ; define this fn | |
) | |
(def srv ((o port 8080)) | |
(wipe quit-server?*) | |
(prn "albert/httpd: singin' on port " port) | |
(thread:httpd-server port) | |
) | |
(def httpd-server (port) | |
(w/socket s port | |
(until quit-server?* | |
(let (i o ip) (socket-accept s) | |
(let servethread (thread (serve-request i o ip)) | |
(thread | |
(sleep max-thread-life*) | |
(unless (dead servethread) | |
(prn "albert/httpd: heckler on " ip " wasting our time") | |
) | |
(kill-thread servethread) | |
(force-close i o) | |
) | |
) | |
) | |
) | |
) | |
) | |
(def serve-request (i o ip) | |
(let request (parse-request-headers:read-request-headers i) | |
(if ((request "headers") "Content-Length") | |
(do (= (request "body") (read-request-body i (int ((request "headers") "Content-Length")))) | |
(= (request "query") (parse-args (request "body")))) | |
) | |
(let response (http-handler request) | |
(w/stdout o | |
(prn "HTTP/1.1 " (response "code") " " (response-codes* (response "code"))) | |
(prn "Content-Length: " (len (response "body"))) | |
(maptable (fn (field content) (prn field ": " content)) (response "headers")) | |
(prn) | |
(pr (response "body")) | |
) | |
) | |
) | |
(close i o) | |
) | |
(def read-request-headers (i) | |
(let request nil | |
(until (is (= line (readline i)) "") | |
(= request (cons line request)) | |
) | |
(rev request) | |
) | |
) | |
(def parse-request-headers (headers) | |
(let request (table) | |
(let request-line (car headers) | |
(let (method uri version) (tokens request-line) | |
(= (request "method") (upcase method) | |
(request "uri") (urldecode uri) | |
(request "http-version") (upcase version)) | |
) | |
) | |
(= (request "headers") (table)) | |
(let header-fields (cdr headers) | |
(each field header-fields | |
(let parts (tokens field #\:) | |
(= ((request "headers") (car parts)) (trim (sjoin (cdr parts) ":") 'both)) | |
) | |
) | |
) | |
request | |
) | |
) | |
(def read-request-body (i content-length) | |
(let body (string) | |
(repeat content-length | |
(= body (+ body (readc i))) | |
) | |
body | |
) | |
) | |
(def parse-args (argstr) ; "foo=bar&baz=42" -> (("foo" "bar") ("baz" "42")) -- from http.arc | |
(map [map urldecode (tokens _ #\=)] (tokens argstr #\&))) | |
(def sjoin (parts (o delim "")) | |
(with (joined "" ii 1) | |
(each part parts | |
(= joined (+ joined part)) | |
(if (isnt ii (len parts)) | |
(= joined (+ joined delim))) | |
(++ ii) | |
) | |
joined | |
) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(= response-codes* | |
(obj | |
100 "Continue" ; 100-199 -- informational | |
101 "Switching Protocols" | |
102 "Processing" | |
200 "OK" ; 200-299 -- success | |
201 "Created" | |
202 "Accepted" | |
203 "Non-Authoritative Information" | |
204 "No Content" | |
205 "Reset Content" | |
206 "Partial Content" | |
207 "Multi-Status" | |
300 "Multiple Choices" ; 300-399 -- redirection | |
301 "Moved Permanently" | |
302 "Found" | |
303 "See Other" | |
304 "Not Modified" | |
305 "Use Proxy" | |
306 "Switch Proxy" | |
307 "Temporary Redirect" | |
400 "Bad Request" ; 400-499 -- client error | |
401 "Unauthorized" | |
402 "Payment Required" | |
403 "Forbidden" | |
404 "Not Found" | |
405 "Method Not Allowed" | |
406 "Not Acceptable" | |
407 "Proxy Authentication Required" | |
408 "Request Timeout" | |
409 "Conflict" | |
410 "Gone" | |
411 "Length Required" | |
412 "Precondition Failed" | |
413 "Request Entity Too Large" | |
414 "Request-URI Too Long" | |
415 "Unsupported Media Type" | |
416 "Requested Range Not Satisfiable" | |
417 "Expectation Failed" | |
418 "I'm a teapot" ; srsly | |
422 "Unprocessable Entity" | |
423 "Locked" | |
424 "Failed Dependency" | |
425 "Unordered Collection" | |
426 "Upgrade Required" | |
500 "Internal Server Error" ; 500-599 -- server made a boo-boo | |
501 "Not Implemented" | |
502 "Bad Gateway" | |
503 "Service Unavailable" | |
504 "Gateway Timeout" | |
505 "HTTP Version Not Supported" | |
506 "Variant Also Negotiates" | |
507 "Insufficient Storage" | |
510 "Not Extended" | |
) | |
) | |
(= ext-mime-types* | |
(obj | |
"gif" "image/gif" | |
"png" "image/png" | |
"jpg" "image/jpeg" | |
"css" "text/css" | |
"js" "application/javascript" | |
) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(def alb-default-header (title) (string "<!DOCTYPE html> | |
<html> | |
<head> | |
<title>" title "</title> | |
<style> | |
body { | |
font-family: Helvetica Neue, Helvetica, sans-serif; | |
color: #333; | |
} | |
code { | |
font-family: Anonymous Pro, Anonymous, Andale Mono, Courier, monospaced; | |
font-size: 11pt; | |
} | |
a:link, a:hover, a:visited, a:active { | |
color: #333; | |
text-decoration: none; | |
border-bottom: 1px solid #ccc; | |
} | |
</style> | |
</head> | |
<body>")) | |
(= alb-default-footer "\n</body>\n</html>") | |
(get "/" | |
(prn (alb-default-header "Hello. This is Albert.") " | |
<h1>Welcome to Albert.</h1> | |
<h2>Albert is a web framework for Arc.</h2> | |
<p>This is the default page. Here is a quick demonstration of Albert’s simplicity:</p> | |
<pre><code>(get \"/\" | |
(prn \"Hello, world!\") | |
) | |
</code></pre> | |
<p> | |
Many more exciting things are possible with Arc and Albert. You can handle requests with | |
the <code>GET</code>, <code>POST</code>, <code>PUT</code>, and <code>DELETE</code> request | |
methods with simple modifications to the above code, and any arbitrary HTTP request method | |
using the <code>add-route</code> method. You can replace the <a href=\"/404\">404 | |
handler</a> by defining method <code>(not-found req)</code>. | |
</p>" alb-default-footer) | |
) | |
(def not-found (req) | |
(prn (alb-default-header "Albert is sorry, but…") " | |
<h1>Uh-oh, looks like that page isn’t defined.</h1> | |
<h2>This page is the default not-found handler.</h2> | |
<p> | |
Check your regular expressions, perhaps. Make sure you’ve defined your route using | |
the correct method. Helpful tools for this are: | |
<a href=\"http://barebones.com/products/bbedit/\">BBEdit</a> or any comparable text editor | |
which has a regexp engine built in, and <a href=\"http://curl.haxx.se/\">cURL,</a> | |
whose verbose option allows you to see what’s going on at the HTTP level. | |
</p> | |
<p>To replace this (rather boring) default handler with your own, try something like this:</p> | |
<pre><code>(def not-found (req) | |
(prn \"Aww, shucks.\") | |
) | |
</code></pre> | |
<p> | |
This behaves exactly like a request to a defined route, except that (for obvious reasons) | |
<code>matches</code> is not defined. | |
</p>" alb-default-footer) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(= routes* nil) | |
(mac header (name value) | |
`(= ((resp "headers") ,name) ,value) | |
) | |
(mac response-code (code) | |
`(= (resp "code") ,code) | |
) | |
(def http-handler (req) | |
(let resp (obj "code" 200 "headers" (obj "Content-Type" "text/html; charset=utf-8")) | |
(= (resp "body") | |
(let handler (match-route (req "method") (req "uri")) | |
(tostring | |
(if handler | |
(let _resp (table) | |
(= _resp ((handler "fn") req (re-match-pat (handler "uri") (req "uri")))) | |
(if (_resp "code") (= (resp "code") (_resp "code"))) | |
) | |
; (if (file-exists (string "static" (req "uri"))) | |
; (let fcontent (w/infile (string "static" (req "uri"))) | |
; (prn fcontent) | |
; (= ((resp "headers") "Content-Type") (ext-mime-types* (last (tokens uri #\.)))) | |
; ) | |
(let _code 404 | |
(response-code _code) | |
(not-found req) | |
) | |
; ) | |
) | |
) | |
) | |
) | |
resp | |
) | |
) | |
(def add-route (method uri handler) | |
(= routes* (cons (obj "method" (downcase method) "uri" (+ "^" uri "$") "fn" handler) routes*)) | |
) | |
(def match-route (method uri) | |
(car (mem | |
(fn (_) (and (re-match-pat (_ "uri") uri) (is (_ "method") (downcase method)))) | |
routes* | |
)) | |
) | |
(mac get (uri . body) `(add-route "get" ,uri | |
(fn (req matches) (let resp (table) ,@body resp)))) | |
(mac post (uri . body) `(add-route "post" ,uri | |
(fn (req matches) (let resp (table) ,@body resp)))) | |
(mac put (uri . body) `(add-route "put" ,uri | |
(fn (req matches) (let resp (table) ,@body resp)))) | |
(mac delete (uri . body) `(add-route "delete" ,uri | |
(fn (req matches) (let resp (table) ,@body resp)))) | |
; default behaviour | |
(load "albert/routes-defaults.arc") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment