Package org.xlightweb.client

Source Code of org.xlightweb.client.CacheHandler

/*
*  Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb.client;

import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;


import org.xlightweb.BodyDataSink;
import org.xlightweb.BodyForwarder;
import org.xlightweb.HttpRequest;
import org.xlightweb.HttpResponse;
import org.xlightweb.HttpUtils;
import org.xlightweb.IHttpExchange;
import org.xlightweb.IHttpRequest;
import org.xlightweb.IHttpRequestHandler;
import org.xlightweb.IHttpRequestHeader;
import org.xlightweb.IHttpResponse;
import org.xlightweb.IHttpResponseHandler;
import org.xlightweb.IHttpResponseHeader;
import org.xlightweb.InvokeOn;
import org.xlightweb.NonBlockingBodyDataSource;
import org.xlightweb.client.HttpCache.CacheEntry;
import org.xlightweb.client.HttpCache.IValidationHandler;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.ILifeCycle;



/**
* Cache handler
* @author grro@xlightweb.org
*/
final class CacheHandler implements IHttpRequestHandler, ILifeCycle {
   
   
    /**
     * CookieHandler is unsynchronized by config. See HttpUtils$RequestHandlerInfo
     */
 
  private static final Logger LOG = Logger.getLogger(CacheHandler.class.getName());

  static final String XHEADER_NAME = "X-Cache";
  static final String SKIP_CACHE_HANDLING = "org.xlighhtweb.client.cachehandler.skipcachehandling";

  private final IHttpCache cache;
 
    
    // statistics
    private int countCacheHit = 0;
    private int countCacheMiss = 0;
    private long countCacheableResponse = 0;
    private long countNonCacheableResponse = 0;
 
 
 
  public CacheHandler(HttpClient httpClient, int maxSizeByte) {
      cache = new HttpCache(httpClient);
      cache.setMaxSize(maxSizeByte);  
    }
 
 
  public void onInit() {
  }

 
  public void onDestroy() throws IOException {
    cache.close();   
  }

  void setSharedCache(boolean isSharedCache) {
      cache.setSharedCache(isSharedCache);
  }
 
  boolean isSharedCache() {
      return cache.isSharedCache();
  }
     
  void setMaxCacheSizeBytes(int sizeBytes) {
      cache.setMaxSize(sizeBytes);
  }
 
  int getMaxCacheSizeBytes() {
      return cache.getMaxSize();
  }
 
  int getCurrentCacheSizeBytes() {
        return cache.getCurrentSize();
    }
 
  int getCountCacheHit() {
      return countCacheHit;
  }
   
  int getCountCacheMiss() {
        return countCacheMiss;
    }
 
  long getCountCacheableResponse() {
      return countCacheableResponse;
  }
 
  long getNonCountCacheableResponse() {
        return countNonCacheableResponse;
    }
 
  List<String> getCacheInfo() {
      List<String> result = new ArrayList<String>();
      for (CacheEntry entry : cache.getEntries()) {
          result.add(entry.getRequest().getMethod() + " " + entry.getRequest().getRequestUrl().toString() + " (size " + DataConverter.toFormatedBytesSize(entry.getSize()) + ", age " + DataConverter.toFormatedDuration((System.currentTimeMillis() - entry.getCacheDate().getTime())) + ")");
      }
     
      return result;
  }
 
 
 
  /**
   * {@inheritDoc}
   */
  public void onRequest(final IHttpExchange exchange) throws IOException {

      final IHttpRequest request = exchange.getRequest();

     
      // skip cache handling?
      if ((request.getAttribute(SKIP_CACHE_HANDLING) != null) && (request.getAttribute(SKIP_CACHE_HANDLING).equals("true"))) {
         exchange.forward(request);
         return;
      }

      // is request not cacheable?
      if (!HttpCache.isCacheable(request)) {
          exchange.forward(request);
          return;
      }

     
      Date minFresh = new Date();
        Date maxOld = null;
        boolean isOnlyIfCached = false;
     
      // handle requests cache control directive
      String cacheControl = request.getHeader("Cache-Control");
     
      if (cacheControl != null) {
          for (String directive : cacheControl.split(",")) {
                directive = directive.trim();
                String directiveLower = directive.toLowerCase();
               
                if (directive.equalsIgnoreCase("no-cache") || directive.equalsIgnoreCase("no-store")) {
                    exchange.forward(request);
                    return;
                }
  
               
                if (directiveLower.startsWith("min-fresh=")) {
                    String minRefresh = directive.substring("min-fresh=".length(), directive.length()).trim();
                    minFresh = new Date(System.currentTimeMillis() + HttpUtils.parseLong(minRefresh, 0));
                }      
               
                if (directiveLower.startsWith("max-stale")) {
                    if (directive.length() > "max-stale=".length()) {
                        String maxStale = directive.substring("max-stale=".length(), directive.length()).trim();
                        minFresh = new Date(System.currentTimeMillis() - (1000L * (HttpUtils.parseLong(maxStale, 365 * 24 * 60 * 60))));
                    } else {
                        minFresh = new Date(System.currentTimeMillis() - (1000L * (365L * 24L * 60L * 60L)));
                    }
                }
               
                if (directiveLower.startsWith("max-age=")) {
                    String maxAge = directive.substring("max-age=".length(), directive.length()).trim();
                    maxOld = new Date(System.currentTimeMillis() - (1000L * HttpUtils.parseLong(maxAge, 0)));
                }

               
                if (directive.equalsIgnoreCase("only-if-cached")) {
                    isOnlyIfCached = true;
                }
          }
      }
     
        
      // handle caching
      try {
            CacheEntry ce = cache.get(request, minFresh);
            // is cache entry valid?
            if (ce != null) {
               
                if ((maxOld != null) && (ce.isAfter(maxOld))) {
                    forwardForCache(exchange);
                    return;
                }
               
                // must revalidate?
                if (ce.mustRevalidate(minFresh)) {
                   
                    if (isOnlyIfCached) {
                        countCacheMiss++;
                        exchange.sendError(504);
                       
                    } else {
                        IValidationHandler validationHdl = new IValidationHandler() {
                           
                            public void onRevalidated(boolean isNotModified, CacheEntry ce) {
                               
                                if (isNotModified) {
                                    try {
                                        countCacheHit++;
                                        IHttpResponse resp = ce.newResponse();
                                        resp.setHeader(XHEADER_NAME, "HIT - revalidated (xLightweb)");
                                        exchange.send(resp);
                                    } catch (IOException ioe) {
                                        exchange.sendError(ioe);
                                    }
                                } else  {
                                    try {
                                        countCacheMiss++;
                                        IHttpResponse resp = ce.newResponse();
                                        exchange.send(resp);
                                    } catch (IOException ioe) {
                                        exchange.sendError(ioe);
                                    }
                                }
                            }
                           
                            public void onException(IOException ioe) {
                                exchange.sendError(ioe);
                            }
                           
                        };

                        ce.revalidate(validationHdl);
                    }
                 
                // no, return cached response
                } else {
                    countCacheHit++;
                    IHttpResponse resp = ce.newResponse();
                    resp.setHeader(XHEADER_NAME, "HIT  (xLightweb)");

                    exchange.send(resp);
                }
              
            // no, forward request and intercept response
            } else {
                countCacheMiss++;

                if (isOnlyIfCached) {
                    exchange.sendError(504);
                } else {
                    forwardForCache(exchange);
                }
            }
           
        } catch (IOException ioe) {
            exchange.sendError(ioe);
        }
  } 
 
 
 
  private void forwardForCache(final IHttpExchange exchange) throws IOException {
 
      IHttpRequest request = exchange.getRequest();
      final IHttpRequestHeader headerCopy = request.getRequestHeader().copy();           
           
      final Interaction interaction = new Interaction();
     
     
        if (request.hasBody()) {

            final List<ByteBuffer> bodyCopy = new ArrayList<ByteBuffer>();
           
            NonBlockingBodyDataSource dataSource = request.getNonBlockingBody();
           
           
            ForwarderResponseHandler forwardResponseHandler = new ForwarderResponseHandler(interaction, exchange);
            BodyDataSink dataSink = exchange.forward(request.getRequestHeader(), forwardResponseHandler);

            BodyForwarder bodyForwarder = new BodyForwarder(dataSource, dataSink) {
               
                int currentSize = 0;
               
                @Override
                public void onData(NonBlockingBodyDataSource bodyDataSource, BodyDataSink bodyDataSink) throws BufferUnderflowException, IOException {
                    ByteBuffer[] data = bodyDataSource.readByteBufferByLength(bodyDataSource.available());
                   
                    if (currentSize < cache.getMaxSizeCacheEntry()) {
                        for (ByteBuffer buf : data) {
                            ByteBuffer bufCopy = buf.duplicate();
                            currentSize += bufCopy.remaining();
                            bodyCopy.add(bufCopy);
                        }
                    }
                   
                    bodyDataSink.write(data);
                }
               
               
                @Override
                public void onComplete() {
                    if (currentSize < cache.getMaxSizeCacheEntry()) {
                        try {
                            interaction.setRequest(new HttpRequest(headerCopy, bodyCopy));
                        } catch (IOException ioe) {
                            if (LOG.isLoggable(Level.FINE)) {
                                LOG.fine("error occured by creating/registering cachedResponse " + ioe.toString());
                            }
                        }
                    } else {
                        if (LOG.isLoggable(Level.FINE)) {
                            LOG.fine("do not cache interaction (to large or request not complete)");
                        }
                    }
                }                       
            };
           
            dataSource.setDataHandler(bodyForwarder);
           
           
        } else {
            interaction.setRequest(new HttpRequest(headerCopy));
           
            ForwarderResponseHandler forwardResponseHandler = new ForwarderResponseHandler(interaction, exchange);
            exchange.forward(exchange.getRequest(), forwardResponseHandler);
        }       
  }
 
 
 
   
    @Execution(Execution.NONTHREADED)
    private final class ForwarderResponseHandler implements IHttpResponseHandler {
       
        private final Interaction interaction;
        private final IHttpExchange exchange;

       
        public ForwarderResponseHandler(Interaction interaction, IHttpExchange exchange) {
            this.interaction = interaction;
            this.exchange = exchange;
        }
       
           
        @InvokeOn(InvokeOn.HEADER_RECEIVED)
        public void onResponse(IHttpResponse response) throws IOException {

            // is response cacheable?
            if (HttpCache.isCacheable(response, isSharedCache())) {
                final IHttpResponseHeader responseHeader = response.getResponseHeader();
               
                if (response.hasBody()) {
                    final NonBlockingBodyDataSource dataSource = response.getNonBlockingBody();
                    BodyDataSink dataSink = exchange.send(responseHeader);

                    BodyForwarder bodyForwarder = new BodyForwarder(dataSource, dataSink) {

                        private final List<ByteBuffer> responseBodyCopy = new ArrayList<ByteBuffer>();
                       
                        @Override
                        public void onData(NonBlockingBodyDataSource bodyDataSource, BodyDataSink bodyDataSink) throws BufferUnderflowException, IOException {
                            ByteBuffer[] data = bodyDataSource.readByteBufferByLength(bodyDataSource.available());
                           
                            for (ByteBuffer buf : data) {
                                responseBodyCopy.add(buf.duplicate());
                            }
                           
                            bodyDataSink.write(data);
                        }
                       
                       
                        @Override
                        public void onComplete() {
                            try {
                                IHttpResponse responseCopy = new HttpResponse(responseHeader.copy(), responseBodyCopy);
                                interaction.setResponse(responseCopy);
                               
                            } catch (IOException ioe) {
                                if (LOG.isLoggable(Level.FINE)) {
                                    LOG.fine("error occured by creating/registering cachedResponse " + ioe.toString());
                                }
                            }
                        }
                    };

                    dataSource.setDataHandler(bodyForwarder);
                   
                } else {
                    interaction.setResponse(HttpUtils.copy(response));
                    exchange.send(response);
                }


            // response is not cacheable
            } else {
                countNonCacheableResponse++;
                exchange.send(response);
                return;               
            }
        }
       
      
       
           
        public void onException(IOException ioe) throws IOException {
            exchange.sendError(ioe);
        }
    }
   
   
   
    private final class Interaction {
       
        private IHttpRequest request = null;
        private IHttpResponse response = null;
        private final long startTime;
       
       
        public Interaction() {
            startTime = System.currentTimeMillis();
        }
       
       
        public synchronized void setRequest(IHttpRequest request) {
            this.request = request;
           
            if (response != null) {
                addToCache();
            }
        }

       
        public synchronized void setResponse(IHttpResponse response) {
            this.response = response;
           
            if (request != null) {
                addToCache();
            }
        }
       
       
        private void addToCache() {
           
            try {
                countCacheableResponse++;
                cache.register(request, System.currentTimeMillis() - startTime, response);
               
            } catch (IOException ioe) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("error occured by adding interaction to cache " + ioe.toString());
                }
            }
           
            request = null;
            response = null;
        }      
    }
}
TOP

Related Classes of org.xlightweb.client.CacheHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.