doc improvements: enter! and 'more'
svn: r8348
This commit is contained in:
parent
f210fc2ea4
commit
63c5d8a7cb
|
@ -45,7 +45,7 @@
|
|||
(check-latest mod))
|
||||
|
||||
(define ((enter-load/use-compiled orig re?) path name)
|
||||
(printf " [~aloading ~a]\n" (if re? "re-" "") path)
|
||||
(fprintf (current-error-port) " [~aloading ~a]\n" (if re? "re-" "") path)
|
||||
(if name
|
||||
;; Module load:
|
||||
(let ([code (get-module-code path
|
||||
|
|
|
@ -12,21 +12,21 @@
|
|||
@(define quick @other-manual['(lib "quick.scrbl" "scribblings/quick")])
|
||||
@(define guide @other-manual['(lib "guide.scrbl" "scribblings/guide")])
|
||||
|
||||
@(define break-eval (make-base-eval))
|
||||
@interaction-eval[#:eval break-eval
|
||||
(define (show-reload)
|
||||
(printf " [re-loading serve.ss]\n"))]
|
||||
@interaction-eval[#:eval break-eval
|
||||
@(define more-eval (make-base-eval))
|
||||
@interaction-eval[#:eval more-eval
|
||||
(define (show-load re?)
|
||||
(fprintf (current-error-port) " [~aloading serve.ss]\n" (if re? "re-" "")))]
|
||||
@interaction-eval[#:eval more-eval
|
||||
(define (serve n) void)]
|
||||
@interaction-eval[#:eval break-eval
|
||||
@interaction-eval[#:eval more-eval
|
||||
(define (show-break)
|
||||
(fprintf (current-error-port) "^Cuser break"))]
|
||||
@interaction-eval[#:eval break-eval
|
||||
@interaction-eval[#:eval more-eval
|
||||
(define (show-fail n)
|
||||
(error 'tcp-listen
|
||||
"listen on ~a failed (address already in use)"
|
||||
n))]
|
||||
@interaction-eval[#:eval break-eval (require xml net/url)]
|
||||
@interaction-eval[#:eval more-eval (require xml net/url)]
|
||||
|
||||
@(define (whole-prog which [last? #f])
|
||||
(let ([file (format "step~a.txt" which)])
|
||||
|
@ -47,9 +47,12 @@ processes, which is the subject of this tutorial.
|
|||
|
||||
Specifically, we show how to build a secure, multi-threaded,
|
||||
servlet-extensible, continuation-based web server. We use much more of
|
||||
the language than in @|quick|, and beware that the last couple of
|
||||
sections present material that is normally considered difficult. So if
|
||||
you're still new to Scheme, you may want to skip to @|guide|.
|
||||
the language than in @|quick|, and we expect you to click on syntax or
|
||||
function names that you don't recognize (which will take you to the
|
||||
relevant documentation). Beware that the last couple of sections
|
||||
present material that is normally considered difficult, so if you're
|
||||
still new to Scheme and have relatively little programming experience,
|
||||
you may want to skip to @|guide|.
|
||||
|
||||
To get into the spirit of this tutorial, we suggest that you set
|
||||
DrScheme aside for a moment, and switch to raw @exec{mzscheme} in a
|
||||
|
@ -109,7 +112,8 @@ scheme
|
|||
Back in @exec{mzscheme}, try loading the file and running @scheme[go]:
|
||||
|
||||
@interaction[
|
||||
(eval:alts (enter! "serve.ss") (printf " [loading serve.ss]\n"))
|
||||
#:eval more-eval
|
||||
(eval:alts (enter! "serve.ss") (show-load #f))
|
||||
(eval:alts (go) 'yep-it-works)
|
||||
]
|
||||
|
||||
|
@ -185,8 +189,8 @@ Copy the above three definitions---@scheme[serve],
|
|||
@filepath{serve.ss} and re-load:
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
(eval:alts (enter! "serve.ss") (show-reload))
|
||||
#:eval more-eval
|
||||
(eval:alts (enter! "serve.ss") (show-load #t))
|
||||
(eval:alts (serve 8080) (void))
|
||||
]
|
||||
|
||||
|
@ -206,7 +210,7 @@ interrupts the server loop:
|
|||
@onscreen{Stop} button once.}
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
#:eval more-eval
|
||||
(eval:alts (serve 8080) (show-break))
|
||||
(eval:alts code:blank (void))
|
||||
]
|
||||
|
@ -215,7 +219,7 @@ Unfortunately, we cannot now re-start the server with the same port
|
|||
number:
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
#:eval more-eval
|
||||
(eval:alts (serve 8080) (show-fail 8080))
|
||||
]
|
||||
|
||||
|
@ -244,8 +248,8 @@ server thread and TCP listener:
|
|||
Try the new one:
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
(eval:alts (enter! "serve.ss") (show-reload))
|
||||
#:eval more-eval
|
||||
(eval:alts (enter! "serve.ss") (show-load #t))
|
||||
(define stop (serve 8081))
|
||||
]
|
||||
|
||||
|
@ -254,7 +258,7 @@ can shut down and restart the server on the same port number as often
|
|||
as you like:
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
#:eval more-eval
|
||||
(stop)
|
||||
(define stop (serve 8081))
|
||||
(stop)
|
||||
|
@ -283,10 +287,10 @@ thread, we can put each individual connection into its own thread:
|
|||
With this change, our server can now handle multiple threads at
|
||||
once. The handler is so fast that this fact will be difficult to
|
||||
detect, however, so try inserting @scheme[(sleep (random 10))] before
|
||||
the @scheme[handle] call above. If you make multiple connects from the
|
||||
web browser at the same time, some will return right away, and some
|
||||
will take up to 10 seconds. The random delays will be independent of
|
||||
the order in which you started the connections.
|
||||
the @scheme[handle] call above. If you make multiple connections from
|
||||
the web browser at roughly the same time, some will return soon, and
|
||||
some will take up to 10 seconds. The random delays will be independent
|
||||
of the order in which you started the connections.
|
||||
|
||||
@; ----------------------------------------------------------------------
|
||||
@section{Terminating Connections}
|
||||
|
@ -298,7 +302,7 @@ like to implement a timeout for each connection thread.
|
|||
|
||||
One way to implement the timeout is to create a second thread that
|
||||
waits for 10 seconds, and then kills the thread that calls
|
||||
@scheme[handle]. Threads are lightweight enough that this
|
||||
@scheme[handle]. Threads are lightweight enough in Scheme that this
|
||||
watcher-thread strategy works well:
|
||||
|
||||
@schemeblock[
|
||||
|
@ -321,15 +325,15 @@ could add code to the watcher thread to close the streams as well as
|
|||
kill the thread, but Scheme offers a more general shutdown mechanism:
|
||||
@defterm{custodians}. A custodian is a kind of container for all
|
||||
resources other than memory, and it supports a
|
||||
@scheme[custodian-shutdown-all] that terminates and closes all
|
||||
resources within the container, whether they're threads, streams, or
|
||||
other kinds of limited resources.
|
||||
@scheme[custodian-shutdown-all] operation that terminates and closes
|
||||
all resources within the container, whether they're threads, streams,
|
||||
or other kinds of limited resources.
|
||||
|
||||
Whenever a thread or stream is created, it is placed into the current
|
||||
custodian as determined by the @scheme[current-custodian]
|
||||
parameter. To place everything about a connection into a custodian, we
|
||||
@scheme[parameterize] all the resources creations to go into a new
|
||||
one:
|
||||
@scheme[parameterize] all the resource creations to go into a new
|
||||
custodian:
|
||||
|
||||
@schemeblock[
|
||||
(define (accept-and-handle listener)
|
||||
|
@ -347,11 +351,10 @@ one:
|
|||
]
|
||||
|
||||
With this implementation, @scheme[in], @scheme[out], and the thread
|
||||
that calls @scheme[handle] all belong to the @scheme[cust]
|
||||
custodian. In addition, if we later change @scheme[handle] so that it,
|
||||
say, opens a file, then the file handles will also belong to
|
||||
@scheme[cust], so they will be reliably closed when @scheme[cust] is
|
||||
shut down.
|
||||
that calls @scheme[handle] all belong to @scheme[cust]. In addition,
|
||||
if we later change @scheme[handle] so that it, say, opens a file, then
|
||||
the file handles will also belong to @scheme[cust], so they will be
|
||||
reliably closed when @scheme[cust] is shut down.
|
||||
|
||||
In fact, it's a good idea to change @scheme[serve] to that it uses a
|
||||
custodian, too:
|
||||
|
@ -372,7 +375,7 @@ custodian, too:
|
|||
That way, the @scheme[main-cust] created in @scheme[serve] not only
|
||||
owns the TCP listener and the main server thread, it also owns every
|
||||
custodian created for a connection. Consequently, the revised shutdown
|
||||
procedure for the server immediately terminates any active connection,
|
||||
procedure for the server immediately terminates all active connections,
|
||||
in addition to the main server loop.
|
||||
|
||||
@whole-prog["4"]
|
||||
|
@ -381,8 +384,8 @@ After updating the @scheme[serve] and @scheme[accept-and-handle]
|
|||
functions as above, here's how you can simulate a malicious client:
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
(eval:alts (enter! "serve.ss") (show-reload))
|
||||
#:eval more-eval
|
||||
(eval:alts (enter! "serve.ss") (show-load #t))
|
||||
(define stop (serve 8081))
|
||||
(eval:alts (define-values (cin cout) (tcp-connect "localhost" 8081)) (void))
|
||||
]
|
||||
|
@ -392,7 +395,7 @@ the stream that sends data from the server back to the client, you'll
|
|||
find that the server has shut down the connection:
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
#:eval more-eval
|
||||
(eval:alts (read-line cin) eof)
|
||||
]
|
||||
|
||||
|
@ -400,7 +403,7 @@ Alternatively, you don't have to wait 10 seconds if you explicitly
|
|||
shut down the server:
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
#:eval more-eval
|
||||
(eval:alts (define-values (cin2 cout2) (tcp-connect "localhost" 8081)) (void))
|
||||
(stop)
|
||||
(eval:alts (read-line cin2) eof)
|
||||
|
@ -426,7 +429,7 @@ takes a Scheme value that looks like HTML and turns it into actual
|
|||
HTML:
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
#:eval more-eval
|
||||
(xexpr->string '(html (head (title "Hello")) (body "Hi!")))
|
||||
]
|
||||
|
||||
|
@ -456,7 +459,7 @@ The @schememodname[net/url] library gives us @scheme[string->url],
|
|||
for getting from a string to parts of the URL that it represents:
|
||||
|
||||
@interaction[
|
||||
#:eval break-eval
|
||||
#:eval more-eval
|
||||
(define u (string->url "http://localhost:8080/foo/bar?x=bye"))
|
||||
(url-path u)
|
||||
(map path/param-path (url-path u))
|
||||
|
@ -490,7 +493,7 @@ path element, like @scheme["foo"], to a handler function:
|
|||
|
||||
With the new @scheme[require] import and new @scheme[handle],
|
||||
@scheme[dispatch], and @scheme[dispatch-table] definitions, our
|
||||
``Hello World!'' server has turn into an error server. You don't have
|
||||
``Hello World!'' server has turned into an error server. You don't have
|
||||
to stop the server to try it out. After modifying @filepath{serve.ss}
|
||||
with the new pieces, evaluate @scheme[(enter! "serve.ss")] and then
|
||||
try again to connect to the server. The web browser should show an
|
||||
|
@ -519,7 +522,7 @@ supplies through a form.
|
|||
|
||||
The following helper function constructs an HTML form. The
|
||||
@scheme[label] argument is a string to show the user. The
|
||||
@scheme[next-url] argument is destination for the form results. The
|
||||
@scheme[next-url] argument is a destination for the form results. The
|
||||
@scheme[hidden] argument is a value to propagate through the form as a
|
||||
hidden field. When the user responds, the @scheme["number"] field in
|
||||
the form holds the user's value:
|
||||
|
@ -559,7 +562,7 @@ many ``hello''s as a user wants:
|
|||
|
||||
As usual, once you have added these to your program, update with
|
||||
@scheme[(enter! "serve.ss")], and then visit
|
||||
@tt{http://localhost:8081/many}. Provide a number, and then you'll get
|
||||
@tt{http://localhost:8081/many}. Provide a number, and you'll receive
|
||||
a new page with that many ``hello''s.
|
||||
|
||||
@; ----------------------------------------------------------------------
|
||||
|
@ -574,7 +577,7 @@ The solution to this class of problems is to limit the memory use of a
|
|||
connection. Inside @scheme[accept-and-handle], after the definition of
|
||||
@scheme[cust], add the line
|
||||
|
||||
@scheme[(custodian-limit-memory cust (* 50 1024 1024))]
|
||||
@schemeblock[(custodian-limit-memory cust (* 50 1024 1024))]
|
||||
|
||||
@whole-prog["7"]
|
||||
|
||||
|
@ -583,17 +586,19 @@ way that memory accounting is defined, @scheme[cust] might also be
|
|||
charged for the core server implementation and all of the libraries
|
||||
loaded on start-up, so the limit cannot be too small. Also,
|
||||
garbage-collector overhead means that the actual memory use of the
|
||||
system can be some small multiple of 50 MB. The main guarantee is that
|
||||
different connections will not be charged for each other's memory use.
|
||||
system can be some small multiple of 50 MB. An important guarantee,
|
||||
however, is that different connections will not be charged for each
|
||||
other's memory use, so one misbehaving connection will not interfere
|
||||
with a different one.
|
||||
|
||||
So, with the new line above, and assuming that you have a couple of
|
||||
hundred megabytes available for the @exec{mzscheme} process to use,
|
||||
then with the above limit, you shouldn't be able to crash the web
|
||||
server by requesting a ridiculously large number of ``hello''s.
|
||||
you shouldn't be able to crash the web server by requesting a
|
||||
ridiculously large number of ``hello''s.
|
||||
|
||||
Given the @scheme["many"] example, it's a small step to a web server
|
||||
that accepts from clients arbitrary code to execute on the server. In
|
||||
that case, there are many additional security issues besides limiting
|
||||
that accepts arbitrary Scheme code to execute on the server. In that
|
||||
case, there are many additional security issues besides limiting
|
||||
processor time and memory consumption. The
|
||||
@schememodname[scheme/sandbox] library provides support to managing
|
||||
all those other issues.
|
||||
|
@ -608,10 +613,10 @@ topic: @defterm{continuations}. In fact, this facet of a web server
|
|||
needs @defterm{delimited continuations}, which PLT Scheme provides.
|
||||
|
||||
The problem solved by continuations is related to servlet sessions and
|
||||
user input, where a computation spans multiple client
|
||||
connections. Often, client-side computation (as in AJAX) is the right
|
||||
solution to the problem, but many problems are best solved with a
|
||||
mixture of techniques (e.g., to take advantage of the browser's
|
||||
user input, where a computation spans multiple client connections
|
||||
@cite["Queinnec00"]. Often, client-side computation (as in AJAX) is the
|
||||
right solution to the problem, but many problems are best solved with
|
||||
a mixture of techniques (e.g., to take advantage of the browser's
|
||||
``back'' button).
|
||||
|
||||
As the multi-connection computation becomes more complex, propagating
|
||||
|
@ -712,7 +717,7 @@ around the cal to @scheme[dispatch]:
|
|||
|
||||
Now, we can implement @scheme[send/suspend]. We use @scheme[call/cc]
|
||||
in the guise of @scheme[let/cc], which captures the current
|
||||
computation up to an enclosing @scheme[prompt], and binds that
|
||||
computation up to an enclosing @scheme[prompt] and binds that
|
||||
computation to an identifier---@scheme[k], in this case:
|
||||
|
||||
@schemeblock[
|
||||
|
@ -746,8 +751,8 @@ generated tag:
|
|||
|
||||
When the user submits the form, the handler associated with the form's
|
||||
URL is the old computation, stored as a continuation in the dispatch
|
||||
table. Invoking the continuation as a function restores the old
|
||||
computation, passing the @scheme[query] arguments as back to that
|
||||
table. Calling the continuation (like a function) restores the old
|
||||
computation, passing the @scheme[query] argument back to that
|
||||
computation.
|
||||
|
||||
@whole-prog["9" #t]
|
||||
|
@ -774,8 +779,9 @@ Scheme, including papers on MrEd @cite["Flatt99"], memory accounting
|
|||
delimited continuations @cite["Flatt07"].
|
||||
|
||||
The PLT Scheme distribution includes a production-quality web server
|
||||
that addresses all of the design points mentioned here and more. See
|
||||
@other-manual['(lib "web-server/scribblings/web-server.scrbl")].
|
||||
that addresses all of the design points mentioned here and more
|
||||
@cite["Krishnamurthi07"]. See @other-manual['(lib
|
||||
"web-server/scribblings/web-server.scrbl")].
|
||||
|
||||
@; ----------------------------------------------------------------------
|
||||
|
||||
|
@ -803,6 +809,20 @@ that addresses all of the design points mentioned here and more. See
|
|||
#:date "2007"
|
||||
#:url "http://www.cs.utah.edu/plt/publications/icfp07-fyff.pdf")
|
||||
|
||||
(bib-entry #:key "Krishnamurthi07"
|
||||
#:author "Shriram Krishnamurthi, Peter Hopkins, Jay McCarthy, Paul T. Graunke, Greg Pettyjohn, and Matthias Felleisen"
|
||||
#:title "Implementation and Use of the PLT Scheme Web Server"
|
||||
#:location @italic{Higher-Order and Symbolic Computation}
|
||||
#:date "2007"
|
||||
#:url "http://www.cs.brown.edu/~sk/Publications/Papers/Published/khmgpf-impl-use-plt-web-server-journal/paper.pdf")
|
||||
|
||||
(bib-entry #:key "Queinnec00"
|
||||
#:author "Christian Queinnec"
|
||||
#:title "The Influence of Browsers on Evaluators or, Continuations to Program Web Servers"
|
||||
#:location "International Conference on Functional Programming"
|
||||
#:date "2000"
|
||||
#:url "http://www-spi.lip6.fr/~queinnec/Papers/webcont.ps.gz")
|
||||
|
||||
(bib-entry #:key "Wick04"
|
||||
#:author "Adam Wick and Matthew Flatt"
|
||||
#:title "Memory Accounting without Partitions"
|
||||
|
|
|
@ -7,16 +7,33 @@
|
|||
@note-lib-only[scheme/control]
|
||||
|
||||
The @scheme[scheme/control] library provides various control operators
|
||||
from the research literature on higher-order control operators. These
|
||||
control operators are implemented in terms of
|
||||
@scheme[call-with-continuation-prompt],
|
||||
from the research literature on higher-order control operators, plus a
|
||||
few extra convenience forms. These control operators are implemented
|
||||
in terms of @scheme[call-with-continuation-prompt],
|
||||
@scheme[call-with-composable-continuations], etc., and they generally
|
||||
work sensibly together. Many are redundant; for example,
|
||||
@scheme[reset] and @scheme[shift] are aliases.
|
||||
|
||||
@; ----------------------------------------------------------------------
|
||||
|
||||
@defproc[(abort [v any/c] ...) any]{
|
||||
|
||||
Returns the @scheme[v]s to a prompt using the default continuation
|
||||
prompt tag and the default abort handler.
|
||||
|
||||
That is, @scheme[(abort v ...)] is equivalent to
|
||||
|
||||
@schemeblock[
|
||||
(abort-current-continuation
|
||||
(default-continuation-prompt-tag)
|
||||
(lambda () (values v ...)))
|
||||
]}
|
||||
|
||||
@; ----------------------------------------------------------------------
|
||||
|
||||
@deftogether[(
|
||||
@defform[(% expr)]
|
||||
@defform/none[(% expr handler-expr)]
|
||||
@defform*[[(% expr)
|
||||
(% expr handler-expr)]]
|
||||
@defproc[(fcontrol [v any/c]) any]
|
||||
)]{
|
||||
|
||||
|
|
34
collects/scribblings/reference/enter.scrbl
Normal file
34
collects/scribblings/reference/enter.scrbl
Normal file
|
@ -0,0 +1,34 @@
|
|||
#lang scribble/doc
|
||||
@(require "mz.ss"
|
||||
(for-label scheme/enter))
|
||||
|
||||
@title[#:tag "enter"]{Interactive Module Loading}
|
||||
|
||||
@note-lib[scheme/enter]
|
||||
|
||||
@defform*[[(enter! module-path)
|
||||
(enter! #f)]]{
|
||||
|
||||
Intended for use in a @tech{REPL}, such as when @exec{mzscheme} is
|
||||
started in interactive mode. When a @scheme[module-path] is provided
|
||||
(in the same sense as for @scheme[require]), the corresponding module
|
||||
is loaded or invoked, and the current @tech{namespace} is changed to
|
||||
the body of the module via @scheme[module->namespace]. When
|
||||
@scheme[#f] is provided, then the current @tech{namespace} is restored
|
||||
to the original one.
|
||||
|
||||
If invoking @scheme[module-path] requires loading any files, then
|
||||
modification dates of the files are recorded. If the file is modified,
|
||||
then a later @scheme[enter!] re-loads the module from source; see also
|
||||
@secref["module-redeclare"]. Similarly if a later @scheme[enter!]
|
||||
transitively @scheme[require]s a modified module, then the required
|
||||
module is re-loaded. Re-loading support works only for modules that
|
||||
are first loaded (either directly or indirectly through transitive
|
||||
@scheme[require]s) via @scheme[enter!].
|
||||
|
||||
After switching namespaces to the designated module, @scheme[enter!]
|
||||
automatically requires @scheme[scheme/enter] into the namespace, so
|
||||
that @scheme[enter!] can be used to switch namespaces again.
|
||||
|
||||
When it loads or re-loads a module from a file, @scheme[enter!] prints
|
||||
a message to @scheme[(current-error-port)].}
|
|
@ -9,3 +9,4 @@
|
|||
@include-section["collects.scrbl"]
|
||||
@include-section["info.scrbl"]
|
||||
@include-section["help.scrbl"]
|
||||
@include-section["enter.scrbl"]
|
||||
|
|
Loading…
Reference in New Issue
Block a user