diff --git a/collects/tests/web-server/dispatchers/dispatch-servlets-test.rkt b/collects/tests/web-server/dispatchers/dispatch-servlets-test.rkt index d9a5b2e97a..afd0df4a68 100644 --- a/collects/tests/web-server/dispatchers/dispatch-servlets-test.rkt +++ b/collects/tests/web-server/dispatchers/dispatch-servlets-test.rkt @@ -69,6 +69,8 @@ (build-path example-servlets "add-ssd.rkt")) (test-add-two-numbers mkd "add-formlets.rkt - send/formlet" (build-path example-servlets "add-formlets.rkt")) + (test-add-two-numbers mkd "add-page.rkt" + (build-path example-servlets "add-page.rkt")) (test-equal? "count.rkt - state" (let* ([d (mkd (build-path example-servlets "count.rkt"))] [ext (lambda (c) diff --git a/collects/web-server/default-web-root/htdocs/servlets/examples/add-page.rkt b/collects/web-server/default-web-root/htdocs/servlets/examples/add-page.rkt new file mode 100644 index 0000000000..fdbdd340c3 --- /dev/null +++ b/collects/web-server/default-web-root/htdocs/servlets/examples/add-page.rkt @@ -0,0 +1,27 @@ +#lang racket/base +(require web-server/servlet + web-server/page) +(provide (all-defined-out)) +(define interface-version 'v1) +(define timeout +inf.0) + +(define (request-number which-number) + (let/ec esc + (page + `(html (head (title "Enter a Number to Add")) + (body ([bgcolor "white"]) + (form ([action ,(embed/url + (lambda/page () + (esc + (string->number + (get-binding 'number)))))] + [method "post"]) + "Enter the " ,which-number " number to add: " + (input ([type "text"] [name "number"] [value ""])) + (input ([type "submit"] [name "enter"] [value "Enter"])))))))) + +(define/page (start) + `(html (head (title "Sum")) + (body ([bgcolor "white"]) + (p "The answer is " + ,(number->string (+ (request-number "first") (request-number "second"))))))) diff --git a/collects/web-server/page.rkt b/collects/web-server/page.rkt new file mode 100644 index 0000000000..c6829d25cf --- /dev/null +++ b/collects/web-server/page.rkt @@ -0,0 +1,3 @@ +#lang racket/base +(require "page/page.rkt") +(provide (all-from-out "page/page.rkt")) \ No newline at end of file diff --git a/collects/web-server/page/page.rkt b/collects/web-server/page/page.rkt index 011c34e4ab..25319de91f 100644 --- a/collects/web-server/page/page.rkt +++ b/collects/web-server/page/page.rkt @@ -1,6 +1,8 @@ #lang racket/base (require web-server/servlet racket/stxparam + racket/list + racket/contract (for-syntax racket/base)) (define-syntax-parameter embed/url @@ -48,20 +50,22 @@ [(binding) b])) -(define (get-binding id #:format [format 'string]) +(define (get-binding id [req (current-request)] + #:format [format 'string]) (convert-binding format (bindings-assq (binding-id->bytes id) - (request-bindings/raw (current-request))))) + (request-bindings/raw req)))) -(define (get-bindings id #:format [format 'string]) +(define (get-bindings id [req (current-request)] + #:format [format 'string]) (define id-bs (binding-id->bytes id)) (filter-map (λ (b) (and (bytes=? id-bs (binding-id b)) (convert-binding format b))) - (request-bindings/raw (current-request)))) + (request-bindings/raw req))) (provide embed/url page @@ -71,7 +75,7 @@ [current-request (parameter/c (or/c false/c request?))] [binding-id/c contract?] [binding-format/c contract?] - [get-binding (->* (binding-id/c) (#:format binding-format/c) + [get-binding (->* (binding-id/c) (request? #:format binding-format/c) (or/c false/c string? bytes? binding?))] - [get-bindings (->* (binding-id/c) (#:format binding-format/c) + [get-bindings (->* (binding-id/c) (request? #:format binding-format/c) (listof (or/c string? bytes? binding?)))]) \ No newline at end of file diff --git a/collects/web-server/scribblings/page.scrbl b/collects/web-server/scribblings/page.scrbl new file mode 100644 index 0000000000..89df9bd592 --- /dev/null +++ b/collects/web-server/scribblings/page.scrbl @@ -0,0 +1,70 @@ +#lang scribble/doc +@(require "web-server.rkt") +@(require (for-label web-server/servlet + web-server/page + racket/promise + racket/list + xml)) + +@title[#:tag "page"]{Page: Short-hand for Common Patterns} + +@defmodule[web-server/page] + +The @web-server provides a simple utility library for building Web applications that consistent mostly of @racket[send/suspend/dispatch]-created pages and request handling. + +Most Web applications rely heavily on @racket[send/suspend/dispatch] and typically use the pattern: +@racketblock[ + (send/suspend/dispatch + (λ (my-embed/url) + .... (my-embed/url other-page) ....))] + +@defform[(page e ...)]{ + +The @racket[page] macro automates this by expanding @racket[(page e ...)] to a usage of @racket[send/suspend/dispatch] where the syntax parameter @racket[embed/url] is bound to the argument of @racket[send/suspend/dispatch]. + +} + +@defidform[embed/url]{ +When used inside @racket[page] syntactically, a rename transformer for the procedure embedding function; otherwise, a syntax error.} + +A simple example: +@racketblock[ + (page + `(html + (body + (a ([href + ,(embed/url + (λ (req) + "You clicked!"))]) + "Click me"))))] + +Similarly, many Web applications make use almost exclusively of functions that are arguments to @racket[embed/url] and immediately invoke @racket[send/suspend/dispatch]. + +@deftogether[[@defform[(lambda/page formals e ...)] + @defform[(define/page (id . formals) e ...)]]]{ +The @racket[lambda/page] and @racket[define/page] automate this by expanding to functions that accept a request as the first argument (followed by any arguments specified in @racket[formals]) and immediately wrap their body in @racket[page]. This functions also cooperate with @racket[get-binding] by binding the request to the @racket[current-request] parameter. +} + +The binding interface of @racketmodname[web-server/http] is powerful, but subtle to use conveniently due to its protection against hostile clients. + +@deftogether[[ +@defparam[current-request req request?] +@defthing[binding-id/c contract?] +@defthing[binding-format/c contract?] +@defproc[(get-binding [id binding-id/c] [req request? (current-request)] [#:format format binding-format/c 'string]) + (or/c false/c string? bytes? binding?)] +@defproc[(get-bindings [id binding-id/c] [req request? (current-request)] [#:format format binding-format/c 'string]) + (listof (or/c string? bytes? binding?))] +]]{ + + The @racket[get-binding](s) interface attempts to resolve this by providing a powerful interface with convenient defaults. + + @racket[get-binding] extracts the first binding of a form input from a request, while @racket[get-bindings] extracts them all. + + They accept a form identifier (@racket[id]) as either a byte string, a string, or a symbol. In each case, the user input is compared in a case-sensitive way with the form input. + + They accept an optional request argument (@racket[req]) that defaults to the value of the @racket[current-request] parameter used by @racket[lambda/page] and @racket[define/page]. + + Finally, they accept an optional keyword argument (@racket[format]) that specifies the desired return format. The default, @racket['string], produces a UTF-8 string (or @racket[#f] if the byte string cannot be converted to UTF-8.) The @racket['bytes] format always produces the raw byte string. The @racket['file] format produces the file upload content (or @racket[#f] if the form input was not an uploaded file.) The @racket['binding] format produces the binding object. + +} diff --git a/collects/web-server/scribblings/web-server.scrbl b/collects/web-server/scribblings/web-server.scrbl index abbb140488..cdac7e0fdf 100644 --- a/collects/web-server/scribblings/web-server.scrbl +++ b/collects/web-server/scribblings/web-server.scrbl @@ -16,8 +16,7 @@ This manual describes the Racket libraries for building Web applications. The @secref["http"] section describes the common library function for manipulating HTTP requests and creating HTTP responses. In particular, this section covers cookies, authentication, and request bindings. -The final three sections (@secref["dispatch"], @secref["formlets"], and @secref["templates"]) cover utility libraries that -ease the creation of typical Web applications. +The final four sections (@secref["dispatch"], @secref["formlets"], @secref["templates"], and @secref["page"]) cover utility libraries that ease the creation of typical Web applications. This manual closes with a frequently asked questions section: @secref["faq"]. @@ -33,6 +32,7 @@ This manual closes with a frequently asked questions section: @secref["faq"]. @include-section["dispatch.scrbl"] @include-section["formlets.scrbl"] @include-section["templates.scrbl"] +@include-section["page.scrbl"] @include-section["faq.scrbl"]