Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Server load balancing architectures, Part 1: Transport-level load balancing

High scalability and availability for server farms

  • Print
  • Feedback

Page 4 of 6

Listing 2. memcached-based HttpResponse cache

interface IHttpResponseCache {

   IHttpResponse put(String key, IHttpResponse response) throws IOException;

   void remove(String key) throws IOException;

   IHttpResponse get(String key) throws IOException;
}



class RemoteHttpResponseCache implements IHttpResponseCache {

   private MemcachedClient memCachedClient;

   public RemoteHttpResponseCache(InetSocketAddress... cacheServers) throws IOException {
      memCachedClient = new MemcachedClient(Arrays.asList(cacheServers));
   }

   public IHttpResponse put(String key, IHttpResponse response) throws IOException {
      byte[] bodyData = response.getBlockingBody().readBytes();

      memCachedClient.set(key, 3600, bodyData);
      return null;
   }


   public IHttpResponse get(String key) throws IOException {
      byte[] bodyData = (byte[]) memCachedClient.get(key);
      if (bodyData != null) {
         return new HttpResponse(200, "text/plain", bodyData);
      } else {
         return null;
      }
   }


   public void remove(String key) throws IOException {
      memCachedClient.delete(key);
   }
}

Listing 2 and the rest of this article's example code also uses the xLightweb HTTP library. Listing 3 shows an example business service implementation. The onRequest(...) method -- similar to the Servlet API's goGet(...) or doPost(...) method -- is called each time a request header is received. The exchange.send() method sends the response.

Listing 3. Example business service implementation

class MyRequestHandler implements IHttpRequestHandler {

   public void onRequest(IHttpExchange exchange) throws IOException {

      IHttpRequest request = exchange.getRequest();

      int customerId = request.getRequiredIntParameter("id");
      long amount = request.getRequiredLongParameter("amount");
      //...


      // perform some operations
      //..
      String response = ...

      // and return the response
      exchange.send(new HttpResponse(200, "text/plain", response));
   }
}


class Server {

   public static void main(String[] args) throws Exception {
      HttpServer httpServer = new HttpServer(8180, new MyRequestHandler());
      httpServer.run();
   }
}

Based on the HttpResponse cache, a simple caching solution can be implemented that caches the HTTP response for an HTTP request. If the same request is received twice, the corresponding response can be taken from the cache, without calling the business service. This requires intercepting the request-handling flow. This can be done by the interceptor shown in Listing 4.

Listing 4. Cache-supported business service example

class CacheInterceptor implements IHttpRequestHandler {

   private IHttpResponseCache cache;

   public CacheInterceptor(IHttpResponseCache cache) {
      this.cache = cache;
   }


   public void onRequest(final IHttpExchange exchange) throws IOException {

      IHttpRequest request = exchange.getRequest();

      // check if request is cacheable (Cache-Control header, ...)
      // ...
      boolean isCacheable = ...


      // if request is not cacheable forward it to the next handler of the chain
      if (!isCacheable) {
         exchange.forward(request);
         return;
      }

      // create the cache key
      StringBuilder sb = new StringBuilder(request.getRequestURI());
      TreeSet<String> sortedParamNames = new TreeSet<String>(request.getParameterNameSet());
      for (String paramName : sortedParamNames) {
         sb.append(URLEncoder.encode(paramName) + "=");

         List<String> paramValues = Arrays.asList(request.getParameterValues(paramName));
         Collections.sort(paramValues);
         for (String paramValue : paramValues) {
            sb.append(URLEncoder.encode(paramValue) + ", ");
         }
      }
      final String cacheKey = URLEncoder.encode(sb.toString());

      // is request in cache?
      IHttpResponse cachedResponse = cache.get(cacheKey);
      if (cachedResponse != null) {
         IHttpResponse response = HttpUtils.copy(cachedResponse);
         response.setHeader("X-Cached", "true");
         exchange.send(response);

      // .. no -> forward it to the next handler of the chain
      } else {

         // define a intermediate response handler to intercept and copy the response
         IHttpResponseHandler respHdl = new IHttpResponseHandler() {

            @InvokeOn(InvokeOn.MESSAGE_RECEIVED)
            public void onResponse(IHttpResponse response) throws IOException {
               cache.put(cacheKey, HttpUtils.copy(response));
               exchange.send(response);  // forward the response to the client
            }

            public void onException(IOException ioe) throws IOException {
               exchange.sendError(ioe);  // forward the error to the client
            }
         };

         // forward the request to the next handler of the chain
         exchange.forward(request, respHdl);
      }
   }
}


class Server {

   public static void main(String[] args) throws Exception {
      RequestHandlerChain handlerChain = new RequestHandlerChain();
      handlerChain.addLast(new CacheInterceptor(new RemoteHttpResponseCache(new InetSocketAddress(cachSrv1, 11211), new InetSocketAddress(cachSrv2, 11211))));
      handlerChain.addLast(new MyRequestHandler());

      HttpServer httpServer = new HttpServer(8180, handlerChain);
      httpServer.run();
   }
}

The CacheInterceptor in Listing 4 uses the memcached-based implementation to cache responses, based on the hashcode of dedicated header attributes. If the cache contains a response for this hashcode, the request is not forwarded to the business-service handler. Instead, the response is returned from the cache. If the cache does not contain a response, the request is forwarded by adding a response handler to intercept the response flow. If a response is received from the business-service handler, the response is added to the cache. (Note that Listing 4 does not show cache invalidation. Often dedicated business operations require the cache entry to be invalidated.)

  • Print
  • Feedback

Resources

More