[Gd-chatter] r11718 - in trunk/libraries/network/koala: sources/examples/koala-basics sources/koala www/demo
cgay at gwydiondylan.org
cgay at gwydiondylan.org
Thu Feb 28 11:15:12 CET 2008
Author: cgay
Date: Thu Feb 28 11:15:09 2008
New Revision: 11718
Modified:
trunk/libraries/network/koala/sources/examples/koala-basics/main.dylan
trunk/libraries/network/koala/sources/koala/dsp.dylan
trunk/libraries/network/koala/sources/koala/headers.dylan
trunk/libraries/network/koala/sources/koala/library-unix.dylan
trunk/libraries/network/koala/sources/koala/library.dylan
trunk/libraries/network/koala/sources/koala/responders.dylan
trunk/libraries/network/koala/sources/koala/response.dylan
trunk/libraries/network/koala/sources/koala/server.dylan
trunk/libraries/network/koala/www/demo/footer.dsp
trunk/libraries/network/koala/www/demo/home.dsp
trunk/libraries/network/koala/www/demo/login.dsp
Log:
job: koala
* invoke-action -> invoke-responder, which matches the terminology
visible to the user already, and export invoke-responder so users
can support different types of responders.
* Made invoke-handler pass correct arguments to responders.
* The entire url-tail match is now available to responders via the
match: keyword parameter.
* koala-basics uses url-map-definer
* "output" is now a function, which makes it available everywhere,
not just in tags.
* Got rid of page-definer and responder-definer. They don't add
much over url-map definer.
* In url-map-definer, allow action targets to be expressions and
don't require (or allow) comma between the url and "action".
* Made page-template slot of <dylan-server-page> be per instance
instead of each-subclass, so it's not necessary to create a new
class for every top-level template you want to display.
Modified: trunk/libraries/network/koala/sources/examples/koala-basics/main.dylan
==============================================================================
--- trunk/libraries/network/koala/sources/examples/koala-basics/main.dylan (original)
+++ trunk/libraries/network/koala/sources/examples/koala-basics/main.dylan Thu Feb 28 11:15:09 2008
@@ -4,28 +4,18 @@
/*
-As distributed, the examples in this file should be available starting at
-/demo/home.dsp. Just start koala-app.exe and this example should be loaded
-as a Koala module.
+To use this example web application (if I may glorify it with that name),
+compile this library and invoke the executable with --config <filename>.
+You will probably have to fix the config file to make the dsp-root setting
+point at the directory containing the "demo" directory that has all the
+.dsp template files in it.
Each page demonstrates a feature of Dylan Server Pages. You should be able
to find the code corresponding to a particular URL by searching for that
URL in this file. Some XML-RPC methods are defined near the bottom.
-During development it's much easier to write your web application as an executable
-that uses the koala HTTP server project, since you can run and debug it directly
-under Functional Developer. To do this, you'll need to call start-server to get
-the HTTP server started up. (See "UNCOMMENT THIS" below.)
-
-In production the idea is to run koala-app.exe, build your web app as a DLL, and
-put the DLL in the koala-app "modules" directory. This way multiple web apps can
-be run on the same server. Of course, you may not need multiple apps...
-
-Following are examples of various ways to define static and dynamic handlers
-for web URLs.
-
-Note that any URLs registered for dynamic pages hide URLs corresponding to files in
-the document root directory. i.e., the dynamic URL takes precedence.
+Note that any URLs registered for dynamic pages hide URLs corresponding to
+files in the document root directory. i.e., the dynamic URL takes precedence.
*/
@@ -39,62 +29,46 @@
define constant $demo-base-url :: <byte-string> = "/demo";
-// This is used throughout this file to create absolute URLs.
-define function demo-url
- (url :: <byte-string>) => (absolute-url :: <byte-string>)
- concatenate($demo-base-url, url)
-end;
-
-//// Responders -- the lowest level API for responding to a URL
-
-// Responds to a single URL.
-define responder responder1 (demo-url("/responder1"))
- select (request-method(current-request()))
- #"get", #"post"
- => format(output-stream(current-response()),
- "<html><body>This is the output of a 'define responder' form."
- "<p>Use your browser's Back button to return to the example."
- "</body></html>");
- end;
-end;
-
-// Responds to a single directory (i.e., prefix) URL.
-define directory responder dir1 ("/dir1")
- let request = current-request();
- select (request.request-method)
- #"get", #"post"
- => format(output-stream(current-response()),
- "<html><body>This is a directory responder. The part of the url after "
- "the directory was %s."
- "<p>Use your browser's Back button to return to the example."
- "</body></html>",
- request.request-url-tail);
- end;
-end;
+// The lowest level API for responding to a URL is just a function
+// That does output to the response's output stream. The simplest
+// way to do that is to use the "output" function. The only thing
+// special about a responder function is that it must accept keyword
+// arguments. The match: argument is always passed and is always a
+// <regex-match> object. See the documentation for url-map for
+// details.
+
+define function responder1 (#key)
+ output("<html><body>This is the output of a simple responder function."
+ "<p>Use your browser's Back button to return to the example."
+ "</body></html>");
+end function responder1;
+
+define function prefix1 (#key)
+ output("<html><body>This is a prefix responder. The part of the url after "
+ "the prefix was %s."
+ "<p>Use your browser's Back button to return to the example.</p>"
+ "</body></html>",
+ // Note that we could also use "#key match" above and then get the
+ // entire match with match-group(match, 0).
+ current-request().request-tail-url);
+end function prefix1;
//// Page abstraction
// Slightly higher level than responders. Gives you the convenience of not
-// having to figure out whether it's a GET, POST, HEAD, request, and the ability
+// having to figure out whether it's a GET, POST, etc, request, and the ability
// to dispatch on your own page classes. Just define methods for
-// respond-to that dispatch on your page class.
+// respond-to* that dispatch on your page class.
// Note that you may override respond-to-get and respond-to-post instead of
// overriding respond-to(== #"get") and respond-to(== #"post") as a convenience
// since these are by far the most common request methods.
-// This defines a <hello-world-page> class which is a subclass of <page>, and a
-// variable called *hello-world-page* which is an instance of
-// <hello-world-page>. It associates the URLs /hello-world and /hello with the
-// *hello-world-page* instance.
-//
-define page hello-world-page (<page>)
- (url: demo-url("/hello-world"),
- alias: demo-url("/hello"))
+define class <hello-world-page> (<page>)
end;
// Respond to a GET for <hello-world-page>. Note the use of do-query-values to
@@ -104,45 +78,45 @@
//
define method respond-to-get
(page :: <hello-world-page>)
- let stream :: <stream> = output-stream(current-response());
- format(stream, "<html>\n<head><title>Hello World</title></head>\n"
- "<body>Hello there.<p>");
- format(stream, "%s<br>", if (count-query-values() > 0)
- "Query values are:"
- else
- "No query values were passed in the URL."
- end);
+ output("<html>\n<head><title>Hello World</title></head>\n"
+ "<body>Hello there.<p>");
+ output("%s<br>", if (count-query-values() > 0)
+ "Query values are:"
+ else
+ "No query values were passed in the URL."
+ end);
do-query-values(method (key, val)
- format(stream, "key = %s, val = %s<br>\n", key, val);
+ output("key = %s, val = %s<br>\n", key, val);
end);
- format(stream,
- "<p>Use your browser's Back button to return to the "
+ output("<p>Use your browser's Back button to return to the "
"demo.</body></html>");
-end;
+end method respond-to-get;
//// Dylan Server Pages
-// Dylan Server Pages are also usually defined with the "define page"
-// macro, but you must also specify the source: argument which is a
-// file that contains normal content plus DSP tags.
+// A DSP is basically just a page that is associated with a top-level
+// template. When creating a <dylan-server-page> you must pass the
+// source: "foo/my.dsp" argument.
// In general you will define respond-to-get/post methods for your
// <dylan-server-page>s that do some processing based on the query
-// values in the request and then call next-method() to process the
-// template for the page or call process-template on some other page
-// to display its output instead (e.g., an error template).
-
-// Any plain content is output directly to the output stream, and
-// tags invoke the corresponding tag definition.
-
-// Note that the .dsp source file doesn't have to be under the
-// *document-root* directory.
+// values in the request and then call process-template(page) to
+// process the template for the page or call process-template on
+// some other page to display its output instead (e.g., an error
+// template).
+
+// Any plain content in the template is output directly to the output
+// stream, and tags invoke the corresponding tag definition.
+
+// Note that the .dsp source file doesn't have to be under Koala's
+// document root directory. (Does it need to be under the DSP root?
+// Check this. --cgay)
// This defines a class that will be used for all our example pages.
// It must be a subclass of <dylan-server-page> so that all the
// template parsing will happen. If we define all our tags to be
-// specialized on this class they can be used in any example page.
+// specialized on this class they can be used in any demo page.
//
define class <demo-page> (<dylan-server-page>)
end;
@@ -155,38 +129,24 @@
define taglib demo ()
end;
+// <demo:base-url/> tag, so templates don't have to hard-code absolute urls.
+//
define tag base-url in demo
(page :: <demo-page>)
()
- format(output-stream(current-response()), $demo-base-url);
-end;
-
-define page home-page (<demo-page>)
- (url: demo-url("/home.dsp"),
- alias: "/demo/",
- source: "demo/home.dsp")
-end;
-
-define page hello-page (<demo-page>)
- (url: demo-url("/hello.dsp"),
- source: "demo/hello.dsp")
+ // output(format-string, format-arg, ...) sends output directly to the
+ // response stream.
+ output($demo-base-url);
end;
// Defines a tag that looks like <demo:hello/> in the DSP source
-// file. i.e., it has no body. Note that the "output" function is
-// defined by the "define tag" macro and could be defined as
-// curry(format, output-stream(current-response))
+// file. i.e., it has no body.
define tag hello in demo
(page :: <demo-page>)
()
output("Hello, world!");
end;
-define page args-page (<demo-page>)
- (url: demo-url("/args.dsp"),
- source: "demo/args.dsp")
-end;
-
// This tag demonstrates the use of tag keyword arguments. The tag call looks
// like this: <demo:show-keys arg1="100" arg2="foo"/>
// Note that since arg1 is typed as an <integer> it is automatically parsed to
@@ -196,45 +156,43 @@
define tag show-keys in demo
(page :: <demo-page>)
(arg1 :: <integer>, arg2)
- format(output-stream(current-response()),
- "The value of arg1 + 1 is %=. The value of arg2 is %=.",
+ output("The value of arg1 + 1 is %=. The value of arg2 is %=.",
arg1 + 1, arg2);
end;
-
+// Named methods can be used in various control flow tags defined in the
+// "dsp" taglib. For example, <dsp:if test="logged-in?">...</dsp:if>
+//
define named-method logged-in? in demo
(page :: <demo-page>)
let session = get-session(current-request());
session & get-attribute(session, #"username");
end;
-define page example-login-page (<demo-page>)
- (url: demo-url("/login.dsp"),
- source: "demo/login.dsp")
+define class <login-page> (<demo-page>)
end;
-define page example-logout-page (<demo-page>)
- (url: demo-url("/logout.dsp"),
- source: "demo/logout.dsp")
+define constant *login-page* = make(<login-page>, source: "demo/login.dsp");
+
+define class <logout-page> (<demo-page>)
end;
define method respond-to-get
- (page :: <example-logout-page>)
+ (page :: <logout-page>)
let session = get-session(current-request());
remove-attribute(session, #"username");
remove-attribute(session, #"password");
- next-method(); // Must call this if you want the DSP template to be processed.
-end;
+ // Process the template for this page...
+ next-method();
+end method respond-to-get;
// The login page POSTs to the welcome page...
-define page example-welcome-page (<demo-page>)
- (url: demo-url("/welcome.dsp"),
- source: "demo/welcome.dsp")
+define class <welcome-page> (<demo-page>)
end;
// ...so handle the POST by storing the form values in the session.
define method respond-to-post
- (page :: <example-welcome-page>)
+ (page :: <welcome-page>)
let username = get-query-value("username");
let password = get-query-value("password");
let username-supplied? = username & username ~= "";
@@ -243,15 +201,11 @@
let session = get-session(current-request());
set-attribute(session, #"username", username);
set-attribute(session, #"password", password);
- // process the DSP template for the welcome page.
+ // Process the template for this page...
next-method();
else
note-form-error("You must supply <b>both</b> a username and password.");
- // ---*** TODO: Calling respond-to(#"get", ...) probably isn't quite right.
- // If we're redirecting to another page should the query/form values
- // be cleared first? Probably want to call process-page instead,
- // but with the existing request?
- respond-to-get(*example-login-page*);
+ respond-to-get(*login-page*);
end;
end method respond-to-post;
@@ -264,17 +218,12 @@
let username
= get-query-value("username")
| get-attribute(get-session(get-request(response)), #"username");
- username & write(output-stream(response), username);
+ username & output(username);
end;
//// iterator
-define page iterator-page (<demo-page>)
- (url: demo-url("/iterator.dsp"),
- source: "demo/iterator.dsp")
-end;
-
define thread variable *repetition-number* = 0;
// An iterating tag. Note the use of the "body" modifier in "define body tag".
@@ -301,21 +250,16 @@
define tag display-iteration-number in demo
(page :: <demo-page>)
()
- format(output-stream(current-response()), "%d", *repetition-number*);
+ output("%d", *repetition-number*);
end;
//// table generation
-define page table-page (<demo-page>)
- (url: demo-url("/table.dsp"),
- source: "demo/table.dsp")
-end;
-
// This method is used as the row-generator function for a dsp:table call.
// It must return a <sequence>.
define named-method animal-generator in demo
- (page :: <table-page>)
+ (page :: <demo-page>)
#[#["dog", "perro", "gou3"],
#["cat", "gato", "mao1"],
#["cow", "vaca", "niu2"]]
@@ -323,7 +267,7 @@
// The row-generator for the table with no rows.
define named-method no-rows-generator in demo
- (page :: <table-page>)
+ (page :: <demo-page>)
#[]
end;
@@ -331,29 +275,60 @@
(page :: <demo-page>)
()
let row = current-row();
- format(output-stream(current-response()), "%s", row[0]);
+ output("%s", row[0]);
end;
define tag spanish-word in demo
(page :: <demo-page>)
()
let row = current-row();
- format(output-stream(current-response()), "%s", row[1]);
+ output("%s", row[1]);
end;
define tag pinyin-word in demo
(page :: <demo-page>)
()
let row = current-row();
- format(output-stream(current-response()), "%s", row[2]);
+ output("%s", row[2]);
end;
define tag row-bgcolor in demo
(page :: <demo-page>)
()
- write(output-stream(current-response()),
- if(even?(current-row-number())) "#EEEEEE" else "#FFFFFF" end);
-end;
+ output(if(even?(current-row-number()))
+ "#EEEEEE"
+ else
+ "#FFFFFF"
+ end);
+end;
+
+define url-map
+ //prefix "/demo"; ...would be nice
+ url ("/demo", "/demo/home")
+ // Note that when a page just has a template but no special respond-to*
+ // method there's no need to define a new page subclass for it. You
+ // just make a <dylan-server-page> (or subclass thereof) and specify
+ // the source: for the template.
+ action GET () => make(<demo-page>, source: "demo/home.dsp");
+ url "/demo/responder1"
+ action GET () => responder1; // regex defaults to "^$"
+ url "/demo/prefix1"
+ action GET (".*") => prefix1;
+ url ("/demo/hello-world", "/demo/hello")
+ action GET () => make(<hello-world-page>);
+ url "/demo/args"
+ action GET () => make(<demo-page>, source: "demo/args.dsp");
+ url "/demo/login"
+ action GET () => *login-page*;
+ url "/demo/logout"
+ action (GET, POST) () => make(<logout-page>, source: "demo/logout.dsp");
+ url "/demo/welcome"
+ action POST () => make(<welcome-page>, source: "demo/welcome.dsp");
+ url "/demo/iterator"
+ action GET () => make(<demo-page>, source: "demo/iterator.dsp");
+ url "/demo/table"
+ action GET () => make(<demo-page>, source: "demo/table.dsp");
+end url-map;
Modified: trunk/libraries/network/koala/sources/koala/dsp.dylan
==============================================================================
--- trunk/libraries/network/koala/sources/koala/dsp.dylan (original)
+++ trunk/libraries/network/koala/sources/koala/dsp.dylan Thu Feb 28 11:15:09 2008
@@ -80,6 +80,16 @@
define open primary class <page> (<object>)
end;
+// Method on generic defined in server.dylan
+define method invoke-responder
+ (request :: <request>,
+ action :: <page>,
+ arguments :: <sequence>)
+ => ()
+ // todo -- Decide how to pass arguments to the page respond-to* methods?
+ process-page(action)
+end method invoke-responder;
+
// This is the method registered as the response function for all <page>s.
// See register-page.
define method process-page (page :: <page>)
@@ -153,32 +163,6 @@
end block;
end function url-to-page;
-// ---TODO: Test this and export it.
-// Register URLs for all files matching the given pathname spec as instances
-// of the given page class.
-define method register-pages-as
- (path :: <locator>, page-class :: subclass(<file-page-mixin>),
- #key descend? = #t, file-type)
- // url-dir always ends in '/'
- local method doer (url-dir, directory, name, type)
- select (type)
- #"file" =>
- let file = merge-locators(as(<file-locator>, name),
- as(<directory-locator>, directory));
- register-page(name, make(page-class,
- source: file,
- url: concatenate(url-dir, name)));
- #"directory" =>
- let dir = subdirectory-locator(as(<directory-locator>, directory), name);
- when (descend?)
- do-directory(curry(doer, concatenate(url-dir, name, "/")),
- dir);
- end;
- end;
- end;
- do-directory(curry(doer, "/"), path);
-end;
-
//
// Page mixin classes and related methods
//
@@ -684,65 +668,22 @@
// Dylan Server Pages
//
+// The only required init arg for this class is source:, which should
+// be the location of the top-level template.
+//
define open primary class <dylan-server-page> (<file-page-mixin>, <page>)
// A sequence of strings and functions. Strings are output directly
// to the network stream. The functions are created by 'define tag'.
- each-subclass slot page-template :: false-or(<dsp-template>) = #f;
-end;
-
-// define page my-dsp (<dylan-server-page>) (url: "/hello", source: make-locator(...), ...)
-// slot foo :: <integer> = bar;
-// ...
-// end;
-define macro page-definer
- { define page ?:name (?superclasses:*) (?make-args:*)
- ?slot-specs:*
- end }
- => { page-aux(?name; ?superclasses; ?make-args; ?slot-specs);
- if (has-url?(?make-args))
- register-page-urls("*" ## ?name ## "*", ?make-args)
- end
- }
-
- { define directory page ?:name (?superclasses:*) (?make-args:*)
- ?slot-specs:*
- end }
- => { page-aux(?name; ?superclasses; ?make-args; ?slot-specs);
- if (has-url?(?make-args))
- register-page-urls("*" ## ?name ## "*", ?make-args, prefix?: #t)
- end
- }
-
-end;
-
-define macro page-aux
- { page-aux(?:name; ?superclasses:*; ?make-args:*; ?slot-specs:*) }
- => { define class "<" ## ?name ## ">" (?superclasses) ?slot-specs end;
- define variable "*" ## ?name ## "*" = make("<" ## ?name ## ">", ?make-args) }
+ slot page-template :: false-or(<dsp-template>) = #f;
end;
-define function has-url? (#key url :: false-or(<string>), #all-keys)
- => (url-provided? :: <boolean>);
- if (url)
- #t
- end;
-end;
-
-define function register-page-urls
- (page :: <page>, #key url :: <string>, alias, #all-keys)
- => (responder :: <function>)
- let responder = register-page(url, page);
- when (alias)
- for (alias in iff(instance?(alias, <string>),
- list(alias),
- alias))
- add-responder(alias, responder);
- end;
- end;
- responder
+// For convenience in responders, tags, named-methods, etc.
+//
+define function output
+ (format-string, #rest format-args)
+ apply(format, output-stream(current-response()), format-string, format-args)
end;
-
// define tag foo in tlib (page) () do-stuff end
// define body tag foo in tlib (page, do-body) (foo, bar :: <integer>) do-stuff end
//
@@ -758,10 +699,7 @@
end }
=> { define tag-aux #f ?tag ?taglib-spec
(?page, _do-body) (?tag-parameters)
- begin
- let ?=output = curry(format, current-response().output-stream);
- ?body; // semicolon is needed even when ?body ends in semicolon.
- end;
+ ?body; // semicolon is needed even when ?body ends in semicolon.
_do-body(); // process the tag body
end
}
@@ -771,10 +709,7 @@
end }
=> { define tag-aux #t ?tag ?taglib-spec
(?page, ?do-body) (?tag-parameters)
- begin
- let ?=output = curry(format, current-response().output-stream);
- ?body;
- end;
+ ?body
end
}
@@ -876,7 +811,8 @@
display-template(page.page-template, page);
end;
-define method display-template (tmplt :: <dsp-template>, page :: <dylan-server-page>)
+define method display-template
+ (tmplt :: <dsp-template>, page :: <dylan-server-page>)
log-debug("Displaying template %s", tmplt.source | "not set");
let stream = current-response().output-stream;
for (item in tmplt.entries)
Modified: trunk/libraries/network/koala/sources/koala/headers.dylan
==============================================================================
--- trunk/libraries/network/koala/sources/koala/headers.dylan (original)
+++ trunk/libraries/network/koala/sources/koala/headers.dylan Thu Feb 28 11:15:09 2008
@@ -47,13 +47,13 @@
#key if-exists? :: <symbol> = #"append")
let old = element(headers, key, default: #f);
// typically there is only one header for given key, so favor that.
- if (~old | if-exists? == #"replace")
+ if (~old | if-exists? = #"replace")
headers[key] := data;
- elseif (if-exists? == #"append")
+ elseif (if-exists? = #"append")
headers[key] := iff(instance?(old, <pair>),
concatenate!(old, list(data)),
list(old, data));
- elseif (if-exists? == #"error")
+ elseif (if-exists? = #"error")
error("Attempt to add header %= which has already been added", key);
end;
end add-header;
Modified: trunk/libraries/network/koala/sources/koala/library-unix.dylan
==============================================================================
--- trunk/libraries/network/koala/sources/koala/library-unix.dylan (original)
+++ trunk/libraries/network/koala/sources/koala/library-unix.dylan Thu Feb 28 11:15:09 2008
@@ -158,6 +158,7 @@
add-responder,
remove-responder,
find-responder,
+ invoke-responder,
<request>,
*request*, // Holds the active request, per thread.
current-request, // Returns the active request of the thread.
@@ -193,6 +194,7 @@
<response>,
output-stream,
clear-output,
+ output,
set-content-type,
add-header,
add-cookie,
@@ -354,10 +356,9 @@
page-template,
page-template-setter,
- <dylan-server-page>, // Subclass this using the "define page" macro
- page-definer, // Defines a new page class
- process-template, // Call this (or next-method()) from respond-to-get/post if
- // you decide you want the DSP template to be processed.
+ <dylan-server-page>,
+ process-template,
+ process-page,
<taglib>,
taglib-definer,
tag-definer, // Defines a new DSP tag function and registers it with a page
Modified: trunk/libraries/network/koala/sources/koala/library.dylan
==============================================================================
--- trunk/libraries/network/koala/sources/koala/library.dylan (original)
+++ trunk/libraries/network/koala/sources/koala/library.dylan Thu Feb 28 11:15:09 2008
@@ -158,11 +158,11 @@
add-responder,
remove-responder,
find-responder,
+ invoke-responder,
<request>,
*request*, // Holds the active request, per thread.
current-request, // Returns the active request of the thread.
current-response, // Returns the active response of the thread.
- request-query-string,
request-query-values, // get the keys/vals from the current GET or POST request
request-method, // Returns #"get", #"post", etc
request-host,
@@ -193,6 +193,7 @@
<response>,
output-stream,
clear-output,
+ output,
set-content-type,
add-header,
add-cookie,
@@ -271,10 +272,6 @@
request-url,
request-tail-url;
- // Debugging
- create
- print-object;
-
// Files
create
static-file-responder;
@@ -354,10 +351,9 @@
page-template,
page-template-setter,
- <dylan-server-page>, // Subclass this using the "define page" macro
- page-definer, // Defines a new page class
- process-template, // Call this (or next-method()) from respond-to-get/post if
- // you decide you want the DSP template to be processed.
+ <dylan-server-page>,
+ process-template,
+ process-page,
<taglib>,
taglib-definer,
tag-definer, // Defines a new DSP tag function and registers it with a page
Modified: trunk/libraries/network/koala/sources/koala/responders.dylan
==============================================================================
--- trunk/libraries/network/koala/sources/koala/responders.dylan (original)
+++ trunk/libraries/network/koala/sources/koala/responders.dylan Thu Feb 28 11:15:09 2008
@@ -123,10 +123,19 @@
/* Example usage
define url-map
url "/wiki",
- action GET () => show-page,
- action GET () => show-page;
+ action get () => show-page,
+ action get () => show-page;
+ url "/wiki/login"
+ action post ("/(?<name>:\\w+") => login;
+ url
end;
*/
+// It might be nice to add a prefix clause to this. e.g.,
+// prefix: "/demo"
+// so that all urls are prefixed with that string. But that might
+// be better handled by "define web-application", which perhaps this
+// macro can be expanded to at some point.
+//
define macro url-map-definer
{ define url-map
?urls
@@ -138,13 +147,13 @@
{ ?url ; ... } => { ?url ; ... }
url:
- { url ?location:expression , ?definitions }
+ { url ?location:expression ?definitions }
=> { begin
let responder = make(<responder>);
?definitions ;
?location ;
end }
- { url ( ?locations ) , ?definitions }
+ { url ( ?locations ) ?definitions }
=> { begin
let responder = make(<responder>);
?definitions ;
@@ -162,14 +171,16 @@
{ } => { }
{ ?definition , ... } => { ?definition ; ... }
+ // I'd like to get rid of the parens around ?request-methods.
+ // Not quite sure how yet though. --cgay
definition:
- { action ( ?request-methods ) ( ?regex ) => ?action:name }
+ { action ( ?request-methods ) ( ?regex ) => ?action:expression }
=> { begin
let regex = compile-regex(?regex, use-cache: #t);
let actions = list(?action);
?request-methods
end }
- { action ?request-method:name ( ?regex ) => ?action:name }
+ { action ?request-method:name ( ?regex ) => ?action:expression }
=> { begin
let regex = compile-regex(?regex);
let actions = list(?action);
@@ -177,7 +188,7 @@
end }
{ action ( ?request-methods ) ( ?regex ) => ( ?action-sequence:* ) }
=> { begin
- let regex = compile-regex(?regex);
+ let regex = compile-regex(?regex, use-cache: #t);
let actions = list(?action-sequence);
?request-methods
end }
@@ -187,7 +198,6 @@
let actions = list(?action-sequence);
?request-method
end }
-
request-methods:
{ } => { }
{ ?request-method , ... } => { ?request-method ; ... }
Modified: trunk/libraries/network/koala/sources/koala/response.dylan
==============================================================================
--- trunk/libraries/network/koala/sources/koala/response.dylan (original)
+++ trunk/libraries/network/koala/sources/koala/response.dylan Thu Feb 28 11:15:09 2008
@@ -66,7 +66,7 @@
response.%output-stream := make(<string-stream>, direction: #"output");
else
signal(make(<koala-error>,
- format-string: "unbuffered responses aren't supported yet."));
+ format-string: "Unbuffered responses aren't supported yet."));
end
end;
@@ -81,11 +81,13 @@
// Exported
//
define method set-content-type
- (response :: <response>, content-type :: <object>, #key if-exists? = #"replace")
+ (response :: <response>, content-type :: <object>,
+ #key if-exists? = #"replace")
let out = response.%output-stream;
if (out & stream-size(out) ~= 0)
raise(<koala-api-error>,
- "Attempt to set the Content-Type header after reply has begun to be sent.");
+ "Attempt to set the Content-Type header after some content "
+ "has already been generated.");
else
add-header(response.response-headers, "Content-Type", content-type,
if-exists?: if-exists?);
Modified: trunk/libraries/network/koala/sources/koala/server.dylan
==============================================================================
--- trunk/libraries/network/koala/sources/koala/server.dylan (original)
+++ trunk/libraries/network/koala/sources/koala/server.dylan Thu Feb 28 11:15:09 2008
@@ -147,23 +147,20 @@
required-init-keyword: client:;
end;
-define generic read-request (request :: <basic-request>);
-define generic invoke-handler (request :: <basic-request>);
-
-define generic request-keep-alive? (request :: <basic-request>) => (well? :: <boolean>);
-
define method initialize (request :: <basic-request>, #key, #all-keys)
next-method();
request.request-client.client-request := request;
end;
-define inline function request-socket (request :: <basic-request>)
- => (socket :: <tcp-socket>)
+define inline function request-socket
+ (request :: <basic-request>)
+ => (socket :: <tcp-socket>)
request.request-client.client-socket
end;
-define inline function request-server (request :: <basic-request>)
- => (server :: <server>)
+define inline function request-server
+ (request :: <basic-request>)
+ => (server :: <server>)
request.request-client.client-server
end;
@@ -872,11 +869,14 @@
if (actions)
// Invoke each action function with keyword arguments matching the names
// of the named groups in the first regular expression that matches the
- // tail of the url, if any.
+ // tail of the url, if any. Also pass the entire match as the match:
+ // argument so unnamed groups and the entire match can be accessed.
let arguments = #[];
if (match)
- arguments := make(<simple-object-vector>,
- size: 2 * match.groups-by-name.size);
+ arguments := make(<stretchy-vector>,
+ size: 2 * (match.groups-by-name.size + 1));
+ add!(arguments, #"match");
+ add!(arguments, match);
for (group keyed-by name in match.groups-by-name)
if (group)
add!(arguments, as(<symbol>, name));
@@ -885,7 +885,7 @@
end for;
end if;
for (action in actions)
- invoke-action(request, action, arguments)
+ invoke-responder(request, action, arguments)
end;
else
resource-not-found-error(url: url);
@@ -901,7 +901,7 @@
define inline function find-actions
(request :: <request>)
- => (actions, match :: false-or(<regex-match>))
+ => (actions, match)
let rmap = request.request-responder.responder-map;
let responders = element(rmap, request.request-method, default: #f);
if (responders)
@@ -911,6 +911,7 @@
for (actions keyed-by regex in responders)
log-debug("regex -> actions: %= -> %=", regex.regex-pattern, actions);
let match = regex-search(regex, url-tail);
+ log-debug("find-actions: match: %=", match);
if (match)
return(actions, match)
end if;
@@ -919,13 +920,15 @@
end if;
end function find-actions;
-define generic invoke-action
+// Clients can override this to create other types of responders.
+//
+define open generic invoke-responder
(request :: <request>,
action :: <object>,
arguments :: <sequence>)
=> ();
-define method invoke-action
+define method invoke-responder
(request :: <request>,
action :: <object>,
arguments :: <sequence>)
@@ -933,15 +936,7 @@
log-warning("Unknown action %= in action sequence.", action);
end;
-define method invoke-action
- (request :: <request>,
- action :: <dylan-server-page>,
- arguments :: <sequence>)
- => ()
- respond-to(request.request-method, action);
-end;
-
-define method invoke-action
+define method invoke-responder
(request :: <request>,
action :: <function>,
arguments :: <sequence>)
Modified: trunk/libraries/network/koala/www/demo/footer.dsp
==============================================================================
--- trunk/libraries/network/koala/www/demo/footer.dsp (original)
+++ trunk/libraries/network/koala/www/demo/footer.dsp Thu Feb 28 11:15:09 2008
@@ -13,7 +13,7 @@
</dsp:if>
</td>
<td width="50%" align="right">
- <a href="<demo:base-url/>/home.dsp">Go back to demo home</a>
+ <a href="<demo:base-url/>/home">Go back to demo home</a>
</td>
</tr>
</table>
Modified: trunk/libraries/network/koala/www/demo/home.dsp
==============================================================================
--- trunk/libraries/network/koala/www/demo/home.dsp (original)
+++ trunk/libraries/network/koala/www/demo/home.dsp Thu Feb 28 11:15:09 2008
@@ -21,22 +21,24 @@
<h3>Contents</h3>
<ol>
- <li><a href="<demo:base-url/>/hello.dsp">Hello World</a></li>
- <li><a href="<demo:base-url/>/args.dsp">A tag with arguments</a></li>
- <li><a href="<demo:base-url/>/login.dsp">Login (demonstrates sessions)</a></li>
- <li><a href="<demo:base-url/>/logout.dsp">Logout (demonstrates sessions)</a></li>
- <li><a href="<demo:base-url/>/iterator.dsp?n=3">Iterator (demonstrates query
+ <li><a href="<demo:base-url/>/hello">Hello World</a></li>
+ <li><a href="<demo:base-url/>/args">A tag with arguments</a></li>
+ <li><a href="<demo:base-url/>/login">Login (demonstrates sessions)</a></li>
+ <li><a href="<demo:base-url/>/logout">Logout (demonstrates sessions)</a></li>
+ <li><a href="<demo:base-url/>/iterator?n=3">Iterator (demonstrates query
values and body tags)</a></li>
- <li><a href="<demo:base-url/>/table.dsp">Table Generation</a></li>
+ <li><a href="<demo:base-url/>/table">Table Generation</a></li>
</ol>
<p>
<h3>Low-level Koala API</h3>
-
+
<ol>
<li><a href="<demo:base-url/>/responder1">
A responder (the most basic way to respond to a URL)
</a></li>
+ <li><a href="<demo:base-url/>/prefix1/one/two/three">A prefix responder.
+ Same as above, but matches any url starting with /prefix1.</a></li>
<li><a href="<demo:base-url/>/hello?a=1&b=2">Hello World (a non-DSP page)</a></li>
</ol>
Modified: trunk/libraries/network/koala/www/demo/login.dsp
==============================================================================
--- trunk/libraries/network/koala/www/demo/login.dsp (original)
+++ trunk/libraries/network/koala/www/demo/login.dsp Thu Feb 28 11:15:09 2008
@@ -12,7 +12,7 @@
<dsp:show-form-notes/>
- <form action="<demo:base-url/>/welcome.dsp"
+ <form action="<demo:base-url/>/welcome"
method="post"
enctype="application/x-www-form-urlencoded">
More information about the chatter
mailing list