RESTful Web Services
Date de publication : 29 mai 2008
Par
Nicolas Zozol (http://www.edupassion.com) (Blog)
Introduction
I. Global Architecture
II. Recovering request datas
II-1. Recovering parameters
II-2. Recovering request URI
II-3. Recovering request PostBody
III. Dealing with different Http methods
IV. Analyser les URI
IV-1. Objectives
IV-2. Servlet-Mapping
IV-3. URI Analyze
V. Elegance with a JSP Tag
VI. User Identification with cookie or credentials
VI-1. Why a cookie ?
VI-2. Why credentials ?
VI-3. JSP tag scooting for cookie and credentials
VIII. Status code response
Conclusion
Appendix
X-1. Prototype
X-2. ResourceController interface
X-3. Robusta Web Toolkit
Introduction
|
RESTful Web Services are a way to access to Resources on Internet. Say you ask a server all documents written by User 54 : there is an infinity of ways for your browser to ask this Resource, and an infinity of correct responses by the server.
Programmers have written several standardized methods, and notably RPC, SOAP ou REST.
|
A web service deserves the mention "RESTful" if it gets most of the advices from
Roy T. Fielding, inventor of REST.
- HTTP
- Simplicity
- Addressability
- Connectivity
- Statelessness of the server
- URIs define Resources
Like other Web Services, data security is very important. Some says that Resources in a SOAP service are highly secured because unreachables. Data security
in a Stateless server will be described in another article.
I. Global Architecture
|
The Client sends a request to a JSP container (here :Tomcat 6.0.x). The JSP will forward the request URI, parameters and PostBody to the ResourceController.
The JSP will lead the work done by the JavaBean depending on the Http method used.
Notes :
A ResourceController is a JavaBean implementing functions in deep relation with analyzing a request and manipulating a Resource.
The PostBody, unlike the QueryString, constitute parameters of the request that we can't see in the URL.
|
II. Recovering request datas
II-1. Recovering parameters
It's the easy and classic part : let's have a request PUT /wsxseditor/service/document/document.jsp?dataBaseId=24&title=ChangeTheTitle
We need to add to the JSP :
|
< jsp : useBean id = " controller " class = " bean.DocumentController " scope = " request " / >
< jsp : setProperty name = " controller " property = " * " / >
|
The DocumentController must implement setDataBaseId() and setTitle() functions.
II-2. Recovering request URI
Each JSP can deal with the
HttpServletRequest object using ${pageContext.request}. We add to the JSP :
< jsp : setProperty name = " controller " property = " URI " value = " ${pageContext.request.requestURI} " / >
|
DocumentController must now implement interface ResourceController, ou at least the setURI() function.
II-3. Recovering request PostBody
Here is a POST request as seen in the sniffer
Wireshark. Note that a POST request can have a QueryString,
wich can be useful to the algorithme in your ResourceController.
In this classic exemple, I create a new element in my document. This element is represented in XML in the PostBody, and I use the QueryString parameters
fatherId et brotherId to know where to place the element in the document. But recovering the PostBody is more delicate, because we must work with Java Streams.
< %
java.io.BufferedReader br = new java.io.BufferedReader (new java.io.InputStreamReader (request.getInputStream ()));
String line,result= " " ;
while ((line = br.readLine ()) ! = null ) {
result+ = line;
}
br.close ();
controller.setPostBody (result);
% >
|
DocumentController must implement the interface ResourceController, or at least a setPostBody ( ) function.
III. Dealing with different Http methods
Using correctly Http methods GET (Read), POST (Create), PUT (Update) et DELETE (Delete) is in my opinion THE fundamental aspect of a RESTful Web Service.
To know which method has been used by the request, we must ask once again to the HttpServletRequest :
< c : if test = " ${pageContext.request.method=='POST' } " >
< c : choose >
< c : when test = " ${controller.createOK} " >
... some xml ...
< / c : when >
< c : otherwise >
... some xml ...
< / c : otherwise >
< / c : choose >
< / c : if >
< c : if test = " ${ pageContext.request.method == 'PUT' } " >
< c : choose >
< c : when test = " ${controller.updateOK} " >
... some xml ...
< / c : when >
< c : otherwise >
... some xml ...
< / c : otherwise >
< / c : choose >
< / c : if >
|
The ResourceController will implement, depending on possibilities given by the Web Service, isReadOK(), isCreateOK(), is UpdateOK(), isDeleteOK() functions.
IV. Analyser les URI
IV-1. Objectives
We have started the article with the request : PUT /wsxseditor/service/document/document.jsp?dataBaseId=24&title=ChangeTheTitle.
We would like now a more sexy request like this one : PUT /wsxseditor/document/24?title=ChangeTheTitle
URL rewriting is less important than correct use of Http methods, but it will promote exchanges of URLs between different web pages and so
Connectivity. Sexy URLs will also, unlike SOAP, allow to understand in one look the meaning of the request and help so maintainability.
IV-2. Servlet-Mapping
It's easy with
Netbeans to forward the request
/wsxseditor/document/24 to the JSP
/wsxseditor/service/document/document.jsp.
Double-click on the file
web.xml, and fill the form :
web.xml
The web.xml file will look like :
<? xml version="1.0" encoding="UTF-8"? >
< web-app . . . >
< servlet >
< servlet-name > UriAdaptor< / servlet-name >
< jsp-file > /service/document/document.jsp< / jsp-file >
< load-on-startup > 1< / load-on-startup >
< / servlet >
< servlet-mapping >
< servlet-name > UriAdaptor< / servlet-name >
< url-pattern > /document/*< / url-pattern >
< / servlet-mapping >
< / web-app >
|
Because the dataBaseId parameter has disappeared, we must say to the ResourceController that the number 24 represents the dataBaseId.
IV-3. URI Analyze
The ResourceController implements the setURI () function. When we'll transmit the URI to the controller, we will also decode this URI :
public void setUri (String uri) throws NotFoundException {
this .uri = uri;
robusta.rest.j2ee.UrlDecoder decoder= new robusta.rest.j2ee.UrlDecoder ();
this .dataBaseId= decoder.firstNumber (uri);
}
|
UrlDecoder.firstNumber() is a fonction from my
Robusta Toolbox that uses regular expressions to detect the first number of the URI.
You'll find the source code at
this adress.
V. Elegance with a JSP Tag
JSP scripts ( <% java code %> ) are not easily readable and, what is worst, promote copy/paste of the source code. But we can use
a simple, non-intrusive tag JSP to resolve this problem.
Here is the request.tag file in the directory web/WEB-INF/tags :
< % @ tag description= " Conciliate RESTful Web Services and JSPs " pageEncoding= " UTF-8 " % >
< % @ attribute name= " method " % >
< % @ attribute name= " controller " rtexprvalue= " true " type= " base.ResourceController " required= " true " % >
< % @ attribute name= " request " rtexprvalue= " true " type= " javax.servlet.http.HttpServletRequest " required= " true " % >
< %
java.io.BufferedReader br = new java.io.BufferedReader (new java.io.InputStreamReader (request.getInputStream ()));
String line,result= " " ;
while ((line = br.readLine ()) ! = null ) {
result+ = line;
}
br.close ();
controller.setPostRequest (result);
controller.setURI (request.getRequestURI ());
this .method= request.getMethod ();
% >
|
The tag will need the ResourceController and the HttpServletRequest, then create the variable method. The JSP will finally looks like :
< % @ taglib tagdir = " /WEB-INF/tags/ " prefix = " robusta " % >
< jsp : useBean id = " controller " class = " bean.DocumentController " scope = " request " / >
< jsp : setProperty name = " controller " property = " * " / >
< robusta : request controller = " ${controller} " request = " ${pageContext.request} " / >
< c : if test = " ${method == 'POST' } " >
... some xml ...
< / c : if >
|
|
Your bean must implements the ResourceController interface.
|
VI. User Identification with cookie or credentials
VI-1. Why a cookie ?
We will use cookies to read datas that are not strictly confidentials. In theory, the user is responsible for his PC, and have to click on the "disconnect" button.
We can use cookie only if an user fault doesn't lead to a drama.
Wikipedia, for exemple, use a cookie to identify the user, and the server builds a HTML page with a personal DIV on the top right.
Cookie: frwikiUserName=Nicorama; frwiki_session=4f64774a3fkskfea0b2cbc4b6779fc94; frwikiUserID=52490; frwikiToken=abdf769sfcafhbb3c4c719cbdb8950a\r\n
|
By clicking from a link to another on Wikipedia, the "user DIV" stays, but the URL shown on the browser change and you copy/paste this URL and send it to a friend via email.
We do it everydays with Wikipedia or YouTube.
A cookie is so an easy way to keep Adressability, wich also helps a lot for Google pagerank.
Notice that the page is read through http, and not https. That's also why you should not access to very important datas with classic cookies.
Here is a
link to a blog talking about best practices for using cookies.
VI-2. Why credentials ?
|
We call Credentials the value of the http header Authorization : BASIC fdhqjkdkjq==
|
Say now that you waant to delete an user account with a request DELETE / users/58. By clicking on a classic link
or putting an adress in a browser, we only have some GET requests. We must so program this request, now most of the time with Ajax. Putting credentials in this
request will allow to identify the user without cookies.
|
If you use both cookies and credentials at differents points of you application, it's absolutely mandatory
to generate theses datas in a different way. If not, a hacker could easily read the cookie and then making DELETE request
(1)
|
Here is an exemple with Protoype.
new Ajax. Request (" /users/58 " , {
method : " DELETE " ,
requestHeaders :[ " Authorization " , " BASIC fjqkks678dd== " ] ,
onCreate :function (){ } ,
onSuccess :function (){ } ,
onComplete :function (transport){
return transport;
} ,
onException :function (request, exception){
alert (" xhr -- Exception !: " + exception. message);
} ,
onFailure :function (){ }
} );
|
To make this request, Javascript must know the credentials. From navigating from a web page to another, Javascript
will lose this data because we can't store it in a cookie.
|
We generally lose Adressability by using credentials in an Ajax application.
|
VI-3. JSP tag scooting for cookie and credentials
The ResourceController will now implement setAuthorizationValue() function. This function will have a little more work
to do because we send it sometimes the cookie value, sometimes the credential value. The authentication mode must also
be extracted.
< %
boolean credentialsFound = false ;
if (request.getHeader (" Authorization " ) ! = null & & ! request.getHeader (" Authorization " ).equals (" " )) {
controller.setAuthorizationValue (request.getHeader (" Authorization " ));
credentialsFound = true ;
} else {
javax.servlet.http.Cookie[] cookieList = request.getCookies ();
for (int i = 0 ; i < cookieList.length; i+ + ) {
if (cookieList[i].getName ().toLowerCase ().contains (" authorization " )) {
controller.setAuthorizationValue (cookieList[i].getValue ());
credentialsFound = true ;
}
break ;
}
}
}
% >
|
We can now have a final look at the JSP :
% < % @ taglib tagdir= " /WEB-INF/tags/ " prefix= " robusta " % >
% < jsp:useBean id= " controller " class = " controller.documentController " scope= " request " / >
% < jsp:setProperty name= " controller " property= " * " / >
% < robusta:request controller= " ${controller} " request= " ${pageContext.request} " / >
% < c:if test= " ${method == 'POST' } " >
... some xml ...
% < / c:>
% < robusta:response statusCode= " 212 " controller= " ${controller} " response= " ${pageContext.response} " / >
|
Add the line <robusta:response> at the very end of the JSP, or your server will set alone the status code.
You can set the status code in the JSP page with the attribute statusCode, or with the getStatusCode of the ResourceController.
VIII. Status code response
REST coders love to use a large area of status code returned by the server, way beyond 200 or 404. This little tag makes it easy.
In /WEB-INF/tags/response.tag :
< % @ tag description= " Set datas to the response " pageEncoding= " UTF-8 " % >
< % @ attribute name= " statusCode " rtexprvalue= " true " type= " Integer " required= " false " % >
< % @ attribute name= " response " rtexprvalue= " true " type= " javax.servlet.http.HttpServletResponse " required= " true " % >
< % @ attribute name= " controller " rtexprvalue= " true " type= " robusta.rest.ResourceController " required= " false " % >
< % - - any content can be specified here e.g.: - - % >
< %
if (statusCode > 0 ) {
response.setStatus (statusCode);
} else {
if (controller! = null & & controller.getStatusCode () > 0 ) {
response.setStatus (controller.getStatusCode ());
}
}
% >
|
Conclusion
Using theses technics has a huge advantage against classics Servlets : it avoids awfull out.println("\"Here I am\" said Chuck"); and
works with URIs, PostBody and Http methods much more easily than with standard Servlets.
The JSP will be perfect to exchange XML datas with the Ajax Client.
JSF can do the same job, but is slower and more complicated to learn. Moreover JSF is designed to be part of the graphical layer, and that will not help you much with
your web service. And nowadays, graphical layer is more often delagated to the Ajax client.
The tag code is under public domain. The Robusta Toolbox is under GPL 2.0
Appendix
X-1. Prototype
Prototype transform DELETE and PUT requests with a GET method, adding the _method parameter to the request.
Here is a
link with a Prototype version that really makes PUT and DELETE methods - wich is not possible with old or exotic browsers.
X-2. ResourceController interface
Here is the one I use, but it's absloutely not standardized yet.
package robusta.rest;
@author
public interface ResourceController {
public void setURI (String uri);
public void setPostBody (String postBody);
public boolean isReadOK ();
public boolean isCreateOK ();
public boolean isUpdateOK ();
public boolean isDeleteOK ();
public int getStatusCode ();
public void setStatusCode (int statusCode);
public String getMessage ();
public void setAuthorizationValue (String authorizationValue);
public String getCredentials ();
}
|
X-3. Robusta Web Toolkit
The Robusta Web Toolkit(GPL 2.0) presents a few independants package (they are nevertheless in relations with the commons package). For the moment, it's
in hard work and will still be modified.
(1) |
Some protocols like Kerberos can be safely used without https.
|
Les sources présentées sur cette page sont libres de droits
et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation
constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ©
2008 Nicolas Zozol. Aucune reproduction, même partielle, ne peut être
faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc.
sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à
trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.