Package org.xlightweb

Source Code of org.xlightweb.FileServiceRequestHandler$SendFileProcess

/*
*  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;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.connection.IWriteCompletionHandler;
import org.xsocket.connection.IConnection.FlushMode;




/**
* Handler implementation to handle file requests. If the requested file does not exists, the
* handler will forward the request. If the handler is embedded within {@link RequestHandlerChain}
* the next handler will be called. If no successor exists, a 404 will be returned.
*
* If the flag <i>isShowDirectoryTree</i> is set (default is false) the directory tree will be
* printed, in case the request file does not exists. See example:
*
* <pre>
*  String basePath = "public/files";
*  IHttpRequestHandler handler = new FileServiceRequestHandler(basePath, true);  // will show the directory tree, if a directory is requested
*  IServer server = new HttpServer(8080, handler);
*  server.start();
* </pre> 
*
*
* @author grro@xlightweb.org
*/
@Execution(Execution.MULTITHREADED)
public class FileServiceRequestHandler implements IHttpRequestHandler {
 
  private static final Logger LOG = Logger.getLogger(FileServiceRequestHandler.class.getName());

  public static final boolean SHOW_DIRECTORY_TREE_DEFAULT = false;
 
  private static final Map<String, String> MIME_TYPE_MAPPING = HttpUtils.getMimeTypeMapping();
 
  private static final int TRANSFER_BYTE_BUFFER_MAX_MAP_SIZE = Integer.parseInt(System.getProperty("org.xsocket.connection.transfer.mappedbytebuffer.maxsize", "65536"));


  private final File fileBase;
  private final boolean isShowDirectoryTree;
  private final Integer expireSec;
 
  private AtomicInteger pendingSendtransactions = new AtomicInteger(0);
 
  private int countFound = 0;
  private int countNotFound = 0;
  
 
  /**
   * constructor
   *
   * @param fileBasepath   the base path
   *
   * @throws FileNotFoundException  if the base path not exists
   */
  public FileServiceRequestHandler(String fileBasepath) throws FileNotFoundException {
    this(fileBasepath, SHOW_DIRECTORY_TREE_DEFAULT);
  }
 
 

    /**
     * constructor
     *
     * @param fileBasepath   the base path
     * @param expireSec      the expire time sec, which will be added to each response for caching or <code>null</code>
     *
     * @throws FileNotFoundException  if the base path not exists
     */
    public FileServiceRequestHandler(String fileBasepath, Integer expireSec) throws FileNotFoundException {
        this(fileBasepath, expireSec, SHOW_DIRECTORY_TREE_DEFAULT);
    }
 
  /**
   * constructor
   *
   * @param fileBasepath         the base path
   * @param isShowDirectoryTree  true, if the directory tree will been shown, if the requests file is a directory
   *
   * @throws FileNotFoundException  if the base path not exists
   */
  public FileServiceRequestHandler(String fileBasepath, boolean isShowDirectoryTree) throws FileNotFoundException {
      this(fileBasepath, null, isShowDirectoryTree);
  }
 
 
 
  /**
     * constructor
     *
     * @param fileBasepath         the base path
     * @param expireSec            the expire time sec, which will be added to each response for caching or <code>null</code>
     * @param isShowDirectoryTree  true, if the directory tree will been shown, if the requests file is a directory
     *
     * @throws FileNotFoundException  if the base path not exists
     */
    public FileServiceRequestHandler(String fileBasepath, Integer expireSec, boolean isShowDirectoryTree) throws FileNotFoundException {
        this.fileBase = new File(fileBasepath);
        this.expireSec = expireSec;
        this.isShowDirectoryTree = isShowDirectoryTree;
       
        if (!new File(fileBasepath).exists()) {
            throw new FileNotFoundException("base path "+ fileBasepath + "does not exits");
        }
    }

 
  /**
   * returns if the directory tree should be shown
   *
   * @return true, if the directory tree should be shown
   */
  boolean isShowDirectoryTree() {
    return isShowDirectoryTree;
  }
 
 
  /**
   * return the base path
   *
   * @return  the base path
   */
  String getBasepath() {
    return fileBase.getAbsolutePath();
  }
 
 
  /**
   * returns the number of pending send transactions
   * @return the number of pending send transactions
   */
  int getPendingSendTransactions() {
    return pendingSendtransactions.get();
  }

 
  /**
   * returns the number of found
   * @return the number of found
   */
  int getCountFound() {
    return countFound;
  }
 
  /**
   * returns the number of not found
   * @return the number of not found
   */
  int getCountNotFound() {
    return countNotFound;
  }
 
 
  /**
   * {@inheritDoc}
   */
  @Execution(Execution.MULTITHREADED)
  @InvokeOn(InvokeOn.MESSAGE_RECEIVED)
  public void onRequest(IHttpExchange exchange) throws IOException {
   
    IHttpRequest request = exchange.getRequest();
   
    // only GET or POST is supported by this handler
    if (request.getMethod().equalsIgnoreCase("GET") || request.getMethod().equalsIgnoreCase("POST")) {
     
      String requestURI = URLDecoder.decode(request.getRequestURI(), "UTF-8");
      int ctxLength = request.getContextPath().length() + request.getRequestHandlerPath().length();
     
     
      if (requestURI.length() > ctxLength) {

        String filepath = requestURI.substring(ctxLength, requestURI.length());
       
        // file defined?
        if (filepath.length() > 0) {
         
          // converting slash to file system's one
          filepath = filepath.replaceAll("[/\\\\]+", "\\" + File.separator);
 
          // create native path
          String path = fileBase.getAbsolutePath() + filepath;
         
          // removing tailing file separator 
          if (path.endsWith(File.separator)) {
            path = path.substring(0, path.length() - 1);
          }
 
          File file = new File(path);
         
          // does file exits?
          if (file.exists()) {
           
            // is file?
            if (file.isFile()) {
              long lastModified = (file.lastModified() / 1000) * 1000;    // ignore millis
              String ifModifiedSinceRequestHeader = request.getHeader("If-Modified-Since");

              if (ifModifiedSinceRequestHeader != null) {
                long ifModifedSince = DataConverter.toDate(ifModifiedSinceRequestHeader).getTime();
               
                if (lastModified <= ifModifedSince) {
                  IHttpResponse response = new HttpResponse(304);
                  enhanceFoundResponseHeader(response.getResponseHeader(), lastModified, file);
                  if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine(filepath + " requested. returning not modified");
                  }
                 
                  exchange.send(response);
                  return;
                }
              }
             
             
              HttpResponseHeader responseHeader = new HttpResponseHeader(200);
              enhanceFoundResponseHeader(responseHeader, lastModified, file);
              if (LOG.isLoggable(Level.FINE)) {
                LOG.fine(filepath + " requested. returning data");
              }
             
             
              BodyDataSink outChannel = exchange.send(responseHeader, (int) file.length());
              SendFileProcess sendFile = new SendFileProcess(exchange.getConnection().getId(), outChannel, file);
              sendFile.start();
             
              countFound++;
             
              return;
             
            // ... on, it is a directory
            } else {
              handleNotFound(exchange, request, file);
              return;
            }
           
          // file does not exit
          } else {
            handleNotFound(exchange, request, file);
            return;
          }
        }
       
      // no file defined
      } else {
        exchange.sendError(404, request.getRequestURI() + " not found");
        return;
      }
    }   
   
    handleNotFound(exchange, request, fileBase);
 
 
 

  @Execution(Execution.MULTITHREADED)
  private final class SendFileProcess implements IWriteCompletionHandler {
     
    private final String id;
    private final File file;
    private final RandomAccessFile raf;
      private final FileChannel fc;
      private final BodyDataSink outChannel;
     
      private long remaining = 0;
        private long offset = 0;
        private long length = 0;

      private int bufferHashcode = 0;
     
      public SendFileProcess(String id, BodyDataSink outChannel, File file) throws IOException {
        this.id = id;
        this.file = file;
        this.raf = new RandomAccessFile(file, "r");
        fc = raf.getChannel();
          this.outChannel = outChannel;         
          outChannel.setFlushmode(FlushMode.ASYNC);
         
          remaining = file.length();
        }
     
     
      public void start() throws IOException {
        pendingSendtransactions.incrementAndGet();
        write();
      }
     
     
      public void onWritten(int written) throws IOException {
       
        if (LOG.isLoggable(Level.FINE)) {
          if (remaining > 0) {
            LOG.fine("[" + id + "] {" + bufferHashcode + "} data (size=" + written + " bytes) has been written. Writing next chunk");
          } else {
            LOG.fine("[" + id + "] {" + bufferHashcode + "} data (size=" + written + " bytes) has been written.");
          }
          }
         
            write();
      }
     
     
      private void write() throws IOException {
         
        // remaining data to write?
        if (remaining > 0) {
         
          // limit the buffer allocation size
            if (remaining > TRANSFER_BYTE_BUFFER_MAX_MAP_SIZE) {
                  length = TRANSFER_BYTE_BUFFER_MAX_MAP_SIZE;
              } else {
                  length = remaining;
              }
             
              MappedByteBuffer buffer = fc.map(MapMode.READ_ONLY, offset, length);
              ByteBuffer[] bufs = new ByteBuffer[] { buffer };
              bufferHashcode = bufs.hashCode();

          if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("[" + id + "] {" + bufferHashcode + "} writing data (size=" + length + " bytes)");
          }             
              outChannel.write(bufs, this);
                 
              offset += length;
              remaining -= length;

          // no, closing channel
          } else {
            closeFile();
          outChannel.close();
          }
      }
     
      public void onException(IOException ioe) {
          closeFile();
          outChannel.destroy();
      }
     
     
      private void closeFile() {
        try {
          pendingSendtransactions.decrementAndGet();       
          fc.close();
          raf.close();
        } catch (IOException ioe) {
          if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("error occured by clsoing file channel " + file.getAbsolutePath());
          }
        }
      }
  }
   
   
   
  private void enhanceFoundResponseHeader(IHttpResponseHeader responseHeader, long lastModified, File file) {
    responseHeader.setDate(DataConverter.toFormatedRFC822Date(System.currentTimeMillis()));
    if (expireSec == null) {
        responseHeader.setHeader("Last-Modified", DataConverter.toFormatedRFC822Date(lastModified));
    } else {
        HttpUtils.setExpireHeaders(responseHeader, expireSec);
    }
   
    String filename = file.getName();
    int pos = filename.lastIndexOf(".");
    if (pos != -1) {
      String extension = filename.substring(pos + 1, filename.length());
      String mimeType = MIME_TYPE_MAPPING.get(extension);
      if (mimeType == null) {
        responseHeader.setContentType("application/octet-stream");
      } else {
          if (HttpUtils.isTextMimeType(mimeType)) {
              String encoding = HttpUtils.detectEncoding(file);
              if (encoding != null) {
                  mimeType = mimeType + "; charset=" + encoding;
              }
          }
        responseHeader.setContentType(mimeType);
      }
    }
  }
 
   
  private void handleNotFound(IHttpExchange exchange, IHttpRequest request, File file) throws IOException {
   
    countNotFound++;
   
    if ((isShowDirectoryTree) &&
      (file.isDirectory() &&
      (fileBase.getAbsolutePath().length() <= file.getAbsolutePath().length()))) {
        String body = printDirectoryTree(request, file);
        exchange.send(new HttpResponse(200, "text/html", body));
        return;
    }
   
    exchange.forward(request, new HttpResponseHandler(exchange));
  }
 
 
  private static final class HttpResponseHandler implements IHttpResponseHandler {
   
    private IHttpExchange exchange = null;
   
    public HttpResponseHandler(IHttpExchange exchange) {
      this.exchange = exchange;
    }
   
    public void onResponse(IHttpResponse response) throws IOException {
      exchange.send(response);
    }
   
    public void onException(IOException ioe) {
      exchange.sendError(500);
    }
  }
 
 

  private String printDirectoryTree(IHttpRequest request, File directory) throws IOException {
    StringBuilder sb = new StringBuilder();
   
    String requestResource = directory.getAbsolutePath();
    requestResource = requestResource.substring(fileBase.getAbsolutePath().length(), requestResource.length());
   
    if (request.getRequestHandlerPath().length() > 0) {
      requestResource = request.getRequestHandlerPath() + "/" + requestResource;
    }
   
    if (request.getContextPath().length() > 0) {
      requestResource = request.getContextPath() + "/" + requestResource;
    }
   
    requestResource = requestResource.replace("\\", "/");
   
   
    sb.append("<html>\r\n");
    sb.append("  <!-- This page is auto-generated by xLightweb (http://xLightweb.org) -->\r\n");
    sb.append("  <head>\r\n");
    sb.append("    <title>Index of " + requestResource + "</title>\r\n");
    sb.append("  </head>\r\n");
    sb.append("  <body>\r\n");
    sb.append("    <H1 style=\"color:#0a328c;font-size:1.5em;\">Index of " + requestResource + "</H1>\r\n");
    sb.append("    <p style=\"font-size:0.8em;\">\r\n");

   
    sb.append("    <table border=\"0\" style=\"color:#0a328c;font-size:1.0em;\">\r\n");


   
    for (File file : directory.listFiles()) {
      sb.append("      <tr>");
     
      sb.append("        <td align=\"right\">");
     
      if (file.isDirectory()) {
        sb.append("[DIR]");
      } else {
        sb.append("[TXT]");
      }
     
      sb.append("        </td>\r\n");

     
      sb.append("        <td>");
     
     
      sb.append("<a href=");
     
      String[] parts = requestResource.split("/");
      if (parts.length > 0) {
        sb.append(URLEncoder.encode(parts[parts.length - 1], "UTF-8") + "/");
      }
     
      sb.append(URLEncoder.encode(file.getName(), "UTF-8") + "> " + file.getName() + "</a>");
      sb.append("        </td>\r\n");
     
      sb.append("        <td>");
      sb.append(DataConverter.toFormatedDate(file.lastModified()));
      sb.append("        </td>\r\n");
     
      sb.append("        <td align=\"right\">");
      if (!file.isDirectory()) {
        sb.append(DataConverter.toFormatedBytesSize(file.length()));
      } else {
        sb.append("-");
      }
      sb.append("        </td>\r\n");
     
      sb.append("      </tr>");
    }
   
    sb.append("    </table>\r\n");
   
    sb.append("    </p>\r\n");
    sb.append("    <p style=\"font-size:0.8em;\">" + new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z").format(new Date()) + "    xLightweb (" +
          HttpUtils.getImplementationVersion() + ") at " + request.getServerName() +
          " Port " + request.getServerPort() + "</p>\r\n");
    sb.append("  </body>\r\n");
    sb.append("</html>\r\n");
    return sb.toString();
 
}
TOP

Related Classes of org.xlightweb.FileServiceRequestHandler$SendFileProcess

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.