From 1293830a2582cbc9be9bcf8703c37dc35136eed1 Mon Sep 17 00:00:00 2001 From: Eric Schulte Date: Wed, 25 Dec 2013 17:08:09 -0700 Subject: [PATCH] more documentation and examples --- NOTES | 2 +- README | 3 +- doc/emacs-web-server.texi | 105 +++++++++++++++++++++++++-------- emacs-web-server-test.el | 14 +++++ emacs-web-server.el | 8 +-- examples/0-hello-world.el | 7 ++- examples/1-hello-world-utf8.el | 33 ++++++----- examples/2-hello-world-html.el | 9 +-- examples/3-file-server.el | 16 +++-- examples/4-url-param-echo.el | 23 ++++---- examples/5-post-echo.el | 24 ++++---- 11 files changed, 163 insertions(+), 81 deletions(-) diff --git a/NOTES b/NOTES index 26e018ac9..78030da39 100644 --- a/NOTES +++ b/NOTES @@ -2,7 +2,7 @@ * Notes * Tasks [7/9] -** TODO Documentation [0/4] +** STARTED Documentation [0/4] - [ ] introduction - [ ] handlers - [ ] request headers diff --git a/README b/README index 6f7ccd754..78d4d6a7d 100644 --- a/README +++ b/README @@ -12,4 +12,5 @@ STATUS EXAMPLES See the examples/ directory in this repository. The Emacs Web - Server is also used to run https://github.com/eschulte/el-sprunge. + Server is also used to run https://github.com/eschulte/el-sprunge + and https://github.com/eschulte/org-ehtml/tree/emacs-web-server. diff --git a/doc/emacs-web-server.texi b/doc/emacs-web-server.texi index e5ac1c942..30d79cfe2 100644 --- a/doc/emacs-web-server.texi +++ b/doc/emacs-web-server.texi @@ -41,7 +41,7 @@ A copy of the license is included in the section entitled @end ifnottex @menu -* Introduction:: Getting to know the Emacs Web Server +* Introduction:: Overview of the Emacs Web Server * Handlers:: Handlers respond to HTTP requests * Request:: Getting information on HTTP requests * Usage Examples:: Examples demonstrating usage @@ -65,30 +65,32 @@ Appendices @chapter Introduction @cindex introduction -The Emacs Web Server is a Web server implemented in Emacs Lisp using -Emacs network communication primitives. HTTP requests are matched to -handlers (@pxref{Handlers}) which are implemented as Emacs Lisp -functions. Handler functions receive a request object -(@pxref{Request}) which holds information about the request and the -HTTP connection process. Handlers write their responses directly to -the connection process. +The Emacs Web Server is a Web server implemented entirely in Emacs +Lisp. HTTP requests are matched to handlers (@pxref{Handlers}) which +are Emacs Lisp functions. Handlers receive as their only argument a +request object (@pxref{Request}) which holds information about the +request and the process holding HTTP network connection. Handlers +write their responses directly to the network process. A number of examples (@pxref{Usage Examples}) demonstrate usage of the -Emacs Web Server. Finally, the functions defining the interface are +Emacs Web Server. All public functions of the Emacs Web Server are listed (@pxref{Function Index}). @node Handlers, Request, Handlers, Top @chapter Handlers @cindex handlers -The Emacs Web Server is started with the @code{ews-start} function -which takes a ``handlers'' association list which is composed of pairs -of matchers and handler functions. +The function @code{ews-start} takes takes two arguments +@code{handlers} and @code{port}. It starts a server listening on +@code{port} responding to requests with @code{handlers}, an +association list composed of pairs of matchers and handler functions. -@emph{Matchers} may be either a simple regular expression or a -function. A simple matcher consists of an HTTP header and a regular -expression. When the regular expression matches the content of that -header the simple matcher succeeds and the associated handler is +@section Matchers + +Matchers may be a regular expression or a function. Regular +expression matchers consists of an HTTP header and a regular +expression. When the regular expression matches the content of the +given header the matcher succeeds and the associated handler is called. For example the following matches any @code{GET} request whose path starts with the substring ``foo''. @@ -96,9 +98,9 @@ whose path starts with the substring ``foo''. (:GET . "^foo") @end example -A complex matcher is a function which takes the request object +A function matcher is a function which takes the request object (@pxref{Request}) and succeeds when the function returns a non-nil -value. For example the following matcher matches every request +value. For example the following matcher matches every request, @example (lambda (_) t) @@ -108,17 +110,34 @@ and the following matches only requests in which the supplied ``number'' parameter is odd. @example -(lambda (request) (oddp (cdr (assoc "number" request)))) +(lambda (request) + (oddp (string-to-number (cdr (assoc "number" request))))) @end example +@section Handler + +Each handler is a function which takes a request object +(@pxref{Request}) as its only argument. The function may respond to +the request by writing to the network process held in the +@code{process} field of the request object. For example, the +@code{process-send-string} function may be used to write string data +to a request as in the following. + +@example + (process-send-string (process request) "hello world") +@end example + +When the handler function exits the connection is terminated unless +the handler function returns the keyword @code{:keep-alive}. + @node Request, Usage Examples, Handlers, Top @chapter Request @cindex request -Information on requests is stored in a @code{request} object. The +Each HTTP requests is represented using a @code{request} object. The request object is used to decide which handler to call, and is passed -to the called handler. This object holds information on the request -including the request process, all HTTP headers, and parameters. +as an argument to the called handler. The request object holds the +network process, all HTTP headers, and any parameters. The text of the request is parsed into an alist. HTML Headers are keyed using uppercase keywords (e.g., @code{:GET}), and user supplied @@ -140,27 +159,67 @@ These examples demonstrate usage. @node Hello World, Hello World UTF8, Usage Examples, Usage Examples @section Hello World + +The simplest possible ``hello world'' example. The handler consists +of a single (matcher . handler) pair. The function matcher matches +@emph{every} incoming HTTP request. The handler responds by setting +the content type to @code{text/plain}, and then sending the string +``hello world''. When the handler exits the network connection of the +request is closed. + @verbatiminclude ../examples/0-hello-world.el @node Hello World UTF8, Hello World HTML, Hello World, Usage Examples @section Hello World UTF8 + +This example only differs from the previous in that the +``Content-type'' indicates UTF8 encoded data, and the hello world sent +is selected at random from a list of different languages. + @verbatiminclude ../examples/1-hello-world-utf8.el @node Hello World HTML, File Server, Hello World UTF8, Usage Examples @section Hello World HTML @verbatiminclude ../examples/2-hello-world-html.el +This variation of the ``hello world'' example sends a @code{text/html} +response instead of a simple @code{text/plain} response. + @node File Server, URL Parameter Echo, Hello World HTML, Usage Examples @section File Server + +The following example implements a file server which will serve files +from the @code{docroot} document root set to the current working +directory in this example. Three helper functions are used; +@code{ews-subdirectoryp} is used to check if the requested path is +within the document root, if so the file is served and +@code{ews-send-file} is used to appropriately set the mime-type of the +response based on the extension of the file, if not then +@code{ews-send-404} is used to send a default ``File Not Found'' +response. + @verbatiminclude ../examples/3-file-server.el @node URL Parameter Echo, POST Echo, File Server, Usage Examples @section URL Parameter Echo + +This example demonstrates access of URL-encoded parameters in a +@code{GET} request. For example the following URL +@url{http://localhost:9005/example?foo=bar&baz=qux} will render as an +the following HTML table. + +@multitable @columnfractions .5 .5 +@item foo @tab bar +@item baz @tab qux +@end multitable + @verbatiminclude ../examples/4-url-param-echo.el @node POST Echo, Function Index, URL Parameter Echo, Usage Examples @section POST Echo -POST parameters are used for example when HTML forms are submitted. + +The following example echos back the content of the ``message'' field +in a @code{POST} request. @verbatiminclude ../examples/5-post-echo.el diff --git a/emacs-web-server-test.el b/emacs-web-server-test.el index 62dad3e1d..aa8044243 100644 --- a/emacs-web-server-test.el +++ b/emacs-web-server-test.el @@ -165,4 +165,18 @@ org=-+one%0A-+two%0A-+three%0A-+four%0A%0A&beg=646&end=667&path=%2Fcomplex.org") ")))) (ews-stop server)))) +(ert-deftest ews/simple-post () + "Test a simple POST server." + (ews-test-with + '(((:POST . ".*") . + (lambda (request) + (with-slots (process headers) request + (let ((message (cdr (assoc "message" headers)))) + (ews-response-header process 200 + '("Content-type" . "text/plain")) + (process-send-string process + (format "you said %S\n" message))))))) + (should (string= (ews-test-curl-to-string "" nil '(("message" . "foo"))) + "you said \"foo\"\n")))) + (provide 'emacs-web-server-test) diff --git a/emacs-web-server.el b/emacs-web-server.el index a97e56e74..29838f9c1 100644 --- a/emacs-web-server.el +++ b/emacs-web-server.el @@ -24,6 +24,7 @@ (defclass ews-request () ((process :initarg :process :accessor process :initform nil) (pending :initarg :pending :accessor pending :initform "") + (context :initarg :context :accessor context :initform nil) (boundary :initarg :boundary :accessor boundary :initform nil) (headers :initarg :headers :accessor headers :initform (list nil)))) @@ -162,15 +163,12 @@ function. (defun ews-parse-request (request string) "Parse request STRING from REQUEST with process PROC. Return non-nil only when parsing is complete." - (with-slots (process pending boundary headers) request + (with-slots (process pending context boundary headers) request (setq pending (concat pending string)) (let ((delimiter (concat "\r\n" (if boundary (concat "--" boundary) ""))) ;; Track progress through string, always work with the ;; section of string between LAST-INDEX and INDEX. - (last-index 0) index - ;; Current context, either a particular content-type for - ;; custom parsing or nil for no special parsing. - context) + (last-index 0) index) (catch 'finished-parsing-headers ;; parse headers and append to request (while (setq index (string-match delimiter pending last-index)) diff --git a/examples/0-hello-world.el b/examples/0-hello-world.el index 76ae4f3b3..91e819a6d 100644 --- a/examples/0-hello-world.el +++ b/examples/0-hello-world.el @@ -1,7 +1,8 @@ ;;; hello-world.el --- simple hello world server using Emacs Web Server (ews-start '(((lambda (_) t) . - (lambda (proc request) - (ews-response-header proc 200 '("Content-type" . "text/plain")) - (process-send-string proc "hello world")))) + (lambda (request) + (with-slots (process headers) request + (ews-response-header process 200 '("Content-type" . "text/plain")) + (process-send-string process "hello world"))))) 9000) diff --git a/examples/1-hello-world-utf8.el b/examples/1-hello-world-utf8.el index d7804b2c4..0e00362a6 100644 --- a/examples/1-hello-world-utf8.el +++ b/examples/1-hello-world-utf8.el @@ -1,20 +1,21 @@ ;;; hello-world-utf8.el --- utf8 hello world server using Emacs Web Server (ews-start '(((lambda (_) t) . - (lambda (proc request) - (let ((hellos '("こんにちは" - "안녕하세요" - "góðan dag" - "Grüßgott" - "hyvää päivää" - "yá'át'ééh" - "Γεια σας" - "Вiтаю" - "გამარჯობა" - "नमस्ते" - "你好"))) - (ews-response-header proc 200 - '("Content-type" . "text/plain; charset=utf-8")) - (process-send-string proc - (concat (nth (random (length hellos)) hellos) " world")))))) + (lambda (request) + (with-slots (process headers) request + (let ((hellos '("こんにちは" + "안녕하세요" + "góðan dag" + "Grüßgott" + "hyvää päivää" + "yá'át'ééh" + "Γεια σας" + "Вiтаю" + "გამარჯობა" + "नमस्ते" + "你好"))) + (ews-response-header process 200 + '("Content-type" . "text/plain; charset=utf-8")) + (process-send-string process + (concat (nth (random (length hellos)) hellos) " world"))))))) 9001) diff --git a/examples/2-hello-world-html.el b/examples/2-hello-world-html.el index 49d4420c9..5f2587a82 100644 --- a/examples/2-hello-world-html.el +++ b/examples/2-hello-world-html.el @@ -1,9 +1,10 @@ ;;; hello-world-html.el --- html hello world server using Emacs Web Server (ews-start '(((lambda (_) t) . - (lambda (proc request) - (ews-response-header proc 200 '("Content-type" . "text/html")) - (process-send-string proc " + (lambda (request) + (with-slots (process headers) request + (ews-response-header process 200 '("Content-type" . "text/html")) + (process-send-string process " Hello World @@ -11,5 +12,5 @@ hello world -")))) +"))))) 9002) diff --git a/examples/3-file-server.el b/examples/3-file-server.el index ba32c6579..ba7cfb10b 100644 --- a/examples/3-file-server.el +++ b/examples/3-file-server.el @@ -1,7 +1,11 @@ ;;; file-server.el --- serve any files using Emacs Web Server -;; This example uses absolute paths and will try to serve files from -;; the root of the file-system, so don't run it on a public server. -(ews-start - '(((:GET . ".*") . - (lambda (proc request) (ews-send-file proc (cdr (assoc :GET request)))))) - 9004) +(lexical-let ((docroot default-directory)) + (ews-start + (list (cons (cons :GET ".*") + (lambda (request) + (with-slots (process headers) request + (let ((path (substring (cdr (assoc :GET headers)) 1))) + (if (ews-subdirectoryp docroot path) + (ews-send-file process (expand-file-name path docroot)) + (ews-send-404 process))))))) + 9004)) diff --git a/examples/4-url-param-echo.el b/examples/4-url-param-echo.el index 48a5284a3..2277b4d20 100644 --- a/examples/4-url-param-echo.el +++ b/examples/4-url-param-echo.el @@ -1,15 +1,16 @@ ;;; url-param-echo.el --- echo back url-paramed message using Emacs Web Server (ews-start '(((:GET . ".*") . - (lambda (proc request) - (ews-response-header proc 200 '("Content-type" . "text/html")) - (process-send-string proc - (concat "URL Parameters:
" - (mapconcat (lambda (pair) - (format "" - (car pair) (cdr pair))) - (cl-remove-if-not (lambda (el) (stringp (car el))) - request) - "") - "
%s%s
"))))) + (lambda (request) + (with-slots (process headers) request + (ews-response-header process 200 '("Content-type" . "text/html")) + (process-send-string process + (concat "URL Parameters:
" + (mapconcat (lambda (pair) + (format "" + (car pair) (cdr pair))) + (cl-remove-if-not (lambda (el) (stringp (car el))) + headers) + "") + "
%s%s
")))))) 9005) diff --git a/examples/5-post-echo.el b/examples/5-post-echo.el index b4d81d4ad..b7cd78dc9 100644 --- a/examples/5-post-echo.el +++ b/examples/5-post-echo.el @@ -1,16 +1,18 @@ ;;; post-echo.el --- echo back posted message using Emacs Web Server (ews-start '(((:POST . ".*") . - (lambda (proc request) - (let ((message (cdr (assoc "message" request)))) - (ews-response-header proc 200 '("Content-type" . "text/plain")) - (process-send-string proc - (if message - (format "you said %S\n" message) - "This is a POST request, but it has no \"message\".\n"))))) + (lambda (request) + (with-slots (process headers) request + (let ((message (cdr (assoc "message" headers)))) + (ews-response-header process 200 '("Content-type" . "text/plain")) + (process-send-string process + (if message + (format "you said %S\n" message) + "This is a POST request, but it has no \"message\".\n")))))) ((:GET . ".*") . - (lambda (proc request) - (ews-response-header proc 200 '("Content-type" . "text/plain")) - (process-send-string proc - "This is a GET request not a POST request.\n")))) + (lambda (request) + (with-slots (process) request + (ews-response-header process 200 '("Content-type" . "text/plain")) + (process-send-string process + "This is a GET request not a POST request.\n"))))) 9003) -- 2.39.2