2008/11/12

GWT RPC from non-servlet-container - finally

Finally, I get to this part after the first and second rounds.

GwtRpc


A Scala module(object) GwtRpc, is created for this RPC purpose, which defines three traits of interest:
  • GwtRpcSpp
  • RpcService
  • SppHolder
GwtRpcSpp is a simple trait extending GWT's SerializationPolicyProvider, which declares an abstract method getPolicyFileInputStream:

def getPolicyFileInputStream(moduleBaseUrl:String,
strongName:String):Option[InputStream]

moduleBaseUrl and stringName are two parameters carried by client request of GWT RPC. This method is supposed to be implemented to allow users to hook up their own logic about getting serialize policy file generated by GWT RPC.

RpcService is another simple trait which is supposed to be mixed into entities who is going to be the end-implementations of GWT RPC services.
trait RpcService { this:SppHolder with RemoteService =>

def call(final in: => String)(handler: PartialFunction[CallResult, Unit])={
...
}
...
}
Method call is the main entry of a RpcService implementation, which takes two arguments.
  • in - a Call-by-name string: the UTF-8 payload of an RPC request;
  • handler - a partial function which is provided by user to match the result of a call and write result back to response

SppHolder is a container of SerializationPolicyProvider. RpcService declares it as a part of its self type annotation which sort of declares a dependency to SppHolder, only the dependency needs to be fulfilled by its implementation.

Actually, this GwtRpc module is container-independent. It can be used in any container which provides IO stream as abstraction of HTTP request.

The Restlet implementation

Cause I (in this series) take more interesting in serving GWT RPC via Restlet, I would give a simple implementation for Restlet environment.

Generally, Restlet allows you to serve a HTTP request via a Restlet or a Resource. As a result, two traits implementations for each of them are provided respectively.

For Restlet:
trait RpcRestlet extends Restlet with RpcService with SppHolder{this:RemoteService=>
override def handle(req:Request, resp:Response){
req.getMethod match{
case Method.POST=>
call(fromStream(req.getEntity.getStream))(writeRpcResult(resp))
case _ => // do nothing
}
}
}
For Resource:
trait RpcResource extends Resource with RpcService with SppHolder{this:RemoteService=>
override def acceptRepresentation(rep: Representation){
call(fromStream(rep.getStream))(writeRpcResult(getResponse))
}
}
fromStream is a method to read payload from an InputStream. This payload is simple a UTF-8 string and will be decoded to an instance of RpcRequest by GWT's RPC.decodeRequest(String).

writeRpcResult is a functional variable, which takes a Response as argument and returns a PartialFunction:
val writeRpcResult: Response=>PartialFunction[CallStatus, Unit] = {r=>{
case CallSuc(msg)=>
val rep = new StringRepresentation(msg, MediaType.APPLICATION_JSON)
rep.setCharacterSet(CharacterSet.UTF_8)
rep.setDownloadable(true)
rep.setDownloadName("attachment")
r.setEntity(rep)
r.setStatus(Status.SUCCESS_OK)
case CallFail(msg)=>
r.setEntity(new StringRepresentation(msg, MediaType.TEXT_PLAIN))
r.setStatus(Status.SERVER_ERROR_INTERNAL)
}
}
To create an implementation of GWT RemoteService, just do almost same thing as in GWT but change the parent class to either one of above. For example:
abstract class PingServiceImpl extends RpcRestlet with PingService with SppHolder{
def ping(msg:String)={
"Pong-> " + msg + "@" + new Date
}
}

PingServiceImpl is still abstract, because its dependency to SerializationPolicyProvider has not been fulfilled. To make thing simple, I reuse the App created in last installment as the base and make it a SerializationPolicyProvider for the whole Application scope.

The implementation looks like following code, using Directory gwtDir is a lazy way here.

class App extends Application with GwtApp with GwtRpcSpp{self=>
def getPolicyFileInputStream(moduleBaseUrl:String,
strongName:String):Option[InputStream]={
val ref = new Reference(moduleBaseUrl)
val response = gwtDir.get(ref.getLastSegment+"/"+getPolicyFileName(strongName))
if(Status.SUCCESS_OK == response.getStatus)
Some(response.getEntity.getStream)
else
None
}
}
Finally attach ping service to the router of this application and specify the SppHolder dependency:

override def createRoot={
//...
r.attach("/ping", new PingServiceImpl{
def provider = self
})
//...
}
There is no need to change code of GWT client, just let ping service call endpoint http://localhost:8080/ping will done.

For complete source code of this sample, see http://edgebox.googlecode.com/svn/sandbox/gwt-restlet-demo/.

0 comments:

Footer

  © Blogger template 'Grease' by Ourblogtemplates.com 2008

Back to TOP