From 00f2c671a3c5f00b5e2ca5c7518880de3947642f Mon Sep 17 00:00:00 2001
From: Jay McCarthy The table below links to small example servlets. The table below links to small example servlets. Instead of serving files from a special directory verbatim, the Web server executes the contained Scheme code and serves the output. By default, the special directory is named "servlets" within the "default-web-root" of the "web-server" collection directory. Each file in that directory must evaluate to a servlet.
-A servlet is a The last value in the A Evaluating The PLT Servlet Examples
The configuration tool is a larger example.
The `go' links only work when served from the PLT Web server.
\ No newline at end of file
+
+
+
+
+Source Code Evaluate add.ss go count.ss go hello.ss go PLT Servlet Examples
+
The configuration tool is a larger example.
The `go' links only work when served from the PLT Web server.
+
+
+
+
diff --git a/collects/web-server/default-web-root/htdocs/Defaults/documentation/servlet.html b/collects/web-server/default-web-root/htdocs/Defaults/documentation/servlet.html
index fefb5404fd..e0275d4012 100644
--- a/collects/web-server/default-web-root/htdocs/Defaults/documentation/servlet.html
+++ b/collects/web-server/default-web-root/htdocs/Defaults/documentation/servlet.html
@@ -1,34 +1,63 @@
-
+
+Source Code
+Evaluate
+
+
+add.ss
+go
+
+
+count.ss
+go
+
+
+hello.ss
+go
+PLT Web Server: Servlet Interface
unit/sig
that imports the servlet^
-signature and exports nothing. (Search in help-desk
for more information on unit/sig
and on signatures.) To construct a unit/sig
with the appropriate imports, the servlet must require the two modules providingunit/sig
s and the servlet^
signature:
-(require (lib "unitsig.ss")
- (lib "servlet-sig.ss" "web-server"))
-(unit/sig ()
- (import servlet^)
-
...insert servlet code here...)
unit/sig
must be a response to an HTTP request.Response
is one of the following:
-X-expression
representing HTML
- (Search for XML in help-desk
.)(listof string)
where
-(make-response/full
code message seconds mime extras body)
where
-(listof (cons symbol string))
containing extra headers for redirects, authentication, or cookies.(listof string)
(require (lib "servlet-sig.ss" "web-server"))
loads
-the servlet^
signature consisting of the following imports:request
, where a request is (make-request method uri headers bindings host-ip client-ip)
, where
-(Union 'get 'post)
URL
- see the net
collection in help-desk
for details(listof (cons symbol string))
- optional HTTP headers for this request(listof (cons symbol string))
- name value pairs from the form submitted or the query part of the URL.path
part of the URL suplies the file path to the servlet relative to the "servlets" directory. However, paths may also contain extra path components that servlets may use as additional input. For example all of the following URLs refer to the same servlet: http://www.plt-scheme.org/servlets/my-servlet
http://www.plt-scheme.org/servlets/my-servlet/extra
http://www.plt-scheme.org/servlets/my-servlet/extra/directories
The above imports support handling a single input from a Web form. To ease the development of more interactive servlets, the servlet^
signature also provides the following functions:
-
send/suspend : (str -> Response) -> request
URL
that can be used in the document. The argument function must produce a
- response corresponding to the document's body. Requests to the
- given URL
resume the computation at the point
- send/suspend
was invoked. Thus, the argument function normally
- produces an HTML form with the "action" attribute set to the provided
- URL
. The result of send/suspend
represents the
- next request.
+
+
+
+
+ Instead of serving files from a special directory + verbatim, the Web server executes the contained + Scheme code and serves the output. By default, + the special directory is named "servlets" within + the "default-web-root" of the "web-server" + collection directory. Each file in that directory + must evaluate to a servlet.
-send/finish : Response ->
doesn't return adjust-timeout! : Nat -> Void
The servlet-helpers
module, required with
(require (lib "servlet-helpers.ss" "web-server"))
provides a few additional functions helpful for constructing servlets: extract-binding/single : sym (listof (cons sym str)) -> str
This extracts a single value associated with sym in the form bindings. If multiple or zero values are associated with the name, it raises an exception.extract-user-pass : (listof (cons sym str)) -> (U #f (cons str str))
(define (extract-user-pass headers) ...)
Servlets may easily implement password based authentication by extracting password information from the HTTP headers. The return value is either a pair consisting of the username and password from the headers or #f if no password was provided.The Web server caches passwords and servlets for performance reasons. Requesting the URL
http://my-host/conf/refresh-passwords
reloads the password file. After updating a servlet, loading the URL http://my-host/conf/refresh-servlets
causes the server to reload each servlet on the next invocation. This loses any per-servlet state (not per servlet instance state) computed before the unit invocation.
+ A servlet is a module
that provides
+ three values: an
+ interface-version
,
+ a timeout
, and a
+ start
procedure:
+ (module servlet mzscheme
+ (require (lib "servlet.ss" "web-server"))
+ (provide interface-version timeout start)
+ (define interface-version 'v1)
+ (define timeout +inf.0)
+ (define (start initial-request)
+ ...))
+
-The Web server's garbage collect may be invoked at the URL:
http://my-host/conf/collect-garbage
+ The start
procedure should produce a
+ a response to an HTTP request. Please
+ refer to the documentation available via the Help
+ Desk for more information about working with these
+ values and programming servlets.
The Web server caches passwords and servlets for + performance reasons. Requesting the URL
+http://my-host/conf/refresh-passwords
+ reloads the password file. After updating a + servlet, loading the + URL
+http://my-host/conf/refresh-servlets
+ causes the server to reload each servlet on the + next invocation. This loses any per-servlet state (not + per servlet instance state) computed before the unit + invocation.
+ +The Web server's garbage collect may be invoked at + the URL:
+http://my-host/conf/collect-garbage
+
+
+ Powered by
+
The first paragraph
\n") - (sleep 4) - (output-chunk "The second paragraph
\n"))))) +(module incremental mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + (define interface-version 'v1) + (define timeout +inf.0) + (define (start initial-request) + (send/finish + (make-html-response/incremental + (lambda (output-chunk) + (output-chunk "The first paragraph
\n") + (sleep 4) + (output-chunk "The second paragraph
\n")))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/mime.ss b/collects/web-server/default-web-root/servlets/tests/mime.ss index d9302ba450..2dea2d3620 100644 --- a/collects/web-server/default-web-root/servlets/tests/mime.ss +++ b/collects/web-server/default-web-root/servlets/tests/mime.ss @@ -1,8 +1,9 @@ -(require (lib "unit.ss") - (lib "servlet-sig.ss" "web-server")) -(unit - (import servlet^) - (export) - `("text/uber-format" - "uber uber uber" - "-de-doo")) +(module mime mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + (define interface-version 'v1) + (define timeout +inf.0) + (define (start initial-request) + `("text/uber-format" + "uber uber uber" + "-de-doo"))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/bad-xexpr.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/bad-xexpr.ss new file mode 100644 index 0000000000..6d7d17c43d --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/bad-xexpr.ss @@ -0,0 +1,10 @@ +(module bad-xexpr mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + (define (start initial-request) + (send/back + `(html (a ([href url]) + "Title"))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/bus-error.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/bus-error.ss new file mode 100644 index 0000000000..e5a329ba90 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/bus-error.ss @@ -0,0 +1,14 @@ +(module bus-error mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 120) + + (define (end request) + `(html (body "End"))) + + (define (start request) + (send/suspend/callback + `(html (body (p (a ([href ,start]) "Forward")) + (p (a ([href ,end]) "End"))))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/button.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/button.ss new file mode 100644 index 0000000000..da50c14af1 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/button.ss @@ -0,0 +1,9 @@ +(module button mzscheme + (require (lib "mred.ss" "mred") + (lib "class.ss")) + (provide interface-version timeout start) + (define interface-version 'v1) + (define timeout +inf.0) + (define (start initial-request) + (let ([b (new button% (label "Button"))]) + (list #"text/plain" "Button")))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/cell-example.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/cell-example.ss new file mode 120000 index 0000000000..114adb316f --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/cell-example.ss @@ -0,0 +1 @@ +/Users/jay/Development/Projects/papers/web-cells/cell-example.ss \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/counter-cells.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/counter-cells.ss new file mode 100644 index 0000000000..0e8650818f --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/counter-cells.ss @@ -0,0 +1,46 @@ +(module counter-cells mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + (define (start _) + (main-page)) + + (define the-counter (make-web-cell:local 0)) + (define the-header (make-web-cell:local (box "Main page"))) + + (define (counter) + (send/suspend/dispatch + (lambda (embed/url) + `(html (h2 ,(number->string (web-cell:local-ref the-counter))) + (a ([href ,(embed/url + (lambda _ + (web-cell:local-mask the-counter + (add1 (web-cell:local-ref the-counter))) + (counter)))]) + "Increment") + (br) + (a ([href ,(embed/url + (lambda _ + 'exit))]) + "Exit"))))) + + (define (main-page) + (send/suspend/dispatch + (lambda (embed/url) + `(html (h2 ,(unbox (web-cell:local-ref the-header))) + (form ([method "POST"] + [action ,(embed/url + (lambda (req) + (set-box! (web-cell:local-ref the-header) + (extract-binding/single 'header (request-bindings req))) + (main-page)))]) + (input ([type "text"] [name "header"])) + (input ([type "submit"]))) + (br) + (a ([href ,(embed/url + (lambda _ + (counter) + (main-page)))]) + "View Counter")))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/counter-cps.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/counter-cps.ss new file mode 100644 index 0000000000..6b8aaf4653 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/counter-cps.ss @@ -0,0 +1,36 @@ +(module counter-cps mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + (define (start _) + (main-page)) + + (define the-counter (make-parameter 0)) + + (define (counter k) + (send/suspend/dispatch + (lambda (embed/url) + `(html (h2 ,(number->string (the-counter))) + (a ([href ,(embed/url + (lambda _ + (parameterize ([the-counter (add1 (the-counter))]) + (counter k))))]) + "Increment") + (br) + (a ([href ,(embed/url + (lambda _ + (k 'exit)))]) + "Exit"))))) + + (define (main-page) + (send/suspend/dispatch + (lambda (embed/url) + `(html (h2 "Main page") + (a ([href ,(embed/url + (lambda _ + (counter + (lambda (v) + (main-page)))))]) + "View Counter")))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/counter.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/counter.ss new file mode 100644 index 0000000000..fccaf10462 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/counter.ss @@ -0,0 +1,35 @@ +(module counter mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + (define (start _) + (main-page)) + + (define the-counter (make-parameter 0)) + + (define (counter) + (send/suspend/dispatch + (lambda (embed/url) + `(html (h2 ,(number->string (the-counter))) + (a ([href ,(embed/url + (lambda _ + (parameterize ([the-counter (add1 (the-counter))]) + (counter))))]) + "Increment") + (br) + (a ([href ,(embed/url + (lambda _ + 'exit))]) + "Exit"))))) + + (define (main-page) + (send/suspend/dispatch + (lambda (embed/url) + `(html (h2 "Main page") + (a ([href ,(embed/url + (lambda _ + (counter) + (main-page)))]) + "View Counter")))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/cust.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/cust.ss new file mode 100644 index 0000000000..c8f34fe2d0 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/cust.ss @@ -0,0 +1,16 @@ +(module cust mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define servlet-cust (current-custodian)) + (define timeout 30) + (define interface-version 'v1) + + (define (start ir) + `(html + (head (title "Custodian test")) + (body + (p ,(if (eq? (current-custodian) servlet-cust) + "It didn't work." + "It did work.")))))) + \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/expiration.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/expiration.ss new file mode 100644 index 0000000000..b6cec86438 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/expiration.ss @@ -0,0 +1,36 @@ +(module expiration mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define timeout (* 60 3)) + (define interface-version 'v1) + + (define (start initial-request) + (parameterize ([current-servlet-continuation-expiration-handler + (lambda (request-for-expired) + (send/back + `(html (body (p "You lose! (Default)")))))]) + (let loop ([request initial-request]) + (send/suspend/dispatch + (lambda (embed/url) + `(html + (head (title "Expiration demo")) + (body (p "Open each of the links below in a new window. Then click the link in 'Forget' window. Then reload each window.") + (p (a ([href ,(embed/url loop)]) + "Loop")) + (p (a ([href ,(embed/url + loop + (lambda (request-for-expired) + (send/back + `(html (head (title "Expiration demo")) + (body (p "You win! (Special)"))))))]) + "Loop w/ Expiration")) + (p (a ([href ,(embed/url + (lambda (request) + (loop + (send/forward + (lambda (k-url) + `(html (head (title "Expiration demo")) + (body (p (a ([href ,k-url]) "Forget the past.")))))))))]) + "Prepare to forget the past.")))))))))) + \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/fault.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/fault.ss new file mode 100644 index 0000000000..9986d1abd6 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/fault.ss @@ -0,0 +1,12 @@ +(module fault mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + (define interface-version 'v1) + (define timeout 30) + (define X 0) + (define (start request) + (send/suspend + (lambda (k-url) + `(a ([href ,k-url]) "Click"))) + (set! X (add1 X)) + (format "~a" "fault"))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/form.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/form.ss new file mode 100644 index 0000000000..c9c4da17ea --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/form.ss @@ -0,0 +1,28 @@ +(module form mzscheme + (require (lib "servlet.ss" "web-server") + (lib "pretty.ss")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + (define (start _) + (define req + (send/suspend + (lambda (k-url) + `(html + (body + (form ([action ,k-url] [method "POST"]) + (input ([type "checkbox"] [name "checkbox"])) + (input ([type "text"] [name "text"])) + (input ([type "submit"] [name "submit"])))))))) + (define binds + (request-bindings req)) + (define sport + (open-output-string)) + (define pp + (parameterize ([current-output-port sport]) + (pretty-print binds))) + `(html + (body + (pre + ,(get-output-string sport)))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/fupload.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/fupload.ss new file mode 100644 index 0000000000..50225836c8 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/fupload.ss @@ -0,0 +1,24 @@ +(module fupload mzscheme + (require (lib "servlet.ss" "web-server") + (lib "plt-match.ss")) + (provide (all-defined)) + + (define timeout 60) + (define interface-version 'v1) + (define (start initial-request) + (send/suspend/callback + `(html (head) + (body + (form ([action + ,(lambda (request) + (define b (request-bindings/raw request)) + (match (bindings-assq #"file" b) + [(struct binding:file (_ filename _)) + `(html + (body + ,(bytes->string/utf-8 filename)))]))] + [method "post"] + [enctype "multipart/form-data"]) + (h3 "Submit for an Assignment") + (input ([name "file"] [type "file"] [size "30"])) + (input ([type "submit"] [value "Submit"])))))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/hang.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/hang.ss new file mode 100644 index 0000000000..01b89deaf0 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/hang.ss @@ -0,0 +1,15 @@ +(module hang mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + + (define (start initial-request) + (send/suspend + (lambda (k-url) + `(html (a ([href ,k-url]) "Next")))) + (send/suspend + (lambda (k-url) + `(html (a ([href ,k-url]) "Error")))) + (/ 1 0))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/hod-0618.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/hod-0618.ss new file mode 100644 index 0000000000..e71bb36980 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/hod-0618.ss @@ -0,0 +1,19 @@ +(module hod-0618 mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + (define interface-version 'v1) + (define timeout 30) + (define (start _) + `(html + (body + ,(extract-binding/single + 'test:case + (request-bindings + (send/suspend + (lambda (k-url) + `(html + (body + (form ([method "POST"] + [action ,k-url]) + (input ([type "text"] [name "test:case"])) + (input ([type "submit"]))))))))))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/inf-essence.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/inf-essence.ss new file mode 100644 index 0000000000..98989df0a4 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/inf-essence.ss @@ -0,0 +1,13 @@ +(module inf-essence mzscheme + (define a (alarm-evt +inf.0)) + + (let loop () + (sync + (handle-evt a + (lambda _ + (printf "Infinity has passed.~n"))) + (handle-evt (alarm-evt (+ (current-inexact-milliseconds) + (* 1000 1))) + (lambda _ + (printf "One second has passed.~n")))) + (loop))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/inf.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/inf.ss new file mode 100644 index 0000000000..6ffee51d7c --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/inf.ss @@ -0,0 +1,12 @@ +(module inf mzscheme + (provide interface-version timeout start) + + (define interface-version 'v1) + + (define timeout +inf.0) + + ; start : request -> response + (define (start initial-request) + `(html (head (title "A Test Page")) + (body ([bgcolor "white"]) + (p "This is a simple module servlet."))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/instance-expiration-2.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/instance-expiration-2.ss new file mode 100644 index 0000000000..07c76af031 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/instance-expiration-2.ss @@ -0,0 +1,24 @@ +(module instance-expiration-2 mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define timeout 5) + (define interface-version 'v2) + + (define (instance-expiration-handler expired-request) + ; Not allowed to call any (lib "servlet.ss" "web-server") methods + ; (I can't enforce this, however, so if you accidentally do weird things will happen.) + (send/suspend (lambda (k) + `(html (head (title "You win.")) (body "You win."))))) + + (define (start initial-request) + (send/suspend/dispatch + (lambda (embed/url) + `(html + (head (title "Instance expiration demo")) + (body (p (a ([href ,(embed/url + (lambda (request) + `(html (head (title "Instance expiration demo")) + (body (p "Reload in a few minutes.") + (p "(or change the instance id to something made up.")))))]) + "Click this link.")))))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/instance-expiration.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/instance-expiration.ss new file mode 100644 index 0000000000..54edd46e17 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/instance-expiration.ss @@ -0,0 +1,23 @@ +(module instance-expiration mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define timeout 5) + (define interface-version 'v2) + + (define (instance-expiration-handler expired-request) + ; Not allowed to call any (lib "servlet.ss" "web-server") methods + ; (I can't enforce this, however, so if you accidentally do weird things will happen.) + `(html (head (title "You win.")) (body "You win."))) + + (define (start initial-request) + (send/suspend/dispatch + (lambda (embed/url) + `(html + (head (title "Instance expiration demo")) + (body (p (a ([href ,(embed/url + (lambda (request) + `(html (head (title "Instance expiration demo")) + (body (p "Reload in a few minutes.") + (p "(or change the instance id to something made up.")))))]) + "Click this link.")))))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-fix-param.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-fix-param.ss new file mode 100644 index 0000000000..f94c381af0 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-fix-param.ss @@ -0,0 +1,11 @@ +(module jas01-fix-param mzscheme + (require (lib "servlet.ss" "web-server")) + (provide get-time) + + (define load-time + (make-web-cell:local #f)) + + (define (get-time) + (web-cell:local-ref load-time)) + + (web-cell:local-set! load-time (current-seconds))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-fix.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-fix.ss new file mode 100644 index 0000000000..e6a74d521a --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-fix.ss @@ -0,0 +1,14 @@ +(module jas01-fix mzscheme + (require (lib "servlet.ss" "web-server") + "jas01-fix-param.ss") + + (provide interface-version timeout start) + (define interface-version 'v1) + (define timeout +inf.0) + + ; start : request -> response + (define (start initial-request) + (report-errors-to-browser send/finish) + `(html (head (title "Servlet Parameter Test")) + (body (h1 "Servlet Parameter Test") + ,(number->string (get-time)))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-param.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-param.ss new file mode 100644 index 0000000000..d04ce39850 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-param.ss @@ -0,0 +1,9 @@ +(module jas01-param mzscheme + (provide get-time) + + (define load-time (make-parameter #f)) + + (define (get-time) + (load-time)) + + (load-time (current-seconds))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-test.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-test.ss new file mode 100644 index 0000000000..b1d7eb2157 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01-test.ss @@ -0,0 +1,10 @@ +(module jas01-test mzscheme + (define start #f) + (thread-wait + (thread + (lambda () + (set! start (dynamic-require "jas01.ss" 'start))))) + (thread-wait + (thread + (lambda () + (printf "~S~n" (start 'foo)))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/jas01.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01.ss new file mode 100644 index 0000000000..c9dac7152d --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/jas01.ss @@ -0,0 +1,14 @@ +(module jas01 mzscheme + (require (lib "servlet.ss" "web-server") + "jas01-param.ss") + + (provide interface-version timeout start) + (define interface-version 'v1) + (define timeout +inf.0) + + ; start : request -> response + (define (start initial-request) + (report-errors-to-browser send/finish) + `(html (head (title "Servlet Parameter Test")) + (body (h1 "Servlet Parameter Test") + ,(number->string (get-time)))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/lock.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/lock.ss new file mode 100644 index 0000000000..6e15bf4e3e --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/lock.ss @@ -0,0 +1,24 @@ +(module lock mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v2-transitional) + (define timeout 60) + (define (instance-expiration-handler _) + `(html (body "Error"))) + (define (start _) + (send/suspend/dispatch + (lambda (embed/url) + `(html (body + (p (a ([href ,(embed/url + (lambda _ + (sleep 5) + (second "Slow")))]) + "Slow")) + (p (a ([href ,(embed/url + (lambda _ + (second "Fast")))]) + "Fast"))))))) + (define (second label) + `(html (body ,label + ,(number->string (current-seconds)))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/none-test.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/none-test.ss new file mode 100644 index 0000000000..96df38bf74 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/none-test.ss @@ -0,0 +1,10 @@ +(module none-test mzscheme + (require (lib "none.ss" "web-server" "managers")) + (provide (all-defined)) + + (define interface-version 'v2-transitional) + (define manager (create-none-manager + (lambda (failed-request) + `(html (body (h2 "Error")))))) + (define (start initial-request) + `(html (body (h2 "Look Ma, No Instance!"))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/plus.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/plus.ss new file mode 100644 index 0000000000..902a702e9c --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/plus.ss @@ -0,0 +1,28 @@ +(module plus mzscheme + (require (lib "servlet.ss" "web-server") + (lib "pretty.ss")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + (define (start _) + (define req + (send/suspend + (lambda (k-url) + `(html + (body + (form ([action ,k-url] [method "POST"]) + (input ([type "checkbox"] [name "checkbox"])) + (input ([type "text"] [name "text+foo"])) + (input ([type "submit"] [name "submit"])))))))) + (define binds + (request-bindings req)) + (define sport + (open-output-string)) + (define pp + (parameterize ([current-output-port sport]) + (pretty-print binds))) + `(html + (body + (pre + ,(get-output-string sport)))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/pr5490.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/pr5490.ss new file mode 100644 index 0000000000..755544e421 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/pr5490.ss @@ -0,0 +1,8 @@ +(module pr5490 mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 120) + (define (start ireq) + (send/finish '(("paul") "...")))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/pr5565.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/pr5565.ss new file mode 100644 index 0000000000..21d9bb836b --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/pr5565.ss @@ -0,0 +1,19 @@ +(module pr5565 mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 120) + (define (start ireq) + (define p + (send/suspend + (build-suspender `("Test of Page 2") + `((input ([type "submit"][value "pls test with and without topping"])))))) + (define q + (send/suspend + (build-suspender `("Bug") + `((input ([type "text"][name "x"])))))) + (define r (extract-binding/single `x (request-bindings q))) + (send/suspend + (build-suspender `("Result of test") + (list r))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/pr7359.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/pr7359.ss new file mode 100644 index 0000000000..341848b0b2 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/pr7359.ss @@ -0,0 +1,12 @@ +(module pr7359 mzscheme + (require (lib "serialize.ss") + (lib "servlet.ss" "web-server")) + (provide interface-version timeout start) + + (define interface-version 'v1) + (define timeout +inf.0) + + (define-serializable-struct foo ()) + (define (start req) + (deserialize (serialize (make-foo))) + `(html (body "Made it")))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/pr7533.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/pr7533.ss new file mode 100644 index 0000000000..372ef37de5 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/pr7533.ss @@ -0,0 +1,19 @@ +(module pr7533 mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + (define (start initial-request) + (define start-time (current-seconds)) + (let loop ((last-time start-time)) + (let ((time (current-seconds))) + (send/suspend + (lambda (k) + `(html + (form ((action ,k) (method "post")) + (p ,(format "It has been ~a seconds since starting (~a seconds since last iteration)." + (- time start-time) + (- time last-time))) + (input ((type "submit"))))))) + (loop time))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/pr7935-other.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/pr7935-other.ss new file mode 100644 index 0000000000..c86b989e4e --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/pr7935-other.ss @@ -0,0 +1,10 @@ +(module pr7935-other mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + + (define (start initial-request) + ;(report-errors-to-browser send/back) + (/ 1 0))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/ssd.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/ssd.ss new file mode 100644 index 0000000000..65327e0f58 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/ssd.ss @@ -0,0 +1,18 @@ +(module ssd mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + (define interface-version 'v1) + (define timeout 120) + (define (start ir) + (printf "X~n") + (send/suspend/dispatch + (lambda (embed/url) + `(html (head) + (body + (ul + ,@(map (lambda (i) + `(li (a ([href ,(embed/url + (lambda (r) + `(html (head) (body ,i))))]) + ,(number->string i)))) + `(1 2 3 4 5 6 7 8 9 0))))))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/static.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/static.ss new file mode 100644 index 0000000000..7e669a00f0 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/static.ss @@ -0,0 +1,9 @@ +(module static mzscheme + (require (lib "servlet.ss" "web-server")) + (provide interface-version timeout start) + (define interface-version 'v1) + (define timeout +inf.0) + (define (start initial-request) + (list + #"text/html" + "Foo"))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/update.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/update.ss new file mode 100644 index 0000000000..2057700245 --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/update.ss @@ -0,0 +1,11 @@ +(module update mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define timeout 60) + (define interface-version 'v1) + (define (start initial-request) + (send/back + `(html (head) + (body + (h1 "Hey")))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/new-suite/utf8.ss b/collects/web-server/default-web-root/servlets/tests/new-suite/utf8.ss new file mode 100644 index 0000000000..ad0d2c251b --- /dev/null +++ b/collects/web-server/default-web-root/servlets/tests/new-suite/utf8.ss @@ -0,0 +1,17 @@ +(module utf8 mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + + (define interface-version 'v1) + (define timeout 60) + (define (start initial-request) + (extract-binding/single + 'name + (request-bindings + (send/suspend + (lambda (k-url) + `(html + (body + (form ([action ,k-url]) + (input ([type "text"] [name "name"])) + (input ([type "submit"]))))))))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/size.ss b/collects/web-server/default-web-root/servlets/tests/size.ss index 23e3db0c2d..4c272a4812 100644 --- a/collects/web-server/default-web-root/servlets/tests/size.ss +++ b/collects/web-server/default-web-root/servlets/tests/size.ss @@ -1,23 +1,23 @@ -(require (lib "unit.ss") - (lib "servlet-sig.ss" "web-server")) - -(let* ([line-size 80] - [build-a-str - (lambda (n) - (list->string (let loop ([n n]) - (cond - [(zero? n) (list #\newline)] - [else (cons #\a (loop (sub1 n)))]))))] - [line (build-a-str (sub1 line-size))] - [html-overhead 68]) - (unit - (import servlet^) - (export) - +(module size mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + (define interface-version 'v1) + (define timeout +inf.0) + + (define line-size 80) + (define (build-a-str n) + (list->string (let loop ([n n]) + (cond + [(zero? n) (list #\newline)] + [else (cons #\a (loop (sub1 n)))])))) + (define line (build-a-str (sub1 line-size))) + (define html-overhead 68) + (define (start initial-request) + (define bindings (request-bindings initial-request)) (define size (- (string->number (cdr (assq 'size bindings))) html-overhead)) (define nlines (quotient size line-size)) (define extra (remainder size line-size)) `(html (head (title "A Page")) (body (p ,@(vector->list (make-vector nlines line)) - ,(build-a-str extra)))))) + ,(build-a-str extra)))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/test.ss b/collects/web-server/default-web-root/servlets/tests/test.ss index db7170d924..1bd7da8d20 100644 --- a/collects/web-server/default-web-root/servlets/tests/test.ss +++ b/collects/web-server/default-web-root/servlets/tests/test.ss @@ -1,9 +1,11 @@ -(require (lib "unit.ss") - (lib "servlet-sig.ss" "web-server")) -(let ([count 0]) - (unit - (import servlet^) - (export) +(module test mzscheme + (require (lib "servlet.ss" "web-server")) + (provide (all-defined)) + (define interface-version 'v1) + (define timeout +inf.0) + (define count 0) + + (define (start initial-request) (with-handlers ([void (lambda (exn) `(html (body (p ,(exn-message exn)))))]) (set! count (add1 count)) `(html (head (title "Testing 1...2...3")) @@ -12,4 +14,4 @@ (br) "Count = " ,(number->string count) (br) - ,(format "Here are the headers:~n~s~n" (request-headers initial-request)))))))) + ,(format "Here are the headers:~n~s~n" (request-headers initial-request)))))))) \ No newline at end of file diff --git a/collects/web-server/default-web-root/servlets/tests/url.ss b/collects/web-server/default-web-root/servlets/tests/url.ss index ffee691cda..65f027e329 100644 --- a/collects/web-server/default-web-root/servlets/tests/url.ss +++ b/collects/web-server/default-web-root/servlets/tests/url.ss @@ -1,10 +1,13 @@ -(require (lib "unit.ss") - (lib "servlet-sig.ss" "web-server") - (lib "url.ss" "net")) -(let ([count 0]) - (unit - (import servlet^) - (export) +(module url mzscheme + (require (lib "servlet.ss" "web-server") + (lib "url.ss" "net")) + (provide (all-defined)) + (define interface-version 'v1) + (define timeout +inf.0) + + (define count 0) + + (define (start initial-request) (set! count (add1 count)) `(html (head (title "URL Test")) (body (p "The method requested is: " ,(format "~s" (request-method initial-request))) diff --git a/collects/web-server/dispatchers/dispatch-servlets.ss b/collects/web-server/dispatchers/dispatch-servlets.ss index a98e1bf7c0..2f87d5c8a1 100644 --- a/collects/web-server/dispatchers/dispatch-servlets.ss +++ b/collects/web-server/dispatchers/dispatch-servlets.ss @@ -2,7 +2,6 @@ (require (lib "url.ss" "net") (lib "kw.ss") (lib "plt-match.ss") - (lib "unit.ss") (lib "string.ss") (lib "contract.ss")) (require "dispatch.ss" @@ -11,7 +10,6 @@ "../private/response.ss" "../response-structs.ss" "../servlet.ss" - "../sig.ss" "../private/configuration.ss" "../private/util.ss" "../managers/manager.ss" @@ -308,14 +306,6 @@ ;;;;;; (XXX: I don't know what 'typed-model-split-store0 was, so it was removed.) ;;;; A response (define (load-servlet/path a-path) - (define (v0.servlet->v1.lambda servlet) - (lambda (initial-request) - (define servlet@ (unit-from-context servlet^)) - (invoke-unit - (compound-unit - (import) (export) - (link (((S : servlet^)) servlet@) - (() servlet S)))))) (define (v0.response->v1.lambda response-path response) (define go (box @@ -333,16 +323,6 @@ ; XXX load/use-compiled breaks errortrace (define s (load/use-compiled a-path)) (cond - ;; signed-unit servlet - ; MF: I'd also like to test that s has the correct import signature. - [(unit? s) - (make-servlet (current-custodian) - (current-namespace) - (create-timeout-manager - default-servlet-instance-expiration-handler - timeouts-servlet-connection - timeouts-default-servlet) - (v0.servlet->v1.lambda s))] ; FIX - reason about exceptions from dynamic require (catch and report if not already) ;; module servlet [(void? s) @@ -359,7 +339,7 @@ timeouts-servlet-connection timeout) (v1.module->v1.lambda timeout start)))] - [(v2-transitional) ; XXX: Undocumented + [(v2 v2-transitional) ; XXX: Undocumented (let ([start (dynamic-require module-name 'start)] [manager (with-handlers ([exn:fail:contract? diff --git a/collects/web-server/private/configure.ss b/collects/web-server/private/configure.ss index adade47611..ec87e7344f 100644 --- a/collects/web-server/private/configure.ss +++ b/collects/web-server/private/configure.ss @@ -1,22 +1,22 @@ (module configure mzscheme - (require (lib "unit.ss") - (lib "servlet-sig.ss" "web-server") + (require (lib "servlet.ss" "web-server") (lib "url.ss" "net") (lib "etc.ss") (lib "list.ss") (lib "pretty.ss") (lib "file.ss") - (lib "contract.ss") (only (lib "configuration.ss" "web-server") default-configuration-table-path) (lib "configuration-table-structs.ss" "web-server" "private") (lib "parse-table.ss" "web-server" "private") (lib "configuration-util.ss" "web-server" "private") (lib "util.ss" "web-server" "private")) - (provide/contract - [servlet unit?] - ; XXX contract - [servlet-maker (string? . -> . unit?)]) + (provide + interface-version timeout + start) + + (define timeout (* 12 60 60)) + (define interface-version 'v1) ;; FIX ; - fuss with changing absolute paths into relative ones internally @@ -30,780 +30,774 @@ ; - change all configuration paths (in the configure servlet and in the server) to ; use a platform independent representation (i.e. a listof strings) - ; servlet-maker : str -> (unit/sig servlet^ -> ()) - (define (servlet-maker default-configuration-path) - (unit - (import servlet^) - (export) - - (define CONFIGURE-SERVLET-NAME "configure.ss") - (define WIDE "70") - - (adjust-timeout! (* 12 60 60)) - (error-print-width 800) ; 10-ish lines - - ; passwords = (listof realm) - ; realm = (make-realm str str (listof user-pass)) - (define-struct realm (name pattern allowed)) - - ; user-pass = (make-user-pass sym str) - (define-struct user-pass (user pass)) - - (define doc-dir "Defaults/documentation") - - (define edit-host-button-name "Edit Minor Details") - - ; build-footer : str -> html - (define (build-footer base) - (let ([scale (lambda (n) (number->string (round (/ n 4))))]) - `(p "Powered by " - (a ([href "http://www.plt-scheme.org/"]) - (img ([width ,(scale 211)] [height ,(scale 76)] - [src ,(string-append base doc-dir "/plt-logo.gif")])))))) - - (define footer (build-footer "/")) - - ; access-error-page : html - (define access-error-page - `(html (head (title "Web Server Configuration Access Error")) - (body ([bgcolor "white"]) - (p "You must connect to the configuration tool from the machine the server runs on using 127.0.0.1 for the host part of the URL.") - ,footer))) - - ; permission-error-page : path -> html - (define (permission-error-page configuration-path) - `(html (head (title "Web Server Configuration Permissions Error")) - (body ([bgcolor "white"]) - (p "You must have read and write access to " - (code ,(path->string configuration-path)) - " in order to configure the server.")))) - - ; check-ip-address : request -> request - (define (check-ip-address request) - (unless (string=? "127.0.0.1" (request-host-ip request)) - (send/finish access-error-page)) - request) - - (check-ip-address initial-request) - - (define web-base (directory-part default-configuration-path)) - - ; more here - abstract with static pages? - (define web-server-icon - `(img ([src ,(string-append "/" doc-dir "/web-server.gif")] - ;[width "123"] [height "115"] - [width "61"] [height "57"]))) - - ; interact : (str -> response) -> bindings - (define (interact page) - (request-bindings (check-ip-address (send/suspend page)))) - - ; choose-configuration-file : -> doesn't - (define (choose-configuration-file) - (let ([configuration-path (ask-for-configuration-path)]) - (let loop () - (if (file-exists? configuration-path) - (let ([perms (file-or-directory-permissions configuration-path)]) - ; race condition - changing the permissions after the check - ; will result in an exception later (which serves them right) - (if (and (memq 'write perms) (memq 'read perms)) - (configure-top-level configuration-path) - (send/finish (permission-error-page configuration-path)))) - (begin (send/suspend (copy-configuration-file configuration-path)) - (with-handlers ([exn:fail:filesystem:exists? send-exn]) - (let-values ([(base name must-be-dir) (split-path configuration-path)]) - (ensure-directory-shallow base)) - (copy-file default-configuration-path configuration-path)) - (loop)))))) - - ; copy-configuration-file : path -> html - (define (copy-configuration-file configuration-path) - (build-suspender - '("Copy Configuration File") - `((h1 "Copy Configuration File") - (p "The configuration file " - (blockquote (code ,(path->string configuration-path))) - "does not exist. Would you like to copy the default configuration to this " - "location?") - (center (input ([type "submit"] [name "ok"] [value "Copy"])))))) - - ; ask-for-configuration-path : -> path - (define (ask-for-configuration-path) - (build-path - (extract-binding/single - 'path - (request-bindings (send/suspend configuration-path-page))))) - - ; configuration-path-page : str -> html - (define configuration-path-page - (build-suspender - '("Choose a Configuration File") - `((h1 "Choose a Web Server Configuration File") - ,web-server-icon - (p "Choose a Web server configuration file to edit. " - (br) - "This Web server uses the configuration in " - (blockquote (code ,(path->string default-configuration-path)))) - (table (tr (th "Configuration path") - (td (input ([type "text"] [name "path"] [size ,WIDE] - [value ,(path->string default-configuration-path)])))) - (tr (td ([colspan "2"] [align "center"]) - (input ([type "submit"] [name "choose-path"] [value "Select"])))))))) - - ; configure-top-level : path -> doesn't - (define (configure-top-level configuration-path) - (with-handlers ([exn:fail:filesystem:exists? send-exn]) - (let ([original-configuration (read-configuration configuration-path)]) - (let loop ([configuration original-configuration]) - (let* ([update-bindings (interact (request-new-configuration-table configuration original-configuration))] - [form-configuration - (delete-hosts (update-configuration configuration update-bindings) - (foldr (lambda (b acc) - (if (string=? "Delete" (cdr b)) - (cons (symbol->string (car b)) acc) - acc)) - null - update-bindings))] - [new-configuration - (cond - [(assq 'add-host update-bindings) - (add-virtual-host form-configuration (extract-bindings 'host-prefixes update-bindings))] - [(reverse-assoc edit-host-button-name update-bindings) - => - (lambda (edit) - ; write the configuration twice when editing a host: once before and once after. - ; The after may never happen if the user doesn't continue - (write-configuration form-configuration configuration-path) - (configure-hosts form-configuration (string->number (symbol->string (car edit)))))] - [else form-configuration])]) - (write-configuration new-configuration configuration-path) - (loop new-configuration)))))) - - ; switch-to-current-port : configuration-table -> (U #f configuration-table) - ; doesn't work - the browser doesn't send the port and it wouldn't be reliable anyway - ; perhaps the server could include it? - '(define (switch-to-current-port old) - (let ([current-port (url-port (request-uri initial-request))]) - (and (not (= current-port (configuration-table-port old))) - (make-configuration-table - current-port - (configuration-table-max-waiting old) - (configuration-table-initial-connection-timeout old) - (configuration-table-default-host old) - (configuration-table-virtual-hosts old))))) - - ; send-exn : tst -> doesn't - (define (send-exn exn) - (send/back (exception-error-page exn))) - - ; reverse-assoc : a (listof (cons b a)) -> (U #f (cons b a)) - (define (reverse-assoc x lst) - (cond - [(null? lst) #f] - [else (if (equal? x (cdar lst)) - (car lst) - (reverse-assoc x (cdr lst)))])) - - ; add-virtual-host : configuration-table (listof str) -> configuration-table - (define (add-virtual-host conf existing-prefixes) - (update-hosts conf (cons (cons "my-host.my-domain.org" - (configuration-table-default-host conf)) - (configuration-table-virtual-hosts conf)))) - - ; update-hosts : configuration-table (listof (cons str host-table)) - (define (update-hosts conf new-hosts) - (make-configuration-table - (configuration-table-port conf) - (configuration-table-max-waiting conf) - (configuration-table-initial-connection-timeout conf) - (configuration-table-default-host conf) - new-hosts)) - - ; delete-hosts : configuration-table (listof str) -> configuration-table - ; pre: (>= (length (configuration-table-virtual-hosts conf)) (max to-delete)) - (define (delete-hosts conf to-delete) - ; the if is not needed, it just avoids some work - (if (null? to-delete) - conf - (update-hosts - conf - (drop (configuration-table-virtual-hosts conf) to-delete)))) - - ; drop : (listof a) (listof str) -> (listof a) - ; pre: (apply < to-delete) - ; to delete the entries in to-filter indexed by to-delete - (define (drop to-filter to-delete) - (let loop ([to-filter to-filter] [to-delete (map string->number to-delete)] [i 0]) - (cond - [(null? to-delete) to-filter] - [else (if (= i (car to-delete)) - (loop (cdr to-filter) (cdr to-delete) (add1 i)) - (cons (car to-filter) (loop (cdr to-filter) to-delete (add1 i))))]))) - - ; configure-hosts : configuration-table (U #f nat) -> configuration-table - ; n is either the virtual host number or #f for the default virtual host - (define (configure-hosts old n) - (if n - (update-hosts old - ; more here - consider restructuring this map. Perhaps it is fine. - ; Perhaps it should short circuit. Perhaps the number of virtual hosts - ; is small so it doesn't matter. Perhaps that is a sloppy way to think/program. - ; The code is really a functional array update except it's on a list. - (map (lambda (host this-n) - (if (= n this-n) - (cons (car host) (configure-host (cdr host))) - host)) - (configuration-table-virtual-hosts old) - (build-list (length (configuration-table-virtual-hosts old)) (lambda (x) x)))) + (define default-configuration-path default-configuration-table-path) + (define (set-config-path! new) + (set! default-configuration-path new)) + + (define CONFIGURE-SERVLET-NAME "configure.ss") + (define WIDE "70") + + ; passwords = (listof realm) + ; realm = (make-realm str str (listof user-pass)) + (define-struct realm (name pattern allowed)) + + ; user-pass = (make-user-pass sym str) + (define-struct user-pass (user pass)) + + (define doc-dir "Defaults/documentation") + + (define edit-host-button-name "Edit Minor Details") + + ; build-footer : str -> html + (define (build-footer base) + (let ([scale (lambda (n) (number->string (round (/ n 4))))]) + `(p "Powered by " + (a ([href "http://www.plt-scheme.org/"]) + (img ([width ,(scale 211)] [height ,(scale 76)] + [src ,(string-append base doc-dir "/plt-logo.gif")])))))) + + (define footer (build-footer "/")) + + ; access-error-page : html + (define access-error-page + `(html (head (title "Web Server Configuration Access Error")) + (body ([bgcolor "white"]) + (p "You must connect to the configuration tool from the machine the server runs on using 127.0.0.1 for the host part of the URL.") + ,footer))) + + ; permission-error-page : path -> html + (define (permission-error-page configuration-path) + `(html (head (title "Web Server Configuration Permissions Error")) + (body ([bgcolor "white"]) + (p "You must have read and write access to " + (code ,(path->string configuration-path)) + " in order to configure the server.")))) + + ; check-ip-address : request -> request + (define (check-ip-address request) + (unless (string=? "127.0.0.1" (request-host-ip request)) + (send/finish access-error-page)) + request) + + (define web-base (directory-part default-configuration-path)) + + ; more here - abstract with static pages? + (define web-server-icon + `(img ([src ,(string-append "/" doc-dir "/web-server.gif")] + ;[width "123"] [height "115"] + [width "61"] [height "57"]))) + + ; interact : (str -> response) -> bindings + (define (interact page) + (request-bindings (check-ip-address (send/suspend page)))) + + ; choose-configuration-file : -> doesn't + (define (choose-configuration-file) + (let ([configuration-path (ask-for-configuration-path)]) + (let loop () + (if (file-exists? configuration-path) + (let ([perms (file-or-directory-permissions configuration-path)]) + ; race condition - changing the permissions after the check + ; will result in an exception later (which serves them right) + (if (and (memq 'write perms) (memq 'read perms)) + (configure-top-level configuration-path) + (send/finish (permission-error-page configuration-path)))) + (begin (send/suspend (copy-configuration-file configuration-path)) + (with-handlers ([exn:fail:filesystem:exists? send-exn]) + (let-values ([(base name must-be-dir) (split-path configuration-path)]) + (ensure-directory-shallow base)) + (copy-file default-configuration-path configuration-path)) + (loop)))))) + + ; copy-configuration-file : path -> html + (define (copy-configuration-file configuration-path) + (build-suspender + '("Copy Configuration File") + `((h1 "Copy Configuration File") + (p "The configuration file " + (blockquote (code ,(path->string configuration-path))) + "does not exist. Would you like to copy the default configuration to this " + "location?") + (center (input ([type "submit"] [name "ok"] [value "Copy"])))))) + + ; ask-for-configuration-path : -> path + (define (ask-for-configuration-path) + (build-path + (extract-binding/single + 'path + (request-bindings (send/suspend configuration-path-page))))) + + ; configuration-path-page : str -> html + (define configuration-path-page + (build-suspender + '("Choose a Configuration File") + `((h1 "Choose a Web Server Configuration File") + ,web-server-icon + (p "Choose a Web server configuration file to edit. " + (br) + "This Web server uses the configuration in " + (blockquote (code ,(path->string default-configuration-path)))) + (table (tr (th "Configuration path") + (td (input ([type "text"] [name "path"] [size ,WIDE] + [value ,(path->string default-configuration-path)])))) + (tr (td ([colspan "2"] [align "center"]) + (input ([type "submit"] [name "choose-path"] [value "Select"])))))))) + + ; configure-top-level : path -> doesn't + (define (configure-top-level configuration-path) + (with-handlers ([exn:fail:filesystem:exists? send-exn]) + (let ([original-configuration (read-configuration configuration-path)]) + (let loop ([configuration original-configuration]) + (let* ([update-bindings (interact (request-new-configuration-table configuration original-configuration))] + [form-configuration + (delete-hosts (update-configuration configuration update-bindings) + (foldr (lambda (b acc) + (if (string=? "Delete" (cdr b)) + (cons (symbol->string (car b)) acc) + acc)) + null + update-bindings))] + [new-configuration + (cond + [(assq 'add-host update-bindings) + (add-virtual-host form-configuration (extract-bindings 'host-prefixes update-bindings))] + [(reverse-assoc edit-host-button-name update-bindings) + => + (lambda (edit) + ; write the configuration twice when editing a host: once before and once after. + ; The after may never happen if the user doesn't continue + (write-configuration form-configuration configuration-path) + (configure-hosts form-configuration (string->number (symbol->string (car edit)))))] + [else form-configuration])]) + (write-configuration new-configuration configuration-path) + (loop new-configuration)))))) + + ; switch-to-current-port : configuration-table -> (U #f configuration-table) + ; doesn't work - the browser doesn't send the port and it wouldn't be reliable anyway + ; perhaps the server could include it? + #;(define (switch-to-current-port old) + (let ([current-port (url-port (request-uri initial-request))]) + (and (not (= current-port (configuration-table-port old))) (make-configuration-table - (configuration-table-port old) + current-port (configuration-table-max-waiting old) (configuration-table-initial-connection-timeout old) - (configure-host (configuration-table-default-host old)) - (configuration-table-virtual-hosts old)))) - - ; configure-host : host-table -> host-table - (define (configure-host old) - (let* ([bindings (interact (request-new-host-table old))] - [new (update-host-table old bindings)]) - (when (assq 'edit-passwords bindings) - (let* ([paths (host-table-paths new)] - [password-path - ;; build-path-unless-absolute is defined in configuration.ss - (build-path-unless-absolute - (build-path-unless-absolute web-base (paths-host-base paths)) - (paths-passwords paths))]) - (unless (file-exists? password-path) - (write-to-file password-path ''())) - (configure-passwords password-path))) - new)) - - (define restart-message - `((h3 (font ([color "red"]) "Restart the Web server to use the new settings.")))) - - ; request-new-configuration-table : configuration-table configuration-table -> str -> html - (define (request-new-configuration-table old orig) - (build-suspender - '("PLT Web Server Configuration") - `((h1 "PLT Web Server Configuration Management") - ,web-server-icon - "copyright 2001 by Paul Graunke and PLT" - (hr) - (h2 "Basic Configuration") - (table - ,(make-tr-num "Port" 'port (configuration-table-port old)) - ,(make-tr-num "Maximum Waiting Connections" - 'waiting (configuration-table-max-waiting old)) - ,(make-tr-num "Initial Connection Timeout (seconds)" 'time-initial - (configuration-table-initial-connection-timeout old))) - (hr) - (h2 "Host Name Configuration") - (p "The Web server accepts requests on behalf of multiple " (em "hosts") - " each corresponding to a domain name." - " The table below maps domain names to host specific configurations.") - (table ([width "50%"]) - (tr (th ([align "left"]) "Name") ;(th "Host configuration path") - (th "Host Directory") - (th nbsp) - (th nbsp)) - (tr (td ,"Default Host") - (td ,(make-field-size "text" 'default-host-root - (table->host-root (configuration-table-default-host old)) - WIDE)) - (td ([align "center"]) - (input ([type "submit"] [name "default"] [value ,edit-host-button-name]))) - (td nbsp)) - ,@(map (lambda (host n) - `(tr (td ,(make-field "text" 'host-regexps (car host))) - (td ,(make-field-size "text" 'host-roots (table->host-root (cdr host)) WIDE)) - (td ([align "center"]) - (input ([type "submit"] [name ,n] [value ,edit-host-button-name]))) - (td ([align "center"]) - (input ([type "submit"] [name ,n] [value "Delete"]))))) - (configuration-table-virtual-hosts old) - (build-list (length (configuration-table-virtual-hosts old)) number->string)) - (tr (td (input ([type "submit"] [name "add-host"] [value "Add Host"]))) - (td nbsp); (input ([type "submit"] [name "configure"] [value "Delete"])) - ;(td (input ([type "submit"] [name "edit-host-details"] [value "Edit"]))) - (td nbsp))) - (hr) - (table ([width "90%"]) - ,@(if (equal? old orig) ; This only tests eq? because structures are more opaque now. - null - `((tr (td ,@restart-message)))) - (tr (td (input ([type "submit"] [name "configure"] [value "Update Configuration"]))))) - (hr) - ,footer))) - - ; table->host-root : host-table -> str - (define (table->host-root t) - (path->string (build-path-unless-absolute web-base (paths-host-base (host-table-paths t))))) - - ; gen-make-tr : nat -> xexpr sym str [xexpr ...] -> xexpr - (define (gen-make-tr size-n) - (let ([size-str (number->string size-n)]) - (lambda (label tag default-text . extra-tds) - `(tr (td (a ([href ,(format "/~a/terms/~a.html" doc-dir tag)]) ,label)) - (td ,(make-field-size "text" tag (format "~a" default-text) size-str)) - . ,extra-tds)))) - - (define make-tr-num (gen-make-tr 20)) - - (define make-tr-str (gen-make-tr 70)) - - ; make-field : str sym str -> xexpr - (define (make-field type label value) - (make-field-size type label value "30")) - - ; make-field-size : str sym str str -> xexpr - (define (make-field-size type label value size) - `(input ([type ,type] [name ,(symbol->string label)] [value ,value] [size ,size]))) - - ; update-configuration : configuration-table bindings -> configuration-table - (define (update-configuration old bindings) - (let ([ubp (un-build-path web-base)]) ;; web-base returned by directory-part is a path - (make-configuration-table - (string->nat (extract-binding/single 'port bindings)) - (string->nat (extract-binding/single 'waiting bindings)) - (string->num (extract-binding/single 'time-initial bindings)) - (update-host-root (configuration-table-default-host old) - (ubp (build-path (extract-binding/single 'default-host-root bindings)))) - (map (lambda (h root pattern) - (cons pattern (update-host-root (cdr h) (ubp (build-path root))))) - (configuration-table-virtual-hosts old) - (extract-bindings 'host-roots bindings) - (extract-bindings 'host-regexps bindings))))) - - ; update-host-root : host-table str -> host-table - (define (update-host-root host new-root) - (host-table<-paths host (paths<-host-base (host-table-paths host) new-root))) - - ; host-table<-paths : host-table paths -> host-table - ; more here - create these silly functions automatically from def-struct macro - (define (host-table<-paths host paths) - (make-host-table - (host-table-indices host) - (host-table-log-format host) - (host-table-messages host) - (host-table-timeouts host) - paths)) - - ; paths<-host-base : paths str -> paths - ; more here - create these silly functions automatically from def-struct macro - (define (paths<-host-base paths host-base) - (make-paths (paths-conf paths) - host-base - (paths-log paths) - (paths-htdocs paths) - (paths-servlet paths) - (paths-mime-types paths) - (paths-passwords paths))) - - ; string->num : str -> nat - (define (string->num str) - (or (string->number str) (error 'string->nat "~s is not a number" str))) - - ; string->nat : str -> nat - (define (string->nat str) - (let ([n (string->number str)]) - (if (and n (integer? n) (exact? n) (>= n 0)) - n - (error 'string->nat "~s is not exactly a natural number" str)))) - - ; request-new-host-table : host-table -> str -> response - (define (request-new-host-table old) - (let* ([timeouts (host-table-timeouts old)] - [paths (host-table-paths old)] - [m (host-table-messages old)] - [host-root (build-path-unless-absolute web-base (paths-host-base paths))] - [conf (build-path-unless-absolute host-root (paths-conf paths))]) - (build-suspender - '("Configure Host") - `((h1 "PLT Web Server Host configuration") - (input ([type "submit"] [value "Save Configuration"])) - (hr) - (table - (tr (th ([colspan "2"]) "Paths")) - ,(make-tr-str "Log file" - 'path-log (build-path-unless-absolute host-root (paths-log paths))) - ,(make-tr-str "Web document root" - 'path-htdocs (build-path-unless-absolute host-root (paths-htdocs paths))) - ,(make-tr-str "Servlet root" - 'path-servlet (build-path-unless-absolute host-root (paths-servlet paths))) - ,(make-tr-str "MIME Types" - 'path-mime-types (build-path-unless-absolute host-root (paths-mime-types paths))) - ,(make-tr-str "Password File" - 'path-password (build-path-unless-absolute host-root (paths-passwords paths))) - (tr (td ([colspan "2"]) - ,(make-field "submit" 'edit-passwords "Edit Passwords"))) - (tr (td ([colspan "2"]) (hr))) - (tr (th ([colspan "2"]) "Message Paths")) - ,(make-tr-str "Servlet error" 'path-servlet-message - (build-path-unless-absolute conf (messages-servlet m))) - ,(make-tr-str "Access Denied" 'path-access-message - (build-path-unless-absolute conf (messages-authentication m))) - ,(make-tr-str "Servlet cache refreshed" 'servlet-refresh-message - (build-path-unless-absolute conf (messages-servlets-refreshed m))) - ,(make-tr-str "Password cache refreshed" 'password-refresh-message - (build-path-unless-absolute conf (messages-passwords-refreshed m))) - ,(make-tr-str "File not found" 'path-not-found-message - (build-path-unless-absolute conf (messages-file-not-found m))) - ,(make-tr-str "Protocol error" 'path-protocol-message - (build-path-unless-absolute conf (messages-protocol m))) - ,(make-tr-str "Collect garbage" 'path-collect-garbage-message - (build-path-unless-absolute conf (messages-collect-garbage m))) - (tr (td ([colspan "2"]) (hr))) - (tr (th ([colspan "2"]) "Timeout Seconds")) - ,(make-tr-num "Default Servlet" 'time-default-servlet (timeouts-default-servlet timeouts)) - ,(make-tr-num "Password" 'time-password (timeouts-password timeouts)) - ,(make-tr-num "Servlet Connection" 'time-servlet-connection (timeouts-servlet-connection timeouts)) - ,(make-tr-num "per Byte When Transfering Files" 'time-file-per-byte (timeouts-file-per-byte timeouts)) - ,(make-tr-num "Base When Transfering Files" 'time-file-base (timeouts-file-base timeouts))) - (hr) - (input ([type "submit"] [value "Save Configuration"])) - ,footer)))) - - ; update-host-table : host-table (listof (cons sym str)) -> host-table - (define (update-host-table old bindings) - (let* ([eb (lambda (tag) (build-path (extract-binding/single tag bindings)))] - [paths (host-table-paths old)] - [host-root (paths-host-base paths)] - [expanded-host-root (build-path-unless-absolute web-base host-root)] - [conf (build-path-unless-absolute expanded-host-root (paths-conf paths))] - [ubp (un-build-path expanded-host-root)] - [eb-host-root (lambda (tag) (ubp (eb tag)))] - [ubp-conf (un-build-path conf)] - [eb-conf (lambda (tag) (ubp-conf (eb tag)))]) - (make-host-table - (host-table-indices old) - (host-table-log-format old) - (apply make-messages - (map eb-conf '(path-servlet-message path-access-message servlet-refresh-message password-refresh-message path-not-found-message path-protocol-message path-collect-garbage-message))) - (apply make-timeouts - (map (lambda (tag) (string->number (extract-binding/single tag bindings))) - '(time-default-servlet time-password time-servlet-connection time-file-per-byte time-file-base))) - (let ([old-paths (host-table-paths old)]) - (apply make-paths - (paths-conf old-paths) - ((un-build-path web-base) - (build-path (paths-host-base old-paths))) - (map eb-host-root '(path-log path-htdocs path-servlet path-mime-types path-password))))))) - - ; un-build-path : path -> path -> string - ; (GregP) Theory: this should return a string not a path so that the result can be - ; written to the configuration file. - (define (un-build-path possible-base) - (let ([base-list (path->list possible-base)]) - (lambda (path) - (let ([path-list (path->list path)]) - (cond - [(suffix base-list path-list) - => (lambda (x) (path->string (apply build-path x)))] - [else - (path->string path)]))))) - - ; suffix : (listof a) (listof a) -> (U #f (listof a)) - ; to return the extra elements in b after removing all elements from a in order - (define (suffix a b) - (cond - [(null? a) (if (null? b) #f b)] - [else (cond - [(null? b) #f] - [else (and (equal? (car a) (car b)) - (suffix (cdr a) (cdr b)))])])) - - ; Password Configuration - - ; configure-passwords : path -> void - (define (configure-passwords password-path) - (edit-passwords - password-path - (if (file-exists? password-path) - (call-with-input-file password-path read-passwords) - null))) - - ; edit-passwords : path passwords -> passwords - (define (edit-passwords which-one passwords) - (let* ([bindings (interact (password-updates which-one passwords))] - [to-deactivate (extract-bindings 'deactivate bindings)] - [again - (lambda (new-passwords) - (write-to-file which-one (format-passwords new-passwords)) - (edit-passwords which-one new-passwords))]) - (cond - [(assq 'edit bindings) - => (lambda (edit) - (again (drop (map (let ([to-edit (string->number (cdr edit))]) - (lambda (r n) - (if (= to-edit n) - (edit-realm r) - r))) - passwords - (build-list (length passwords) (lambda (x) x))) - to-deactivate)))] - [(assq 'add bindings) - (again (cons (make-realm "new realm" "" null) - (drop passwords to-deactivate)))] - [else (drop passwords to-deactivate)]))) - - ; password-updates : path passwords -> request - (define (password-updates which-one passwords) - (let ([which-one (path->string which-one)]) - (build-suspender - `("Updating Passwords for " ,which-one) - `((h1 "Updating Passwords for ") - (h3 ,which-one) - (h2 "You may wish to " (font ([color "red"]) "backup") " this password file.") - (p "Each authentication " (em "realm") " password protects URLs that match a pattern. " - "Choose a realm to edit below:") - (table - (tr (th "Realm Name") (th "Delete") (th "Edit")) - . ,(map (lambda (realm n) - `(tr (td ,(realm-name realm)) - (td ,(make-field "checkbox" 'deactivate n)) - (td ,(make-field "radio" 'edit n)))) - passwords - (build-list (length passwords) number->string))) - ,(make-field "submit" 'add "Add Realm") - ,(make-field "submit" 'edit-button "Edit") - ,footer)))) - - ; edit-realm : realm -> realm - (define (edit-realm realm) - (let* ([bindings (interact (realm-updates realm))] - [new-name (extract-binding/single 'realm-name bindings)] - [new-pattern (extract-binding/single 'realm-pattern bindings)] - [new-allowed - (drop (map (lambda (u p) (make-user-pass (string->symbol u) p)) - (extract-bindings 'user bindings) - (extract-bindings 'pass bindings)) - (extract-bindings 'deactivate bindings))]) - ; more here - check something? Everything is a string or symbol, though. - (cond - [(assq 'add-user bindings) - (edit-realm (make-realm new-name new-pattern - (cons (make-user-pass 'ptg "Scheme-is-cool!") new-allowed)))] - [(assq 'update bindings) - (make-realm new-name new-pattern new-allowed)] - [else (error 'edit-realm "Didn't find either 'add-user or 'update in ~s" bindings)]))) - - ; realm-updates : realm -> request - (define (realm-updates realm) - (build-suspender - `("Update Authentication Realm " ,(realm-name realm)) - `((h1 "Update Authentication Realm") - (table - ,(make-tr-str "Realm Name" 'realm-name (realm-name realm)) - ,(make-tr-str "Protected URL Path Pattern" 'realm-pattern (realm-pattern realm))) - (hr) - (table - (tr (th "User Name") (th "Password") (th "Delete")) - . ,(map (lambda (x n) - `(tr (td ,(make-field "text" 'user (symbol->string (user-pass-user x)))) - (td ,(make-field "text" 'pass (user-pass-pass x))) - (td ,(make-field "checkbox" 'deactivate n)))) - (realm-allowed realm) - (build-list (length (realm-allowed realm)) number->string))) - (input ([type "submit"] [name "add-user"] [value "Add User"])) - (input ([type "submit"] [name "update"] [value "Update Realm"])) - ,footer))) - - ; read-passwords : iport -> passwords - ; only works if the file starts with (quote ...) - (define (read-passwords in) - (let ([raw (read in)]) - (unless (and (pair? raw) (eq? 'quote (car raw)) - (null? (cddr raw))) - (error 'read-passwords "The password file must be quoted to use the configuration tool.")) - (map (lambda (raw-realm) - ; more here - error checking - (make-realm (car raw-realm) - (cadr raw-realm) - (map (lambda (x) (make-user-pass (car x) (cadr x))) - (cddr raw-realm)))) - (cadr raw)))) - - ; format-passwords : passwords -> s-expr - (define (format-passwords passwords) - (list 'quote - (map (lambda (r) - (list* (realm-name r) - (realm-pattern r) - (map (lambda (x) - (list (user-pass-user x) (user-pass-pass x))) - (realm-allowed r)))) - passwords))) - - ; Little Helpers - - ; initialization-error-page : response - (define initialization-error-page - `(html (head (title "Web Server Configuration Program Invocation Error")) - (body ([bgcolor "white"]) - (p "Please direct your browser directly to the " - (a ([href ,(url->string (request-uri initial-request))]) "configuration program,") - " not through another URL.") - ,footer))) - - ; done-page : html - (define done-page - ; more-here - consider adding more useful information - `(html (head (title "done")) - (body ([bgcolor "white"]) - (h2 "Configuration Saved.") - (p "Click your browser's back button to continue configuring the server.") - ,footer))) - - ; exception-error-page : TST -> html - (define (exception-error-page exn) - `(html (head (title "Error")) - (body ([bgcolor "white"]) - (p "Servlet exception: " - (pre ,(exn->string exn))) - ,footer))) - - (define must-select-host-page - `(html (head (title "Web Server Configuration Error")) - (body ([bgcolor "white"]) - (p "Please select which host to edit before clicking the Edit button.") - ,footer))) - - ; io - - ; read-configuration : path -> configuration-table - (define (read-configuration configuration-path) - (parse-configuration-table (call-with-input-file configuration-path read))) - - ; write-configuration : configuration-table path -> void - ; writes out the new configuration file and - ; also copies the configure.ss servlet to the default-host's servlet directory - (define (write-configuration new configuration-path) - (ensure-configuration-servlet configuration-path (configuration-table-default-host new)) - (ensure-configuration-paths new) - (write-configuration-table new configuration-path)) - - ; ensure-configuration-servlet : path host-table -> void - (define (ensure-configuration-servlet configuration-path host) - (let* ([paths (host-table-paths host)] - [root (build-path-unless-absolute web-base - (paths-host-base paths))] - [servlets-path - (build-path (build-path-unless-absolute root (paths-servlet paths)) "servlets")]) - (ensure-config-servlet configuration-path servlets-path) - (let ([defaults (build-path "Defaults")]) - (ensure* (collection-path "web-server" "default-web-root" "htdocs") - (build-path-unless-absolute root (paths-htdocs paths)) - defaults)))) - - ; ensure-configuration-paths : configuration-table -> void - ; to ensure that all the referenced config files exist for an entire configuration - (define (ensure-configuration-paths configuration) - (ensure-host-configuration (configuration-table-default-host configuration)) - (for-each (lambda (x) (ensure-host-configuration (cdr x))) - (configuration-table-virtual-hosts configuration))) - - ; ensure-host-configuration : host-table -> void - ; to ensure that all the referenced config files exist for a virtual host - (define (ensure-host-configuration host) - (let* ([paths (host-table-paths host)] - [host-base (build-path-unless-absolute web-base (paths-host-base paths))] - [conf (build-path-unless-absolute host-base (paths-conf paths))] - [log (build-path-unless-absolute host-base (paths-log paths))]) - ; skip passwords since a missing file is an okay default - (ensure-directory-shallow conf) - (ensure-directory-shallow host-base) - ;(ensure-file log ...) ; empty log file is okay - (ensure-directory-shallow (build-path-unless-absolute host-base (paths-htdocs paths))) - (ensure-directory-shallow (build-path-unless-absolute host-base (paths-servlet paths))) - (let* ([messages (host-table-messages host)] - ; more here maybe - check default config file instead? maybe not - [from-conf (collection-path "web-server" "default-web-root" "conf")] - [copy-conf - (lambda (from to) - (let ([to-path (build-path-unless-absolute conf to)]) - ; more here - check existance of from path? - (copy-file* (build-path from-conf from) to-path)))]) - (copy-conf "passwords-refresh.html" (messages-passwords-refreshed messages)) - (copy-conf "servlet-refresh.html" (messages-servlets-refreshed messages)) - (copy-conf "forbidden.html" (messages-authentication messages)) - (copy-conf "protocol-error.html" (messages-protocol messages)) - (copy-conf "not-found.html" (messages-file-not-found messages)) - (copy-conf "servlet-error.html" (messages-servlet messages)) - (copy-conf "collect-garbage.html" (messages-collect-garbage messages))))) - - ; ensure-file : path path path -> void - ; to copy (build-path from name) to (build-path to name), creating directories as - ; needed if the latter does not already exist. - (define (ensure-file from to name) - (let ([to (simplify-path to)]) - (ensure-directory-shallow to) - (let ([to-path (build-path to name)]) - (unless (file-exists? to-path) - (copy-file (build-path from name) to-path))))) - - ; copy-file* : str str -> void - (define (copy-file* from-path to-path) - (unless (file-exists? to-path) - (let-values ([(to-path-base to-path-name must-be-dir?) (split-path to-path)]) - (ensure-directory-shallow to-path-base)) - (copy-file from-path to-path))) - - ; ensure* : path path path -> void - ;; GregP: Don't know what the heck this does (thanks Paul) - ;; but the first two arguments are now paths. - (define (ensure* from to name) - (ensure-directory-shallow to) - (let ([p (build-path from name)]) - (cond - [(directory-exists? p) - (unless (member (path->string name) '("CVS" ".svn")) ; yuck - (let ([dest (build-path to name)]) - (ensure-directory-shallow dest) - (for-each (lambda (x) (ensure* p dest x)) - (directory-list p))))] - [(file-exists? p) - (ensure-file from to name)]))) - - ; ensure-directory-shallow : path -> void - (define (ensure-directory-shallow to) - (unless (directory-exists? to) - ; race condition - someone else could make the directory - (make-directory* to))) - - ; ensure-config-servlet : str path -> void - ; to create, if necessary, a stub configuration servlet that includes the main configuration servlet - ; at the desired location in a new web tree - (define (ensure-config-servlet configuration-path servlets-path) - (ensure-directory-shallow servlets-path) - (let ([file-path (build-path servlets-path CONFIGURE-SERVLET-NAME)]) - (unless (file-exists? file-path) ; more here - check that it's a well formed servlet? - (call-with-output-file - file-path - (lambda (out) - (pretty-print - `(require (lib ,CONFIGURE-SERVLET-NAME "web-server")) - out) - (newline out) - (pretty-print - `(servlet-maker ,(path->string configuration-path)) - out)))))) - - ; extract-definition : sym (listof s-expr) -> s-expr - ; to return the rhs from (def name rhs) not (def (name . args) body) - (define (extract-definition name defs) - (or (ormap (lambda (def) - (and (pair? def) (eq? 'define (car def)) - (pair? (cdr def)) (eq? name (cadr def)) - (pair? (cddr def)) - (caddr def))) - defs) - (error 'extract-definition "definition for ~a not found" name))) - - ; passwords = str (i.e. path to a file) - - (define build-path-maybe-expression->file-name caddr) - - ; main - (choose-configuration-file))) + (configuration-table-default-host old) + (configuration-table-virtual-hosts old))))) - (define servlet (servlet-maker default-configuration-table-path))) + ; send-exn : tst -> doesn't + (define (send-exn exn) + (send/back (exception-error-page exn))) + + ; reverse-assoc : a (listof (cons b a)) -> (U #f (cons b a)) + (define (reverse-assoc x lst) + (cond + [(null? lst) #f] + [else (if (equal? x (cdar lst)) + (car lst) + (reverse-assoc x (cdr lst)))])) + + ; add-virtual-host : configuration-table (listof str) -> configuration-table + (define (add-virtual-host conf existing-prefixes) + (update-hosts conf (cons (cons "my-host.my-domain.org" + (configuration-table-default-host conf)) + (configuration-table-virtual-hosts conf)))) + + ; update-hosts : configuration-table (listof (cons str host-table)) + (define (update-hosts conf new-hosts) + (make-configuration-table + (configuration-table-port conf) + (configuration-table-max-waiting conf) + (configuration-table-initial-connection-timeout conf) + (configuration-table-default-host conf) + new-hosts)) + + ; delete-hosts : configuration-table (listof str) -> configuration-table + ; pre: (>= (length (configuration-table-virtual-hosts conf)) (max to-delete)) + (define (delete-hosts conf to-delete) + ; the if is not needed, it just avoids some work + (if (null? to-delete) + conf + (update-hosts + conf + (drop (configuration-table-virtual-hosts conf) to-delete)))) + + ; drop : (listof a) (listof str) -> (listof a) + ; pre: (apply < to-delete) + ; to delete the entries in to-filter indexed by to-delete + (define (drop to-filter to-delete) + (let loop ([to-filter to-filter] [to-delete (map string->number to-delete)] [i 0]) + (cond + [(null? to-delete) to-filter] + [else (if (= i (car to-delete)) + (loop (cdr to-filter) (cdr to-delete) (add1 i)) + (cons (car to-filter) (loop (cdr to-filter) to-delete (add1 i))))]))) + + ; configure-hosts : configuration-table (U #f nat) -> configuration-table + ; n is either the virtual host number or #f for the default virtual host + (define (configure-hosts old n) + (if n + (update-hosts old + ; more here - consider restructuring this map. Perhaps it is fine. + ; Perhaps it should short circuit. Perhaps the number of virtual hosts + ; is small so it doesn't matter. Perhaps that is a sloppy way to think/program. + ; The code is really a functional array update except it's on a list. + (map (lambda (host this-n) + (if (= n this-n) + (cons (car host) (configure-host (cdr host))) + host)) + (configuration-table-virtual-hosts old) + (build-list (length (configuration-table-virtual-hosts old)) (lambda (x) x)))) + (make-configuration-table + (configuration-table-port old) + (configuration-table-max-waiting old) + (configuration-table-initial-connection-timeout old) + (configure-host (configuration-table-default-host old)) + (configuration-table-virtual-hosts old)))) + + ; configure-host : host-table -> host-table + (define (configure-host old) + (let* ([bindings (interact (request-new-host-table old))] + [new (update-host-table old bindings)]) + (when (assq 'edit-passwords bindings) + (let* ([paths (host-table-paths new)] + [password-path + ;; build-path-unless-absolute is defined in configuration.ss + (build-path-unless-absolute + (build-path-unless-absolute web-base (paths-host-base paths)) + (paths-passwords paths))]) + (unless (file-exists? password-path) + (write-to-file password-path ''())) + (configure-passwords password-path))) + new)) + + (define restart-message + `((h3 (font ([color "red"]) "Restart the Web server to use the new settings.")))) + + ; request-new-configuration-table : configuration-table configuration-table -> str -> html + (define (request-new-configuration-table old orig) + (build-suspender + '("PLT Web Server Configuration") + `((h1 "PLT Web Server Configuration Management") + ,web-server-icon + "copyright 2001 by Paul Graunke and PLT" + (hr) + (h2 "Basic Configuration") + (table + ,(make-tr-num "Port" 'port (configuration-table-port old)) + ,(make-tr-num "Maximum Waiting Connections" + 'waiting (configuration-table-max-waiting old)) + ,(make-tr-num "Initial Connection Timeout (seconds)" 'time-initial + (configuration-table-initial-connection-timeout old))) + (hr) + (h2 "Host Name Configuration") + (p "The Web server accepts requests on behalf of multiple " (em "hosts") + " each corresponding to a domain name." + " The table below maps domain names to host specific configurations.") + (table ([width "50%"]) + (tr (th ([align "left"]) "Name") ;(th "Host configuration path") + (th "Host Directory") + (th nbsp) + (th nbsp)) + (tr (td ,"Default Host") + (td ,(make-field-size "text" 'default-host-root + (table->host-root (configuration-table-default-host old)) + WIDE)) + (td ([align "center"]) + (input ([type "submit"] [name "default"] [value ,edit-host-button-name]))) + (td nbsp)) + ,@(map (lambda (host n) + `(tr (td ,(make-field "text" 'host-regexps (car host))) + (td ,(make-field-size "text" 'host-roots (table->host-root (cdr host)) WIDE)) + (td ([align "center"]) + (input ([type "submit"] [name ,n] [value ,edit-host-button-name]))) + (td ([align "center"]) + (input ([type "submit"] [name ,n] [value "Delete"]))))) + (configuration-table-virtual-hosts old) + (build-list (length (configuration-table-virtual-hosts old)) number->string)) + (tr (td (input ([type "submit"] [name "add-host"] [value "Add Host"]))) + (td nbsp); (input ([type "submit"] [name "configure"] [value "Delete"])) + ;(td (input ([type "submit"] [name "edit-host-details"] [value "Edit"]))) + (td nbsp))) + (hr) + (table ([width "90%"]) + ,@(if (equal? old orig) ; This only tests eq? because structures are more opaque now. + null + `((tr (td ,@restart-message)))) + (tr (td (input ([type "submit"] [name "configure"] [value "Update Configuration"]))))) + (hr) + ,footer))) + + ; table->host-root : host-table -> str + (define (table->host-root t) + (path->string (build-path-unless-absolute web-base (paths-host-base (host-table-paths t))))) + + ; gen-make-tr : nat -> xexpr sym str [xexpr ...] -> xexpr + (define (gen-make-tr size-n) + (let ([size-str (number->string size-n)]) + (lambda (label tag default-text . extra-tds) + `(tr (td (a ([href ,(format "/~a/terms/~a.html" doc-dir tag)]) ,label)) + (td ,(make-field-size "text" tag (format "~a" default-text) size-str)) + . ,extra-tds)))) + + (define make-tr-num (gen-make-tr 20)) + + (define make-tr-str (gen-make-tr 70)) + + ; make-field : str sym str -> xexpr + (define (make-field type label value) + (make-field-size type label value "30")) + + ; make-field-size : str sym str str -> xexpr + (define (make-field-size type label value size) + `(input ([type ,type] [name ,(symbol->string label)] [value ,value] [size ,size]))) + + ; update-configuration : configuration-table bindings -> configuration-table + (define (update-configuration old bindings) + (let ([ubp (un-build-path web-base)]) ;; web-base returned by directory-part is a path + (make-configuration-table + (string->nat (extract-binding/single 'port bindings)) + (string->nat (extract-binding/single 'waiting bindings)) + (string->num (extract-binding/single 'time-initial bindings)) + (update-host-root (configuration-table-default-host old) + (ubp (build-path (extract-binding/single 'default-host-root bindings)))) + (map (lambda (h root pattern) + (cons pattern (update-host-root (cdr h) (ubp (build-path root))))) + (configuration-table-virtual-hosts old) + (extract-bindings 'host-roots bindings) + (extract-bindings 'host-regexps bindings))))) + + ; update-host-root : host-table str -> host-table + (define (update-host-root host new-root) + (host-table<-paths host (paths<-host-base (host-table-paths host) new-root))) + + ; host-table<-paths : host-table paths -> host-table + ; more here - create these silly functions automatically from def-struct macro + (define (host-table<-paths host paths) + (make-host-table + (host-table-indices host) + (host-table-log-format host) + (host-table-messages host) + (host-table-timeouts host) + paths)) + + ; paths<-host-base : paths str -> paths + ; more here - create these silly functions automatically from def-struct macro + (define (paths<-host-base paths host-base) + (make-paths (paths-conf paths) + host-base + (paths-log paths) + (paths-htdocs paths) + (paths-servlet paths) + (paths-mime-types paths) + (paths-passwords paths))) + + ; string->num : str -> nat + (define (string->num str) + (or (string->number str) (error 'string->nat "~s is not a number" str))) + + ; string->nat : str -> nat + (define (string->nat str) + (let ([n (string->number str)]) + (if (and n (integer? n) (exact? n) (>= n 0)) + n + (error 'string->nat "~s is not exactly a natural number" str)))) + + ; request-new-host-table : host-table -> str -> response + (define (request-new-host-table old) + (let* ([timeouts (host-table-timeouts old)] + [paths (host-table-paths old)] + [m (host-table-messages old)] + [host-root (build-path-unless-absolute web-base (paths-host-base paths))] + [conf (build-path-unless-absolute host-root (paths-conf paths))]) + (build-suspender + '("Configure Host") + `((h1 "PLT Web Server Host configuration") + (input ([type "submit"] [value "Save Configuration"])) + (hr) + (table + (tr (th ([colspan "2"]) "Paths")) + ,(make-tr-str "Log file" + 'path-log (build-path-unless-absolute host-root (paths-log paths))) + ,(make-tr-str "Web document root" + 'path-htdocs (build-path-unless-absolute host-root (paths-htdocs paths))) + ,(make-tr-str "Servlet root" + 'path-servlet (build-path-unless-absolute host-root (paths-servlet paths))) + ,(make-tr-str "MIME Types" + 'path-mime-types (build-path-unless-absolute host-root (paths-mime-types paths))) + ,(make-tr-str "Password File" + 'path-password (build-path-unless-absolute host-root (paths-passwords paths))) + (tr (td ([colspan "2"]) + ,(make-field "submit" 'edit-passwords "Edit Passwords"))) + (tr (td ([colspan "2"]) (hr))) + (tr (th ([colspan "2"]) "Message Paths")) + ,(make-tr-str "Servlet error" 'path-servlet-message + (build-path-unless-absolute conf (messages-servlet m))) + ,(make-tr-str "Access Denied" 'path-access-message + (build-path-unless-absolute conf (messages-authentication m))) + ,(make-tr-str "Servlet cache refreshed" 'servlet-refresh-message + (build-path-unless-absolute conf (messages-servlets-refreshed m))) + ,(make-tr-str "Password cache refreshed" 'password-refresh-message + (build-path-unless-absolute conf (messages-passwords-refreshed m))) + ,(make-tr-str "File not found" 'path-not-found-message + (build-path-unless-absolute conf (messages-file-not-found m))) + ,(make-tr-str "Protocol error" 'path-protocol-message + (build-path-unless-absolute conf (messages-protocol m))) + ,(make-tr-str "Collect garbage" 'path-collect-garbage-message + (build-path-unless-absolute conf (messages-collect-garbage m))) + (tr (td ([colspan "2"]) (hr))) + (tr (th ([colspan "2"]) "Timeout Seconds")) + ,(make-tr-num "Default Servlet" 'time-default-servlet (timeouts-default-servlet timeouts)) + ,(make-tr-num "Password" 'time-password (timeouts-password timeouts)) + ,(make-tr-num "Servlet Connection" 'time-servlet-connection (timeouts-servlet-connection timeouts)) + ,(make-tr-num "per Byte When Transfering Files" 'time-file-per-byte (timeouts-file-per-byte timeouts)) + ,(make-tr-num "Base When Transfering Files" 'time-file-base (timeouts-file-base timeouts))) + (hr) + (input ([type "submit"] [value "Save Configuration"])) + ,footer)))) + + ; update-host-table : host-table (listof (cons sym str)) -> host-table + (define (update-host-table old bindings) + (let* ([eb (lambda (tag) (build-path (extract-binding/single tag bindings)))] + [paths (host-table-paths old)] + [host-root (paths-host-base paths)] + [expanded-host-root (build-path-unless-absolute web-base host-root)] + [conf (build-path-unless-absolute expanded-host-root (paths-conf paths))] + [ubp (un-build-path expanded-host-root)] + [eb-host-root (lambda (tag) (ubp (eb tag)))] + [ubp-conf (un-build-path conf)] + [eb-conf (lambda (tag) (ubp-conf (eb tag)))]) + (make-host-table + (host-table-indices old) + (host-table-log-format old) + (apply make-messages + (map eb-conf '(path-servlet-message path-access-message servlet-refresh-message password-refresh-message path-not-found-message path-protocol-message path-collect-garbage-message))) + (apply make-timeouts + (map (lambda (tag) (string->number (extract-binding/single tag bindings))) + '(time-default-servlet time-password time-servlet-connection time-file-per-byte time-file-base))) + (let ([old-paths (host-table-paths old)]) + (apply make-paths + (paths-conf old-paths) + ((un-build-path web-base) + (build-path (paths-host-base old-paths))) + (map eb-host-root '(path-log path-htdocs path-servlet path-mime-types path-password))))))) + + ; un-build-path : path -> path -> string + ; (GregP) Theory: this should return a string not a path so that the result can be + ; written to the configuration file. + (define (un-build-path possible-base) + (let ([base-list (path->list possible-base)]) + (lambda (path) + (let ([path-list (path->list path)]) + (cond + [(suffix base-list path-list) + => (lambda (x) (path->string (apply build-path x)))] + [else + (path->string path)]))))) + + ; suffix : (listof a) (listof a) -> (U #f (listof a)) + ; to return the extra elements in b after removing all elements from a in order + (define (suffix a b) + (cond + [(null? a) (if (null? b) #f b)] + [else (cond + [(null? b) #f] + [else (and (equal? (car a) (car b)) + (suffix (cdr a) (cdr b)))])])) + + ; Password Configuration + + ; configure-passwords : path -> void + (define (configure-passwords password-path) + (edit-passwords + password-path + (if (file-exists? password-path) + (call-with-input-file password-path read-passwords) + null))) + + ; edit-passwords : path passwords -> passwords + (define (edit-passwords which-one passwords) + (let* ([bindings (interact (password-updates which-one passwords))] + [to-deactivate (extract-bindings 'deactivate bindings)] + [again + (lambda (new-passwords) + (write-to-file which-one (format-passwords new-passwords)) + (edit-passwords which-one new-passwords))]) + (cond + [(assq 'edit bindings) + => (lambda (edit) + (again (drop (map (let ([to-edit (string->number (cdr edit))]) + (lambda (r n) + (if (= to-edit n) + (edit-realm r) + r))) + passwords + (build-list (length passwords) (lambda (x) x))) + to-deactivate)))] + [(assq 'add bindings) + (again (cons (make-realm "new realm" "" null) + (drop passwords to-deactivate)))] + [else (drop passwords to-deactivate)]))) + + ; password-updates : path passwords -> request + (define (password-updates which-one passwords) + (let ([which-one (path->string which-one)]) + (build-suspender + `("Updating Passwords for " ,which-one) + `((h1 "Updating Passwords for ") + (h3 ,which-one) + (h2 "You may wish to " (font ([color "red"]) "backup") " this password file.") + (p "Each authentication " (em "realm") " password protects URLs that match a pattern. " + "Choose a realm to edit below:") + (table + (tr (th "Realm Name") (th "Delete") (th "Edit")) + . ,(map (lambda (realm n) + `(tr (td ,(realm-name realm)) + (td ,(make-field "checkbox" 'deactivate n)) + (td ,(make-field "radio" 'edit n)))) + passwords + (build-list (length passwords) number->string))) + ,(make-field "submit" 'add "Add Realm") + ,(make-field "submit" 'edit-button "Edit") + ,footer)))) + + ; edit-realm : realm -> realm + (define (edit-realm realm) + (let* ([bindings (interact (realm-updates realm))] + [new-name (extract-binding/single 'realm-name bindings)] + [new-pattern (extract-binding/single 'realm-pattern bindings)] + [new-allowed + (drop (map (lambda (u p) (make-user-pass (string->symbol u) p)) + (extract-bindings 'user bindings) + (extract-bindings 'pass bindings)) + (extract-bindings 'deactivate bindings))]) + ; more here - check something? Everything is a string or symbol, though. + (cond + [(assq 'add-user bindings) + (edit-realm (make-realm new-name new-pattern + (cons (make-user-pass 'ptg "Scheme-is-cool!") new-allowed)))] + [(assq 'update bindings) + (make-realm new-name new-pattern new-allowed)] + [else (error 'edit-realm "Didn't find either 'add-user or 'update in ~s" bindings)]))) + + ; realm-updates : realm -> request + (define (realm-updates realm) + (build-suspender + `("Update Authentication Realm " ,(realm-name realm)) + `((h1 "Update Authentication Realm") + (table + ,(make-tr-str "Realm Name" 'realm-name (realm-name realm)) + ,(make-tr-str "Protected URL Path Pattern" 'realm-pattern (realm-pattern realm))) + (hr) + (table + (tr (th "User Name") (th "Password") (th "Delete")) + . ,(map (lambda (x n) + `(tr (td ,(make-field "text" 'user (symbol->string (user-pass-user x)))) + (td ,(make-field "text" 'pass (user-pass-pass x))) + (td ,(make-field "checkbox" 'deactivate n)))) + (realm-allowed realm) + (build-list (length (realm-allowed realm)) number->string))) + (input ([type "submit"] [name "add-user"] [value "Add User"])) + (input ([type "submit"] [name "update"] [value "Update Realm"])) + ,footer))) + + ; read-passwords : iport -> passwords + ; only works if the file starts with (quote ...) + (define (read-passwords in) + (let ([raw (read in)]) + (unless (and (pair? raw) (eq? 'quote (car raw)) + (null? (cddr raw))) + (error 'read-passwords "The password file must be quoted to use the configuration tool.")) + (map (lambda (raw-realm) + ; more here - error checking + (make-realm (car raw-realm) + (cadr raw-realm) + (map (lambda (x) (make-user-pass (car x) (cadr x))) + (cddr raw-realm)))) + (cadr raw)))) + + ; format-passwords : passwords -> s-expr + (define (format-passwords passwords) + (list 'quote + (map (lambda (r) + (list* (realm-name r) + (realm-pattern r) + (map (lambda (x) + (list (user-pass-user x) (user-pass-pass x))) + (realm-allowed r)))) + passwords))) + + ; Little Helpers + + ; initialization-error-page : request -> response + (define (initialization-error-page initial-request) + `(html (head (title "Web Server Configuration Program Invocation Error")) + (body ([bgcolor "white"]) + (p "Please direct your browser directly to the " + (a ([href ,(url->string (request-uri initial-request))]) "configuration program,") + " not through another URL.") + ,footer))) + + ; done-page : html + (define done-page + ; more-here - consider adding more useful information + `(html (head (title "done")) + (body ([bgcolor "white"]) + (h2 "Configuration Saved.") + (p "Click your browser's back button to continue configuring the server.") + ,footer))) + + ; exception-error-page : TST -> html + (define (exception-error-page exn) + `(html (head (title "Error")) + (body ([bgcolor "white"]) + (p "Servlet exception: " + (pre ,(exn->string exn))) + ,footer))) + + (define must-select-host-page + `(html (head (title "Web Server Configuration Error")) + (body ([bgcolor "white"]) + (p "Please select which host to edit before clicking the Edit button.") + ,footer))) + + ; io + + ; read-configuration : path -> configuration-table + (define (read-configuration configuration-path) + (parse-configuration-table (call-with-input-file configuration-path read))) + + ; write-configuration : configuration-table path -> void + ; writes out the new configuration file and + ; also copies the configure.ss servlet to the default-host's servlet directory + (define (write-configuration new configuration-path) + (ensure-configuration-servlet configuration-path (configuration-table-default-host new)) + (ensure-configuration-paths new) + (write-configuration-table new configuration-path)) + + ; ensure-configuration-servlet : path host-table -> void + (define (ensure-configuration-servlet configuration-path host) + (let* ([paths (host-table-paths host)] + [root (build-path-unless-absolute web-base + (paths-host-base paths))] + [servlets-path + (build-path (build-path-unless-absolute root (paths-servlet paths)) "servlets")]) + (ensure-config-servlet configuration-path servlets-path) + (let ([defaults (build-path "Defaults")]) + (ensure* (collection-path "web-server" "default-web-root" "htdocs") + (build-path-unless-absolute root (paths-htdocs paths)) + defaults)))) + + ; ensure-configuration-paths : configuration-table -> void + ; to ensure that all the referenced config files exist for an entire configuration + (define (ensure-configuration-paths configuration) + (ensure-host-configuration (configuration-table-default-host configuration)) + (for-each (lambda (x) (ensure-host-configuration (cdr x))) + (configuration-table-virtual-hosts configuration))) + + ; ensure-host-configuration : host-table -> void + ; to ensure that all the referenced config files exist for a virtual host + (define (ensure-host-configuration host) + (let* ([paths (host-table-paths host)] + [host-base (build-path-unless-absolute web-base (paths-host-base paths))] + [conf (build-path-unless-absolute host-base (paths-conf paths))] + [log (build-path-unless-absolute host-base (paths-log paths))]) + ; skip passwords since a missing file is an okay default + (ensure-directory-shallow conf) + (ensure-directory-shallow host-base) + ;(ensure-file log ...) ; empty log file is okay + (ensure-directory-shallow (build-path-unless-absolute host-base (paths-htdocs paths))) + (ensure-directory-shallow (build-path-unless-absolute host-base (paths-servlet paths))) + (let* ([messages (host-table-messages host)] + ; more here maybe - check default config file instead? maybe not + [from-conf (collection-path "web-server" "default-web-root" "conf")] + [copy-conf + (lambda (from to) + (let ([to-path (build-path-unless-absolute conf to)]) + ; more here - check existance of from path? + (copy-file* (build-path from-conf from) to-path)))]) + (copy-conf "passwords-refresh.html" (messages-passwords-refreshed messages)) + (copy-conf "servlet-refresh.html" (messages-servlets-refreshed messages)) + (copy-conf "forbidden.html" (messages-authentication messages)) + (copy-conf "protocol-error.html" (messages-protocol messages)) + (copy-conf "not-found.html" (messages-file-not-found messages)) + (copy-conf "servlet-error.html" (messages-servlet messages)) + (copy-conf "collect-garbage.html" (messages-collect-garbage messages))))) + + ; ensure-file : path path path -> void + ; to copy (build-path from name) to (build-path to name), creating directories as + ; needed if the latter does not already exist. + (define (ensure-file from to name) + (let ([to (simplify-path to)]) + (ensure-directory-shallow to) + (let ([to-path (build-path to name)]) + (unless (file-exists? to-path) + (copy-file (build-path from name) to-path))))) + + ; copy-file* : str str -> void + (define (copy-file* from-path to-path) + (unless (file-exists? to-path) + (let-values ([(to-path-base to-path-name must-be-dir?) (split-path to-path)]) + (ensure-directory-shallow to-path-base)) + (copy-file from-path to-path))) + + ; ensure* : path path path -> void + ;; GregP: Don't know what the heck this does (thanks Paul) + ;; but the first two arguments are now paths. + (define (ensure* from to name) + (ensure-directory-shallow to) + (let ([p (build-path from name)]) + (cond + [(directory-exists? p) + (unless (member (path->string name) '("CVS" ".svn")) ; yuck + (let ([dest (build-path to name)]) + (ensure-directory-shallow dest) + (for-each (lambda (x) (ensure* p dest x)) + (directory-list p))))] + [(file-exists? p) + (ensure-file from to name)]))) + + ; ensure-directory-shallow : path -> void + (define (ensure-directory-shallow to) + (unless (directory-exists? to) + ; race condition - someone else could make the directory + (make-directory* to))) + + ; ensure-config-servlet : str path -> void + ; to create, if necessary, a stub configuration servlet that includes the main configuration servlet + ; at the desired location in a new web tree + (define (ensure-config-servlet configuration-path servlets-path) + (ensure-directory-shallow servlets-path) + (let ([file-path (build-path servlets-path CONFIGURE-SERVLET-NAME)]) + (unless (file-exists? file-path) ; more here - check that it's a well formed servlet? + (call-with-output-file + file-path + (lambda (out) + (pretty-print + `(module ,CONFIGURE-SERVLET-NAME mzscheme + (require (lib ,CONFIGURE-SERVLET-NAME "web-server" "private")) + (provide (all-from (lib ,CONFIGURE-SERVLET-NAME "web-server" "private"))) + (set-config-path! ,(path->string configuration-path))) + out) + (newline out)))))) + + ; extract-definition : sym (listof s-expr) -> s-expr + ; to return the rhs from (def name rhs) not (def (name . args) body) + (define (extract-definition name defs) + (or (ormap (lambda (def) + (and (pair? def) (eq? 'define (car def)) + (pair? (cdr def)) (eq? name (cadr def)) + (pair? (cddr def)) + (caddr def))) + defs) + (error 'extract-definition "definition for ~a not found" name))) + + ; passwords = str (i.e. path to a file) + + (define build-path-maybe-expression->file-name caddr) + + ; main + (define (start initial-request) + (error-print-width 800) ; 10-ish lines + (check-ip-address initial-request) + (choose-configuration-file))) \ No newline at end of file diff --git a/collects/web-server/sig.ss b/collects/web-server/sig.ss index f5d92c4e22..136bb5a55f 100644 --- a/collects/web-server/sig.ss +++ b/collects/web-server/sig.ss @@ -3,13 +3,7 @@ (require "private/dispatch-server-sig.ss") (provide ; XXX contract signature (rename dispatch-server^ web-server^) - servlet^ web-config^ web-config/pervasive^ web-config/local^) - - #;(define-signature web-server^ - ((open dispatch-server^))) - - (define-signature servlet^ - (initial-request send/suspend send/finish send/back send/forward adjust-timeout!)) + web-config^ web-config/pervasive^ web-config/local^) ; more here - rename (define-signature web-config/pervasive^