|
|
|
@ -2,13 +2,13 @@
|
|
|
|
|
@(require scribble/manual
|
|
|
|
|
scribble/urls
|
|
|
|
|
scribble/eval
|
|
|
|
|
"../quick/keep.ss"
|
|
|
|
|
"../quick/keep.rkt"
|
|
|
|
|
(for-label scheme
|
|
|
|
|
scheme/enter
|
|
|
|
|
racket/enter
|
|
|
|
|
readline
|
|
|
|
|
net/url
|
|
|
|
|
xml
|
|
|
|
|
scheme/control))
|
|
|
|
|
racket/control))
|
|
|
|
|
|
|
|
|
|
@(define quick @other-manual['(lib "quick.scrbl" "scribblings/quick")])
|
|
|
|
|
@(define guide @other-manual['(lib "guide.scrbl" "scribblings/guide")])
|
|
|
|
@ -16,7 +16,7 @@
|
|
|
|
|
@(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-" ""))))
|
|
|
|
|
(fprintf (current-error-port) " [~aloading serve.rkt]\n" (if re? "re-" ""))))
|
|
|
|
|
@(interaction-eval #:eval more-eval
|
|
|
|
|
(define (serve n) void))
|
|
|
|
|
@(interaction-eval #:eval more-eval
|
|
|
|
@ -39,13 +39,13 @@
|
|
|
|
|
" in plain text: "
|
|
|
|
|
(link file "step " which) ".")))
|
|
|
|
|
|
|
|
|
|
@title{@bold{More}: Systems Programming with PLT Scheme}
|
|
|
|
|
@title{@bold{More}: Systems Programming with Racket}
|
|
|
|
|
|
|
|
|
|
@author["Matthew Flatt"]
|
|
|
|
|
|
|
|
|
|
In contrast to the impression that @|quick| may give, PLT Scheme is
|
|
|
|
|
In contrast to the impression that @|quick| may give, Racket is
|
|
|
|
|
not just another pretty face. Underneath the graphical facade of
|
|
|
|
|
DrScheme lies a sophisticated toolbox for managing threads and
|
|
|
|
|
DrRacket lies a sophisticated toolbox for managing threads and
|
|
|
|
|
processes, which is the subject of this tutorial.
|
|
|
|
|
|
|
|
|
|
Specifically, we show how to build a secure, multi-threaded,
|
|
|
|
@ -54,39 +54,39 @@ 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. If you're
|
|
|
|
|
still new to Scheme and have relatively little programming experience,
|
|
|
|
|
still new to Racket 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
|
|
|
|
|
DrRacket aside for a moment, and switch to raw @exec{racket} in a
|
|
|
|
|
terminal. You'll also need a text editor, such as @exec{emacs} or
|
|
|
|
|
@exec{vi}. Finally, you'll need a web client, perhaps @exec{lynx} or
|
|
|
|
|
@exec{firefox}.
|
|
|
|
|
|
|
|
|
|
@margin-note{Of course, if you're already spoiled, you can keep using
|
|
|
|
|
DrScheme.}
|
|
|
|
|
DrRacket.}
|
|
|
|
|
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
|
@section{Ready...}
|
|
|
|
|
|
|
|
|
|
@link[url:download-drscheme]{Download PLT Scheme}, install, and then
|
|
|
|
|
start @exec{mzscheme} with no command-line arguments:
|
|
|
|
|
@link[url:download-drracket]{Download Racket}, install, and then
|
|
|
|
|
start @exec{racket} with no command-line arguments:
|
|
|
|
|
|
|
|
|
|
@verbatim[#:indent 2]{
|
|
|
|
|
$ mzscheme
|
|
|
|
|
Welcome to MzScheme
|
|
|
|
|
$ racket
|
|
|
|
|
Welcome to Racket
|
|
|
|
|
>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
If you're using a plain terminal, if you have GNU Readline installed
|
|
|
|
|
on your system, and if you'd like Readline support in @exec{mzscheme},
|
|
|
|
|
then evaluate @scheme[(require readline)]. If you also evaluate
|
|
|
|
|
@scheme[(install-readline!)], then your @filepath{~/.mzschemerc} is
|
|
|
|
|
updated to load Readline whenever you start @exec{mzscheme} for
|
|
|
|
|
on your system, and if you'd like Readline support in @exec{racket},
|
|
|
|
|
then evaluate @racket[(require readline)]. If you also evaluate
|
|
|
|
|
@racket[(install-readline!)], then your @filepath{~/.racketrc} is
|
|
|
|
|
updated to load Readline whenever you start @exec{racket} for
|
|
|
|
|
interactive evaluation.
|
|
|
|
|
|
|
|
|
|
@margin-note{Unfortunately, for legal reasons related to GPL vs. LGPL,
|
|
|
|
|
@exec{mzscheme} cannot provide Readline automatically.}
|
|
|
|
|
@exec{racket} cannot provide Readline automatically.}
|
|
|
|
|
|
|
|
|
|
@interaction[
|
|
|
|
|
(eval:alts (require readline) (void))
|
|
|
|
@ -96,11 +96,11 @@ interactive evaluation.
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
|
@section{Set...}
|
|
|
|
|
|
|
|
|
|
In the same directory where you started @exec{mzscheme}, create a text
|
|
|
|
|
file @filepath{serve.ss}, and start it like this:
|
|
|
|
|
In the same directory where you started @exec{racket}, create a text
|
|
|
|
|
file @filepath{serve.rkt}, and start it like this:
|
|
|
|
|
|
|
|
|
|
@schememod[
|
|
|
|
|
scheme
|
|
|
|
|
@racketmod[
|
|
|
|
|
racket
|
|
|
|
|
|
|
|
|
|
(define (go)
|
|
|
|
|
'yep-it-works)
|
|
|
|
@ -111,35 +111,35 @@ scheme
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
|
@section{Go!}
|
|
|
|
|
|
|
|
|
|
Back in @exec{mzscheme}, try loading the file and running @scheme[go]:
|
|
|
|
|
Back in @exec{racket}, try loading the file and running @racket[go]:
|
|
|
|
|
|
|
|
|
|
@interaction[
|
|
|
|
|
#:eval more-eval
|
|
|
|
|
(eval:alts (enter! "serve.ss") (show-load #f))
|
|
|
|
|
(eval:alts (enter! "serve.rkt") (show-load #f))
|
|
|
|
|
(eval:alts (go) 'yep-it-works)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Try modifying @filepath{serve.ss}, and then run @scheme[(enter!
|
|
|
|
|
"serve.ss")] again to re-load the module, and then check your changes.
|
|
|
|
|
Try modifying @filepath{serve.rkt}, and then run @racket[(enter!
|
|
|
|
|
"serve.rkt")] again to re-load the module, and then check your changes.
|
|
|
|
|
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
|
@section{``Hello World'' Server}
|
|
|
|
|
|
|
|
|
|
We'll implement the web server through a @scheme[serve] function that
|
|
|
|
|
We'll implement the web server through a @racket[serve] function that
|
|
|
|
|
takes a IP port number for client connections:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (serve port-no)
|
|
|
|
|
...)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
The server accepts TCP connections through a @defterm{listener}, which
|
|
|
|
|
we create with @scheme[tcp-listen]. To make interactive development
|
|
|
|
|
easier, we supply @scheme[#t] as the third argument to
|
|
|
|
|
@scheme[tcp-listen], which lets us re-use the port number without
|
|
|
|
|
we create with @racket[tcp-listen]. To make interactive development
|
|
|
|
|
easier, we supply @racket[#t] as the third argument to
|
|
|
|
|
@racket[tcp-listen], which lets us re-use the port number without
|
|
|
|
|
waiting on TCP timeouts.
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (serve port-no)
|
|
|
|
|
(define listener (tcp-listen port-no 5 #t))
|
|
|
|
|
...)
|
|
|
|
@ -147,7 +147,7 @@ waiting on TCP timeouts.
|
|
|
|
|
|
|
|
|
|
The server must loop to accept connections from the listener:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (serve port-no)
|
|
|
|
|
(define listener (tcp-listen port-no 5 #t))
|
|
|
|
|
(define (loop)
|
|
|
|
@ -156,11 +156,11 @@ The server must loop to accept connections from the listener:
|
|
|
|
|
(loop))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Our @scheme[accept-and-handle] function accepts a connection using
|
|
|
|
|
@scheme[tcp-accept], which returns two values: a stream for input from
|
|
|
|
|
Our @racket[accept-and-handle] function accepts a connection using
|
|
|
|
|
@racket[tcp-accept], which returns two values: a stream for input from
|
|
|
|
|
the client, and a stream for output to the client.
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (accept-and-handle listener)
|
|
|
|
|
(define-values (in out) (tcp-accept listener))
|
|
|
|
|
(handle in out)
|
|
|
|
@ -171,7 +171,7 @@ the client, and a stream for output to the client.
|
|
|
|
|
To handle a connection, for now, we'll read and discard the request
|
|
|
|
|
header, and then write a ``Hello, world!'' web page as the result:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (handle in out)
|
|
|
|
|
(code:comment @#,t{Discard the request header (up to blank line):})
|
|
|
|
|
(regexp-match #rx"(\r\n|^)\r\n" in)
|
|
|
|
@ -181,23 +181,23 @@ header, and then write a ``Hello, world!'' web page as the result:
|
|
|
|
|
(display "<html><body>Hello, world!</body></html>" out))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Note that @scheme[regexp-match] operates directly on the input stream,
|
|
|
|
|
Note that @racket[regexp-match] operates directly on the input stream,
|
|
|
|
|
which is easier than bothering with individual lines.
|
|
|
|
|
|
|
|
|
|
@whole-prog["1"]
|
|
|
|
|
|
|
|
|
|
Copy the above three definitions---@scheme[serve],
|
|
|
|
|
@scheme[accept-and-handle], and @scheme[handle]---into
|
|
|
|
|
@filepath{serve.ss} and re-load:
|
|
|
|
|
Copy the above three definitions---@racket[serve],
|
|
|
|
|
@racket[accept-and-handle], and @racket[handle]---into
|
|
|
|
|
@filepath{serve.rkt} and re-load:
|
|
|
|
|
|
|
|
|
|
@interaction[
|
|
|
|
|
#:eval more-eval
|
|
|
|
|
(eval:alts (enter! "serve.ss") (show-load #t))
|
|
|
|
|
(eval:alts (enter! "serve.rkt") (show-load #t))
|
|
|
|
|
(eval:alts (serve 8080) (void))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Now point your browser to @tt{http://localhost:8080} (assuming that
|
|
|
|
|
you used @scheme[8080] as the port number, and that the browser is
|
|
|
|
|
you used @racket[8080] as the port number, and that the browser is
|
|
|
|
|
running on the same machine) to receive a friendly greeting from your
|
|
|
|
|
web server.
|
|
|
|
|
|
|
|
|
@ -205,10 +205,10 @@ web server.
|
|
|
|
|
@section{Server Thread}
|
|
|
|
|
|
|
|
|
|
Before we can make the web server respond in more interesting ways, we
|
|
|
|
|
need to get a Scheme prompt back. Typing Ctl-C in your terminal window
|
|
|
|
|
need to get a Racket prompt back. Typing Ctl-C in your terminal window
|
|
|
|
|
interrupts the server loop:
|
|
|
|
|
|
|
|
|
|
@margin-note{In DrScheme, instead of typing Ctl-C, click the
|
|
|
|
|
@margin-note{In DrRacket, instead of typing Ctl-C, click the
|
|
|
|
|
@onscreen{Stop} button once.}
|
|
|
|
|
|
|
|
|
|
@interaction[
|
|
|
|
@ -225,15 +225,15 @@ number:
|
|
|
|
|
(eval:alts (serve 8080) (show-fail 8080))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
The problem is that the listener that we created with @scheme[serve]
|
|
|
|
|
The problem is that the listener that we created with @racket[serve]
|
|
|
|
|
is still listening on the original port number.
|
|
|
|
|
|
|
|
|
|
To avoid this problem, let's put the listener loop in its own thread,
|
|
|
|
|
and have @scheme[serve] return immediately. Furthermore, we'll have
|
|
|
|
|
@scheme[serve] return a function that can be used to shut down the
|
|
|
|
|
and have @racket[serve] return immediately. Furthermore, we'll have
|
|
|
|
|
@racket[serve] return a function that can be used to shut down the
|
|
|
|
|
server thread and TCP listener:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (serve port-no)
|
|
|
|
|
(define listener (tcp-listen port-no 5 #t))
|
|
|
|
|
(define (loop)
|
|
|
|
@ -251,7 +251,7 @@ Try the new one:
|
|
|
|
|
|
|
|
|
|
@interaction[
|
|
|
|
|
#:eval more-eval
|
|
|
|
|
(eval:alts (enter! "serve.ss") (show-load #t))
|
|
|
|
|
(eval:alts (enter! "serve.rkt") (show-load #t))
|
|
|
|
|
(define stop (serve 8081))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
@ -274,7 +274,7 @@ as you like:
|
|
|
|
|
In the same way that we put the main server loop into a background
|
|
|
|
|
thread, we can put each individual connection into its own thread:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (accept-and-handle listener)
|
|
|
|
|
(define-values (in out) (tcp-accept listener))
|
|
|
|
|
(thread
|
|
|
|
@ -288,8 +288,8 @@ 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 connections from
|
|
|
|
|
detect, however, so try inserting @racket[(sleep (random 10))] before
|
|
|
|
|
the @racket[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.
|
|
|
|
@ -304,10 +304,10 @@ 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 in Scheme that this
|
|
|
|
|
@racket[handle]. Threads are lightweight enough in Racket that this
|
|
|
|
|
watcher-thread strategy works well:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (accept-and-handle listener)
|
|
|
|
|
(define-values (in out) (tcp-accept listener))
|
|
|
|
|
(define t (thread
|
|
|
|
@ -322,22 +322,25 @@ watcher-thread strategy works well:
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
This first attempt isn't quite right, because when the thread is
|
|
|
|
|
killed, its @scheme[in] and @scheme[out] streams remain open. We
|
|
|
|
|
killed, its @racket[in] and @racket[out] streams remain open. We
|
|
|
|
|
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:
|
|
|
|
|
kill the thread, but Racket 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] operation that terminates and closes
|
|
|
|
|
@racket[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]
|
|
|
|
|
custodian as determined by the @racket[current-custodian]
|
|
|
|
|
parameter. To place everything about a connection into a custodian, we
|
|
|
|
|
@scheme[parameterize] all the resource creations to go into a new
|
|
|
|
|
@racket[parameterize] all the resource creations to go into a new
|
|
|
|
|
custodian:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@margin-note{See @secref[#:doc '(lib "scribblings/guide/guide.scrbl") "parameterize"]
|
|
|
|
|
for an introduction to parameters.}
|
|
|
|
|
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (accept-and-handle listener)
|
|
|
|
|
(define cust (make-custodian))
|
|
|
|
|
(parameterize ([current-custodian cust])
|
|
|
|
@ -352,16 +355,16 @@ custodian:
|
|
|
|
|
(custodian-shutdown-all cust))))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
With this implementation, @scheme[in], @scheme[out], and the thread
|
|
|
|
|
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.
|
|
|
|
|
With this implementation, @racket[in], @racket[out], and the thread
|
|
|
|
|
that calls @racket[handle] all belong to @racket[cust]. In addition,
|
|
|
|
|
if we later change @racket[handle] so that it, say, opens a file, then
|
|
|
|
|
the file handles will also belong to @racket[cust], so they will be
|
|
|
|
|
reliably closed when @racket[cust] is shut down.
|
|
|
|
|
|
|
|
|
|
In fact, it's a good idea to change @scheme[serve] so that it uses a
|
|
|
|
|
In fact, it's a good idea to change @racket[serve] so that it uses a
|
|
|
|
|
custodian, too:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (serve port-no)
|
|
|
|
|
(define main-cust (make-custodian))
|
|
|
|
|
(parameterize ([current-custodian main-cust])
|
|
|
|
@ -374,7 +377,7 @@ custodian, too:
|
|
|
|
|
(custodian-shutdown-all main-cust)))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
That way, the @scheme[main-cust] created in @scheme[serve] not only
|
|
|
|
|
That way, the @racket[main-cust] created in @racket[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 all active connections,
|
|
|
|
@ -382,17 +385,17 @@ in addition to the main server loop.
|
|
|
|
|
|
|
|
|
|
@whole-prog["4"]
|
|
|
|
|
|
|
|
|
|
After updating the @scheme[serve] and @scheme[accept-and-handle]
|
|
|
|
|
After updating the @racket[serve] and @racket[accept-and-handle]
|
|
|
|
|
functions as above, here's how you can simulate a malicious client:
|
|
|
|
|
|
|
|
|
|
@interaction[
|
|
|
|
|
#:eval more-eval
|
|
|
|
|
(eval:alts (enter! "serve.ss") (show-load #t))
|
|
|
|
|
(eval:alts (enter! "serve.rkt") (show-load #t))
|
|
|
|
|
(define stop (serve 8081))
|
|
|
|
|
(eval:alts (define-values (cin cout) (tcp-connect "localhost" 8081)) (void))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Now wait 10 seconds. If you try reading from @scheme[cin], which is
|
|
|
|
|
Now wait 10 seconds. If you try reading from @racket[cin], which is
|
|
|
|
|
the stream that sends data from the server back to the client, you'll
|
|
|
|
|
find that the server has shut down the connection:
|
|
|
|
|
|
|
|
|
@ -422,12 +425,12 @@ URLs.
|
|
|
|
|
To parse the incoming URL and to more easily format HTML output, we'll
|
|
|
|
|
require two extra libraries:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(require xml net/url)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
The @schememodname[xml] library gives us @scheme[xexpr->string], which
|
|
|
|
|
takes a Scheme value that looks like HTML and turns it into actual
|
|
|
|
|
The @racketmodname[xml] library gives us @racket[xexpr->string], which
|
|
|
|
|
takes a Racket value that looks like HTML and turns it into actual
|
|
|
|
|
HTML:
|
|
|
|
|
|
|
|
|
|
@interaction[
|
|
|
|
@ -435,11 +438,11 @@ HTML:
|
|
|
|
|
(xexpr->string '(html (head (title "Hello")) (body "Hi!")))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
We'll assume that our new @scheme[dispatch] function (to be written)
|
|
|
|
|
We'll assume that our new @racket[dispatch] function (to be written)
|
|
|
|
|
takes a requested URL and produces a result value suitable to use with
|
|
|
|
|
@scheme[xexpr->string] to send back to the client:
|
|
|
|
|
@racket[xexpr->string] to send back to the client:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (handle in out)
|
|
|
|
|
(define req
|
|
|
|
|
(code:comment @#,t{Match the first line to extract the request:})
|
|
|
|
@ -456,8 +459,8 @@ takes a requested URL and produces a result value suitable to use with
|
|
|
|
|
(display (xexpr->string xexpr) out))))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
The @schememodname[net/url] library gives us @scheme[string->url],
|
|
|
|
|
@scheme[url-path], @scheme[path/param-path], and @scheme[url-query]
|
|
|
|
|
The @racketmodname[net/url] library gives us @racket[string->url],
|
|
|
|
|
@racket[url-path], @racket[path/param-path], and @racket[url-query]
|
|
|
|
|
for getting from a string to parts of the URL that it represents:
|
|
|
|
|
|
|
|
|
|
@interaction[
|
|
|
|
@ -468,11 +471,11 @@ for getting from a string to parts of the URL that it represents:
|
|
|
|
|
(url-query u)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
We use these pieces to implement @scheme[dispatch]. The
|
|
|
|
|
@scheme[dispatch] function consults a hash table that maps an initial
|
|
|
|
|
path element, like @scheme["foo"], to a handler function:
|
|
|
|
|
We use these pieces to implement @racket[dispatch]. The
|
|
|
|
|
@racket[dispatch] function consults a hash table that maps an initial
|
|
|
|
|
path element, like @racket["foo"], to a handler function:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (dispatch str-path)
|
|
|
|
|
(code:comment @#,t{Parse the request as a URL:})
|
|
|
|
|
(define url (string->url str-path))
|
|
|
|
@ -493,17 +496,17 @@ path element, like @scheme["foo"], to a handler function:
|
|
|
|
|
(define dispatch-table (make-hash))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
With the new @scheme[require] import and new @scheme[handle],
|
|
|
|
|
@scheme[dispatch], and @scheme[dispatch-table] definitions, our
|
|
|
|
|
With the new @racket[require] import and new @racket[handle],
|
|
|
|
|
@racket[dispatch], and @racket[dispatch-table] definitions, our
|
|
|
|
|
``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
|
|
|
|
|
to stop the server to try it out. After modifying @filepath{serve.rkt}
|
|
|
|
|
with the new pieces, evaluate @racket[(enter! "serve.rkt")] and then
|
|
|
|
|
try again to connect to the server. The web browser should show an
|
|
|
|
|
``Unknown page'' error in red.
|
|
|
|
|
|
|
|
|
|
We can register a handler for the @scheme["hello"] path like this:
|
|
|
|
|
We can register a handler for the @racket["hello"] path like this:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(hash-set! dispatch-table "hello"
|
|
|
|
|
(lambda (query)
|
|
|
|
|
`(html (body "Hello, World!"))))
|
|
|
|
@ -511,25 +514,25 @@ We can register a handler for the @scheme["hello"] path like this:
|
|
|
|
|
|
|
|
|
|
@whole-prog["5"]
|
|
|
|
|
|
|
|
|
|
After adding these lines and evaluating @scheme[(enter! "serve.ss")],
|
|
|
|
|
After adding these lines and evaluating @racket[(enter! "serve.rkt")],
|
|
|
|
|
opening @tt{http://localhost:8081/hello} should produce the old
|
|
|
|
|
greeting.
|
|
|
|
|
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
|
@section{Servlets and Sessions}
|
|
|
|
|
|
|
|
|
|
Using the @scheme[query] argument that is passed to a handler by
|
|
|
|
|
@scheme[dispatch], a handler can respond to values that a user
|
|
|
|
|
Using the @racket[query] argument that is passed to a handler by
|
|
|
|
|
@racket[dispatch], a handler can respond to values that a user
|
|
|
|
|
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 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
|
|
|
|
|
@racket[label] argument is a string to show the user. The
|
|
|
|
|
@racket[next-url] argument is a destination for the form results. The
|
|
|
|
|
@racket[hidden] argument is a value to propagate through the form as a
|
|
|
|
|
hidden field. When the user responds, the @racket["number"] field in
|
|
|
|
|
the form holds the user's value:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (build-request-page label next-url hidden)
|
|
|
|
|
`(html
|
|
|
|
|
(head (title "Enter a Number to Add"))
|
|
|
|
@ -547,7 +550,10 @@ the form holds the user's value:
|
|
|
|
|
Using this helper function, we can create a servlet that generates as
|
|
|
|
|
many ``hello''s as a user wants:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@margin-note{See @secref[#:doc '(lib "scribblings/guide/guide.scrbl") "for"]
|
|
|
|
|
for an introduction to forms like @racket[for/list].}
|
|
|
|
|
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (many query)
|
|
|
|
|
(build-request-page "Number of greetings:" "/reply" ""))
|
|
|
|
|
|
|
|
|
@ -563,23 +569,23 @@ many ``hello''s as a user wants:
|
|
|
|
|
@whole-prog["6"]
|
|
|
|
|
|
|
|
|
|
As usual, once you have added these to your program, update with
|
|
|
|
|
@scheme[(enter! "serve.ss")], and then visit
|
|
|
|
|
@racket[(enter! "serve.rkt")], and then visit
|
|
|
|
|
@tt{http://localhost:8081/many}. Provide a number, and you'll receive
|
|
|
|
|
a new page with that many ``hello''s.
|
|
|
|
|
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
|
@section{Limiting Memory Use}
|
|
|
|
|
|
|
|
|
|
With our latest @scheme["many"] servlet, we seem to have a new
|
|
|
|
|
With our latest @racket["many"] servlet, we seem to have a new
|
|
|
|
|
problem: a malicious client could request so many ``hello''s that the
|
|
|
|
|
server runs out of memory. Actually, a malicious client could also
|
|
|
|
|
supply an HTTP request whose first line is arbitrarily long.
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
connection. Inside @racket[accept-and-handle], after the definition of
|
|
|
|
|
@racket[cust], add the line
|
|
|
|
|
|
|
|
|
|
@schemeblock[(custodian-limit-memory cust (* 50 1024 1024))]
|
|
|
|
|
@racketblock[(custodian-limit-memory cust (* 50 1024 1024))]
|
|
|
|
|
|
|
|
|
|
@whole-prog["7"]
|
|
|
|
|
|
|
|
|
@ -591,15 +597,15 @@ 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,
|
|
|
|
|
hundred megabytes available for the @exec{racket} process to use,
|
|
|
|
|
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 arbitrary Scheme code to execute on the server. In that
|
|
|
|
|
Given the @racket["many"] example, it's a small step to a web server
|
|
|
|
|
that accepts arbitrary Racket 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
|
|
|
|
|
@racketmodname[racket/sandbox] library provides support to managing
|
|
|
|
|
all those other issues.
|
|
|
|
|
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
@ -607,9 +613,9 @@ all those other issues.
|
|
|
|
|
|
|
|
|
|
As a systems example, the problem of implementing a web server exposes
|
|
|
|
|
many system and security issues where a programming language can
|
|
|
|
|
help. The web-server example also leads to a classic, advanced Scheme
|
|
|
|
|
help. The web-server example also leads to a classic, advanced Racket
|
|
|
|
|
topic: @defterm{continuations}. In fact, this facet of a web server
|
|
|
|
|
needs @defterm{delimited continuations}, which PLT Scheme provides.
|
|
|
|
|
needs @defterm{delimited continuations}, which Racket provides.
|
|
|
|
|
|
|
|
|
|
The problem solved by continuations is related to servlet sessions and
|
|
|
|
|
user input, where a computation spans multiple client connections
|
|
|
|
@ -619,11 +625,11 @@ a mixture of techniques (e.g., to take advantage of the browser's
|
|
|
|
|
``back'' button).
|
|
|
|
|
|
|
|
|
|
As the multi-connection computation becomes more complex, propagating
|
|
|
|
|
arguments through @scheme[query] becomes increasingly tedious. For
|
|
|
|
|
arguments through @racket[query] becomes increasingly tedious. For
|
|
|
|
|
example, we can implement a servlet that takes two numbers to add by
|
|
|
|
|
using the hidden field in the form to remember the first number:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (sum query)
|
|
|
|
|
(build-request-page "First number:" "/one" ""))
|
|
|
|
|
|
|
|
|
@ -647,7 +653,7 @@ using the hidden field in the form to remember the first number:
|
|
|
|
|
While the above works, we would much rather write such computations in
|
|
|
|
|
a direct style:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (sum2 query)
|
|
|
|
|
(define m (get-number "First number:"))
|
|
|
|
|
(define n (get-number "Second number:"))
|
|
|
|
@ -656,32 +662,32 @@ a direct style:
|
|
|
|
|
(hash-set! dispatch-table "sum2" sum2)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
The problem is that @scheme[get-number] needs to send an HTML response
|
|
|
|
|
The problem is that @racket[get-number] needs to send an HTML response
|
|
|
|
|
back for the current connection, and then it must obtain a response
|
|
|
|
|
through a new connection. That is, somehow it needs to convert the
|
|
|
|
|
page generated by @scheme[build-request-page] into a @scheme[query]
|
|
|
|
|
page generated by @racket[build-request-page] into a @racket[query]
|
|
|
|
|
result:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (get-number label)
|
|
|
|
|
(define query
|
|
|
|
|
... (build-request-page label ...) ...)
|
|
|
|
|
(number->string (cdr (assq 'number query))))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Continuations let us implement a @scheme[send/suspend] operation that
|
|
|
|
|
performs exactly that operation. The @scheme[send/suspend] procedure
|
|
|
|
|
Continuations let us implement a @racket[send/suspend] operation that
|
|
|
|
|
performs exactly that operation. The @racket[send/suspend] procedure
|
|
|
|
|
generates a URL that represents the current connection's computation,
|
|
|
|
|
capturing it as a continuation. It passes the generated URL to a
|
|
|
|
|
procedure that creates the query page; this query page is used as the
|
|
|
|
|
result of the current connection, and the surrounding computation
|
|
|
|
|
(i.e., the continuation) is aborted. Finally, @scheme[send/suspend]
|
|
|
|
|
(i.e., the continuation) is aborted. Finally, @racket[send/suspend]
|
|
|
|
|
arranges for a request to the generated URL (in a new connection) to
|
|
|
|
|
restore the aborted computation.
|
|
|
|
|
|
|
|
|
|
Thus, @scheme[get-number] is implemented as follows:
|
|
|
|
|
Thus, @racket[get-number] is implemented as follows:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (get-number label)
|
|
|
|
|
(define query
|
|
|
|
|
(code:comment @#,t{Generate a URL for the current computation:})
|
|
|
|
@ -695,40 +701,39 @@ Thus, @scheme[get-number] is implemented as follows:
|
|
|
|
|
(string->number (cdr (assq 'number query))))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
We still have to implement @scheme[send/suspend]. Plain Scheme's
|
|
|
|
|
@scheme[call/cc] is not quite enough, so we import a library of
|
|
|
|
|
control operators:
|
|
|
|
|
We still have to implement @racket[send/suspend]. For that task, we
|
|
|
|
|
import a library of control operators:
|
|
|
|
|
|
|
|
|
|
@schemeblock[(require scheme/control)]
|
|
|
|
|
@racketblock[(require racket/control)]
|
|
|
|
|
|
|
|
|
|
Specifically, we need @scheme[prompt] and @scheme[abort] from
|
|
|
|
|
@schememodname[scheme/control]. We use @scheme[prompt] to mark the
|
|
|
|
|
Specifically, we need @racket[prompt] and @racket[abort] from
|
|
|
|
|
@racketmodname[racket/control]. We use @racket[prompt] to mark the
|
|
|
|
|
place where a servlet is started, so that we can abort a computation
|
|
|
|
|
to that point. Change @scheme[handle] by wrapping an @scheme[prompt]
|
|
|
|
|
around the call to @scheme[dispatch]:
|
|
|
|
|
to that point. Change @racket[handle] by wrapping an @racket[prompt]
|
|
|
|
|
around the call to @racket[dispatch]:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (handle in out)
|
|
|
|
|
....
|
|
|
|
|
(let ([xexpr (prompt (dispatch (list-ref req 1)))])
|
|
|
|
|
....))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
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 to an identifier---@scheme[k], in this case:
|
|
|
|
|
Now, we can implement @racket[send/suspend]. We use @racket[call/cc]
|
|
|
|
|
in the guise of @racket[let/cc], which captures the current
|
|
|
|
|
computation up to an enclosing @racket[prompt] and binds that
|
|
|
|
|
computation to an identifier---@racket[k], in this case:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (send/suspend mk-page)
|
|
|
|
|
(let/cc k
|
|
|
|
|
...))
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Next, we generate a new dispatch tag, and we record the mapping from
|
|
|
|
|
the tag to @scheme[k]:
|
|
|
|
|
the tag to @racket[k]:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (send/suspend mk-page)
|
|
|
|
|
(let/cc k
|
|
|
|
|
(define tag (format "k~a" (current-inexact-milliseconds)))
|
|
|
|
@ -737,10 +742,10 @@ the tag to @scheme[k]:
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
Finally, we abort the current computation, supplying instead the page
|
|
|
|
|
that is built by applying the given @scheme[mk-page] to a URL for the
|
|
|
|
|
that is built by applying the given @racket[mk-page] to a URL for the
|
|
|
|
|
generated tag:
|
|
|
|
|
|
|
|
|
|
@schemeblock[
|
|
|
|
|
@racketblock[
|
|
|
|
|
(define (send/suspend mk-page)
|
|
|
|
|
(let/cc k
|
|
|
|
|
(define tag (format "k~a" (current-inexact-milliseconds)))
|
|
|
|
@ -751,21 +756,21 @@ 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. Calling the continuation (like a function) restores the old
|
|
|
|
|
computation, passing the @scheme[query] argument back to that
|
|
|
|
|
computation, passing the @racket[query] argument back to that
|
|
|
|
|
computation.
|
|
|
|
|
|
|
|
|
|
@whole-prog["9" #t]
|
|
|
|
|
|
|
|
|
|
In summary, the new pieces are: @scheme[(require scheme/control)],
|
|
|
|
|
adding @scheme[prompt] inside @scheme[handle], the definitions of
|
|
|
|
|
@scheme[send/suspend], @scheme[get-number], and @scheme[sum2], and
|
|
|
|
|
@scheme[(hash-set! dispatch-table "sum2" sum2)]. Once you have
|
|
|
|
|
In summary, the new pieces are: @racket[(require racket/control)],
|
|
|
|
|
adding @racket[prompt] inside @racket[handle], the definitions of
|
|
|
|
|
@racket[send/suspend], @racket[get-number], and @racket[sum2], and
|
|
|
|
|
@racket[(hash-set! dispatch-table "sum2" sum2)]. Once you have
|
|
|
|
|
the server updated, visit @tt{http://localhost:8081/sum2}.
|
|
|
|
|
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
|
@section{Where to Go From Here}
|
|
|
|
|
|
|
|
|
|
The PLT Scheme distribution includes a production-quality web server
|
|
|
|
|
The Racket distribution includes a production-quality web server
|
|
|
|
|
that addresses all of the design points mentioned here and more.
|
|
|
|
|
To learn more, see the tutorial @other-manual['(lib
|
|
|
|
|
"web-server/scribblings/tutorial/continue.scrbl")], the
|
|
|
|
@ -773,8 +778,8 @@ documentation @other-manual['(lib
|
|
|
|
|
"web-server/scribblings/web-server.scrbl")], or the research paper
|
|
|
|
|
@cite["Krishnamurthi07"].
|
|
|
|
|
|
|
|
|
|
Otherwise, if you arrived here as part of an introduction to PLT
|
|
|
|
|
Scheme, then your next stop is probably @|guide|.
|
|
|
|
|
Otherwise, if you arrived here as part of an introduction to
|
|
|
|
|
Racket, then your next stop is probably @|guide|.
|
|
|
|
|
|
|
|
|
|
If the topics covered here are the kind that interest you, see also
|
|
|
|
|
@secref["concurrency" #:doc '(lib
|
|
|
|
@ -783,10 +788,10 @@ If the topics covered here are the kind that interest you, see also
|
|
|
|
|
"scribblings/reference/reference.scrbl")].
|
|
|
|
|
|
|
|
|
|
Some of this material is based on relatively recent research, and more
|
|
|
|
|
information can be found in papers written by the authors of PLT
|
|
|
|
|
Scheme, including papers on MrEd @cite["Flatt99"], memory accounting
|
|
|
|
|
@cite["Wick04"], kill-safe abstractions @cite["Flatt04"], and
|
|
|
|
|
delimited continuations @cite["Flatt07"].
|
|
|
|
|
information can be found in papers written by the authors of Racket,
|
|
|
|
|
including papers on GRacket (formerly ``MrEd'') @cite["Flatt99"],
|
|
|
|
|
memory accounting @cite["Wick04"], kill-safe abstractions
|
|
|
|
|
@cite["Flatt04"], and delimited continuations @cite["Flatt07"].
|
|
|
|
|
|
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|