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/.