Changed handling of response headers to allow multiple values; added documentation...
authorAdam Chlipala <adamc@hcoop.net>
Sun, 11 Jan 2004 00:30:34 +0000 (00:30 +0000)
committerAdam Chlipala <adamc@hcoop.net>
Sun, 11 Jan 2004 00:30:34 +0000 (00:30 +0000)
doc/manual.tex
src/lib/main.sml
src/lib/web.sig
src/lib/web.sml

index ada1809..a37f7d5 100644 (file)
@@ -110,10 +110,18 @@ Anonymous {\tt fn} functions are available with the SML syntax.
 
 SML {\tt case} expressions are supported.
 
+\subsubsection{\tt iff}
+
+SML {\tt if} expressions are supported, except that the keyword that introduces them is {\tt iff}, to disambiguate from {\tt if} statements.
+
 \subsubsection{\tt raise}
 
 SML {\tt raise} expressions are supported.
 
+\subsubsection{\tt let}
+
+SML {\tt let} expressions are supported.
+
 \subsection{Patterns}
 
 Patterns are identical to SML patterns without support for user-defined infix constructors, though {\tt ::} is supported. Record patterns can include field names with no assigned patterns (the pattern for such a field is taken to be the field name) and "flex record" {\tt ...}'s to stand for unused fields.
index 84c47b3..139686a 100644 (file)
@@ -29,7 +29,7 @@ struct
            val cgiFields = Cgi.cgi_fieldnames ()
            fun mapper name = (name, Cgi.cgi_field_strings name)
        in
-           Web.setHeader ("Content-type", "text/html");
+           Web.addHeader ("Content-type", "text/html");
            Web.pushParams (map mapper cgiFields);
            Templates.beforeFn ();
            case args of
index 71a5881..2ed69e2 100644 (file)
 signature WEB =
 sig
     val for : (int -> unit) -> int * int -> unit
+    (* (for f (min, max)) runs f on i for every i from min to max, inclusive.
+     * min may also be greater than max, for backward order. *)
 
     val print : string -> unit
+    (* Add to the web output buffer *)
     val clear : unit -> unit
+    (* Clear web output buffer *)
     val noOutput : unit -> bool
+    (* True if there is no output in the buffer *)
     val output : unit -> unit
+    (* Flush the buffer *)
 
     val setParam : string * string list -> unit
+    (* Set the values associated with a single URL parameter.
+     * Parameters acquire multiple values when things like the following
+     *   appear in URL's: ...&x=1&x=2&...
+     *)
     val setSingleParam : string * string -> unit
+    (* Set a parameter with a single value *)
     val getParam : string -> string
+    (* Get the first value set for a parameter, or "" if none is set *)
     val getMultiParam : string -> string list
+    (* Get all values of a parameter *)
 
     val pushParams : (string * string list) list -> unit
+    (* Save the current parameter map and set the new ones given *)
     val popParams : unit -> unit
+    (* Restore the last parameter map saved with pushParams *)
     val withParams : (unit -> 'a) -> (string * string list) list -> 'a
+    (* (withParams f params) executes f with params added to the parameter
+     * map, removing them afterwards *)
 
     val getCgi : string -> string option
+    (* Get a named value defined by the CGI protocol, such as HTTP_COOKIE *)
 
     val html : string -> string
+    (* Escape a plaintext string to appear in HTML *)
     val htmlNl : string -> string
+    (* Like html, but changes newlines to <br>'s *)
     val urlEncode : string -> string
+    (* Escape a plaintext string to appear in a URL *)
 
     exception Format of string
     val stoi : string -> int
+    (* Convert a string to an int, raising Format on error *)
     val stor : string -> real
+    (* Convert a string to a real, raising Format on error *)
 
     val summary : unit -> string
+    (* Debugging info on CGI state *)
 
     val getExn : unit -> exn
+    (* Get the current exception when in an exception handler template *)
     val setExn : exn -> unit
+    (* Set the exception to be returned by getExn *)
 
-    val setHeader : string * string -> unit
-    val getHeader : string -> string option
+    val setHeader : string * string list -> unit
+    (* Set the values of an HTTP response header *)
+    val getHeader : string -> string list option
+    (* Get the values of an HTTP response header *)
+    val addHeader : string * string -> unit
+    (* Add a value for a header *)
+    val getSingleHeader : string -> string option
+    (* Get the first value of a header *)
 
     type cookie = {name : string, value : string, expires : Date.date option,
                   domain : string option, path : string option, secure : bool}
+    (* name: Key
+     * value: Data to associate with name
+     * expires: When browser should discard this cookie.
+     *   NONE clears the cookie immediately. SOME of a date clears it at
+     *   that date. Use a very far-off date for "non-expiring" cookies.
+     * domain: If NONE, cookie is valid in current domain.
+     *   If SOME, specifies a particular domain pattern for the cookie.
+     * path: Like domain, but specifies path on a server
+     * secure: If set, cookie may only be transferred over HTTPS
+     *)
     val setCookie : cookie -> unit
+    (* Add a cookie set request to the HTTP response *)
     val getCookie : string -> string option
+    (* Get a cookie's value by looking up its name *)
 
     val remoteHost : unit -> string option
+    (* Get REMOTE_HOST CGI variable *)
 
+    val plusSeconds : Time.time * int -> Time.time
+    (* Add seconds to a time *)
     val minusSeconds : Time.time * int -> Time.time
+    (* Subtract seconds from a time *)
 
     val replaceUrlVar : string * string * string -> string
+    (* (replaceUrlVar (url, name, value)) replaces the value of
+     * URL variable name in url by value. If name is not set in
+     * url, it is added.
+     *)
 end
\ No newline at end of file
index ebd49fb..2394687 100644 (file)
@@ -78,9 +78,17 @@ struct
         ((f ()) handle ex => (popParams (); raise ex))
         before popParams ())
 
-    val headers = ref (StringMap.empty : string StringMap.map)
+    val headers = ref (StringMap.empty : string list StringMap.map)
     fun setHeader (n, v) = headers := StringMap.insert (!headers, n, v)
     fun getHeader n = StringMap.find (!headers, n)
+    fun addHeader (n, v) =
+       headers := StringMap.insert (!headers, n, case getHeader n of
+                                                     NONE => [v]
+                                                   | SOME vs => vs @ [v])
+    fun getSingleHeader n =
+       (case StringMap.find (!headers, n) of
+            SOME (v::_) => SOME v
+          | _ => NONE)
 
     val text = ref ([] : string list)
 
@@ -89,10 +97,12 @@ struct
     fun noOutput () = !text = []
     fun output () =
        (TextIO.print "Status: 200\n";
-        StringMap.appi (fn (n, v) => (TextIO.print n;
-                                      TextIO.print ": ";
-                                      TextIO.print v;
-                                      TextIO.print "\n")) (!headers);
+        StringMap.appi (fn (n, vs) =>
+                           app (fn v => (TextIO.print n;
+                                         TextIO.print ": ";
+                                         TextIO.print v;
+                                         TextIO.print "\n"))
+                               vs) (!headers);
         TextIO.print "\n";
         TextIO.print (String.concat (List.rev (!text))))
 
@@ -188,7 +198,7 @@ struct
                else
                    s
        in
-           setHeader ("Set-Cookie", s)
+           addHeader ("Set-Cookie", s)
        end
 
     fun getCookie n =
@@ -211,6 +221,7 @@ struct
            NONE => getCgi "REMOTE_ADDR"
          | h => h
 
+    fun plusSeconds (t, s) = Time.+ (t, Time.fromSeconds (LargeInt.fromInt s))
     fun minusSeconds (t, s) = Time.- (t, Time.fromSeconds (LargeInt.fromInt s))
 
     fun split (s, ch) =