Saturday, May 5, 2012

Sharing data among services

Services often need to share data. There are many examples: like storing an IBMiConnection using the HttpSession to create a persistent host Job, or a login service that stores configuration information on the session so data services have access to the information without directly obtaining it from the Rich UI application. It may be obvious, but it's worth stating, for invocations to share the same session they must use the same session cookie and the services must be deployed to the same server cluster.

First let's look at how to access the HttpSession in a service:

Setting or getting a session value is pretty easy. First get the HttpSession then use setAttribute or getAttribute. The service is run in a servlet. The servlet's doPost function saves the HttpServletRequest in a thread local variable, this makes it accessible as the service context, ServletContext.getHttpServletRequest().

Here's some sample example service function that returns the HttpSession which is used by the put/get functions:

    function getSession()returns(HttpSession?)
        session HttpSession?;
        request javax.servlet.http.HttpServletRequest? = ServletContext.getHttpServletRequest();
        if(request != null)
            session = request.getSession();
        if(session == null)
            exp InvocationException;
            exp.message = "there is no session";
            throw exp;

    function putSessionValue(key string in, value Object in)
        session HttpSession? = getSession();
        session.setAttribute(key, value);
    function getSessionValue(key string in)returns(Object)
        session HttpSession? = getSession();


Now for a look at the client side:

With dedicated service invocations the session is managed by the browser, with REST service invocations the Rich UI application must manage the sessions.

Using a dedicated service invocation:

If you are accessing the service in a dedicated fashion (your using clause has an HttpProxy), using the same session is easy because the session is managed by the browser that the application is running in. All services invoked in a dedicated fashion are running on the same EGL Rich UI proxy; so session sharing just happens.

Using a REST service invocation:

If you want to access the session in a REST service, it's a bit more complicated because the Rich UI application must manage the session used for the service invocations. I've tried to make it a little easier by encapsulating much of the functionality to work with the session into the CookieSession library. There's information below on where to find this library.

Here are the points you need to design your application to manage the service sessions.

When a Rich UI application makes a REST service invocation the using clause is HTTPRest. The browser still does an HTTPRequest to the EGL Proxy like it did for the dedicated invocation, but it passes information to tell the the EGL Proxy to create a new HTTPRequest to the service.

Sounds complicated but it's a few simple implementation concepts: the service invocation 'returning to' function looks for and extracts a set-cookie:JSESSIONID from the response header, if it is found the JSESSIONID is copied to the request header Cookie value on the variable used for the service invocation.

For each service invocation that wants to use the same session, you must use the same 'using' clause variable, in this case I'm using httpSession1. So each call statement that has using httpSession1 will use the same session. Since we are storing data on the variable it can't be defined in a function, it must be defined in a more global location like a handler or library. If you had four services, two on server A and two on server B you would have two HttpRest variables - one for each server. If all four services are on the same server one variable will do.

    function invokeService()
      call GETREC.getCustomers() using httpSession1 returning to handleCustomersResponse onException handleException;

The 'returning to' function calls processResponseHeadersSession passing the response and the 'using' clause variable.
    function handleCustomersResponse(retResult CUST[] in, http IHttp)
        //process the response headers to handle the Session ID
        CookieSession.processResponseHeadersSession(http.getResponse(), httpSession1);


In the CookieSession library processResponseHeadersSession in the CookieSession library calls extractJSessionId to extracts the set-cookie from the header, if it's found setRequestSessionId puts the JSESSIONID cookie in the request headers of httpSession1. Once the request headers contain the cookie, the same session will be used with each invocation. Each service response should check for a set-cookie meaning there has been a change in session.

    function processResponseHeadersSession(http Response in, httpVar IHttp in)
            // This function gets the jsessionid and puts the key's value in the http.request
            //by putting the key in the http.request each service invocation using that request will use the same session
            //IMPORTANT NOTE:
            //be very careful with http variables and multiple service invocations
            //service can only use the same request if the services are deployed to the same server cluster
            jsessionId string? = extractJSessionId(http);
            if(jsessionId != null)
               setRequestSessionId(jsessionId, httpVar);

The code above is part of an IBMi persistent program example. You can load it from org.eclipse.edt/ibmi/org.eclipse.edt.ibmi.examples.

In closing

You've made it through the concepts and examples and now you want to try this. Well there a small problem in the 0.8.0 release which prevents the set-cookie from working in the development environment. You will need to either deploy the application or get a 0.8.1 build.

I hope you'll look for my next blog where I'll use the concepts above to create persistent IBMi jobs.


No comments:

Post a Comment