This chapter looks at interacting with other systems from within Lift, such as sending email, calling URLs, or scheduling tasks.
Many of the recipes in this chapter have code examples in a project at https://github.com/LiftCookbook/cookbook_around.
Use the Mailer
:
import
net.liftweb.util.Mailer
import
net.liftweb.util.Mailer._
Mailer
.
sendMail
(
From
(
"you@example.org"
),
Subject
(
"Hello"
),
To
(
"other@example.org"
),
PlainMailBodyType
(
"Hello from Lift"
)
)
Mailer
sends the message asynchronously, meaning sendMail
will
return immediately, so you don’t have to worry about the time costs of
negotiating with an SMTP server. However, there’s also a blockingSendMail
method if you do need to wait.
By default, the SMTP server used will be localhost. You can change
this by setting the mail.smtp.host
property.
For example, edit src/mail/resources/props/default.props and add the line:
.
smtp
.
host
=
smtp
.
example
.
org
The signature of sendMail
requires a From
, Subject
, and then any
number of MailTypes
:
-
To
,CC
, andBCC
- The recipient email address
-
ReplyTo
- The address that mail clients should use for replies
-
MessageHeader
- Key/value pairs to include as headers in the message
-
PlainMailBodyType
- A plain-text email sent with UTF-8 encoding
-
PlainPlusBodyType
- A plain-text email, where you specify the encoding
-
XHTMLMailBodyType
- For HTML email (SMTP Authentication)
-
XHTMLPlusImages
- For attachments (Sending Email with Attachments)
In the previous example, we added two types: PlainMailBodyType
and To
. Adding more is as you’d expect:
Mailer
.
sendMail
(
From
(
"you@example.org"
),
Subject
(
"Hello"
),
To
(
"other@example.org"
),
To
(
"someone@example.org"
),
MessageHeader
(
"X-Ignore-This"
,
"true"
),
PlainMailBodyType
(
"Hello from Lift"
)
)
The address-like MailTypes
(To
, CC
, BCC
, ReplyTo
) can be given an optional “personal name”:
From
(
"you@example.org"
,
Full
(
"Example Corporation"
))
This would appear in your mailbox as:
From: Example Corporation <you@example.org>
The default character set is UTF-8. If you need to change this, replace
the use of PlainMailBodyType
with
PlainPlusBodyType("Hello from Lift", "ISO8859_1")
.
Sending Email with Attachments describes email with attachments.
For HTML email, see SMTP Authentication.
You don’t want email sent when developing your Lift application locally, but you do want to see what would have been sent.
Assign a logging function to Mailer.devModeSend
in Boot.scala:
import
net.liftweb.util.Mailer._
import
javax.mail.internet.
{
MimeMessage
,
MimeMultipart
}
Mailer
.
devModeSend
.
default
.
set
(
(
m
:
MimeMessage
)
=>
logger
.
info
(
"Would have sent: "
+
m
.
getContent
)
)
When you send an email with Mailer
, no SMTP server will be contacted, and
instead, you’ll see output to your log:
Would have sent: Hello from Lift
The key part of this recipe is setting a
MimeMessage => Unit
function on Mailer.devModeSend
. We happen to be logging, but you can use this function to handle the email any way you want.
The Lift Mailer
allows you to control how email is sent at each run mode: by default, email is sent for devModeSend
, profileModeSend
, pilotModeSend
, stagingModeSend
, and productionModeSend
; whereas, by default, testModeSend
only logs that a message would have been sent.
The testModeSend
logs a reference to the MimeMessage
, meaning your log
would show a message like:
Sending javax.mail.internet.MimeMessage@4a91a883
This recipe has changed the behaviour of Mailer
when your Lift
application is in developer mode (which it is by default). We’re logging
just the body part of the message.
Java Mail doesn’t include a utility to display all the parts of an email, so if you want more information, you’ll need to roll your own function. For example:
def
display
(
m
:
MimeMessage
)
:
String
=
{
val
nl
=
System
.
getProperty
(
"line.separator"
)
val
from
=
"From: "
+
m
.
getFrom
.
map
(
_
.
toString
).
mkString
(
","
)
val
subj
=
"Subject: "
+
m
.
getSubject
def
parts
(
mm
:
MimeMultipart
)
=
(
0
until
mm
.
getCount
).
map
(
mm
.
getBodyPart
)
val
body
=
m
.
getContent
match
{
case
mm
:
MimeMultipart
=>
val
bodyParts
=
for
(
part
<-
parts
(
mm
))
yield
part
.
getContent
.
toString
bodyParts
.
mkString
(
nl
)
case
otherwise
=>
otherwise
.
toString
}
val
to
=
for
{
rt
<-
List
(
RecipientType
.
TO
,
RecipientType
.
CC
,
RecipientType
.
BCC
)
address
<-
Option
(
m
.
getRecipients
(
rt
))
getOrElse
Array
()
}
yield
rt
.
toString
+
": "
+
address
.
toString
List
(
from
,
to
.
mkString
(
nl
),
subj
,
body
)
mkString
nl
}
Mailer
.
devModeSend
.
default
.
set
(
(
m
:
MimeMessage
)
=>
logger
.
info
(
"Would have sent: "
+
display
(
m
))
)
This would produce output of the form:
Would have sent: From: you@example.org To: other@example.org To: someone@example.org Subject: Hello Hello from Lift
This example display
function is long but mostly straightforward. The body
value handles multipart messages by extracting each body part. This is triggered when sending more structured emails, such as the HTML emails described in SMTP Authentication.
If you want to debug the mail system while it’s actually sending the email, enable the Java Mail debug mode. In default.props add:
mail.debug
=
true
This produces low-level output from the Java Mail system when email is sent:
DEBUG: JavaMail version 1.4.4 DEBUG: successfully loaded resource: /META-INF/javamail.default.providers DEBUG SMTP: useEhlo true, useAuth false DEBUG SMTP: trying to connect to host "localhost", port 25, isSSL false ...
Run modes are described on the Lift wiki.
Give Mailer
a NodeSeq
containing your HTML message:
import
net.liftweb.util.Mailer
import
net.liftweb.util.Mailer._
val
msg
=
<
html
>
<
head
>
<
title
>
Hello
</
title
>
</
head
>
<
body
>
<
h1
>
Hello
</
h1
>
</
body
>
</
html
>
Mailer
.
sendMail
(
From
(
"me@example.org"
),
Subject
(
"Hello"
),
To
(
"you@example.org"
),
msg
)
An implicit converts the NodeSeq
into an XHTMLMailBodyType
. This
ensures the mime type of the email is text/html
. Despite the name of
“XHTML,” the message is converted for transmission using
HTML5 semantics.
The character encoding for HTML email, UTF-8, can be changed by setting
mail.charset
in your Lift properties file.
If you want to set both the text and HTML version of a message, supply each body wrapped in the appropriate BodyType
class:
val
html
=
<
html
>
<
head
>
<
title
>
Hello
</
title
>
</
head
>
<
body
>
<
h1
>
Hello
!</
h1
>
</
body
>
</
html
>
var
text
=
"Hello!"
Mailer
.
sendMail
(
From
(
"me@example.org"
),
Subject
(
"Hello"
),
To
(
"you@example.org"
),
PlainMailBodyType
(
text
),
XHTMLMailBodyType
(
html
)
)
This message would be sent as a multipart/alternative
:
Content-Type: multipart/alternative; boundary="----=_Part_1_1197390963.1360226660982" Date: Thu, 07 Feb 2013 02:44:22 -0600 (CST) ------=_Part_1_1197390963.1360226660982 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit Hello! ------=_Part_1_1197390963.1360226660982 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit <html> <head> <title>Hello</title> </head> <body> <h1>Hello!</h1> </body> </html> ------=_Part_1_1197390963.1360226660982--
When receiving a message with this content, it is up to the mail client to decide which version to show (text or HTML).
For sending with attachments, see Sending Email with Attachments.
Set the Mailer.authenticator
in Boot
with the credentials for your
SMTP server, and enable the mail.smtp.auth
flag in your Lift properties
file.
Modify Boot.scala to include:
import
net.liftweb.util.
{
Props
,
Mailer
}
import
javax.mail.
{
Authenticator
,
PasswordAuthentication
}
Mailer
.
authenticator
=
for
{
user
<-
Props
.
get
(
"mail.user"
)
pass
<-
Props
.
get
(
"mail.password"
)
}
yield
new
Authenticator
{
override
def
getPasswordAuthentication
=
new
PasswordAuthentication
(
user
,
pass
)
}
In this example, we expect the username and password to come from Lift properties, so we need to modify src/main/resources/props/default.props to include them:
.
smtp
.
auth
=
true
.
user
=
me
@example
.
org
.
password
=
correct
horse
battery
staple
.
smtp
.
host
=
smtp
.
sendgrid
.
net
When you send email, the credentials in default.props will be used to authenticate with the SMTP server.
We’ve used Lift properties as a way to configure SMTP authentication. This has the benefit of allowing us to enable authentication for just some run modes. For example, if our default.props did not contain authentication settings, but our production.default.props did, then no authentication would happen in development mode, ensuring we can’t accidentally send email outside of a production environment.
You don’t have to use a properties file for this: the Lift Mailer
also supports JNDI, or you could look up a username and password some other way and set Mailer.authenticator
when you have the values.
However, some mail services such as SendGrid do require mail.smtp.auth=true
to be set, and that should go into your Lift properties file or set as a JVM argument: -Dmail.smtp.auth=true
.
As well as mail.smtp.auth
, there are a range of settings to control the Java Mail API. Examples include controlling port numbers and timeouts.
Use the Mailer
XHTMLPlusImages
to package a message with attachments.
Suppose we want to construct a CSV file and send it via email:
val
content
=
"Planet,Discoverer\r\n"
+
"HR 8799 c, Marois et al\r\n"
+
"Kepler-22b, Kepler Science Team\r\n"
case
class
CSVFile
(
bytes
:
Array
[
Byte
],
filename
:
String
=
"file.csv"
,
mime
:
String
=
"text/csv; charset=utf8; header=present"
)
val
attach
=
CSVFile
(
content
.
mkString
.
getBytes
(
"utf8"
))
val
body
=
<
p
>
Please
research
the
enclosed
.</
p
>
val
msg
=
XHTMLPlusImages
(
body
,
PlusImageHolder
(
attach
.
filename
,
attach
.
mime
,
attach
.
bytes
))
Mailer
.
sendMail
(
From
(
"me@example.org"
,
Subject
(
"Planets"
),
To
(
"you@example.org"
),
msg
)
What’s happening here is that our message is an XHTMLPlusImages
instance, which accepts
a body message and attachment. The attachment, the PlusImageHolder
, is an Array[Byte]
, mime type, and a filename.
XHTMLPlusImages
can also accept more than one PlusImageHolder
if you
have more than one file to attach. Although the name PlusImageHolder
may suggest it is for attachment images, you can attach any kind of data as an Array[Byte]
with an appropriate mime type.
By default, the attachment is sent with an inline
disposition. This controls the Content-Disposition
header in the message, and inline
means the content is intended for display automatically when the message is shown. The alternative is attachment
, and this can be indicated with an optional final parameter to PlusImageHolder
:
PlusImageHolder
(
attach
.
filename
,
attach
.
mime
,
attach
.
bytes
,
attachment
=
true
)
In reality, the mail client will display the message how it wants to, but this extra parameter may give you a little more control.
To attach a premade file, you can use LiftRules.loadResource
to fetch content from the classpath. If our project contained a file called Kepler-22b_System_Diagram.jpg in the src/main/resources/ folder, we could load and attach it like this:
val
filename
=
"Kepler-22b_System_Diagram.jpg"
val
msg
=
for
(
bytes
<-
LiftRules
.
loadResource
(
"/"
+
filename
)
)
yield
XHTMLPlusImages
(
<
p
>
Please
research
this
planet
.</
p
>,
PlusImageHolder
(
filename
,
"image/jpg"
,
bytes
)
)
msg
match
{
case
Full
(
m
)
=>
Mailer
.
sendMail
(
From
(
"me@example.org"
),
Subject
(
"Planet attachment"
),
To
(
"you@example.org"
),
m
)
case
_
=>
logger
.
error
(
"Planet file not found"
)
}
As the content of src/main/resources is included on the classpath, we pass the filename to loadResource
with a leading /
character so the file can be found at the right place on the classpath.
The loadResource
returns a Box[Array[Byte]]
as we have no guarantee the file will exist. We map this to a Box[XHTMLPlusImages]
and match on that result to either send the email or log that the file wasn’t found.
Messages are sent using the multipart/related
mime heading, with an inline
disposition. Lift ticket #1197 links to a discussion regarding multipart/mixed
that may be preferable for working around issues with Microsoft Exchange.
RFC 2183 describes the Content-Disposition
header.
Use net.liftweb.util.Schedule
:
import
net.liftweb.util.Schedule
import
net.liftweb.util.Helpers._
Schedule
(()
=>
println
(
"doing it"
),
30
seconds
)
This would cause “doing it” to be printed on the console 30 seconds from now.
The signature for Schedule
used previously expects a function of type () => Unit
, which is the thing we want to happen in the future, and a TimeSpan
from Lift’s TimeHelpers
, which is when we want it to happen. The 30 seconds
value gives us a TimeSpan
via the Helpers._
import, but there’s a variation called perform
that accepts a Long
millisecond value if you prefer that:
Schedule
.
perform
(()
=>
println
(
"doing it"
),
30
*
1000L
)
Behind the scenes, Lift is making use of the ScheduledExecutorService
from java.util.concurrent
and, as such, returns a ScheduledFuture[Unit]
. You can use this future to cancel
the operation before it runs.
It may be a surprise to find that you can call Schedule
with just a function as an argument, and not a delay value. This version runs the function immediately, but on a worker thread. This is a convenient way to asynchronously run other tasks without going to the trouble of creating an actor for the purpose.
There is also a Schedule.schedule
method that will send an
actor a specified message after a given delay. This takes a TimeSpan
delay, but again there’s also a Schedule.perform
version that accepts a Long
as a delay.
Run Tasks Periodically includes an example of scheduling with actors.
ScheduledFuture
is documented via the Java Doc for Future
. If you’re building complex, low-level, cancellable concurrency functions, it’s advisable to have a copy of Java Concurrency in Practice close by (written by Goetz, et al., Addison-Wesley Professional).
Use net.liftweb.util.Schedule
ensuring that you call schedule
again
during your task to reschedule it. For example, using an actor:
import
net.liftweb.util.Schedule
import
net.liftweb.actor.LiftActor
import
net.liftweb.util.Helpers._
object
MyScheduledTask
extends
LiftActor
{
case
class
DoIt
()
case
class
Stop
()
private
var
stopped
=
false
def
messageHandler
=
{
case
DoIt
if
!
stopped
=>
Schedule
.
schedule
(
this
,
DoIt
,
10
minutes
)
// ... do useful work here
case
Stop
=>
stopped
=
true
}
}
The example creates a LiftActor
for the work to be done. On receipt of
a DoIt
message, the actor reschedules itself before doing whatever
useful work needs to be done. In this way, the actor will be called
every 10 minutes.
The Schedule.schedule
call is ensuring that this
actor is sent the
DoIt
message after 10 minutes.
To start this process off, possibly in Boot.scala, just send the
DoIt
message to the actor:
MyScheduledTask
!
MyScheduledTask
.
DoIt
To ensure the process stops correctly when Lift shuts down, we register
a shutdown hook in Boot.scala to send the Stop
message to prevent
future reschedules:
LiftRules
.
unloadHooks
.
append
(
()
=>
MyScheduledTask
!
MyScheduledTask
.
Stop
)
Without the Stop
message, the actor would continue to be rescheduled
until the JVM exits. This may be acceptable, but note that during
development with SBT, without the Stop
message, you will continue to
schedule tasks after issuing the container:stop
command.
Schedule returns a ScheduledFuture[Unit]
from the Java concurrency
library, which allows you to cancel
the activity.
Use Dispatch, “a library for asynchronous HTTP interaction.”
Before you start, include Dispatch dependency in build.sbt:
libraryDependencies
+=
"net.databinder.dispatch"
%%
"dispatch-core"
%
"0.9.5"
Using the example from the Dispatch documentation, we can make an HTTP request to try to determine the country from the service at http://www.hostip.info/use.html:
import
dispatch._
val
svc
=
url
(
"http://api.hostip.info/country.php"
)
val
country
:
Promise
[
String
]
=
Http
(
svc
OK
as
.
String
)
println
(
country
())
Note that the result country
is not a String
but a Promise[String]
, and we use apply
to wait for the resulting value.
The result printed will be a country code such as GB
, or XX
if the country cannot be determined from your IP address.
This short example expects a 200 (OK) status result and turns the result into a String
, but that’s a tiny part of what Dispatch is capable of. We’ll explore further in this section.
What if the request doesn’t return a 200? In that case, with the code we have, we’d get an exception such as: “Unexpected response status: 404.” There are a few ways to change that.
We can ask for an Option
:
val
result
:
Option
[
String
]
=
country
.
option
()
As you’d expect, this will give a None
or Some[String]
. However, if you have debug level logging enabled in your application, you’ll see the request and response and error messages from the underlying Netty library. You can tune these messages by adding a logger setting to default.logback.xml file:
<logger
name=
"com.ning.http.client"
level=
"WARN"
/>
A second possibility is to use either
with the usual convention that the Right
is the expected result and Left
signifies a failure:
country
.
either
()
match
{
case
Left
(
status
)
=>
println
(
status
.
getMessage
)
case
Right
(
cc
)
=>
println
(
cc
)
}
This will print a result as we are forcing the evaluation with an apply via either()
.
Promise[T]
implements map
, flatMap
, filter
, fold
, and all the usual methods you’d expect it to allow you to compose. This means you can use the promise with a for
comprehension:
val
codeLength
=
for
(
cc
<-
country
)
yield
cc
.
length
Note that codeLength
is a Promise[Int]
. To get the value, you can evaluate codeLength()
and you’ll get a result of 2
.
As well as extracting string values with as.String
, there are other options, including:
-
as.Bytes
-
To work with
Promise[Array[Byte]]
-
as.File
-
To write to a file, as in
Http(svc > as.File(new File("/tmp/cc")))
-
as.Response
-
To allow you to provide a
client.Response => T
function to use on the response -
as.xml.Elem
- To parse an XML response
As an example of as.xml.Elem
:
val
svc
=
url
(
"http://api.hostip.info/?ip=12.215.42.19"
)
val
country
=
Http
(
svc
>
as
.
xml
.
Elem
)
println
(
country
.
map
(
_
\\
"description"
)())
This example is parsing the XML response to the request, which returns a Promise[scala.xml.Elem]
. We’re picking out the description node of the XML via a map
, which will be a Promise[NodeSeq]
that we then force to evaluate. The output is something like:
<gml:description
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:gml=
"http://www.opengis.net/gml"
>
This is the Hostip Lookup Service</gml:description>
That example assumes the request is going to be well formed. In addition to the core Databinder library, there are extensions for JSoup and TagSoup to assist in parsing HTML that isn’t necessarily well formed.
For example, to use JSoup, include the dependency:
libraryDependencies
+=
"net.databinder.dispatch"
%%
"dispatch-jsoup"
%
"0.9.5"
You can then use the features of JSoup, such as picking out elements of a page using CSS selectors:
import
org.jsoup.nodes.Document
val
svc
=
url
(
"http://www.example.org"
).
setFollowRedirects
(
true
)
val
title
=
Http
(
svc
>
as
.
jsoup
.
Document
).
map
(
_
.
select
(
"h1"
).
text
).
option
println
(
title
()
getOrElse
"unknown title"
)
Here we are applying JSoup’s select
function to pick out the <h1>
element on the page, taking the text of the element, which we turn into a Promise[Option[String]]
. The result, unless example.org has changed, will be “Example Domain.”
As a final example of using Dispatch, we can pipe a request into Lift’s JSON library:
import
net.liftweb.json._
import
com.ning.http.client
object
asJson
extends
(
client
.
Response
=>
JValue
)
{
def
apply
(
r
:
client.Response
)
=
JsonParser
.
parse
(
r
.
getResponseBody
)
}
val
svc
=
url
(
"http://api.hostip.info/get_json.php?ip=212.58.241.131"
)
val
json
:
Promise
[
JValue
]
=
Http
(
svc
>
asJson
)
case
class
HostInfo
(
country_name
:
String
,
country_code
:
String
)
implicit
val
formats
=
DefaultFormats
val
hostInfo
=
json
.
map
(
_
.
extract
[
HostInfo
])()
The URL we’re calling returns a JSON representation for location information of the IP address we’ve passed.
By providing a Response => JValue
to Dispatch, we’re able to pass the response body through to the JSON parser. We can then map on the Promise[JValue]
to apply whatever Lift JSON functions we want to. In this case, we’re extracting a simple case class.
The result would show hostInfo
as:
HostInfo
(
UNITED
KINGDOM
,
GB
)
The Dispatch documentation is well written and guides you through the way Dispatch approaches HTTP. Do spend some time with it.
For questions about Dispatch, the best place is the Dispatch Google Group.
The previous major version of Dispatch, 0.8.x (“Dispatch Classic”), is quite different from the “reboot” of the project as version 0.9. Consequently, examples you may see that use 0.8.x will need some conversion to run with 0.9.x. Nathan Hamblen’s blog describes the change.
For working with JSoup, take a look at the JSoup Cookbook.
Get Lift Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.