IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

RESTful Web Services

Date de publication : 29 mai 2008

Par Nicolas Zozol (http://www.edupassion.com) (Blog)
 

This article explains how to use JSPs to fully dissect HTTP request in the objective to build an easy RESTful Web Service without any framework neither auto-genrerated code by an IDE. You'll find the fr original french version here .

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
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>
warning 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 ?

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

warning 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(){/*OK*/},
            onComplete:function(transport){
                return transport;
            },
            onException:function(request, exception){
                alert("xhr -- Exception !:"+exception.message);              
            },
            onFailure:function(){/*FAIL*/}
            
            
        });
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.

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



<%
			/* following of the robusta:request tag */			
			
            /* Credentials read in the header : Authorization:Basic xxxyyy or in the cookie : Authorization:Basic-xxxyyy
             * In your cookies, you will use sometimes myToken instead of Authorization associated to Basic. The implementation below will change
             */
            boolean credentialsFound = false;
			//priority is to the header over the cookie : if there are credentials in the header "Authorization", we don't care of the cookie
            if (request.getHeader("Authorization") != null && !request.getHeader("Authorization").equals("")) {
                controller.setAuthorizationValue(request.getHeader("Authorization"));
                credentialsFound = true;
            } else {//This is my implementation to read into the cookie.
                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;

/**
 * A ResourceController is implemented by Controllers - you can then design nice customs JSP tags
 * @author Nicolas Zozol - Edupassion.com - Robusta Web - nzozol@edupassion.com
 */
public interface ResourceController {

    public void setURI(String uri);
    public void setPostBody(String postBody);
    
    /* Functions for differents http methods */
    public boolean isReadOK();
    public boolean isCreateOK();
    public boolean isUpdateOK();
    public boolean isDeleteOK();
    
    /* StatusCode and optional message sent */    
    public int getStatusCode();    
    public void setStatusCode(int statusCode);    
    public String getMessage();
    
    /* security : you can extend the interface or use Utility Classes */
    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.

Valid XHTML 1.1!Valid CSS!

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.