diff --git a/collects/web-server/scribblings/tutorial/continue.scrbl b/collects/web-server/scribblings/tutorial/continue.scrbl
index d91f2c2af5..9efe94eabe 100644
--- a/collects/web-server/scribblings/tutorial/continue.scrbl
+++ b/collects/web-server/scribblings/tutorial/continue.scrbl
@@ -5,27 +5,32 @@
(for-label db)
"tutorial-util.rkt")
+@(define xexpr @tech[#:doc '(lib "xml/xml.scrbl")]{X-expression})
+
@title{Continue: Web Applications in Racket}
@author[(author+email "Danny Yoo" "dyoo@cs.wpi.edu")
(author+email "Jay McCarthy" "jay@cs.byu.edu")]
-How do we make dynamic web applications? This tutorial will show how we
-can build web applications using Racket. As our working example,
-we'll build a simple web journal (a ``blog''). We'll cover how to start
-up a web server, how to generate dynamic web content, and how to
-interact with the user.
+How do we make dynamic web applications?
+In this tutorial, we show how to use Racket to achieve this goal.
+We explain how to start up a web server, how to generate dynamic web content,
+and how to interact with the user. Our working example will be a simple
+web journal---a ``blog.''
-The target audience for this tutorial are students who've gone through
-the design and use of structures in
-@italic{@link["http://www.htdp.org/"]{How to Design Programs}}, with
-some higher-order functions, @racket[local], and a minor bit of
-mutation.
+This tutorial is intended for students who have read enough of
+@italic{@link["http://www.htdp.org/"]{How to Design Programs}} to know how to
+use structures, higher-order functions, the @racket[local] syntax, and a little
+bit of mutation.
@section{Getting Started}
-Everything you needed in this tutorial is provided in @link["http://racket-lang.org/"]{Racket}.
-We will be using the DrRacket Module language. Enter the following into the Definition window.
+Everything you need in this tutorial is provided in
+@link["http://racket-lang.org/"]{Racket}; we will be using the DrRacket module
+language.
+
+Enter the following into DrRacket's Definitions window, then press the
+@onscreen{Run} button.
@racketmod[
web-server/insta
@@ -36,17 +41,17 @@ web-server/insta
(body (h1 "Under construction")))))
]
-Press the @onscreen{Run} button. If a web browser comes up with an ``Under
-Construction'' page, then clap your hands with delight: you've built
-your first web application! It doesn't do much yet, but we will get
-there. Press the @onscreen{Stop} button to shut the server down for now.
+If a web browser comes up with an ``Under Construction'' page, then clap your
+hands with delight, because you've built your first web application! We
+haven't yet gotten it to do much, but we'll get there. For now,
+press the @onscreen{Stop} button to shut the server down.
@section{The Application}
-We want to motivate this tutorial by showing how to develop a blog.
-Users should be able to create posts and add comments to
-any posts. We'll take an iterative approach, with one or two pitfalls
-along the way. The game plan, roughly, will be:
+We want to motivate this tutorial by showing you how to develop a blog.
+Users of the blog should be able to create new posts and add comments to
+existing posts. We'll approach the task iteratively, pointing out one or two
+pitfalls along the way. The game plan will be approximately as follows:
@itemize[
@item{Show a static list of posts.}
@@ -56,13 +61,13 @@ along the way. The game plan, roughly, will be:
@item{Serialize our data structures to disk.}
]
-By the end of this tutorial, we'll have a simple blogging application.
+By the end of the tutorial, we'll have a simple blogging application up and
+running.
@section{Basic Blog}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-1)]
-We start by considering our data definitions. We want to represent a
-list of posts. Let's say that a post is:
+We begin by defining the necessary data structures. A post is:
@racketblock[(struct post (title body))]
@@ -70,100 +75,108 @@ list of posts. Let's say that a post is:
@bold{Exercise.} Make a few examples of posts.
-A blog, then, will be a list of posts:
+Next we define a blog to be simply a list of posts:
@(defthing blog (listof post?))
-As a very simple example of a blog:
+Here, then, is a very simple example of a blog:
@racketblock[
(define BLOG (list (post "First Post!"
"Hey, this is my first post!")))
]
-Now that we have a sample blog structure, let's get our web
-application to show it.
+Now let's get our web application to show it.
@section{Rendering HTML}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-1)]
-When a web browser visits our application's URL, the browser
-constructs a request structure and sends it off to our web
-application. Our start function will consume requests and produce
-responses. One basic kind of response is to show an HTML page,
-represented as an X-expression in Racket, by using
-@racket[response/xexpr].
+When a web browser visits our blog's URL, the browser constructs a request
+structure and sends it across the network to our application. We need a
+function, which we'll call @racket[start], to consume such requests and produce
+responses to them. One basic kind of response is to show an HTML page; this is
+done by the function @racket[response/xexpr], which takes an @xexpr
+representing the desired HTML. An @xexpr is defined as
+@; Very unsure of this change. A brief look through the xml and web-server
+@; code did not turn up a flat-rec-contract definition of xexpr/c.
+@; -AWest, 18-Dec-2011.
@racketblock[
(define xexpr/c
(flat-rec-contract
xexpr
(or/c string?
- (or/c (cons/c symbol? (listof xexpr))
- (cons/c symbol?
- (cons/c (listof (list/c symbol? string?))
- (listof xexpr)))))))]
+ (cons/c symbol? (listof xexpr))
+ (cons/c symbol?
+ (cons/c (listof (list/c symbol? string?))
+ (listof xexpr))))))]
-For example:
+and the following examples illustrate how natural it is to use @|xexpr|s
+to represent HTML.
-The HTML @tt{hello} is represented as @racket["hello"]. Strings are
-automatically escaped when output. This guarantees valid HTML.
-Therefore, the value @racket["Unfinished tag"] is rendered as
-@tt{<b>Unfinished tag} not @tt{Unfinished tag}. Similarly,
-@racket["Finished tag"] is rendered as
-@tt{<i>Finished tag</i>} not @tt{Finished tag}.
+The first alternative in @racket[xexpr/c] is @racket[string?]. For example,
+the HTML @tt{hello} is represented as @racket["hello"]. To guarantee valid
+HTML, strings are automatically escaped when output. Thus, the @xexpr
+@racket["Unfinished tag"] is rendered as the HTML @tt{<b>Unfinished
+tag}, and not as @tt{Unfinished tag}. Similarly, @racket["Finished
+tag"] is rendered as @tt{<i>Finished tag</i>}, and not as
+@tt{Finished tag}.
-@tt{
This is an example
} is
+The second alternative in @racket[xexpr/c] is the recursive contract
+@racket[(cons/c symbol? (listof xexpr))]. For example, the HTML @tt{This is
+an example
} is represented by the @xexpr
@racket['(p "This is an example")].
-@tt{Past} is
+And finally, the third alternative in @racket[xexpr/c] allows for parameters in
+HTML tags. As examples, @tt{Past} is represented by
-@racket['(a ((href "link.html")) "Past")].
+@racket['(a ((href "link.html")) "Past")]
-@tt{This is
another
example.} is
+and @tt{This is
another
example.} is represented by
@racket['(p "This is " (div ((class "emph")) "another") " example.")].
-We can produce these @racket[xexpr]s by using @racket[cons] and @racket[list] directly.
-Doing so, however, can be notationally heavy. Consider:
+We could also have produced these @|xexpr|s ``manually,'' using
+@racket[cons] and @racket[list], but that can get notationally heavy. For
+example, the following Racket expressions both evaluate to the same
+@|xexpr|:
@racketblock[
(list 'html (list 'head (list 'title "Some title"))
(list 'body (list 'p "This is a simple static page.")))
]
-vs:
+@racketblock[] @; an ugly hack to insert some vertical space -AWest,
+@; 18-Dec-2011
+
@racketblock[
'(html (head (title "Some title"))
(body (p "This is a simple static page.")))
]
-They both produce the same @racket[xexpr], but the latter is a lot
-easier to type and read. We've been using the extended list
-abbreviation form described in @link["http://htdp.org/2003-09-26/Book/curriculum-Z-H-17.html#node_chap_13"]{Section 13} of @link["http://htdp.org/"]{How to Design Programs}:
-by using a leading forward quote mark to concisely represent the list
-structure, we can construct static html responses with aplomb.
+But the latter is much easier to read and type, because it uses a leading
+forward quote mark to express the list structure concisely. This is
+how to construct static html responses with aplomb! (For more on
+the extended list abbreviation form, see
+@link["http://htdp.org/2003-09-26/Book/curriculum-Z-H-17.html#node_chap_13"]{Section
+13} of @link["http://htdp.org/"]{How to Design Programs}.)
-However, we can run into a problem when we use simple list
-abbreviation with dynamic content. If we have expressions to inject
-into the @racket[xexpr] structure, we can't use a simple list-abbreviation
-approach because those expressions will be treated literally as part
-of the list structure!
+It turns out, however, that this simple kind of list abbreviation cannot
+produce web content that is dynamic. For if we try to inject expressions into
+an @|xexpr| constructed by simple list abbreviation, those expressions will be
+treated as part of the list structure, literally! What we need instead is a
+notation that gives us the convenience of quoted list abbreviations, but that
+also allows us to treat portions of the list structure as normal
+expressions. That is, we would like to define a @emph{template} whose
+placeholders can be expressed easily and filled in dynamically.
-We want a notation that gives us the convenience of quoted list
-abbreviations, but with the option to treat a portion of the structure
-as a normal expression. That is, we would like to define a template
-whose placeholders can be easily expressed and filled in dynamically.
-
-Racket provides this templating functionality with quasiquotation.
-Quasiquotation uses a leading back-quote in front of the whole
-structure. Like regular quoted list abbreviation, the majority of the
-list structure will be literally preserved in the nested list result.
-In places where we'd like a subexpression's value to be plugged in, we
-prepend an unquoting comma in front of the subexpression. As an
-example:
+Racket provides this templating functionality, in the form of a notation called
+quasiquote. In quasiquotation a list is abbreviated not with a leading forward
+quote but with a leading back quote. If we wish any subexpression of this
+backquoted list to be evaluated normally (``unquoted''), then all we
+have to do is place a comma in front that subexpression. For example:
@racketblock[
@code:comment{render-greeting: string -> response}
@@ -174,8 +187,11 @@ example:
(body (p ,(string-append "Hello " a-name))))))
]
+@; Suggestion: insert here a reference to the Guide's discussion of
+@; quasiquotation, akin to the reference to HtDP a couple of paragraphs
+@; above. -AWest, 18-Dec-2011
@bold{Exercise.} Write a function that consumes a @racket[post] and produces
-an @racket[xexpr] representing that content.
+an @xexpr representing that content.
@defthing[render-post (post? . -> . xexpr/c)]
@@ -196,14 +212,27 @@ to a post.
@centerline{------------}
-If an expression produces a list of @racket[xexpr] fragments, we may
-want to splice in the elements of a list into our template, rather
-plug in the whole list itself. In these situations, we can use the
-splicing form @racket[,@expression].
+We will sometimes want to embed a list of @|xexpr|s into
+another list that acts as a template. For example, given the list of
+@|xexpr|s @racket['((li "Larry") (li "Curly") (li "Moe"))], we may want
+to create the single @|xexpr|
-As an example, we may want a helper function that transforms a
-@racket[xexpr] list into a fragment representing an unordered, itemized
-HTML list:
+@racket['(ul (li "Larry")
+ (li "Curly")
+ (li "Moe"))]
+
+This can't be done using plain unquoting, because placing a comma in front of
+@racket['("Larry" "Curly" "Moe")] will unquote the entire list, yielding
+the malformed expression @racket['(ul ((li "Larry") (li "Curly") (li
+"Moe")))].
+
+Instead, we must splice the list in, like so: @racket[`(ul ,@((li "Larry") (li "Curly") (li
+"Moe")))]. The unquote-splicing form, @racket[,@expression],
+allows us conveniently to splice a list of @|xexpr| fragments into a
+larger template list. To generalize
+the example, here are two helper functions that convert any list of
+@|xexpr|s into one
+@|xexpr| representing an unordered, itemized HTML list:
@racketblock[
@code:comment{render-as-itemized-list: (listof xexpr) -> xexpr}
@@ -220,30 +249,30 @@ HTML list:
]
@bold{Exercise.} Write a function @racket[render-posts] that consumes a @racket[(listof post?)]
-and produces an @racket[xexpr] for that content.
+and produces an @|xexpr| for that content.
@defthing[render-posts ((listof post?) . -> . xexpr/c)]
-As examples:
+As examples,
@racketblock[
(render-posts empty)
]
-should produce:
+should produce
@racketblock[
'(div ((class "posts")))
]
-While
+and
@racketblock[
(render-posts (list (post "Post 1" "Body 1")
(post "Post 2" "Body 2")))
]
-should produce:
+should produce
@racketblock[
'(div ((class "posts"))
@@ -265,85 +294,88 @@ If we press Run, we should see the blog posts in our web browser.
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-2
web-server/servlet)]
-Our application still seems a bit static: although we're building the
-page dynamically, we haven't yet provided a way for an external user
-to add new posts. Let's tackle that now. Let's provide a form that
-will let the user add a new blog entry. When the user presses the
-submit button, we want the user to see the new post at the top of the
+Our application is still a bit too static: we build the
+page dynamically, but we don't yet provide a way for the user
+to create new posts. Let's tackle that now, by providing a form that
+lets the user add a new blog entry. When the user presses the
+submit button, we want the new post to appear at the top of the
page.
-Until now, we've been passing around a @racket[request] object without doing
-anything with it. As we might expect, the @racket[request] object isn't meant
-to be ignored so much! When a user fills out a web form and submits
-it, that user's browser constructs a new @racket[request] that holds the form
-values in it. We can use the function @racket[request-bindings] to grab at
-the values that the user has filled out. The type of @racket[request-bindings]
-is:
+We haven't yet done anything with the @racket[request] object that
+we've been passing around. As you may already have guessed, it isn't
+really supposed to be ignored so much!
+When a user fills out a web form and submits it, the user's browser constructs
+a new @racket[request] that contains the form's
+values, which we can extract on our end, using the function
+@racket[request-bindings]:
@defthing[request-bindings (request? . -> . bindings?)]
-Along with @racket[request-bindings], there's another function called
-@racket[extract-binding/single] that takes this as well as a name, and returns
-the value associated to that name.
+To extract a single web form value from a set of bindings, Racket provides
+the function @racket[extract-binding/single], which also takes the name
+of the corresponding field of the web form:
@defthing[extract-binding/single (symbol? bindings? . -> . string?)]
-Finally, we can check to see if a name exists in a binding with
+To verify that a set of bindings contains a particular field, use
@racket[exists-binding?]:
@defthing[exists-binding? (symbol? bindings? . -> . boolean?)]
With these functions, we can design functions that consume @racket[request]s
-and do something useful.
+and respond to them usefully.
-@bold{Exercise.} Write a function @racket[can-parse-post?] that consumes a @racket[bindings?].
+@bold{Exercise.} Write a function @racket[can-parse-post?] that consumes a set
+of bindings.
It should produce @racket[#t] if there exist bindings both for the symbols
@racket['title] and @racket['body], and @racket[#f] otherwise.
@defthing[can-parse-post? (bindings? . -> . boolean?)]
-@bold{Exercise.} Write a function @racket[parse-post] that consumes a bindings.
-Assume that the bindings structure has values for the symbols @racket['title]
-and @racket['body]. @racket[parse-post] should produce a post containing those values.
+@bold{Exercise.} Write a function @racket[parse-post] that consumes a set of
+bindings.
+Assuming that the bindings structure has values for the symbols @racket['title]
+and @racket['body], @racket[parse-post] should produce a post containing those
+values.
@defthing[parse-post (bindings? . -> . post?)]
Now that we have these helper functions, we can extend our web
application to handle form input. We'll add a small form at the
-bottom, and adjust out program to handle the addition of new posts.
-Our start method, then, will first see if the request has a parsable
-post, extend its set of posts if it can, and finally display those
-blog posts.
+bottom of the web page, and we'll adjust our program to handle the addition of new posts.
+So our new @racket[start] method will check that the @racket[request] has a
+parsable post, will then try to extend the set of posts, and will finally display the new set of blog posts:
@external-file["iteration-2.rkt"]
-This appears to work... but there's an issue with this! Try to add
+This solution seems to work, but it has a flaw! Try to add
two new posts. What happens?
@section{Advanced Control Flow}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-3)]
For the moment, let's ignore the admittedly huge problem of having a
-blog that only accepts one new blog entry. Don't worry! We will fix
-this.
+blog that accepts only one new blog entry. Don't worry, we'll fix
+this!
-But there's a higher-level problem with our program: although we do
-have a function, @racket[start], that can respond to requests directed at our
-application's URL, that @racket[start] function is starting to get overloaded
-with a lot of responsibility. Conceptually, @racket[start] is now handling
-two different kinds of requests: it's either a request for showing a
-blog, or a request for adding a new blog post.
+The more pressing problem right now is a higher-level one: although we
+do
+have a function, @racket[start], that responds to @racket[request]s directed
+at our application's URL, that function has begun to take on
+too much responsibility. In particular, @racket[start] now handles
+two different kinds of @racket[request]s: those for showing a
+blog, and those for adding new blog posts. It has become a kind of traffic cop
+--- a dispatcher --- for all of our web application's behaviors, including any
+new functionality we may want to add later. Life would be easier for
+@racket[start]
+(and for us) if different kinds of @racket[request]s were instead
+directed automatically to different functions. Is this possible in Racket?
-What's happening is that @racket[start] is becoming a traffic cop --- a
-dispatcher --- for all the behavior of our web application. As far as
-we know, if we want to add more functionality to our application,
-start needs to know how to deal. Can we can get different kinds of
-requests to automatically direct themselves to different functions?
-
-The web server library provides a function, @racket[send/suspend/dispatch],
-that allows us to create URLs that direct to different parts of our
-application. Let's demonstrate a dizzying example. In a new file,
-enter the following in the definition window.
+Yes! The web server library provides a function,
+@racket[send/suspend/dispatch], that allows us to create URLs that direct
+@racket[request]s aimed at them to specific functions in our application. We
+demonstrate with a dizzying example. In a new file, enter the following in
+DrRacket's Definitions window.
@racketmod[
web-server/insta
@@ -374,24 +406,23 @@ web-server/insta
This is a web application that goes round and round. When a user
first visits the application, the user starts off in @racket[phase-1]. The
-page that's generated has a hyperlink that, when clicked, continues to
+generated page has a hyperlink that, when clicked, continues to
@racket[phase-2]. The user can click back, and falls back to @racket[phase-1], and the
cycle repeats.
Let's look more closely at the @racket[send/suspend/dispatch] mechanism.
-@racket[send/suspend/dispatch] consumes a response-generating function, and it
-gives that response-generator a function called @racket[embed/url] that we'll
+@racket[send/suspend/dispatch] consumes a response-generating function and
+gives it another function, called @racket[embed/url], that we'll
use to build special URLs. What makes these URLs special is this:
-when a web browser visits these URLs, our web application restarts,
-but not from start, but from the handler that we associate to the URL.
-In @racket[phase-1], the use of @racket[embed/url] associates the link with @racket[phase-2], and
-vice versa.
+when a web browser visits one of them, our web application restarts,
+not from @racket[start], but from the handler that we associate with the
+URL. In the handler @racket[phase-1], the use of @racket[embed/url] associates
+the link with the handler @racket[phase-2], and vice versa.
-We can be more sophisticated about the handlers associated with
-@racket[embed/url]. Because the handler is just a request-consuming function,
-it can be defined within a @racket[local]. Consequently, a local-defined
-handler knows about all the variables that are in the scope of its
-definition. Here's another loopy example:
+We can be even more sophisticated about the handlers associated with
+@racket[embed/url]. Because a handler is just a request-consuming function,
+it can be defined within a @racket[local] and so can see
+all the other variables in the scope of its definition. Here's another loopy example:
@racketmod[
web-server/insta
@@ -417,85 +448,80 @@ web-server/insta
]
This example shows that we can accumulate the results of an
-interaction. Even though the user start off by visiting and seeing
-zero, the handlers produced by next-number-handler continue the
-interaction, accumulating a larger and larger number.
+interaction. Even though the user starts off by visiting and seeing
+zero, the handlers produced by @racket[next-number-handler] continue the interaction, accumulating a larger and larger number.
-Now that we've been going a little bit in circles, let's move forward
-back to the blog application. We will adjust the form's action so it
-directs to a URL that's associated to a separate handler.
+We're going in circles now, so let's move forward and return to our blog
+application. We'll adjust the form's action so that it directs a submission
+@racket[request] to a URL associated with a separate handler, called
+@racket[insert-post-handler].
@external-file["iteration-3.rkt"]
-Note that the structure of the @racket[render-blog-page] function looks very
-similar to that of our last @racket[show-counter] example. The user can
-finally add and see multiple posts to their blog.
+Note that the structure of the @racket[render-blog-page] function is very
+similar to that of our last @racket[show-counter] example. The user can
+finally add and see multiple posts to the blog.
-Unfortunately, there's still a problem. To see the problem: add a few
-posts to the system, and then open up a new browser window. In the
-new browser window, visit the web application's URL. What happens?
+Unfortunately, our design still suffers from a problem, which can be seen by
+adding a few posts to the system, and then visiting the web application's URL
+in a new browser window. What happens when you try this?
@section{Share and Share Alike}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-4)]
-We have run into another flaw with our application: each browser
-window keeps track of its own distinct blog. That defeats the point a
-blog for most people, that is, to share with others! When we insert a
-new post, rather than create a new blog value, we'd like to make a
-structural change to the existing blog. (HTDP Chapter 41). So
-let's add mutation into the mix.
+The problem with our application is that each browser
+window keeps track of its own distinct blog. For most people, this defeats
+the purpose of a blog, which is to share with others! When we insert a
+new post, rather than creating a new blog value, we'd like to make a
+structural change to the existing
+blog. (@italic{@link["http://www.htdp.org/"]{How to Design Programs}},
+Chapter 41). So let's add mutation to the mix.
-There's one small detail we need to touch: in the web-server language,
-structures are immutable by default. We'll want to override this
-default and get get access to the structure mutators. To do so, we
-adjust our structure definitions with the @racket[#:mutable] keyword.
-
-Earlier, we had said that a @racket[blog] was a list of @racket[post]s,
-but because we want to allow the blog to be changed, let's revisit our
-definition so that a blog is a mutable structure:
+There's one small detail we need to touch on: by default, structures in the
+@racket[web-server] language are immutable. To gain access to structure
+mutators, we'll need to override this default, by adding the @racket[#:mutable]
+keyword to some of our structure definitions. In particular, if we want to
+allow changes to a blog, we must change our definition of the blog structure to
+the following:
@racketblock[(struct blog (posts) #:mutable)]
@defstruct*[blog ([posts (listof post?)])]
-Mutable structures provide functions to change the fields of a
-structure; in this case, we now have a structure mutator called
-@racket[set-blog-posts!],
+A mutable structure provides functions that change its fields; in this case, we
+are provided the structure mutator @racket[set-blog-posts!],
+which allows us to change the posts of a blog:
@defthing[set-blog-posts! (blog? (listof post?) . -> . void)]
-and this will allow us to change the posts of a blog.
+
@bold{Exercise.} Write a function @racket[blog-insert-post!]
@defthing[blog-insert-post! (blog? post? . -> . void)]
-The intended side effect of the function will be to extend the blog's
-posts.
+whose intended side effect is to extend a blog's post.
@centerline{------------}
-Since we've changed the data representation of a blog, we'll need to
-revise our web application to use the updated representation. One
-other thing to note is that, within the web application, because we're
-sharing the same blog value, we don't need to pass it around with our
-handlers anymore: we can get at the current blog through our @racket[BLOG]
-variable.
-
-After doing the adjustments incorporating @racket[insert-blog-post!], and doing
-a little variable cleanup, our web application now looks like this:
+We must now modify the web application to use our new data representation of a
+blog. Since the blog is now referred to by the global variable
+@racket[BLOG], it no longer needs to be passed as a parameter to handlers like
+@racket[render-blog-page]. Here is our updated web application, after
+adjustments that incorporate @racket[insert-blog-post!], and after a bit of
+variable cleanup:
@external-file["iteration-4.rkt"]
-Open two windows that visit our web application, and start adding in
-posts from both windows. We should see that both browsers are sharing
-the same blog.
+Now visit the blog from two separate browser windows and add
+posts from each of them. You'll be glad to see that both windows share
+the same blog!
@section{Extending the Model}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-5)]
-Next, let's extend the application so that each post can hold a list
-of comments. We refine the data definition of a blog to be:
+Next, let's extend the application so that a post can include a list
+of comments. The data definition becomes:
@defstruct*[post ([title string?] [body string?] [comments (listof string?)]) #:mutable]
@@ -515,127 +541,116 @@ list of comments.
@bold{Exercise.} Adjust @racket[render-post] so that the produced fragment will include the
comments in an itemized list.
-@bold{Exercise.} Because we've extended a post to include comments, other
-post-manipulating parts of the application may need to be adjusted,
-such as uses of @racket[post]. Identify and fix any other part of the
-application that needs to accommodate the post's new structure.
+@bold{Exercise.} Because we've extended @racket[post] to include comments, you
+also need to adjust other, post-manipulating parts of the application,
+such as uses of @racket[post].
@centerline{------------}
-Once we've changed the data structure of the posts and adjusted our
-functions to deal with this revised structure, the web application
-should be runnable. The user may even see some of the fruits
-of our labor: if the initial @racket[BLOG] has a post with a comment, the user
-should see those comments now. But obviously, there's something
-missing: the user doesn't have the user interface to add comments to a
-post!
+Now that we've adjusted our functions to accommodate @racket[post]'s new
+structure, our web application should be runnable. The user may even see some
+of the fruits of our labor: if the initial @racket[BLOG] has a post with
+comments, the user should now see them. On the other hand, something is
+obviously missing: the user is given no interface through which to add
+comments!
+
@section{Breaking Up the Display}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-5)]
How should we incorporate comments more fully into the user's web
-experience? Seeing all the posts and comments all on one page may
-be a bit overwhelming. Perhaps we should hold off on showing the
-comments on the main blog page. Let's present a secondary "detail"
-view of a post, and present the comments there.
+experience? Seeing all the posts and comments on one page may
+be a bit overwhelming, so maybe we should hold off on showing the
+comments on the main blog page. Instead, let's make a secondary
+``detail'' view of a post and present its comments there.
+Accordingly, the top-level view of a blog will show only the title and body of
+a post, and the number of its comments.
-The top-level view of a blog then can show the blog's title and body.
-We can also show a count of how many comments are associated to the
-post.
-
-So now we need some way to visit a post's detail page. One way to do
-this is to hyperlink each post's title: if the user wants to see the
-detail page of a post, user should be able to click the title to get
-there. From that post's detail page, we can even add a form to let
-the user add new comments.
-
-Here's a diagram of a simple page flow of our web application that
-should let us add comments.
+So now we need a way to visit a post's detail page. One way to do
+this is to hyperlink a post's title: if one wishes to see a post's
+detail page, one should only have to click the post's title. In that post's
+detail page, we can even add a form to let the user add new comments.
+The page flow of this new version of our web application is then depicted
+simply as:
@image{scribblings/tutorial/images/flow1.png}
-Each point in the diagram corresponds to a request-consuming handler.
-As we might suspect, we'll be using @racket[send/suspend/dispatch] some more.
+Each node (bubble) in this diagram corresponds to a request-consuming handler.
+As you might expect, we'll be using @racket[send/suspend/dispatch] some more.
Every arrow in the diagram will be realized as a URL that we generate
with @racket[embed/url].
-This has a slightly messy consequence: previously, we've been
-rendering the list of posts without any hyperlinks. But since any
-function that generates a special dispatching URL uses @racket[embed/url] to do
-it, we'll need to adjust @racket[render-posts] and @racket[render-post] to consume and
-use @racket[embed/url] itself when it makes those hyperlinked titles.
+This approach has a slightly messy consequence. Previously we rendered the list
+of posts without any hyperlinks. But since any function that generates a
+special dispatching URL must use @racket[embed/url] to do so, we'll need to
+adjust @racket[render-posts] and @racket[render-post] to consume and use
+@racket[embed/url] itself when it makes those hyperlinked titles.
-Our web application now looks like:
+We now have a pretty sophisticated web application, one that permits the
+creation of posts and the addition of comments. Here is what it looks like:
@external-file["iteration-5.rkt"]
-We now have an application that's pretty sophisticated: we can add
-posts and write comments. Still, there's a problem with this: once
-the user's in a @racket[post-detail-page], they can't get back to the blog
-without pressing the browser's back button! That's disruptive. We
-should provide a page flow that lets us get back to the main
-blog-viewing page, to keep the user from every getting "stuck" in a
-dark corner of the web application.
+But it still suffers from a problem: once in a @racket[post-detail-page], the
+only way for the user to return to the blog is to use the Back button!
+That's disruptive, and it might allow the user get ``stuck'' in a
+dark corner of the web application. To solve this problem, let's improve the
+page flow.
@section{Adding a Back Button}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-6)]
-Here's a diagram of a our revised page flow of our web application.
-Maybe we can just add a BACK link from the @racket[render-post-detail-page]
-that gets us back to viewing the top-level blog.
+Perhaps we should simply add a BACK link from the
+@racket[render-post-detail-page], one that returns us to the top-level
+blog. Here's the corresponding page flow diagram:
@image{scribblings/tutorial/images/flow2.png}
@bold{Exercise.} Adjust @racket[render-post-detail-page] to include another link that goes
back to @racket[render-blog-page].
-To make this more interesting, maybe we should enrich the flow a
-bit more. We can give the user a choice right before committing to
-their comment. Who knows? They may have a change of heart.
+And since a user may have a change of heart about a comment,
+let's enrich the flow to give the user a chance to back out of submitting one.
@image{scribblings/tutorial/images/flow3.png}
-Although this seems complicated, the shape of our handlers will look
-more-or-less like what we had before. After we've added all the
-handlers, our web application is fairly functional.
+Note that, although this change may seem complicated, it doesn't affect the
+general shape of our handlers:
@external-file["iteration-6.rkt"]
+
@section{Decorating With Style!}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-7
web-server/insta/insta)]
-We have an application that's functionally complete, but is visual
-lacking. Let's try to improve its appearance. One way we can do this
-is to use a cascading style sheet. A style sheet can add visual panache
-to our web pages. For example, if we'd like to turn all of our
-paragraphs green, we might add the following style declaration within
-our response.
+Our web application is now functionally complete. But it's visually
+lacking, so let's try to improve its appearance. One way to add visual panache
+to our web pages is to use a cascading style sheet. For example, if we'd like
+to make all of our paragraphs green, we might insert the following style
+declaration into a response.
@racket['(style ((type "text/css")) "p { color: green }")]
-It's tempting to directly embed this style information into our
-@racket[response]s. However, our source file is already quite busy. We
-often want to separate the logical representation of our application
-from its presentation. Rather than directly embed the .css in the
-HTML response, let's instead add a link reference to an separate .css
-file.
+It is tempting to embed such declarations directly into our
+@racket[response]s. But our source file is already quite busy, and, as a
+matter of principle, we should separate logical representation
+from visual presentation. So, rather than embed the
+.css in the HTML response directly, let's instead add a link reference to a
+separate .css file.
-Until now, all the content that our web application has produced has
-come from a response generating handler. Of course, we know that not
-everything needs to be dynamically generated: it's common to have
-files that won't be changing. We should be able to serve these static
-resources (images, documents, .css files) alongside our web
-applications.
-
-To do this, we set aside a path to store these files, and then tell
-the web server where that directory is. The function
+Up till now, all the content produced by our web application has
+come from a response-generating handler. But this dynamic generation of HTML
+is not necessary for content that doesn't change.
+Examples of such static resources include images, documents, and .css files.
+To serve them alongside our web applications, we inform the web server of a
+directory that we have created specially for static files. The function
@racket[static-files-path],
@defthing[static-files-path (path-string? -> void)]
-tells the web server to look in the given path when it receives a URL
-that looks like a static resource request.
+tells the web server to look in the given path whenever it receives a URL
+that looks like a request for a static resource.
@bold{Exercise.} Create a simple web application called @filepath{test-static.rkt} with the
following content:
@@ -655,9 +670,9 @@ web-server/insta
(static-files-path "htdocs")
]
-Make a subdirectory called @filepath{htdocs} rooted in the same directory as
-the @filepath{test-static.rkt} source. Finally, just to see that we can serve
-this .css page, create a very simple .css file @filepath{test-static.css} file
+Make a subdirectory called @filepath{htdocs}, rooted in the same directory as
+the @filepath{test-static.rkt} source. Just to see that we can serve
+this .css page, create a very simple .css file @filepath{test-static.css}
in @filepath{htdocs/} with the following content:
@verbatim{
@@ -671,64 +686,60 @@ h2 { font-size: small }
span.hot { color: red }
}
-At this point, run the application and look at the browser's output.
-We should see a Spartan web page, but it should still have some color
+Now run the application and look at the browser's output.
+A Spartan web page should appear, but it should still have some color
in its cheeks.
@centerline{------------}
@bold{Exercise.}
-Improve the presentation of the blog web application by writing your
-an external style sheet to your tastes. Adjust all of the HTML
+Improve the presentation of the blog web application by writing
+an external style sheet that suits your tastes. Adjust all of the HTML
response handlers to include a link to the style sheet.
@section{The Double Submit Bug}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-7
web-server/servlet)]
-There's yet another a subtle problem in our application. To see it,
-bring our blog application up again, and add a post. Then reload the
+Our application has yet another subtle problem. To see it,
+bring the blog application up again, and add a post. Then reload the
page. Reload the page again.
-What's happening is a well-known problem: it's an instance of the
-"double-submit" problem. If a user presses reload, a request is sent
-over to our application. This wouldn't be such a bad thing, if not
-for the fact that we're handling certain requests by mutating our
-application's data structures.
+What you are observing is the well known ``double-submit'' problem.
+Whenever a user presses Reload, a request is sent to our application, and the
+problem is that some requests make the application mutate data structures.
-A common pattern that web developers use to dodge the double
-submission problem is to handle state-mutating request in a peculiar
-way: once the user sends over a request that affects change to the
-system, we then redirect them off to a different URL that's safe to
-reload. To make this happen, we will use the function @racket[redirect/get].
+A common technique that web developers use to dodge the double-submission
+problem is to redirect state-mutating requests to a different URL, one that is
+safe to reload. This trick is implemented in Racket by the function
+@racket[redirect/get]:
@defthing[redirect/get (-> request?)]
-This @racket[redirect/get] function has an immediate side effect: it forces the
-user's browser to follow a redirection to a safe URL, and gives us
-back that fresh new request.
+Its immediate side effect is to force the user's browser to follow a
+redirection to a safe URL, and it gives us back that fresh new request.
-For example, let's look at a toy application that lets the users add
+For example, consider a toy application that lets the user add
names to a roster:
@external-file["no-use-redirect.rkt"]
-This application suffers the same problem as our blog: if the user
+This application suffers from the same problem as our blog does: if the user
adds a name, and then presses reload, then the same name will be added
twice.
-We can fix this by changing a single expression. Can you see what
-changed?
+We can fix this by changing a single expression; can you find it
+below?
@external-file["use-redirect.rkt"]
-Double-submit, then, is painlessly easy to mitigate. Whenever we have
-handlers that mutate the state of our system, we use @racket[redirect/get] when
-we send our response back.
+So the double-submit bug is easy to prevent: whenever you have
+handlers that mutate the state of the system, use @racket[redirect/get] when
+sending back your response.
@bold{Exercise.}
-Revise the blog application with @racket[redirect/get] to address the
-double-submit problem.
+Use @racket[redirect/get] to fix the double-submit bug in the blog
+application.
With these minor fixes, our blog application now looks like this:
@@ -738,16 +749,16 @@ With these minor fixes, our blog application now looks like this:
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-8
web-server/scribblings/tutorial/examples/model)]
-If we "turn off the lights" by closing the program, then the state of
+If we ``turn off the lights'' by closing the program, the state of
our application disappears into the ether. How do we get our
-ephemeral state to stick around? Before we tackle that question, we
-should consider: what do we want to save? There's some state that we
-probably don't have a lingering interest in, like requests. What we
-care about saving is our model of the blog.
+ephemeral state to stick around? Before we tackle this problem, note that it
+does not apply to all of the application's state, for we have no long-term
+interest in things like requests. What we do care about saving is our model of
+the blog.
If we look closely at our web application program, we see a seam
between the model of our blog, and the web application that uses that
-model. Let's isolate the model: it's all the stuff near the top:
+model. Let's isolate the model; it's all the stuff near the top:
@racketblock[
(struct blog (posts) #:mutable)
@@ -758,11 +769,10 @@ model. Let's isolate the model: it's all the stuff near the top:
]
In realistic web applications, the model and the web application are
-separated by some wall of abstraction. The theory is that, if we do
-this separation, it should be easier to then make isolated changes
-without breaking the entire system. Let's do this: we will first rip
-the model out into a separate file. Once we've done that, then we'll
-look into making the model persist.
+separated by a wall of abstraction. In theory, this separation
+allows us to make isolated changes in future without breaking the entire
+system. So let's start separating. First we'll rip the model out into a
+separate file, and then we'll look into making the model persistent.
Create a new file called @filepath{model.rkt} with the following content.
@@ -777,19 +787,18 @@ additional expression that looks a little odd at first:
(provide (all-defined-out))
]
-which tells Racket to allow other files to have access to
+It tells Racket to grant other files access to
everything that's defined in the @filepath{model.rkt} file.
-
-We change our web application to use this model. Going back to our
-web application, we rip out the old model code, and replace it with an
-expression that let's use use the new model.
+Now we go back to our web application and change it to use this model, by
+replacing the deleted model code with the expression
@racketblock[
(require "model.rkt")
]
-which hooks up our web application module to the @racketmodname["model.rkt"] module.
+which hooks our web application module up to the @racketmodname["model.rkt"]
+module.
@external-file["iteration-8.rkt"]
@@ -797,37 +806,41 @@ which hooks up our web application module to the @racketmodname["model.rkt"] mod
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-9
web-server/scribblings/tutorial/examples/model-2)]
-Now that the model is separated into a separate module, we can more easily modify
-its functionality, and in particular, make it persistent.
+Now that the model resides in a separate module, we can more easily modify
+it and, in particular, can make it persistent.
The first step is to make the model structures serializable. Earlier, we made the
-structures mutable by adding @racket[#:mutable] to their definitions. We can make
-the structures serializable by adding @racket[#:prefab]. This tells Racket that
-these structures can be "previously fabricated", that is, created before the program
-started running---which is exactly what we want when restoring the blog data from disk.
-Our blog structure definition now looks like:
+structures mutable by adding @racket[#:mutable] to their
+definitions. Similarly, when the keyword @racket[#:prefab] is added to the
+definition of a structure, Racket understands that the structure can be
+``previously fabricated,'' that is, created before the program started
+running---which is exactly what we want when restoring the blog data from
+disk. Our blog structure definition now looks like:
@racketblock[
(struct blog (posts) #:mutable #:prefab)
]
-Now @racket[blog] structures can be read from the outside world with @racket[read] and written
-with @racket[write]. However, we need to make sure everything inside a @racket[blog] structure is
-also marked as @racket[#:prefab]. If we had a more complicated structure, we would need to ensure
-that everything (transitively) in the structure was @racket[#:prefab]'d.
+@racket[blog] structures can now be read from the outside world with
+@racket[read] and written to it with @racket[write]. But we also need to make
+sure that everything inside a @racket[blog] structure is also (transitively) marked
+as @racket[#:prefab].
@bold{Exercise.} Write the new structure definition for posts.
-At this point, we @emph{can} read and write the blog to disk. Now let's actually do it.
-
-First, we'll make a place to record in the model where the blog lives on disk. So, we need to change
-the blog structure again. Now it will be:
+At this point, we @emph{can} read and write the blog to disk. So let's do it.
+First, we'll add to the model a path pointing to where the blog resides on
+disk:
@defstruct*[blog ([home string?] [posts (listof post?)]) #:mutable]
+Notice that we will need to convert the path into a string. Why didn't we just
+make the blog structure contain paths? Answer: They can't be used with
+@racket[read] and @racket[write].
+
@bold{Exercise.} Write the new structure definition for blogs.
-Then, we'll make a function that allows our application to initialize the blog:
+Next we create a function that allows our application to initialize the blog:
@racketblock[
@code:comment{initialize-blog! : path? -> blog}
@@ -849,18 +862,16 @@ Then, we'll make a function that allows our application to initialize the blog:
the-blog))
]
-@racket[initialize-blog!] takes a path and tries to @racket[read] from it. If the path contains
-a @racket[blog] structure, then @racket[read] will parse it, because @racket[blog]s are @racket[#:prefab].
-If there is no file at the path, or if the file has some spurious data, then @racket[read] or
-@racket[with-input-from-file] will throw an exception. @racket[with-handlers] provides an
-exception handler that will return the default @racket[blog] structure for all
-kinds of errors.
+@racket[initialize-blog!] takes a path and tries to @racket[read] from it. If
+the path contains a @racket[blog] structure, then @racket[read] will parse it,
+because @racket[blog]s are @racket[#:prefab]. If there is no file at the path,
+or if the file has some spurious data, then @racket[read] or
+@racket[with-input-from-file] will throw an exception. @racket[with-handlers]
+supplies an exception handler that reacts to any error by returning the default
+@racket[blog] structure. After @racket[the-blog] is bound to the newly read
+(or default) structure, we set the home to the correct path.
-After @racket[the-blog] is bound to the newly read (or default) structure, we set the home to the
-correct path. (Notice that we need to convert the path into a string. Why didn't we just make the blog
-structure contain paths? Answer: They can't be used with @racket[read] and @racket[write].)
-
-Next, we will need to write a function to save the model to the disk.
+Next we need a function to save the model to the disk:
@racketblock[
@code:comment{save-blog! : blog -> void}
@@ -873,62 +884,72 @@ Next, we will need to write a function to save the model to the disk.
#:exists 'replace)))
]
-@racket[save-blog!] @racket[write]s the model into its home .
-It provides @racket[with-output-to-file] with an @racket[#:exists] flag that tells it to replace the
-file contents if the file at @racket[blog-home] exists.
+@racket[save-blog!] @racket[write]s the model to its home; by supplying an
+@racket[#:exists] clause to @racket[with-output-to-file], it ensures that the
+old contents on disk will be overwritten.
-This function can now be used to save the blog structure whenever we modify it. Since we only ever modify the
-blog structure in the model, we only need to update @racket[blog-insert-post!] and @racket[post-insert-comment!].
+This function can now be used to save the blog structure whenever it is
+modified by the user. Since modifications are made only by the model, only
+@racket[blog-insert-post!] and @racket[post-insert-comment!] will need to be
+updated.
-@bold{Exercise.} Change @racket[blog-insert-post!] and @racket[post-insert-comment!] to call @racket[save-blog!].
+@bold{Exercise.} Change @racket[blog-insert-post!] and
+@racket[post-insert-comment!] to call @racket[save-blog!].
@centerline{------------}
-You may have had a problem when trying to update @racket[post-insert-comment!]. It needs to call @racket[save-blog!]
-with the blog structure. But, it wasn't passed the blog as an argument. We'll need to add that argument and change the
-application appropriately. While we're at it, let's change @racket[blog-insert-post!] to accept the contents of the
-post structure, rather the structure itself, to better abstract the model interface:
+You may have noticed a problem when trying to update
+@racket[post-insert-comment!]: the function has no blog to pass to
+@racket[save-blog!]. We will therefore need to give it a blog argument and
+change the application appropriately. While we're at it, let's change
+@racket[blog-insert-post!] to accept the contents of the post
+structure, rather the structure itself. This improves the model's interface, by
+making it more abstract:
@defthing[blog-insert-post! (blog? string? string? . -> . void)]
@defthing[post-insert-comment! (blog? post? string? . -> . void)]
@bold{Exercise.} Write the new definitions of @racket[blog-insert-post!] and @racket[post-insert-comment!].
-(Remember to call @racket[save-blog!].)
+Remember to call @racket[save-blog!].
+In the previous iteration of the model, we used @racket[(provide
+(all-defined-out))] to expose all of the model's definitions. This
+transgresses the principle of abstraction, which tells us to hide
+implementation details like private functions and internal data structures.
+We'll conform to that principle now, by using a form of
+@racket[provide] that names the exposed definitions explicitly.
-In our last iteration of our model, we used @racket[(provide
-(all-defined-out))] to expose all of the model's definitions. But we
-often want to hide things like private functions and internal data
-structures from others. We'll do that here by using a form of
-@racket[provide] that explicitly names the exposed definitions.
-
-For example, if we wanted to limit the exposed functions to
-@racket[blog-insert-post!] and @racket[post-insert-comment!], we can
+For example, if we wanted to limit the module's exposure to the functions
+@racket[blog-insert-post!] and @racket[post-insert-comment!], we could
do this:
@racketblock[
(provide blog-insert-post!
post-insert-comment!)
]
-
-Of course, this set of functions is too minimal! Let's change the
-@racket[provide] line in the model to:
+But this is exposing too little! So let's change the @racket[provide] line in
+the model to:
@racketblock[
(provide blog? blog-posts
post? post-title post-body post-comments
initialize-blog!
blog-insert-post! post-insert-comment!)
]
-which captures the essential interactions we do with a blog.
+
+Since these nine functions are all we need from the module, this degree of
+exposure is just right.
@centerline{------------}
-The last step is to change the application. We need to call @racket[initialize-blog!] to read in the blog structure, and we
-need to pass the blog value that is returned around the application, because there is no longer a @racket[BLOG] export.
+The last step is to change the application. We need to call
+@racket[initialize-blog!] to read in the blog structure, and, since there is no
+longer a a @racket[BLOG] export, we need to pass the returned blog value around
+the application.
-First, change @racket[start] to call @racket[initialize-blog!] with a path in our home directory:
+First, change @racket[start] to call @racket[initialize-blog!] with a path in
+our home directory:
@racketblock[
(define (start request)
@@ -939,9 +960,10 @@ First, change @racket[start] to call @racket[initialize-blog!] with a path in ou
request))
]
-@bold{Exercise.} Thread the @racket[blog] structure through the application appropriately to give
-@racket[blog-insert-post!] and @racket[post-insert-comment!] the correct values. (You'll also need to
-change how @racket[render-blog-page] adds new posts.)
+@bold{Exercise.} Thread the @racket[blog] structure through the application
+appropriately to give @racket[blog-insert-post!] and
+@racket[post-insert-comment!] the correct values. You'll also need to change
+how @racket[render-blog-page] adds new posts.
@centerline{------------}
@@ -955,9 +977,11 @@ And our application is:
@centerline{------------}
-This approach to persistence can work surprisingly well for simple applications. But as our application's needs
-grow, we will have to deal with concurrency issues, the lack of a simply query language over our data model, etc.
-So, in the next section, we'll talk about how to use an SQL database to store our blog model.
+This approach to persistence can work surprisingly well for simple
+applications. But as our application's needs grow, we will have to deal with
+concurrency issues, the lack of a simple query language over our data model,
+etc. So, in the next section, we'll explain how to use an SQL database to
+store our blog model.
@section{Using an SQL database}
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/dummy-10
@@ -965,45 +989,42 @@ So, in the next section, we'll talk about how to use an SQL database to store ou
web-server/scribblings/tutorial/dummy-sqlite)]
@(require (for-label web-server/scribblings/tutorial/dummy-sqlite))
-Our next task is to employ an SQL database for the blog model. We'll
-be using SQLite with the @racketmodname[db] library. We add the
-following to the top of our model:
+To employ an SQL database, we use the following bindings from the
+@racketmodname[db] library: @racket[connection?], @racket[sqlite3-connect],
+@racket[table-exists?], @racket[query-exec], @racket[query-list], and
+@racket[query-value]. Import them by adding the following to the top of the
+model:
@racketblock[
(require db)
]
-We will use the following bindings from the @racketmodname[db]
-library: @racket[connection?], @racket[sqlite3-connect],
-@racket[table-exists?], @racket[query-exec], @racket[query-list], and
-@racket[query-value].
-
-The first thing we should do is decide on the relational structure of
-our model. We will use the following tables:
+Next, we define a relational structure for our model using the following
+tables:
@verbatim{
CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT, body TEXT)
CREATE TABLE comments (pid INTEGER, content TEXT)
}
-Each post will have an identifier, a title, and a body. This is the
-same as our old Racket structure, except we've added the
-identifier. (Actually, there was always an identifier---the memory
-pointer---but now we have to make it explicit in the database.)
+Like the Racket structure, a post in the database has a title and a body,
+but it also has an identifier. (Actually, the Racket structure had an
+identifier as well---the memory pointer---but the database requires it to be
+explicit.)
-Each comment is tied to a post by the post's identifier and has
-textual content. We could have chosen to serialize comments with
+As for the comments, each has some textual content and is connected to a post
+via identifier. We could have chosen to serialize comments with
@racket[write] and add a new TEXT column to the posts table to store
-the value. By adding a new comments table, we are more in accord with
-the relational style.
+the value. But a separate comments table conforms better to
+relational style.
-A @racket[blog] structure will simply be a container for the database
+A @racket[blog] structure is now simply a container for the database
handle:
@defstruct*[blog ([db connection?])]
-@bold{Exercise.} Write the @racket[blog] structure definition. (It
-does not need to be mutable or serializable.)
+@bold{Exercise.} Write the @racket[blog] structure definition. It
+does not need to be mutable or serializable.
We can now write the code to initialize a @racket[blog] structure:
@racketblock[
@@ -1030,13 +1051,13 @@ We can now write the code to initialize a @racket[blog] structure:
the-blog)
]
-With the @racket['create] mode, @racket[sqlite3-connect] will
-create a database if one does not already exist at the @racket[home]
-path. But, we still need to initialize the database with the table
-definitions and initial data.
+Given the @racket['create] flag, @racket[sqlite3-connect] creates a database if
+one does not already exist at the @racket[home] path.
-We used @racket[blog-insert-post!] and @racket[post-insert-comment!]
-to initialize the database. Let's see their implementation:
+We still need to initialize the database with the table
+definitions and initial data. Previously we used @racket[blog-insert-post!] and
+@racket[post-insert-comment!] for this purpose; here are their new
+implementations:
@racketblock[
@code:comment{blog-insert-post!: blog? string? string? -> void}
@@ -1059,29 +1080,29 @@ to initialize the database. Let's see their implementation:
@centerline{------------}
-Note that the SQL queries above use SQL placeholders, written
-@litchar{?}, instead of @racket[format] placeholders, written
-@litchar{~a}. This way, the query is submitted as-is to SQLite, which
-parses it and then applies the arguments. This approach ensures that
-the arguments are treated as data.
+Note that the SQL queries above use the SQL placeholder, @litchar{?}, to
+perform string substitution. If they had performed it with
+@racket[format] and @litchar{~a} instead, a malicious user could submit a post with a
+title like @racket["null', 'null') and INSERT INTO accounts (username,
+password) VALUES ('ur','hacked"] and get @racket[query-exec] to make two
+INSERTs instead of one. This is called an SQL injection attack.
+
+SQL placeholders prevent such attacks by ensuring that the query is submitted
+as-is to SQLite, which then parses it and applies the arguments. This approach
+ensures that the arguments are treated strictly as data.
-If we had used @racket[format] to do simple string substitution
-instead, a malicious user could submit a post with a title like,
-@racket["null', 'null') and INSERT INTO accounts (username, password)
-VALUES ('ur','hacked"] and get @racket[query-exec] to make two INSERTs
-instead of one. This is called an SQL injection attack.
@centerline{------------}
-In @racket[post-insert-comment!], we used @racket[post-id], but we
-have not yet defined the new @racket[post] structure. It @emph{seems}
-like a @racket[post] should be represented by an integer id, because
-the post table contains an integer as the identifying value.
+In @racket[post-insert-comment!] we use @racket[post-id] but we
+have not yet defined the new @racket[post] structure. Since the post table
+schema uses an integer as identifier, it would seem sufficient
+to do the same for the @racket[post] structure.
+However, a structure so defined would not indicate which blog, and consequently
+which database, a post belongs to. We would thus be unable to extract
+the title or body values.
-However, we cannot tell from this structure what blog this posts
-belongs to, and therefore, what database; so, we could not extract the
-title or body values, since we do not know what to query. Therefore,
-we should associate the blog with each post:
+The solution, of course, is to associate the blog with each post:
@defstruct*[post ([blog blog?] [id integer?])]
@@ -1101,9 +1122,9 @@ The only function that creates posts is @racket[blog-posts]:
"SELECT id FROM posts"))))
]
-@racket[query-list] is used with queries that return a single
-column (e.g., @racket["SELECT id FROM posts"]). It returns a list of
-that column's values.
+@racket[query-list] can be used for queries that return a single
+column (e.g., @racket["SELECT id FROM posts"]), and it returns a list of that
+column's values.
At this point we can write the functions that operate on posts:
@racketblock[
@@ -1127,7 +1148,7 @@ value (that is, one row and one column).
@centerline{------------}
The only change that we need to make to the application is to require
-the new model. The interface is exactly the same!
+the new model. Note that its interface remains unchanged!
@centerline{------------}
@@ -1149,7 +1170,9 @@ web-server/insta
@(require (for-label web-server/formlets))
-We'll now go back to the application code. One of the poor design choices we made earlier is the loose connection between the names of form elements in the display and in the form processing code:
+Now let's go back to the application code. One of our poor design choices is
+to have made a loose connection between the name used to identify a form
+element in the rendering code, and the name used for it in the extracting code:
@racketblock[
@code:comment{render-blog-page: blog request -> doesnt'}
@@ -1186,9 +1209,20 @@ We'll now go back to the application code. One of the poor design choices we mad
@(define formlets
@tech[#:doc '(lib "web-server/scribblings/web-server.scrbl")]{formlets})
-The Racket Web framework provides @formlets to abstract both the display and the processing associated with a form, leaving the names in the HTML implicit.
+The Racket Web framework provides @formlets to abstract these names away, by
+adjusting them automatically in the HTML, and by presenting the following
+interface for the display and processing of forms:
+@itemize[
+@item{@racket[formlet-display] takes a @formlet and returns its rendering as a
+list of X-expressions. This will generate unique names for its form elements.}
+
+@item{@racket[formlet-process] takes a @formlet and a request and processes the
+@formlet, i.e., extracts the bindings from the request using the
+names generated by @racket[formlet-display].}
+]
-Here is a @formlet for @racket[render-blog-page]:
+A @formlet is created using the @racket[formlet] syntax. For example, here is
+a @formlet for @racket[render-blog-page]:
@racketblock[
@code:comment{new-post-formlet : formlet (values string? string?)}
@@ -1200,25 +1234,48 @@ Here is a @formlet for @racket[render-blog-page]:
(values title body)))
]
-The first argument of @racket[formlet] is a quoted X-expression that will be rendered when the @formlet is, but with two caveats: @racket[#%#] represents a list of X-expressions and @racket[,{_formlet . => . _id}] embeds the @formlet @racket[_formlet] in this @formlet and names the result of its processing stage @racket[_id].
-
-The second argument of @racket[formlet] is the processing stage of the @formlet where all the identifiers on the right-hand side of @racket[=>] are bound to the results of processing of the sub-@|formlets|.
-
-There are two functions that operate on @|formlets|:
+The first argument in the @racket[formlet] syntax determines how
+@racket[formlet-display] should display the @|formlet|.
+It is a quoted @|xexpr|, with two important differences:
@itemize[
-@item{@racket[formlet-display] takes a @formlet and returns its rendering as a list of X-expressions. This will generate unique names for its form elements.}
-
-@item{@racket[formlet-process] takes a @formlet and a request and returns its processing. This automatically extracts the bindings from the request using the names generated by @racket[formlet-display].}
-]
+@item{@racket[#%#] introduces a list of @|xexpr|s}
+@item{
+@racket[,{_formlet . => . _id}] embeds the @formlet @racket[_formlet] as a
+sub@|formlet| and it attaches the name @racket[_id] to the result of processing
+this sub@|formlet|.
-In @racket[new-post-formlet] we used @racket[input-string], a library @formlet that renders as @racketresult[`(input ([type "text"] [name ,_fresh_name]))] and is processed as @racket[(extract-binding/single _fresh_name (request-bindings request))]. Thus, @racket[(formlet-dispay new-post-formlet)] returns:
+For example, @racket[input-string] is itself a library
+@formlet that yields a string, and @racket[,{input-string . => . title}] embeds
+@racket[input-string] in @racket[new-post-formlet] and associates the name
+@racket[title] to that string.
+
+@racket[input-string] is rendered as @racketresult[`(input ([type "text"] [name
+,_fresh_name]))], so @racket[(formlet-dispay
+new-post-formlet)] is rendered as:
@racketresultblock[
(list '(input ([type "text"] [name "input_0"]))
'(input ([type "text"] [name "input_1"])))
]
-And @racket[(formlet-process new-post-formlet _request)] where @racket[_request] binds @racketresult["input_0"] to @racketresult["Title"] and @racketresult["input_1"] to @racketresult["Body"] will return @racketresult[(values "Title" "Body")].
+}
-We can use @racket[new-post-formlet] in @racket[render-blog-page] as follows:
+
+]
+
+The second argument of @racket[formlet] determines how @racket[formlet-process]
+should process the @|formlet|. That is, it specifies how to group and order the
+results of processing the @formlet's sub@|formlet|s: the identifiers on the
+right-hand side of @racket[=>] are bound to the results of processing the
+sub@|formlets|.
+
+For example, @racket[input-string] is processed as
+@racket[(extract-binding/single _fresh_name (request-bindings
+request))]. Thus, if @racket[_request] binds @racketresult["input_0"] to
+@racketresult["Title"] and @racketresult["input_1"] to @racketresult["Body"],
+then @racket[(formlet-process new-post-formlet _request)] returns
+@racketresult[(values "Title" "Body")].
+
+
+Finally, here is how to use @racket[new-post-formlet] in @racket[render-blog-page]:
@racketblock[
@code:comment{render-blog-page: blog request -> doesn't return}
@code:comment{Sends an HTML page of the content of the}
@@ -1254,14 +1311,14 @@ Our application is now:
@section{Leaving DrRacket}
-So far, to run our application, we've been pressing @onscreen{Run} in DrRacket. If we were to actually deploy
-an application, we'd need to do this differently.
+We've been in the habit of pressing the @onscreen{Run} button to run our
+application in DrRacket. But if we were actually to deploy an application, we'd
+need to launch it by a different method.
@(require (for-label web-server/servlet-env)
(for-label web-server/managers/lru))
-The simplest way to do this is to use @racketmodname[web-server/servlet-env].
-
+The simplest alternative is to use @racketmodname[web-server/servlet-env].
First, change the first lines in your application from
@racketmod[
web-server/insta
@@ -1290,43 +1347,54 @@ Second, add the following at the bottom of your application:
"/servlets/APPLICATION.rkt")
]
-You can change the value of the @racket[#:port] parameter to use a different port.
+Regarding the parameters of @racket[serve/servlet]:
+@itemize[
+@item{You can change the value of the @racket[#:port] parameter to use a
+different port.}
-@racket[#:listen-ip] is set to @racket[#f] so that the server will listen on @emph{all} available IPs.
+@item{@racket[#:listen-ip] is set to @racket[#f] so that the server will listen
+on @emph{all} available IPs.}
-You should change @racket[_your-path-here] to be the path to the parent of your @racket[htdocs] directory.
+@item{You should change @racket[_your-path-here] to be the path to the parent
+of your @racket[htdocs] directory.}
-You should change @racket["APPLICATION.rkt"] to be the name of your application.
+@item{You should change @racket["APPLICATION.rkt"] to be the name of your
+application.}
+]
Third, to run your server, you can either press @onscreen{Run} in DrRacket, or type
@commandline{racket -t }
-(With your own file name, of course.) Both of these will start a Web server for your application.
+(using your own file name, of course). Both of these will start a Web server
+for your application.
@centerline{------------}
-@racket[serve/servlet] takes other options and there are more advanced ways of starting the Web Server,
-but you'll have to refer to the Racket Web Server Reference Manual for details.
+@racket[serve/servlet] takes other parameters and there are more advanced ways
+of starting the Web Server, but you'll have to refer to the Racket Web Server
+Reference Manual for details.
@section{Using HTTPS}
-This final task that we'll cover is using the server in HTTPS mode.
-This requires an SSL certificate and private key. This is very platform specific, but we will provide
-the details for using OpenSSL on UNIX:
+Finally, here are instructions for using the server in HTTPS mode.
+This requires an SSL certificate and a private key. It is also very
+platform-specific, but here are the details for using OpenSSL on UNIX:
@commandline{openssl genrsa -des3 -out private-key.pem 1024}
-This will generate a new private key, but it will have a passphrase on it. You can remove this via:
+This will generate a new private key, but with a passphrase, which you
+can remove as follows:
@commandline{openssl rsa -in private-key.pem -out private-key.pem}
@commandline{chmod 400 private-key.pem}
-Now, we generate a self-signed certificate:
+Now we generate a self-signed certificate:
@commandline{openssl req -new -x509 -nodes -sha1 -days 365 -key private-key.pem > server-cert.pem}
-(Each certificate authority has different instructions for generating certificate signing requests.)
+(Each certificate authority has a different way of generating
+certificate-signing requests.)
We can now start the server with:
@@ -1337,6 +1405,9 @@ The Web Server will start on port 443 (which can be overridden with the @exec{-p
@section{Moving Forward}
-As you move forward on your own applications, you may find many useful packages on PLaneT. There are interfaces to other
-databases. Many tools for generating HTML, XML, and Javascript output. Etc. There is also an active community of
-users on the Racket mailing list. We welcome new users!
+As you move forward with your own applications, you may find many PLaneT packages
+to be useful. There are interfaces to other databases, many tools for generating
+output in HTML, XML, Javascript, etc.
+
+There is also an active community of users on the Racket mailing list. We
+welcome new users!
\ No newline at end of file