Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
4921 lines (3586 sloc) 90.5 KB
#LyX 1.6.10 created this file. For more info see http://www.lyx.org/
\lyxformat 345
\begin_document
\begin_header
\textclass book
\use_default_options false
\language english
\inputencoding auto
\font_roman default
\font_sans default
\font_typewriter default
\font_default_family default
\font_sc false
\font_osf false
\font_sf_scale 100
\font_tt_scale 100
\graphics default
\paperfontsize default
\spacing single
\use_hyperref false
\papersize default
\use_geometry false
\use_amsmath 1
\use_esint 1
\cite_engine basic
\use_bibtopic false
\paperorientation portrait
\secnumdepth 3
\tocdepth 3
\paragraph_separation indent
\defskip medskip
\quotes_language english
\papercolumns 1
\papersides 1
\paperpagestyle default
\tracking_changes false
\output_changes false
\author ""
\author ""
\end_header
\begin_body
\begin_layout Chapter
Advanced Lift Architecture
\begin_inset CommandInset label
LatexCommand label
name "cha:Advanced-Lift-Guts"
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Box Framed
position "t"
hor_pos "c"
has_inner_box 0
inner_pos "t"
use_parbox 0
width "100col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
This chapter is still under active development.
The contents will change.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Congratulations! You've either made it through the introduction to Lift,
or maybe you've just skipped Basics and jumped right to here to Advanced;
either way, the next group of chapters will be exciting.
\end_layout
\begin_layout Standard
In this chapter we're going to dive into some of the advanced guts of Lift
so that you have a thorough understanding of what's going on before we
explore further.
\end_layout
\begin_layout Section
Architectural Overview
\end_layout
\begin_layout Standard
Before we jump into the specific details of the architecture, let's refresh
our memories.
Figure
\begin_inset CommandInset ref
LatexCommand ref
reference "fig:Architecture-diagram"
\end_inset
highlights the main Lift components and where they live in the ecosystem.
Scala compiles down to Java bytecode, so we sit on top of the JVM.
Lift Applications are typically run in a J(2)EE web container, such as
Jetty or Tomcat.
As we explained in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Entry-into-Lift"
\end_inset
, Lift is set up to act as a Filter
\begin_inset Foot
status open
\begin_layout Plain Layout
\begin_inset CommandInset href
LatexCommand href
target "http://java.sun.com/j2ee/1.4/docs/api/javax/servlet/Filter.html"
\end_inset
\end_layout
\end_inset
that acts as the entry point.
Usage of the rest of the framework varies from application to application,
depending on how simple or complex you make it.
\end_layout
\begin_layout Standard
\align center
\begin_inset Float figure
wide false
sideways false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Architecture
\begin_inset CommandInset label
LatexCommand label
name "fig:Architecture-diagram"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\align center
\begin_inset Graphics
filename images/LiftArchDiagram.pdf
BoundingBox .25in 5.75in 4.85in 10.75in
clip
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The major components outlined in the diagram are:
\end_layout
\begin_layout Description
LiftCore The engine of the framework responsible for request/response lifecycle,
rendering pipeline, invoking user's functions etc.
We don't directly cover the core in this book since essentially all of
the functionality that we do cover sits on top of the core
\end_layout
\begin_layout Description
SiteMap Contains the web pages for a Lift application (chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:SiteMap"
\end_inset
)
\end_layout
\begin_layout Description
LiftRules Allows you to configure Lift.
We cover this in various sections throughout the book
\end_layout
\begin_layout Description
LiftSession The session state representation (section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Session-Management"
\end_inset
)
\end_layout
\begin_layout Description
S The stateful object impersonating the state context for a given request/respon
se lifecycle (section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Advanced-S-Object"
\end_inset
)
\end_layout
\begin_layout Description
SHtml Contains helper functions for XHtml artifacts (chapters
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Forms-in-Lift"
\end_inset
and
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:AJAX-and-COMET"
\end_inset
)
\end_layout
\begin_layout Description
Views LiftView objects impersonating a view as a XML content.
Thus pages can be composed from other sources not only from html files.
(section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Views"
\end_inset
)
\end_layout
\begin_layout Description
LiftResponse Represents the abstraction of a response that will be propagated
to the client.
(section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:LiftResponse-in-Detail"
\end_inset
)
\end_layout
\begin_layout Description
Comet Represents the Comet Actors layer which allows the sending of asynchronous
content to the browser (section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:COMET"
\end_inset
)
\end_layout
\begin_layout Description
ORM - Either Mapper or Record - The lightweight ORM library provided by
Lift.
The Mapper framework is the proposed ORM framework for Lift 1.0 and the
Record framework will be out for next releases.
(chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:mapper_and_record"
\end_inset
)
\end_layout
\begin_layout Description
HTTP
\begin_inset space ~
\end_inset
Auth - You can use either Basic or Digest HTTP authentication in your Lift
application.
This provides you more control as opposed to web-container's HTTP authenticatio
n model.
(section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:HTTP-Authentication"
\end_inset
)
\end_layout
\begin_layout Description
JS
\begin_inset space ~
\end_inset
API The JavaScript abstraction layer.
These are Scala classes/objects that abstract JavaScript artifacts.
Such objects can be combined to build JavaScript code (chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Lift-and-Javascript"
\end_inset
)
\end_layout
\begin_layout Description
Utils Contains a number of helper functions that Lift uses internally and
are available to your application
\end_layout
\begin_layout Section
The Request/Response Lifecycle
\begin_inset CommandInset label
LatexCommand label
name "sec:Request/Response-Lifecycle"
\end_inset
\end_layout
\begin_layout Standard
We briefly discussed the Request/Response Liftcycle in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:The-Rendering-Process"
\end_inset
, and now we're going to cover it in depth.
This will serve not only to familiarize you with the full processing power
of Lift, but also to introduce some of the other advanced topics we'll
be discussing in this and later chapters.
\end_layout
\begin_layout Standard
One important thing we'd like to mention is that most of the configurable
properties are in
\family typewriter
LiftRules
\family default
, and are of type
\family typewriter
RulesSeq
\family default
.
With a
\family typewriter
RulesSeq
\family default
you essentially have a list of functions or values that are applied in
order.
\family typewriter
RulesSeq
\family default
defines a prepend and append method that allows you to add new configuration
items at the beginning or end of the configuration, respectively.
This allows you to prioritize things like partial functions and compose
various methods together to control Lift's behavior.
You can think of a RulesSeq as a Seq on steroids, tweaked for Lift's usage.
\end_layout
\begin_layout Standard
The following list outlines, in order, the process of transforming a Request
into a Response.
We provide references to the sections of the book where we discuss each
step in case you want to branch off.
\end_layout
\begin_layout Enumerate
Execute early functions: this is a mechanism that allows a user function
to be called on the HttpServletRequest before it enters the normal processing
chain.
This can be used for, for example, to set the XHTML output to UTF-8.
This is controlled through
\family typewriter
LiftRules.early
\end_layout
\begin_layout Enumerate
Perform URL Rewriting
\begin_inset Note Note
status open
\begin_layout Plain Layout
This needs split into stateful vs stateless
\end_layout
\end_inset
, which we already covered in detail in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:URL-Rewriting"
\end_inset
.
Controlled via
\family typewriter
LiftRules.rewrite
\family default
, this is useful for creating user-friendly URLs, among other things.
The result of the transformation will be checked for possible rewrites
until there are no more matches or it is explicitly stopped by setting
the
\family typewriter
stopRewriting
\family default
val in ReqwriteResponse to
\family typewriter
true
\family default
.
It is relevant to know that you can have rewriter functions per-session
hence you can have different rewriter in different contexts.
These session rewriters are prended to the LiftRules rewriters before their
application.
\end_layout
\begin_layout Enumerate
Call
\family typewriter
LiftRules.onBeginServicing
\family default
hooks.
This is a mechanism that allows you to add your own hook functions that
will be called when Lift is starting to process the request.
You could set up logging here, for instance.
\end_layout
\begin_layout Enumerate
Check for user-defined stateless dispatch in
\family typewriter
LiftRules.statelessDispatchTable
\family default
.
If the partial functions defined in this table match the request then they
are used to create a
\family typewriter
LiftResponse
\family default
that is sent to the user, bypassing any further processing.
These are very useful for building things like REST APIs.
The term stateless refers to the fact that at the time the dispatch function
is called, the stateful object, called
\family typewriter
S
\family default
, is not available and the
\family typewriter
LiftSession
\family default
is not created yet.
Custom dispatch is covered in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Custom-dispatch-func"
\end_inset
\end_layout
\begin_layout Enumerate
Create a
\family typewriter
LiftSession
\family default
.
The
\family typewriter
LiftSession
\family default
holds various bits of state for the request, and is covered in more detail
in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Session-Management"
\end_inset
.
\end_layout
\begin_layout Enumerate
Call
\family typewriter
LiftSession.onSetupSession
\family default
.
This is a mechanism for adding hook functions that will be called when
the LiftSession is created.
We'll get into more details when we discuss Lift's session management in
section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Session-Management"
\end_inset
.
\end_layout
\begin_layout Enumerate
Initialize the
\family typewriter
S
\family default
object (section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:S-object"
\end_inset
).
The
\family typewriter
S
\family default
object represents the current state of the Request and Response.
\end_layout
\begin_layout Enumerate
Call any
\family typewriter
LoanWrapper
\family default
instances that you've added through
\family typewriter
S.addAround
\family default
.
A
\family typewriter
LoanWrapper
\family default
is a way to insert your own processing into the render pipeline, similar
to how Filter works in the Servlet API.
This means that when your
\family typewriter
LoanWrapper
\family default
implementation is called, Lift passes you a function allowing you to chain
the processing of the request.
With this functionality you can execute your own pre- and post-condition
code.
A simple example of this would be if you need to make sure that something
is configured at the start of processing and cleanly shut down when processing
terminates.
LoanWrappers are covered in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Wrapping-Lift's-processing"
\end_inset
\end_layout
\begin_layout Enumerate
Process the stateful request
\end_layout
\begin_deeper
\begin_layout Enumerate
Check the stateful dispatch functions defined in
\family typewriter
LiftRules.dispatch
\family default
.
This is similar to the stateless dispatch in step #4 except that these
functions are executed in the context of a LiftSession and an
\family typewriter
S
\family default
object (section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:S-object"
\end_inset
).
The first matching partial function is used to generate a
\family typewriter
LiftResponse
\family default
that is returned to the client.
If none of the dispatch functions match then processing continues.
Dispatch functions are covered in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Custom-dispatch-func"
\end_inset
.
This flow is wrapped by LiftSession.onBeginServicing/onEndServicing calls
\end_layout
\begin_layout Enumerate
If this is a
\series bold
Comet
\series default
request, then process it and return the response.
Comet is a method for performing asynchronous updates of the user's page
without a reload.
We cover Comet techniques in chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:AJAX-and-COMET"
\end_inset
\end_layout
\begin_layout Enumerate
If this is an
\series bold
Ajax
\series default
request, execute the user's callback function; the specific function is
mapped via a request parameter (essentially a token).
The result of the callback is returned as the response to the user.
The response can be a JavaScript snippet, an XML construct or virtually
any
\family typewriter
LiftResponse
\family default
.
For an overview of
\family typewriter
LiftResponse
\family default
please see section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:LiftResponse-in-Detail"
\end_inset
.
This flow is wrapped by LiftSession.onBeginServicing/onEndServicing calls.
\end_layout
\begin_layout Enumerate
If this is a regular HTTP request, then:
\end_layout
\begin_deeper
\begin_layout Enumerate
Call
\family typewriter
LiftSession.onBeginServicing
\family default
hooks.
Mostly
\begin_inset Quotes eld
\end_inset
onBegin
\begin_inset Quotes erd
\end_inset
/
\begin_inset Quotes erd
\end_inset
onEnd
\begin_inset Quotes erd
\end_inset
functions are used for logging.
Note that the
\family typewriter
LiftRules
\family default
object also has
\family typewriter
onBeginServicing
\family default
and
\family typewriter
onEndServicing
\family default
functions but these are
\begin_inset Quotes eld
\end_inset
wrapping
\begin_inset Quotes erd
\end_inset
more Lift processing and not just statefull processing.
\end_layout
\begin_layout Enumerate
Check the user-defined dispatch functions that are set per-session (see
\family typewriter
S.addHighLevelSessionDispatcher
\family default
).
This is similar to
\family typewriter
LiftRules.dispatch
\family default
except that you can have different functions set up for a different session
depending on your application logic.
If there is a function applicable, execute it and return its response.
If there is no per-session dispatch function, process the request by executing
the Scala function that user set up for specific events (such as when clicking
a link, or pressing the submit button, or a function that will be executed
when a form field is set etc.).
Please see SHtml obejct
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:SHtml"
\end_inset
.
\end_layout
\begin_layout Enumerate
Check the SiteMap and Loc functions.
We cover SiteMap extensively in chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:SiteMap"
\end_inset
.
\end_layout
\begin_layout Enumerate
Lookup the template based on the request path.
Lift will locate the templates using various approaches:
\end_layout
\begin_deeper
\begin_layout Enumerate
Check the partial functions defined in
\family typewriter
LiftRules.viewDispatch
\family default
.
If there is a function defined for this path invoke it and return an Either[
\begin_inset Formula $()\Rightarrow Can[NodeSeq]$
\end_inset
,LiftView].
This allows you to either return the function for handling the view directly,
or delegate to a
\family typewriter
LiftView
\family default
subclass.
\family typewriter
LiftView
\family default
is covered in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Views"
\end_inset
\end_layout
\begin_layout Enumerate
If no viewDispatch functions match, then look for the template using the
ServletContext's
\family typewriter
getResourceAsStream
\family default
.
\end_layout
\begin_layout Enumerate
If Lift still can't find any templates, it will attempt to locate a View
class whose name matches the first component of the request path under
the
\family typewriter
view
\family default
folder of any packages defined by
\family typewriter
LiftRules.addToPackages
\family default
method.
If an
\family typewriter
InsecureLiftView
\family default
class is found, it will attempt to invoke a function on the class corresponding
to the second component of the request path.
If a
\family typewriter
LiftView
\family default
class is found, it will invoke the
\family typewriter
dispatch
\family default
method on the second component of the request path.
\end_layout
\end_deeper
\begin_layout Enumerate
Process the templates by executing snippets, combining templates etc.
\end_layout
\begin_deeper
\begin_layout Enumerate
Merge <head> elements, as described in section e
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Head-Merge"
\end_inset
\end_layout
\begin_layout Enumerate
Update the internal functions map.
Basically this associates the user's Scala functions with tokens that are
passed around in subsequent requests using HTTP query parameters.
We cover this mechanism in detail in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Lift-Function-Mapping"
\end_inset
\end_layout
\begin_layout Enumerate
Clean up notices (see S.error, S.warning, S.notice) since they were already
rendered they are no longer needed.
Notices are covered in section
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Message-Handling"
\end_inset
.
\end_layout
\begin_layout Enumerate
Call
\family typewriter
LiftRules.convertResponse
\family default
.
Basically this glues together different pieces if information such as the
actual markup, the response headers, cookies, etc into a LiftResponse instance.
\end_layout
\begin_layout Enumerate
Check to see if Lift needs to send HTTP redirect.
For an overview please see
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:HTTP-redirects"
\end_inset
\end_layout
\end_deeper
\begin_layout Enumerate
Call
\family typewriter
LiftSession.onEndServicing
\family default
hooks, the counterparts to
\family typewriter
LiftSession.onBeginServicing
\end_layout
\end_deeper
\begin_layout Enumerate
Call
\family typewriter
LiftRules.performTransform
\family default
.
This is actually configured via the
\family typewriter
LiftRules.responseTransformers
\family default
\family typewriter
RulesSeq
\family default
.
This is a list of functions on
\begin_inset Formula $LiftResponse\Rightarrow LiftResponse$
\end_inset
that allows the user to modify the response before it's sent to the client
\end_layout
\end_deeper
\begin_layout Enumerate
Call
\family typewriter
LiftRules.onEndServicing
\family default
hooks.
These are the stateless end-servicing hooks, called after the S object
context is destroyed.
\end_layout
\begin_layout Enumerate
Call any functions defined in
\family typewriter
LiftRules.beforeSend
\family default
.
This is the last place where you can modify the response before it's sent
to the user
\end_layout
\begin_layout Enumerate
Convert the
\family typewriter
LiftResponse
\family default
to a raw byte stream and send it to client as an HTTP response.
\end_layout
\begin_layout Enumerate
Call any functions defined in
\family typewriter
LiftRules.afterSend
\family default
.
Typically these would be used for cleanup.
\end_layout
\begin_layout Standard
We realize that this is a lot of information to digest in one pass, so as
we continue to cover the specific details of the rendering pipeline you
may want to keep a bookmark here so that you can come back and process
the new information in the greater context of how Lift is working.
\end_layout
\begin_layout Standard
Tyler Weir has created a set of diagrams on the following two pages that
outline Lift's processing at the global level and also for HTTP requests
in particular.
For the visually-oriented these may explain things a bit better.
\begin_inset Newpage clearpage
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Float figure
placement H
wide false
sideways false
status open
\begin_layout Plain Layout
\align center
\begin_inset Graphics
filename images/lift_request_processing_global.png
width 7in
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Lift Global Request Processing
\end_layout
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The
\begin_inset Quotes eld
\end_inset
Process HTTP request
\begin_inset Quotes erd
\end_inset
step is expanded on the following page.
\end_layout
\begin_layout Standard
\begin_inset Newpage pagebreak
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Float figure
placement H
wide false
sideways false
status open
\begin_layout Plain Layout
\align center
\begin_inset Graphics
filename images/lift_request_processing_http.png
height 9in
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Lift HTTP Request Processing
\end_layout
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Newpage pagebreak
\end_inset
\end_layout
\begin_layout Section
Lift Function Mapping
\begin_inset CommandInset label
LatexCommand label
name "sub:Lift-Function-Mapping"
\end_inset
\end_layout
\begin_layout Standard
As we mentioned in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Form-Fundamentals"
\end_inset
, lift utilizes scala closures and functions for almost all processing of
client data.
Because of this, Lift's ability to associate functions with specific form
elements, AJAX calls, etc, is critical to its operation.
This association of functions, commonly known as
\begin_inset Quotes eld
\end_inset
mapping
\begin_inset Quotes erd
\end_inset
is handled through a combination of request parameters, Scala closures
and Session data.
We feel that understanding how mapping works is important if you want to
work on advanced topics.
\end_layout
\begin_layout Standard
At its most basic, mapping of functions is just that; a map of the user's
currently defined functions.
To simplify things, Lift actually uses one of four subclasses of AFuncHolder
\begin_inset Foot
status open
\begin_layout Plain Layout
net.liftweb.http.S.AFuncHolder
\end_layout
\end_inset
:
\end_layout
\begin_layout Description
BinFuncHolder used for binding functions for file uploading.
It will hold a
\begin_inset Formula $FileParamHolder\Rightarrow Any$
\end_inset
function, which is used to process the file data after upload (section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:File-Uploads"
\end_inset
)
\end_layout
\begin_layout Description
SFuncHolder used for binding
\begin_inset Formula $String\Rightarrow Any$
\end_inset
functions.
This function corresponds to a single HTTP query parameter, except that
the parameter name is
\emph on
unique to this request
\emph default
(we'll cover naming shortly)
\end_layout
\begin_layout Description
LFuncHolder used for binding
\begin_inset Formula $List[String]\Rightarrow Any$
\end_inset
functions.
This is essentially the same as SFuncHolder but for multiple values
\end_layout
\begin_layout Description
NFuncHolder used for binding
\begin_inset Formula $()\Rightarrow Any$
\end_inset
functions.
Typically these are used for event callabcks (such as form submission)
\end_layout
\begin_layout Standard
Wherever Lift takes a function callback it is converted to one of these
types behind the scenes.
Also on the backend, each function is assigned a token ID (generated by
\family typewriter
Helpers.nextFuncName
\family default
), which is then added to the session, typically via
\family typewriter
S.addFunctionMap
\family default
or
\family typewriter
S.mapFunc
\family default
.
The token is generally used as the form element name so that the tokens
for a given form are passed back to Lift when the form is submitted; in
AJAX, the token is used as an HTTP query parameter of the AJAX callback
from the client JavaScript code.
In either case, Lift processes the query parameters within LiftSession.runParams
and executes each associated function in the function mapping.
\end_layout
\begin_layout Standard
As a concrete example, let's look at a simple binding in a form.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Function-binding-snippet"
\end_inset
shows a small example snippet that will request a person's name and print
it out when the person clicks the submit button.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Function binding snippet
\begin_inset CommandInset label
LatexCommand label
name "lst:Function-binding-snippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
def greet (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
var name = ""
\end_layout
\begin_layout Plain Layout
def process() = {
\end_layout
\begin_layout Plain Layout
println(name)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
bind("form", xhtml, "name" -> SHtml.text(name, name = _),
\end_layout
\begin_layout Plain Layout
"greet" -> SHtml.submit("Greet", process))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Function-binding-template"
\end_inset
shows the corresponding template using our sample snippet.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Function binding template
\begin_inset CommandInset label
LatexCommand label
name "lst:Function-binding-template"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:surround with="default" at="content">
\end_layout
\begin_layout Plain Layout
<lift:Test.greet form="GET">
\end_layout
\begin_layout Plain Layout
<form:name /> <form:greet />
\end_layout
\begin_layout Plain Layout
</lift:Test.greet>
\end_layout
\begin_layout Plain Layout
</lift:surround>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Finally, listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Function-binding-result"
\end_inset
shows an example of the resulting HTML that's generated when a user views
the template.
As you can see, each of the elements with callbacks has a corresponding
form element with a token ID for the name value.
Since we've used the GET CGI method here (we usually recommend using POST
in the real world), when we submit the form our URL would look like
\family typewriter
/greet.html?F541542594358JE2=...&F541542594359PM4=Greet
\family default
.
For SFuncHolder mappings the value of the request parameter is passed directly.
For NFuncHolders the presence of the token in the query parameter list
is enough to fire the function.
For BinFuncHolder and LFuncHolder mappings some additional processing is
performed to coerce the submitted values into proper values for the functions
to handle.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Function binding result
\begin_inset CommandInset label
LatexCommand label
name "lst:Function-binding-result"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<form method="get" action="/greet.html">
\end_layout
\begin_layout Plain Layout
<input name="F541542594358JE2" type="text" value=""/>
\end_layout
\begin_layout Plain Layout
<input name="F541542594359PM4" type="submit" value="Greet"/>
\end_layout
\begin_layout Plain Layout
</form>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Normally you do not have to directly deal with the function holder classes,
since the generator functions in SHtml handle that internally.
However, if you're in a situation when you need to bind functions by yourself
(such as building your own widget where SHtml doesn't provided needed elements)
, you can use the previously mentioned
\family typewriter
S.addFunctionMap
\family default
or
\family typewriter
S.mapFunc
\family default
to do the
\begin_inset Quotes eld
\end_inset
registration
\begin_inset Quotes erd
\end_inset
for you.
\end_layout
\begin_layout Section
LiftResponse in Detail
\begin_inset CommandInset label
LatexCommand label
name "sec:LiftResponse-in-Detail"
\end_inset
\end_layout
\begin_layout Standard
In some cases, particularly when using dispatch functions (section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Custom-dispatch-func"
\end_inset
), you may want explicit control over what Lift returns to the user.
The LiftResponse trait is the base of a complete hierarchy of response
classes that cover a wide variety of functionality, from simply returning
an HTTP status code to returning a byte stream or your own XML fragments.
In this section we'll cover some of the more common classes.
\end_layout
\begin_layout Subsection
InMemoryResponse
\end_layout
\begin_layout Standard
The
\family typewriter
InMemoryResponse
\family default
allows you to return an array of bytes directly to the user along with
a set of HTTP headers, cookies and a response code.
An example of using
\family typewriter
InMemoryResponse
\family default
was given in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Custom-dispatch-func"
\end_inset
, showing how we can directly generate a chart PNG in memory and send it
to the user.
This is generally useful as long as the data you need to generate and send
is relatively small; when you start getting into larger buffers you can
run into memory constraints as well as garbage collection pressure if you're
serving a large number of requests.
\end_layout
\begin_layout Subsection
StreamingResponse
\end_layout
\begin_layout Standard
The
\family typewriter
StreamingResponse
\family default
class is similar to the
\family typewriter
InMemoryResponse
\family default
, except that instead of reading from a buffer, it reads from an input object.
The input object is not required to be a subclass of
\family typewriter
java.io.InputStream
\family default
, but rather is only required to implement the method
\begin_inset Quotes eld
\end_inset
def read(buf: Array[Byte]): Int
\begin_inset Quotes erd
\end_inset
\begin_inset Foot
status open
\begin_layout Plain Layout
This is done with Scala's structural typing, which we don't cover in this
book.
For more info, see
\begin_inset CommandInset href
LatexCommand href
target "http://scala.sygneca.com/patterns/duck-typing-done-right"
\end_inset
, or the Scala Language Spec, section 3.2.7
\end_layout
\end_inset
.
This allows you to essentially send back anything that can provide an input
stream.
Additionally, you can provide a
\begin_inset Formula $()\Rightarrow Unit$
\end_inset
function (cleanup, if you will) that is called when the input stream is
exhausted.
As an example, let's look at how we could stream a file from our WAR back
to the client.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:streaming-download"
\end_inset
shows how we can retrieve the input stream from our classloader and then
send it directly to the user.
Note that you
\emph on
must
\emph default
know the size of the file you're streaming before sending it.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Streaming download method
\begin_inset CommandInset label
LatexCommand label
name "lst:streaming-download"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
def sendFile () : Box[LiftResponse] = {
\end_layout
\begin_layout Plain Layout
// Locate the file and process it
\end_layout
\begin_layout Plain Layout
LiftRules.getResource("/some-file.txt").map { url =>
\end_layout
\begin_layout Plain Layout
val input = url.openStream()
\end_layout
\begin_layout Plain Layout
val filesize = ...
// must compute or predetermine this.
\end_layout
\begin_layout Plain Layout
StreamingResponse(inPipe,
\end_layout
\begin_layout Plain Layout
() => { input.close },
\end_layout
\begin_layout Plain Layout
filesize,
\end_layout
\begin_layout Plain Layout
(Content-Type -> "text/plain") :: Nil,
\end_layout
\begin_layout Plain Layout
Nil,
\end_layout
\begin_layout Plain Layout
200)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Note that we use the cleanup function to close the input stream once we're
done so that we make sure to release resources.
\end_layout
\begin_layout Subsection
Hierarchy
\begin_inset Note Note
status open
\begin_layout Plain Layout
May remove this after I complete the summary of classes.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The Lift framework makes a lot of things really easy and it provides extremly
useful abstractions as you may have already discovered.
Responses to clients are also abstacted by
\family typewriter
LiftResponse
\family default
trait.
There are numerous response types and here is the simplified view of the
class hierarchy:
\end_layout
\begin_layout Itemize
LiftResponse
\end_layout
\begin_deeper
\begin_layout Itemize
BasicResponse
\end_layout
\begin_deeper
\begin_layout Itemize
InMemoryResponse
\end_layout
\begin_layout Itemize
StreamingResponse
\end_layout
\end_deeper
\begin_layout Itemize
JSonResponse
\end_layout
\begin_layout Itemize
RedirectResponse
\end_layout
\begin_deeper
\begin_layout Itemize
RedirectWithState
\end_layout
\end_deeper
\begin_layout Itemize
ToResponse
\end_layout
\begin_deeper
\begin_layout Itemize
XhtmlRespomse
\end_layout
\begin_layout Itemize
XmlResponse
\end_layout
\begin_layout Itemize
XmlMimeResponse
\end_layout
\begin_layout Itemize
AtomResponse
\end_layout
\begin_layout Itemize
OpenSearchResponse
\end_layout
\begin_layout Itemize
AtomCreatedResponse
\end_layout
\begin_layout Itemize
AtomCategoryResponse
\end_layout
\begin_layout Itemize
AtomServiceResponse
\end_layout
\begin_layout Itemize
CreatedResponse
\end_layout
\end_deeper
\begin_layout Itemize
OkResponse
\end_layout
\begin_layout Itemize
PermRedirectResponse
\end_layout
\begin_layout Itemize
BadResponse
\end_layout
\begin_layout Itemize
UnauthorizedResponse
\end_layout
\begin_layout Itemize
UnauthorizedDigestResponse
\end_layout
\begin_layout Itemize
NotFoundResponse
\end_layout
\begin_layout Itemize
MethodNotAllowedResponse
\end_layout
\begin_layout Itemize
GoneResponse
\end_layout
\end_deeper
\begin_layout Standard
We won't get into details right now on what exactly each and every class/object
does, although their purpose is given away by their names.
It is important to know that whenever you need to return a
\family typewriter
LiftResponse
\family default
reference from one of your functions, for example
\family typewriter
LiftRules.dispatch
\family default
you can you can use one of these classes.
Lift doesn't really provide the HttpServletResponse object, instead all
responses are impersonated by a
\family typewriter
LiftResponse
\family default
instance and it content (the actual payload, http headers, content-type,
cookies etc.) is written internally by Lift to the container's output stream.
\end_layout
\begin_layout Standard
Still let's take a look at a few examples
\end_layout
\begin_layout Subsection
RedirectWithState
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
RedirectWithState example
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// Assume you boot function
\end_layout
\begin_layout Plain Layout
import MessageState._
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def boot = {
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
LiftRules.dispatch.prepend {
\end_layout
\begin_layout Plain Layout
case Req("redirect1" :: _, _, _) => () =>
\end_layout
\begin_layout Plain Layout
Full(RedirectWithState("/page1", "My error" -> Error))
\end_layout
\begin_layout Plain Layout
case Req("redirect2" :: _, _, _) => () =>
\end_layout
\begin_layout Plain Layout
Full(RedirectWithState("/page2",
\end_layout
\begin_layout Plain Layout
RedirectState(() => println("Called on redirect!"),
\end_layout
\begin_layout Plain Layout
"My error" -> Error)))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\end_inset
\end_layout
\begin_layout Standard
First of all we added a DispatchPF function that pattern matches for paths
starting with
\family typewriter
redirect1
\family default
and
\family typewriter
redirect2
\family default
.
Let's see what happens in each case.
\end_layout
\begin_layout Itemize
\family typewriter
redirect1
\family default
- We are returning a RedirectWithState response.
It will do HTTP redirect towards /page1 and the state is impersonated by
the tuple
\begin_inset Quotes eld
\end_inset
MyError
\begin_inset Quotes erd
\end_inset
-> Error.
Because MessageState object holds an implicit conversion function from
Tuple2 to MessageState it suffices to just provide the tuple here.
Essentially we are saying here that when the browser sends the redirect
request to server we already have an Error notice set up and the <lift:msgs>
tag from your /page1 will show this
\begin_inset Quotes eld
\end_inset
My error
\begin_inset Quotes erd
\end_inset
error message.
\end_layout
\begin_layout Itemize
\family typewriter
redirect2
\family default
- Similarly it does an HTTP redirect to browser towards your /page2.
But we are passing now a RedirectState object.
This object holds a () => Unit function that will be executed when browser
send the redirect request and the Notices impersonated by a repeated parameter
(String, NoticeType.Value)*.
In fact the mapping between the actual message and its type: Notice, Warning
or Error.
\end_layout
\begin_layout Subsection
XmlResponse
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
XmlResponse example
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// Assume you boot function
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def boot = {
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
LiftRules.dispatch.prepend {
\end_layout
\begin_layout Plain Layout
case Req("rest" :: Nil, _, _) => () => Full(XmlResponse(
\end_layout
\begin_layout Plain Layout
<persons>
\end_layout
\begin_layout Plain Layout
<name>John</name>
\end_layout
\begin_layout Plain Layout
<name>Jane</name>
\end_layout
\begin_layout Plain Layout
</persons>
\end_layout
\begin_layout Plain Layout
))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\end_inset
\end_layout
\begin_layout Standard
When you are receiving a request with the path /rest the code is returning
an XML response.
The content-type and everything else is taken care of by XmlResponse.
You can build much more complex REST API's an return XML response which
is probably mot commonly used.
\end_layout
\begin_layout Section
Session Management
\begin_inset CommandInset label
LatexCommand label
name "sec:Session-Management"
\end_inset
\end_layout
\begin_layout Standard
Lift is a stateful framework and naturally this state needs to be managed.
You may already be familiar with HttpSession and and how a J(2)EE web container
identifies an HttpSession; either by a JSESSIONID cookie or by a JSESSIONID
URI sequence (in case of URL rewriting).
Similarly, Lift uses a LiftSession reference which is not actually
\begin_inset Quotes eld
\end_inset
persisted
\begin_inset Quotes erd
\end_inset
in HttpSession.
As a matter of fact Lift does not really use the HttpSession provided by
the web container to maintain conversational state, but rather uses a bridge
between the
\family typewriter
HttpSession
\family default
and the
\family typewriter
LiftSession
\family default
.
This bridge is impersonated by
\family typewriter
SessionToServletBridge
\family default
class which implements
\emph on
javax.servlet.http.HttpSessionBindingListener
\emph default
and
\emph on
javax.servlet.http.HttpSessionActivationListener
\emph default
and works like this:
\end_layout
\begin_layout Enumerate
When receiving an HTTP Request and there was no stateless dispatch function
to execute, Lift does the stateful processing.
But before doing that it checks to see if there is a
\family typewriter
LiftSession
\family default
associated with this HTTP session ID.
This mapping is kept on a SessionMaster Scala actor.
\end_layout
\begin_layout Enumerate
If there is no associated LiftSession in the SessionMaster actor, create
it and add a SessionToServletBridge attribute on HttpSession.
This will make Lift aware of the session when the container terminates
the HttpSession or when the HTTP session is about to be passivated or activated.
\end_layout
\begin_layout Enumerate
When the container terminates the HTTP session, SessionToServletBridge sends
a message to the SessionMaster Actor to terminate the LiftSession, which
includes the following steps:
\end_layout
\begin_deeper
\begin_layout Enumerate
Call any defined LiftSession.onAboutToShutdownSession hooks
\end_layout
\begin_layout Enumerate
Send a ShutDown message to all Comet Actors pertaining to this session
\end_layout
\begin_layout Enumerate
Clean up any internal LiftSession state
\end_layout
\begin_layout Enumerate
Call LiftSession.onShutdownSession hooks
\end_layout
\end_deeper
\begin_layout Standard
The SessionMaster Actor is also protected by another watcher Actor.
This watcher Actor receives the Exit messages of the watched Actors.
When it receives an Exit message it will call the users' failure functions
and restart the watched actor (Please see ActorWatcher.failureFuncs).
\end_layout
\begin_layout Standard
Even while Lift is handling session management you still have the ability
to manually add attributes to the HttpSession object.
We do not recommend this unless you really must.
A simpler way to keep your own session variables, is to use
\family typewriter
SessionVar
\family default
s.
For more details about SessionVar please see the fundamental chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Session-and-Request"
\end_inset
\end_layout
\begin_layout Standard
The next question would probably be
\begin_inset Quotes eld
\end_inset
So we have internal session management, how do we cope with that in a clustered
environment? ...
how are sessions replicated?
\begin_inset Quotes erd
\end_inset
the answer is, they aren't.
There is no intention to use the web container's session replication as
these technologies appears to be inferior to other solutions on the market.
Relying on Java serialization brings a lot of performance concerns and
alternative technologies have been investigated and they are still under
investigation.
Until there is a standard session replication technology you can still
cluster you application using
\begin_inset Quotes eld
\end_inset
sticky session
\begin_inset Quotes erd
\end_inset
.
This meas that all requests pertaining to a HTTP session must be processed
by the same cluster node.
This can be done by software or hardware load balancers, as they would
dispatch the requests based on JSESSIONID cookie.
Another approach is that the dispatching is done based on some URI or query
parameters.
For example, a query parameter like serverid=1 is configured in the load
balancer to always be dispatched to the node 1 of the cluster, and so on.
There are some downsides for the sticky session approach.
For instance you are logged in the application and do your stuff.
Suddenly the node designated to your session crashes.
At this moment you lost your session.
The next subsequent request would be automatically dispatched by the load
balancer to another cluster node and depending how your application is
built this may mean that you need to log in again or if part of the state
was persisted in DB you may resume your work from some point avoiding re-login
...
but this is application specific behavior that is beyond the scope of this
discussion.
The advantages of sticky sessions are related with application performance
since in this model the state does not need to be replicated in all cluster
nodes which for significant state information can be quite time/resources
consuming.
\end_layout
\begin_layout Subsection
Lift garbage collection
\end_layout
\begin_layout Standard
As you have seen, Lift tailors Scala functions with client side artifacts
(XHTML input elements, Ajax requests etc.).
Naturally these functions are kept into the session state.
Also for every rendered page, a page ID is generated and functions bound
for these pages as asociated with this page ID.
In order to prevent accumulation of such mappings, Lift has a mechanism
of purging unused functions.
Basically the idea is
\end_layout
\begin_layout Enumerate
On client side, a script periodically sends to the server an Ajax request
impersonating a lift GC request.
\end_layout
\begin_layout Enumerate
On service side Lift updates the timestamps of the functions associated
with this page ID.
The functions older then
\family typewriter
LiftRules.unusedFunctionsLifeTime
\family default
(default value is 10 minutes) become eligible for garbage collection as
they are de-referenced from the current session.
The frequency of such Ajax requests is given by
\family typewriter
LiftRules.liftGCPollingInterval
\family default
.
By default it is set to 75 seconds.
\end_layout
\begin_layout Enumerate
Each Ajax request contains includes the page ID as new function may be bound
as a result of processing the Ajax request, dependin on the application
code.
Such function that are dynamically bound are automatically associated with
the same page ID.
\end_layout
\begin_layout Standard
You can of course turn off this garbage collection mechanism by setting
\family typewriter
LiftRules.enableLiftGC = false
\family default
typically in your Boot.
You can also fine tune the garbage collection mechanims to fit your application
needs, by changing the default LiftRules variables.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
LiftRules gabage collection variables
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
/**
\end_layout
\begin_layout Plain Layout
* By default lift uses a garbage-collection mechanism of removing
\end_layout
\begin_layout Plain Layout
* unused bound functions from LiftSesssion
\end_layout
\begin_layout Plain Layout
* Setting this to false will disable this mechanims and there will
\end_layout
\begin_layout Plain Layout
* be no Ajax polling request attempted.
\end_layout
\begin_layout Plain Layout
*/
\end_layout
\begin_layout Plain Layout
var enableLiftGC = true;
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
/**
\end_layout
\begin_layout Plain Layout
* If Lift garbage collection is enabled, functions that are not seen
\end_layout
\begin_layout Plain Layout
* in the page for this period of time (given in milliseonds) will be
\end_layout
\begin_layout Plain Layout
* discarded, hence eligible for garbage collection.
The default value
\end_layout
\begin_layout Plain Layout
* is 10 minutes.
\end_layout
\begin_layout Plain Layout
*/
\end_layout
\begin_layout Plain Layout
var unusedFunctionsLifeTime: Long = 10 minutes
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
/**
\end_layout
\begin_layout Plain Layout
* The polling interval for background Ajax requests to prevent
\end_layout
\begin_layout Plain Layout
* functions of being garbage collected.
\end_layout
\begin_layout Plain Layout
* Default value is set to 75 seconds.
\end_layout
\begin_layout Plain Layout
*/
\end_layout
\begin_layout Plain Layout
var liftGCPollingInterval: Long = 75 seconds
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
/**
\end_layout
\begin_layout Plain Layout
* The polling interval for background Ajax requests to prevent functions
\end_layout
\begin_layout Plain Layout
* of being garbage collected.
\end_layout
\begin_layout Plain Layout
* This will be applied if the Ajax request will fail.
Default value is
\end_layout
\begin_layout Plain Layout
* set to 15 seconds.
\end_layout
\begin_layout Plain Layout
*/
\end_layout
\begin_layout Plain Layout
var liftGCFailureRetryTimeout: Long = 15 seconds
\end_layout
\end_inset
\end_layout
\begin_layout Section
Miscellaneous Lift Features
\end_layout
\begin_layout Standard
In this section we will discuss various features that can prove helpful
in building rich Lift applications.
\end_layout
\begin_layout Subsection
Wrapping Lift's processing logic
\begin_inset CommandInset label
LatexCommand label
name "sub:Wrapping-Lift's-processing"
\end_inset
\end_layout
\begin_layout Standard
Lift provides the ability to allow user functions to be part of processing
lifecycle.
In these cases Lift allows you to provide your own functions and the actual
Lift's processing function is passed to your function.
Hence your own function is responsible of calling the actual Lift's processing
logic.
\end_layout
\begin_layout Standard
But let's see how exactly you can do this.
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
LoanWrapper example
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Boot {
\end_layout
\begin_layout Plain Layout
def boot {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
S.addAround(new LoanWrapper { // Y
\end_layout
\begin_layout Plain Layout
def apply[T](f: => T): T = {
\end_layout
\begin_layout Plain Layout
println("Y -> hello to the request!")
\end_layout
\begin_layout Plain Layout
val result = f // Let Lift do normal request processing.
\end_layout
\begin_layout Plain Layout
println("Y -> goodbye!")
\end_layout
\begin_layout Plain Layout
result
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
})
\end_layout
\begin_layout Plain Layout
S.addAround(new LoanWrapper { // X
\end_layout
\begin_layout Plain Layout
def apply[T](f: => T): T = {
\end_layout
\begin_layout Plain Layout
println("X -> hello to the request!")
\end_layout
\begin_layout Plain Layout
val result = f // Let Lift do normal request processing.
\end_layout
\begin_layout Plain Layout
println("X -> goodbye!")
\end_layout
\begin_layout Plain Layout
result
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
})
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The code looks pretty straight-forward in the sense that we add two
\family typewriter
LoanWrapper
\family default
instances to the
\family typewriter
S
\family default
object.
(Note that we're using the
\family typewriter
S
\family default
object not
\family typewriter
LiftRules
\family default
meaning that
\family typewriter
LoanWrapper
\family default
s are applicable only for stateful processing.
See
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Request/Response-Lifecycle"
\end_inset
for when exactly
\family typewriter
LoanWrapper
\family default
s are invoked.)
\end_layout
\begin_layout Standard
So let's see what happens when the above code processess a request from
a client.
You can think of the invocation sequence as
\family typewriter
X(Y(f))
\family default
where f is the Lift function that impersonates the core processing.
Therefore you'll see the following output in the console:
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
X -> hello to the request!
\end_layout
\begin_layout Plain Layout
Y -> hello to the request!
\end_layout
\begin_layout Plain Layout
<Lift's logic ...
whatever is printed here>
\end_layout
\begin_layout Plain Layout
Y -> goodbye!
\end_layout
\begin_layout Plain Layout
X -> goodbye!
\end_layout
\end_inset
\end_layout
\begin_layout Standard
This feature allows you use a resource before Lift does and release them
after Lift has finished processing the stateful request and before the
LiftResponse object is constructed.
\end_layout
\begin_layout Subsection
Passing Template Parameters to Snippets
\begin_inset Index
status open
\begin_layout Plain Layout
Parameters
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Snippet parameters
\end_layout
\end_inset
\end_layout
\begin_layout Standard
In addition to the standard attributes for snippets, outlined in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:snippet-tag"
\end_inset
, you can set your own attributes on the snippet element.
Attributes used in this manner are called
\begin_inset Quotes eld
\end_inset
parameters
\begin_inset Quotes erd
\end_inset
.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Snippet-parameter-template"
\end_inset
shows us setting a
\family typewriter
default
\family default
parameter on our
\family typewriter
Ledger.balance
\family default
snippet.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "lst:Snippet-parameter-template"
\end_inset
Defining a Snippet Parameter
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:Ledger.balance default="10">
\end_layout
\begin_layout Plain Layout
<ledger:balance/> as of <ledger:time />
\end_layout
\begin_layout Plain Layout
</lift:Ledger.balance>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The
\family typewriter
S.attr
\family default
function allows us to access all parameters defined on the snippet element
itself, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Snippet-parameter-code"
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "lst:Snippet-parameter-code"
\end_inset
Accessing a Snippet Parameter
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Ledger {
\end_layout
\begin_layout Plain Layout
def balance (content : NodeSeq ) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
val dflt = S.attr("default") openOr "0";
\end_layout
\begin_layout Plain Layout
bind ("ledger", content,
\end_layout
\begin_layout Plain Layout
"balance" -> Text(currentLegdger.formattedBalance),
\end_layout
\begin_layout Plain Layout
"time" -> Text((new java.util.Date).toString))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Computing Attributes with Snippets
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Attributes ! computing via snippet
\end_layout
\end_inset
\end_layout
\begin_layout Standard
You can use snippets to compute tag attributes, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-a-Snippet-compute-attr"
\end_inset
:
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-a-Snippet-compute-attr"
\end_inset
Using a Snippet to Compute an Attribute
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// In your page you can have
\end_layout
\begin_layout Plain Layout
<div lift:snippet="MyDivThing:calcDir"> ...
</div>
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
// Your snippet
\end_layout
\begin_layout Plain Layout
class MyDivThing {
\end_layout
\begin_layout Plain Layout
def calcDir = new UnprefixedAttribute("dir", "rtl", Null)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Processing Element Attributes
\begin_inset Index
status open
\begin_layout Plain Layout
Attributes ! retrieving from elements
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Now we have seen how we can pass xml parameters to snippets but what if
we want to pass parameters on the nodes that will be bound? For instance,
we may want to pass the am/pm information on the time element such as:
\end_layout
\begin_layout LyX-Code
\family typewriter
<ledger:time ampm=
\begin_inset Quotes erd
\end_inset
true
\begin_inset Quotes erd
\end_inset
/>
\end_layout
\begin_layout Standard
to control the time display format.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Element-attribute-retrieval"
\end_inset
shows how we can use the
\family typewriter
BindHelpers
\family default
object to retrieve the current element's attributes.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Retrieving Element Attributes with BindHelpers
\begin_inset CommandInset label
LatexCommand label
name "lst:Element-attribute-retrieval"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Ledger {
\end_layout
\begin_layout Plain Layout
def balance (content : NodeSeq ) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
val dflt = S.attr("default") openOr "0";
\end_layout
\begin_layout Plain Layout
bind ("ledger", content,
\end_layout
\begin_layout Plain Layout
"balance" -> Text(currentLegdger.formattedBalance),
\end_layout
\begin_layout Plain Layout
"time" -> {
\end_layout
\begin_layout Plain Layout
node: NodeSeq => println(BindHelpers.attr("ampm"));
\end_layout
\begin_layout Plain Layout
Text((new java.util.Date).toString))
\end_layout
\begin_layout Plain Layout
})
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Note Note
status open
\begin_layout Plain Layout
Is that
\begin_inset Quotes eld
\end_inset
node:NodeSeq
\begin_inset Quotes erd
\end_inset
necessary?
\end_layout
\end_inset
You can use the
\family typewriter
BindHelpers
\family default
object for obtaining information about node attributes.
This context is maintained internally using
\family typewriter
ThreadLocal
\family default
s and closures.
Note that the context is cleared after the bind method is executed.
In our example above for
\begin_inset Quotes eld
\end_inset
time
\begin_inset Quotes erd
\end_inset
node we are actually binding a function that takes the child nodes of the
\family typewriter
<ledger:time>
\family default
node.
When our function is called by Lift we can access the BindHelpers, such
ass the attributes of the current node.
The sequence
\family typewriter
<string> -> <right-hand-side-expression>
\family default
is turned into a BindParam object using implicit conversions.
It is important to note that BindParam.calcValue function is called in the
correct context so that BindHelpers can be safely used.
\end_layout
\begin_layout Section
Advanced S Object Features
\begin_inset CommandInset label
LatexCommand label
name "sub:Advanced-S-Object"
\end_inset
\end_layout
\begin_layout Standard
The
\family typewriter
S
\family default
, or Stateful, object is a very important part of Lift.
The S context is created when a client request is recieved that needs to
be handled as a stateful reuest.
Please see
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Request/Response-Lifecycle"
\end_inset
for more details on the state creation and handling.
The actual state information is kept inside the S object using ThreadLocal
\begin_inset Foot
status open
\begin_layout Plain Layout
java.lang.ThreadLocal
\end_layout
\end_inset
variables since
\family typewriter
S
\family default
is a singleton.
This means that if you have any code that is executed in the stateful context
you can safely use any
\family typewriter
S
\family default
object goodies, which include:
\end_layout
\begin_layout Subsection
Managing cookies
\end_layout
\begin_layout Standard
You can retrieve cookies from the request or set cookies to be sent in the
response.
Cookies are covered in section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Cookies"
\end_inset
.
\end_layout
\begin_layout Subsection
Localization and Internationalization
\end_layout
\begin_layout Standard
Localization (also called L10N) and Internationalization (also called I18N)
are very important aspects of many web applications that deal with different
languages.
These topics are covered in chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Internationalization"
\end_inset
.
\end_layout
\begin_layout Subsection
Managing the Timezone
\end_layout
\begin_layout Standard
The
\family typewriter
S.timeZone
\family default
function returns the current timezone as computed by the
\begin_inset Newline linebreak
\end_inset
\family typewriter
LiftRules.timeZoneCalculator
\family default
function.
By default, the
\family typewriter
LiftRules
\family default
method simply executes TimeZone.getDefault, but you can provide your own
\begin_inset Formula $Box[HttpServletRequest]\Rightarrow TimeZone$
\end_inset
partial function to define your own behavior.
Examples would include allowing users to choose their own timezone, or
to use geographic lookup of the user's IP address.
\end_layout
\begin_layout Subsection
Per-session DispatchPF functions
\begin_inset Note Note
status open
\begin_layout Plain Layout
Need an example of usage
\end_layout
\end_inset
\end_layout
\begin_layout Standard
You can set DispatchPF functions that operate in the context of a current
session.
Essentially you can bind DispatchPF functions with a given name.
Relevant functions are:
\end_layout
\begin_layout Itemize
\family typewriter
S.highLevelSessionDispatcher
\family default
- returns a
\family typewriter
List[LiftRules.DispatchPF]
\end_layout
\begin_layout Itemize
\family typewriter
S.highLevelSessionDispatchList
\family default
- returns a
\family typewriter
List[DispatchHolder]
\end_layout
\begin_layout Itemize
\family typewriter
S.addHighLevelSessionDispatcher
\family default
- maps a name with a given
\family typewriter
DispatchPF
\end_layout
\begin_layout Itemize
\family typewriter
S.removeHighLevelSessionDispatcher
\family default
- removes the
\family typewriter
DispatchPF
\family default
given its name
\end_layout
\begin_layout Itemize
\family typewriter
S.clearHighLevelSessionDispatcher
\family default
- removes all
\family typewriter
DispatchPF
\family default
associations
\end_layout
\begin_layout Subsection
Session re-writers
\begin_inset Note Note
status open
\begin_layout Plain Layout
Need an example of usage
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Session re-writers are per session functions that allow you to modify a
HTTP request (URI, query parameters etc.) before the request is actually
processed.
This is similar with LiftRules.rewrite variable but you can apply rewriters
per a given session.
Hence you can have different rewrites in diferent contexts.
The relevant functions are:
\end_layout
\begin_layout Itemize
\family typewriter
S.sessionRewriter
\family default
- returns a
\family typewriter
List[RewriteHolder]
\end_layout
\begin_layout Itemize
\family typewriter
S.addSessionRewriter
\family default
- maps a
\family typewriter
LiftRules.RewritePF
\family default
with a given name
\end_layout
\begin_layout Itemize
\family typewriter
S.removeSessionRewriter
\family default
- removes a rewriter by a name
\end_layout
\begin_layout Itemize
\family typewriter
S.clearSessionRewriter
\family default
- remove all session rewriters.
\end_layout
\begin_layout Subsection
Access to HTTP headers
\end_layout
\begin_layout Standard
Accessing HTTP header parameters from the request and adding HTTP header
parameters to the HTTP response represent very common operations.
You can easily perform these operations using the following functions:
\end_layout
\begin_layout Itemize
\family typewriter
S.getHeaders
\family default
- returns a
\family typewriter
List[(String, String)]
\family default
containing all HTTP headers grouped by name and value pair
\end_layout
\begin_layout Itemize
\family typewriter
S.setHeader
\family default
- sets a HTTP header parameter by specifying the name and value pair
\end_layout
\begin_layout Subsection
Manage the document type
\end_layout
\begin_layout Standard
You can also read and write the XML document type set for the current response.
You can use the following functions:
\end_layout
\begin_layout Itemize
\family typewriter
S.getDocType
\family default
- returns the doc type that was set forthe current response
\end_layout
\begin_layout Itemize
\family typewriter
S.setDocType
\family default
- sets a document type for the curent response object.
\end_layout
\begin_layout Subsection
Other functions
\end_layout
\begin_layout Itemize
Access to the raw HttpServletRequest and HttpSession if you really need
it.
\end_layout
\begin_layout Itemize
Managing the function map.
The function map generates an association between a String and a function.
This string represents a query parameter that when Lift receives upon a
HTTP request, it will execute your function.
Normally these names are auto-generated by Lift but you can also provide
you own name.
Please see
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Lift-Function-Mapping"
\end_inset
for more details.
\end_layout
\begin_layout Itemize
Managing wrappers - see
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Wrapping-Lift's-processing"
\end_inset
\end_layout
\begin_layout Itemize
Managing notices - see
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Notices/Warnings/Errors-messages"
\end_inset
\end_layout
\begin_layout Itemize
Managing HTTP redirects - see S.redirectTo functions and
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:LiftResponse-in-Detail"
\end_inset
\end_layout
\begin_layout Itemize
Using XML attibutes of a snippet - see
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Additional-Snippet-Features"
\end_inset
\end_layout
\begin_layout Section
ResourceServer
\begin_inset CommandInset label
LatexCommand label
name "sub:ResourceServer"
\end_inset
\end_layout
\begin_layout Standard
ResourceServer is a Lift component that manages the serving of resources
like JS, CSS etc.
Well the web container can do that right? ...
still container does not serve these resources if they are inside jar files.
The default URI path for serving such resources is given by
\family typewriter
LiftRules.resourceServerPath
\family default
variable which by default it is set to
\begin_inset Quotes eld
\end_inset
classpath
\begin_inset Quotes erd
\end_inset
.
The folder location where the resource is looked up inside jar files is
given by
\family typewriter
ResourceServer.baseResourceLocation
\family default
variable which by default it is set to
\begin_inset Quotes eld
\end_inset
toserve
\begin_inset Quotes erd
\end_inset
.
Let's assume the following folder structure inside you Lift project:
\end_layout
\begin_layout Standard
\family typewriter
lift-proj/src/main/resources/toserve/css/mystyle.css
\end_layout
\begin_layout Standard
Maven will create the toserver folder in the jar/war file generated.
Then in your web page you add something like:
\end_layout
\begin_layout Standard
\family typewriter
<link rel="stylesheet" href="/classpath/css/mystyle.css" type="text/css"/>
\end_layout
\begin_layout Standard
Because the first URI part matches with
\family typewriter
LiftRules.resourceServerPath
\family default
Lift will tell ResouceServer to load this resource from 'toserve' folder.
But it will fail.
There is one thing left to do.
We need to tell ResouceServer to allow the loading of mystyle.css resource.
We can do this from Boot by calling:
\end_layout
\begin_layout Standard
\family typewriter
ResourceServer.allow {
\end_layout
\begin_layout Standard
\family typewriter
case "css" :: _ => true
\end_layout
\begin_layout Standard
\family typewriter
}
\end_layout
\begin_layout Standard
We basically told Lift here to allow any resource found in css folder under
toserve.
Note that toserver comes from
\family typewriter
ResourceServer.baseResourceLocation
\family default
which can be changed.
\end_layout
\begin_layout Section
HTTP Authentication
\begin_inset CommandInset label
LatexCommand label
name "sub:HTTP-Authentication"
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
HTTP ! authentication
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Authentication
\end_layout
\end_inset
\end_layout
\begin_layout Standard
HTTP authentication is described by RFC 2617
\begin_inset Foot
status open
\begin_layout Plain Layout
\begin_inset Flex URL
status open
\begin_layout Plain Layout
http://www.isi.edu/in-notes/rfc2617.txt
\end_layout
\end_inset
\end_layout
\end_inset
.
It describes the means of protecting server resources and allowing access
only to authorized entities.
As you may know, any J(2)EE web container provides HTTP authentication
support using JAAS
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
Java Authentication and Authorization Service.
More information can be found at
\begin_inset Flex URL
status open
\begin_layout Plain Layout
http://java.sun.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html
\end_layout
\end_inset
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Authentication ! JAAS
\end_layout
\end_inset
.
However, this approach has limitations.
For example, if you provide your own
\family typewriter
LoginModule
\family default
or
\family typewriter
CallbackHandler
\family default
implementation this will not be loaded by the web application classloader
but instead by the container classloader (at least in tomcat).
This can lead to dependency loading issues since the web application classloade
r sits below the container's classloader in the delegation chain.
Lift, however, provides supports for both basic and digest authentications
via a simplified, scala-oriented API that you can use directly.
This API provides not only direct support for the HTTP authentication mechanism
s, but also a path and role based authorization mechanism.
The following sections show how we use basic authentication to protect
our REST API (Chapter
\begin_inset CommandInset ref
LatexCommand vref
reference "cha:Web-Services"
\end_inset
).
\end_layout
\begin_layout Subsection
Determining which Resources to Protect
\end_layout
\begin_layout Standard
The first thing we need to do is tell Lift which resources are protected
by authentication.
This is done by configuring
\family typewriter
LiftRules.httpAuthProtectedResources
\begin_inset Index
status open
\begin_layout Plain Layout
httpAuthProtectedResources
\end_layout
\end_inset
\family default
with one or more
\family typewriter
PartialFunction[Req,Box[Role]]
\family default
\begin_inset Foot
status open
\begin_layout Plain Layout
\family typewriter
net.liftweb.http.auth.Role
\end_layout
\end_inset
to match on the request.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:HTTP-auth-define-resources"
\end_inset
shows the PartialFunction defined in our
\family typewriter
DispatchRestAPI
\family default
object (Section
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:REST-Custom-Dispatch"
\end_inset
) used to protect our REST API from unauthorized access.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Defining Protected Resources
\begin_inset CommandInset label
LatexCommand label
name "lst:HTTP-auth-define-resources"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// We explicitly protect GET and PUT requests in our REST API
\end_layout
\begin_layout Plain Layout
import net.liftweb.http.auth.AuthRole
\end_layout
\begin_layout Plain Layout
def protection : LiftRules.HttpAuthProtectedResourcePF = {
\end_layout
\begin_layout Plain Layout
case Req(List("api", "account", accountId), _, PutRequest) =>
\end_layout
\begin_layout Plain Layout
Full(AuthRole("editAcct:" + accountId))
\end_layout
\begin_layout Plain Layout
case Req(List("api", "account", accountId), _, GetRequest) =>
\end_layout
\begin_layout Plain Layout
Full(AuthRole("viewAcct:" + accountId))
\end_layout
\begin_layout Plain Layout
// If the account is public, don't enforce auth
\end_layout
\begin_layout Plain Layout
case Req(List("api", "expense", Expense(e, true)), _, GetRequest) =>
Empty
\end_layout
\begin_layout Plain Layout
case Req(List("api", "expense", Expense(e, _)), _, GetRequest) =>
\end_layout
\begin_layout Plain Layout
Full(AuthRole("viewAcct:" + e.account.obj.open_!.id))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The
\family typewriter
PartialFunction
\family default
matches on the
\family typewriter
Req
\family default
and can either return an
\family typewriter
Empty
\family default
, indicating that the given request does not require authentication, or
a
\family typewriter
Full[Role]
\family default
, that indicates which R
\family typewriter
ole
\family default
a user requires to be authorized to access the given resource.
One important thing to remember is that HTTP authentication and SiteMap
access control (Section
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:SiteMap-Access-Control"
\end_inset
) are synergistic, so make sure that you configure both properly.
We will discuss
\family typewriter
Role
\family default
s further in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Role-Hierarchies"
\end_inset
, but for now you can simply consider them as
\family typewriter
String
\family default
attributes associated with the current session.
Once we've defined which resources are to be protected, we need to hook
our
\family typewriter
PartialFunction
\family default
into
\family typewriter
LiftRules
\family default
in the
\family typewriter
Boot.boot
\family default
method, shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Hooking-Resource-Protection"
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Hooking Resource Protection
\begin_inset CommandInset label
LatexCommand label
name "lst:Hooking-Resource-Protection"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// Hook in our REST API auth
\end_layout
\begin_layout Plain Layout
LiftRules.httpAuthProtectedResource.append(DispatchRestAPI.protection)
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Providing the Authentication Hook
\end_layout
\begin_layout Standard
After we've defined what resources we want to protect, we need to configure
the
\family typewriter
LiftRules.authentication
\family default
function to perform the actual authentication.
Lift supports both HTTP Basic and Digest authentication schemes, which
we'll cover in the next two sections.
\end_layout
\begin_layout Standard
\begin_inset VSpace defskip
\end_inset
\end_layout
\begin_layout Standard
\align center
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "75col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
Note that in these examples we use stateful dispath (Section
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Custom-dispatch-func"
\end_inset
) since the
\family typewriter
User.logUserIn
\family default
method utilizes a backing
\family typewriter
SessionVar
\family default
.
If you use stateless dispatch you will need to provide your own
\family typewriter
RequestVar
\family default
s to store the current user and roles.
\end_layout
\end_inset
\end_layout
\begin_layout Subsubsection
HTTP Basic Authentication
\end_layout
\begin_layout Standard
HTTP Basic authentication is provided by the
\family typewriter
net.liftweb.http.auth.HttpBasicAuthentication
\family default
implementation class, constructed using the authentication realm name as
well as a
\family typewriter
PartialFunction[(String, String, Req), Boolean]
\family default
that actually does the authentication.
The tuple passed to the
\family typewriter
PartialFunction
\family default
consists of the attempted username password, and the request object (
\family typewriter
Req
\family default
).
It's your responsibility to return
\family typewriter
true
\family default
or
\family typewriter
false
\family default
to indicate whether the provided credentials succeed.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Performing-Basic-Authentication"
\end_inset
shows the code in
\family typewriter
Boot.boot
\family default
that PocketChange uses to perform authentication based on the user's email
address and password.
Note that when authentication succeeds for a given user not only do we
return true, but we set the user as logged in (via
\family typewriter
User.logUserIn
\family default
) and we compile a set of all of the
\family typewriter
Role
\family default
s that the user so that Lift knows which protected resources the user may
access.
The
\family typewriter
net.liftweb.http.auth.userRoles
\family default
\family typewriter
RequestVar
\family default
is a built-in construct in Lift that the authentication backend uses for
bookkeeping.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Performing Basic Authentication
\begin_inset CommandInset label
LatexCommand label
name "lst:Performing-Basic-Authentication"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import net.liftweb.http.auth.{AuthRole,HttpBasicAuthentication,userRoles}
\end_layout
\begin_layout Plain Layout
LiftRules.authentication = HttpBasicAuthentication("PocketChange") {
\end_layout
\begin_layout Plain Layout
case (userEmail, userPass, _) => {
\end_layout
\begin_layout Plain Layout
logger.debug("Authenticating: " + userEmail)
\end_layout
\begin_layout Plain Layout
User.find(By(User.email, userEmail)).map { user =>
\end_layout
\begin_layout Plain Layout
if (user.password.match_?(userPass)) {
\end_layout
\begin_layout Plain Layout
logger.debug("Auth succeeded for " + userEmail)
\end_layout
\begin_layout Plain Layout
User.logUserIn(user)
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
// Compute all of the user roles
\end_layout
\begin_layout Plain Layout
userRoles(user.editable.map(acct => AuthRole("editAcct:" + acct.id))
++
\end_layout
\begin_layout Plain Layout
user.allAccounts.map(acct => AuthRole("viewAcct:" + acct.id)))
\end_layout
\begin_layout Plain Layout
true
\end_layout
\begin_layout Plain Layout
} else {
\end_layout
\begin_layout Plain Layout
logger.warn("Auth failed for " + userEmail)
\end_layout
\begin_layout Plain Layout
false
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
} openOr false
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsubsection
HTTP Digest Authentication
\end_layout
\begin_layout Standard
HTTP Digest authentication is provided by the
\family typewriter
net.liftweb.http.auth.HttpDigestAuthentication
\family default
implementation class.
Like Basic authentication, the
\family typewriter
HttpDigestAuthentication
\family default
instance is constructed with a realm name and a
\family typewriter
PartialFunction
\family default
, but in this case the
\family typewriter
PartialFunction
\family default
uses a tuple of
\family typewriter
(String,Req,(String)
\begin_inset Formula $\Rightarrow$
\end_inset
Boolean)
\family default
.
The first parameter is still the username, and the second parameter is
the request instance, but the third parameter is a function that will compute
and compare the digest for authentication based on a
\emph on
plaintext
\emph default
password.
This means that if we want to use Digest authentication, we need to be
able to retrieve a plaintext password for the user from the database somehow.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:HTTP-Digest-Authentication"
\end_inset
shows how we could do this in PocketChange if we modified the
\family typewriter
User.password
\family default
field to simply be a
\family typewriter
MappedString
\family default
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Performing Digest Authentication
\begin_inset CommandInset label
LatexCommand label
name "lst:HTTP-Digest-Authentication"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import net.liftweb.http.auth.{AuthRole,HttpBasicAuthentication,userRoles}
\end_layout
\begin_layout Plain Layout
LiftRules.authentication = HttpBasicAuthentication("PocketChange") {
\end_layout
\begin_layout Plain Layout
case (userEmail, _, authenticates) => {
\end_layout
\begin_layout Plain Layout
logger.debug("Authenticating: " + userEmail)
\end_layout
\begin_layout Plain Layout
User.find(By(User.email, userEmail)).map { user =>
\end_layout
\begin_layout Plain Layout
if (authenticates(user.password.is)) {
\end_layout
\begin_layout Plain Layout
logger.debug("Auth succeeded for " + userEmail)
\end_layout
\begin_layout Plain Layout
User.logUserIn(user)
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
// Compute all of the user roles
\end_layout
\begin_layout Plain Layout
userRoles(user.editable.map(acct => AuthRole("editAcct:" + acct.id))
++
\end_layout
\begin_layout Plain Layout
user.allAccounts.map(acct => AuthRole("viewAcct:" + acct.id)))
\end_layout
\begin_layout Plain Layout
true
\end_layout
\begin_layout Plain Layout
} else {
\end_layout
\begin_layout Plain Layout
logger.warn("Auth failed for " + userEmail)
\end_layout
\begin_layout Plain Layout
false
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
} openOr false
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Another important factor with Digest authentication is that it uses nonces
\begin_inset Foot
status open
\begin_layout Plain Layout
\begin_inset Flex URL
status open
\begin_layout Plain Layout
http://en.wikipedia.org/wiki/Cryptographic_nonce
\end_layout
\end_inset
\end_layout
\end_inset
for authenticating the client, and the nonces have a limited lifetime.
The default nonce lifetime is 30 seconds, but you can configure this by
overriding the
\family typewriter
HttpDigestAuthentication.nonceValidityPeriod
\family default
method.
\end_layout
\begin_layout Subsection
Role Hierarchies
\begin_inset CommandInset label
LatexCommand label
name "sub:Role-Hierarchies"
\end_inset
\end_layout
\begin_layout Standard
So far we've discussed
\family typewriter
Role
\family default
s as essentially flat constructs.
A
\family typewriter
Role
\family default
, however, is an n-ary tree structure, meaning that when we assign a
\family typewriter
Role
\family default
to a protected resource we can actually provide a hierarchy.
Figure
\begin_inset CommandInset ref
LatexCommand ref
reference "fig:Roles-hierarchy-example"
\end_inset
shows an example of one such hierarchy.
In this example, the Admin is the
\begin_inset Quotes eld
\end_inset
superuser
\begin_inset Quotes erd
\end_inset
role for admins, and can do what any sub-role can do and more.
The Site-Admin can monitor the application, the User-Admin can manage users,
and then we specify a set of location-specific roles: the Romania-Admin
that can manage users from Romania, US-Admin that can manage users from
US and UK-Admin that can only manage users from UK.
With this hierarchy a User-Admin can manage users from anywhere but a Site-Admi
n can not manage any users.
A Romania-Admin can't monitor the site, nor it can manage the US or UK
users.
\end_layout
\begin_layout Standard
\begin_inset Float figure
placement H
wide false
sideways false
status open
\begin_layout Plain Layout
\align center
\begin_inset Graphics
filename images/roles.png
lyxscale 10
width 4in
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Roles hierarchy example
\begin_inset CommandInset label
LatexCommand label
name "fig:Roles-hierarchy-example"
\end_inset
\end_layout
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Given this Role hierarchy, Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-Role-Hierarchies"
\end_inset
shows how we can implement this in our code by creating our Role hierarchy
and then using the Role.getRoleByName method to locate the proper Role when
we perform authentication.
In this example we're restricting access to the
\family typewriter
/users/ro
\family default
path to only users with the
\begin_inset Quotes eld
\end_inset
Romania-Admin
\begin_inset Quotes erd
\end_inset
role.
However, our fictional
\begin_inset Quotes eld
\end_inset
John
\begin_inset Quotes erd
\end_inset
user is assigned the
\begin_inset Quotes eld
\end_inset
User-Admin
\begin_inset Quotes erd
\end_inset
role, so he will be able to access that path.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using Role Hierarchies
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-Role-Hierarchies"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import auth._
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Boot {
\end_layout
\begin_layout Plain Layout
def boot = {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
val roles =
\end_layout
\begin_layout Plain Layout
AuthRole("Admin",
\end_layout
\begin_layout Plain Layout
AuthRole("Site-Admin"),
\end_layout
\begin_layout Plain Layout
AuthRole("User-Admin",
\end_layout
\begin_layout Plain Layout
AuthRole("Romania-Admin"),
\end_layout
\begin_layout Plain Layout
AuthRole("US-Admin"),
\end_layout
\begin_layout Plain Layout
AuthRole("UK-Admin")
\end_layout
\begin_layout Plain Layout
)
\end_layout
\begin_layout Plain Layout
)
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
LiftRules.protectedResource.append {
\end_layout
\begin_layout Plain Layout
case (ParsePath("users" :: "ro" :: _, _, _, _)) =>
\end_layout
\begin_layout Plain Layout
roles.getRoleByName("Romania-Admin")
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
LiftRules.authentication = HttpBasicAuthentication("lift") {
\end_layout
\begin_layout Plain Layout
case ("John", "12test34", req) =>
\end_layout
\begin_layout Plain Layout
println("John is authenticated !")
\end_layout
\begin_layout Plain Layout
userRoles(AuthRole("User-Admin"))
\end_layout
\begin_layout Plain Layout
true
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\end_body
\end_document