Part 1: The Lift/Scala Thing...
So first I had to figure out what the deal with Lift is about. Good thing there's a fairly simple tutorial to create a basic HelloWorld lift webapp here. First let's download and install all the bits you need to write a Lift web app, fortunately for me it was just the Scala Eclipse Plugin.
Let us create a maven project:
mvn archetype:generate -U \
-DarchetypeGroupId=net.liftweb \
-DarchetypeArtifactId=lift-archetype-blank \
-DarchetypeVersion=1.0 \
-DremoteRepositories=http://scala-tools.org/repo-releases \
-DgroupId=com.examples.terracotta \
-DartifactId=clusteredCounter \
-Dversion=1.0-SNAPSHOT
cd clusteredCounter
mvn jetty:run
Now you got yourself a fully functioning hello world webapp. Let's add some code where we are actually saving and displaying session data.
add a Lift Snippet that would process url parameters add then to the session and then display all the session attributes:
package com.examples.terracotta.snippet
import javax.servlet.http._
import net.liftweb.http._
import net.liftweb.util._
import net.liftweb.util.Helpers._
import net.liftweb.http.SessionVar
import scala.collection.mutable.HashMap
import scala.xml._
import java.util.Enumeration
import scala.collection.mutable.HashSet
class ViewSubmission {
def showStuff : NodeSeq = {
var title = S.param("title").openOr("")
var url = S.param("url").openOr("")
S.servletSession.get.setAttribute(title, url)
var e = S.servletSession.get.getAttributeNames
val names = new HashSet[String]
while (e.hasMoreElements()) {
val name = e.nextElement().asInstanceOf[String]
names += name
println(names)
}
<table> {
for (name <- names) yield
<tr>
<td>Title</td>
<td>{name}</td>
</tr>
<tr>
<td>Url</td>
<td>{S.servletSession.get.getAttribute(name)}</td>
</tr>
}</table>
}
}
Edit the index.html to show edit and show session data:
<lift:surround with="default" at="content">
<table>
<lift:snippet type="viewSubmission:showStuff" />
</table>
<form>
<tr>
<td>Title</td>
<td>
<input type="text" name="title" />
</td>
</tr>
<tr>
<td>Url</td>
<td>
<input type="text" name="url"/>
</td>
</tr>
<tr>
<td> </td>
<td><input type="submit" value="Add" /></td>
</tr>
</form>
</lift:surround>
Run mvn -Djetty.port=9999 jetty:run and another instance with
mvn -Djetty.port=9999 jetty:run. got to http://localhost:8888 and http://localhost:9999. you can only see the data you added to each local web session.
Now the fun part...
Part 2: Clustering the web session
First we need to add our express web session jar to the pom.xml as a dependency:
<dependency>
<groupid>org.terracotta.session</groupid>
<artifactid>terracotta-session</artifactid>
<version>1.1.0-SNAPSHOT</version>
</dependency>
and also Terracotta Maven Repo information..
<repository>
<id>terracotta-snapshots</id>
<url>http://www.terracotta.org/download/reflector/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
Let us add our terracotta filter for clustering:
<filter>
<filter-name>terracotta</filter-name>
<display-name>Terracotta Session Filter</display-name>
<!-- The filter class is specific to the application server. -->
<filter-class>org.terracotta.session.TerracottaJetty61xSessionFilter</filter-class>
<init-param>
<param-name>tcConfigUrl</param-name>
<!--
<param-value> of type tcConfigUrl has a <param-value> containing the
URL or filepath (for example, /lib/tc-config.xml) to tc-config.xml.
-->
<param-value>localhost:9510</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>terracotta</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Now start our terracotta server, download here
Start are terracotta server and then run the jetty servers again. this time you can see session data on entered in on one server appear on the other.
16 comments:
What about LiftSession, is it clustered also? I'm asking because LiftSession isn't held in the servlet session, but in a separate map. To test, you can create for example a SessionVar, in the top-level file:
object testLiftSession extends SessionVar("init")
and then in the snippet do: testLiftSession("changed")
and see if this propagates.
Adam
Hi there - interesting post. To clarify, Lift is not an MVC framework, we use a view first pattern of our own design (its like a highly modified MVP).
How does this setup handle the functions that lift creates in the session context? As the other commenter points out, LiftSession is held in another map. One of our team looked at this before and found that it was extremely difficult to achieve (although he was not a terracotta expert), so i would be really interested to see if there was a way of clustering via terracotta.
Cheers, Timothy
Since nabib doesn't know jack about Lift/Scala my guess is that this experiment doesn't cluster that other thing but...
If Lift were to make this other thing an ehcache and the items put in that cache are all serializable it would be 2 lines of config to take that part to clusterable with terracotta as well. This is how grails and many other frameworks solve this problem
Not that easy Steve. The may challenge in Lift is function binding where user function can (nd typically do) capture state outside of Lift's control. So it's not about and not setting dumb session attribute per session.
I don't know what session attributes has to do with this but you seem to understand this stuff?
My suggestion was about storing state that needs to be clustered but is NOT in http session in a cache. Who knows, maybe that just won't help?
Can someone enumerate what things need to be clustered that aren't in the session and why they need to be clustered and why they can't be?
You just proved your lacking the understanding of my statements. HTTPSession was an example.
Yes, you are correct I don't understand? Thought I was clear on that. Have I somehow offended you with my ignorance ;-). My goal is to understand and then solve a problem.
Maybe (and hopefully) I misunderstood your tone. So forget about HTTPSession ... we don;t store session data on HTTPSession.
The problem is that in lift we have things like:
var myStuff = computeStuff();
SHtml.link("press me", () = {
// do computation
// use myStuff ..
})
... this tells lift to call this function/lambda when the link is pressed. These functions are bound per LiftSession but Lift has no control over the the object graph referenced by user's functions. So far the attempt to solve cluster replication with terracotta we not very successful (Viktor Klang worked on this a while ago). Relying on Java's serialization is really not an option. So can eh-cache reliably and efficiently "serialize" the object graph ?
Marius, you are correct about Viktor - I spoke with him about this at length the last time we got together at a conference. He said there were a lot of corner cases and it would have been next to impossible to support it with the heavy use of function mapping within lift. I've not tried stuffing a function into EHCache, but it could well work... It would need trying.
@adamwtw, Steve is right.. I don't know anything about lift or scala. This was more to demonstrate how dead easy general HttpSession clustering is with terracotta.
There is definitely work to be down to use the layers built on top of HttpSession in Lift.
@Timothy Perrett Sorry for the misunderstanding about MVC vs MVP.
I think Steve's point about having the values being Serializable is the we don't have to worry about Instrumenting classes to achieve clustering. Otherwise you maybe have to go through this exercise.
http://www.jonasboner.com/2008/01/25/clustering-scala-actors-with-terracotta.html
If we can identify that whatever is eventually put into a map is Serializable, then clustering would be much easier.
Jonas B. is also a Lift committer and at some point I think Dave asked him to look into ways of integrating Lift with Terracotta (Lift has it's own session management and we don't use HTTPSession except for bridging the two). So far personally I didn't see much activity in this area maybe because he was too busy with Akka.
I will be spending a few days with Jonas in the coming month or two, so i'll put this on my list of things to discuss.
I did an exact same setup of this blog, I followed it step by step, but the Lift session isn't shared with me. I tried with terracotta 3.2.1 beta and the latest trunk snapshot.
Possibly I'm doing something wrong, or the Lift session just can't be shared.
Did anyone else try the same setup with or without success?
Brambo, the above discussion is why its not being shared. The writer of this blog did not really understand how Lift manages sessions and state, thus, did not understand how to share it (currently, this is very difficult and we've not found a complete solution). This blog talks about sharing the servlet session, which Lift does not use: thus, its a little redundant to even bother sharing it in the first place. HTH.
Thanks Timothy, it all makes sense now. I kinda had the feeling this was the case but wasn't really sure.
Just to follow up on this, you may be interested in my blog post on this subject:
http://blog.getintheloop.eu/2010/12/10/distributing-critical-state-with-containervar/
Post a Comment