Package org.archive.wayback.resourcestore.locationdb

Source Code of org.archive.wayback.resourcestore.locationdb.FileProxyServlet$FileDataSource

/*
*  This file is part of the Wayback archival access software
*   (http://archive-access.sourceforge.net/projects/wayback/).
*
*  Licensed to the Internet Archive (IA) by one or more individual
*  contributors.
*
*  The IA licenses this file to You under the Apache License, Version 2.0
*  (the "License"); you may not use this file except in compliance with
*  the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/
package org.archive.wayback.resourcestore.locationdb;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.ChunkedInputStream;
import org.archive.util.anvl.ANVLRecord;
import org.archive.wayback.util.http.HttpRequestMessage;
import org.archive.wayback.util.http.HttpResponse;
import org.archive.wayback.util.webapp.AbstractRequestHandler;

/**
* ServletRequestContext interface which uses a ResourceFileLocationDB to
* reverse proxy an incoming HTTP request for a file by name to it's actual
* back-end location. This will also forward HTTP byte range requests to the
* final location.
*
* @author brad
* @version $Date$, $Revision$
*/
public class FileProxyServlet extends AbstractRequestHandler {
  private static final Logger LOGGER = Logger.getLogger(FileProxyServlet.class
      .getName());

  private static final int BUF_SIZE = 4096;
  private static final String RANGE_HTTP_HEADER = "Range";
  private static final String DEFAULT_CONTENT_TYPE = "application/x-gzip";
  private static final String HEADER_BYTES_PREFIX = "bytes=";
  private static final String HEADER_BYTES_SUFFIX= "-";

  private static final String FILE_REGEX = "/([^/]*)$";
  private static final String FILE_OFFSET_REGEX = "/([^/]*)/(\\d*)$";
 
  private static final Pattern FILE_PATTERN =
    Pattern.compile(FILE_REGEX);
  private static final Pattern FILE_OFFSET_PATTERN =
    Pattern.compile(FILE_OFFSET_REGEX);

  private static final long serialVersionUID = 1L;

  private ResourceFileLocationDB locationDB = null;

  private int socketTimeoutMs = 5000;

  private int connectTimeoutMs = 1000;
 
  public boolean handleRequest(HttpServletRequest httpRequest,
      HttpServletResponse httpResponse) throws IOException,
      ServletException {

    ResourceLocation location = parseRequest(httpRequest);
    if(location == null) {
      httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST,
      "no/invalid name");
    } else {

      String urls[] = locationDB.nameToUrls(location.getName());

      if(urls == null || urls.length == 0) {
       
        LOGGER.warning("No locations for " + location.getName());
        httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND,
            "Unable to locate("+ location.getName() +")");
      } else {
       
        DataSource ds = null;
        for(String urlString : urls) {
          try {
            ds = locationToDataSource(urlString,
                location.getOffset());
            if(ds != null) {
              break;
            }
          } catch(IOException e) {
            LOGGER.warning("failed proxy of " + urlString + " " +
                e.getLocalizedMessage());
          }
        }
        if(ds == null) {
          LOGGER.warning("No successful locations for " +
              location.getName());
          httpResponse.sendError(HttpServletResponse.SC_BAD_GATEWAY,
              "failed proxy of ("+ location.getName() +")");
         
        } else {
          httpResponse.setStatus(HttpServletResponse.SC_OK);
          // BUGBUG: this will be broken for non compressed data...
          httpResponse.setContentType(ds.getContentType());
          httpResponse.setBufferSize(BUF_SIZE);
          ds.copyTo(httpResponse.getOutputStream());
        }
      }
    }
    return true;
  }

  private DataSource locationToDataSource(String location, long offset)
    throws IOException {
    DataSource ds = null;
    if(location.startsWith("http://")) {
      URL url = new URL(location);
      String hostname = url.getHost();
      int port = url.getPort();
      if(port == -1) {
        port = 80;
      }
      byte GET[] = "GET".getBytes();
      byte HTTP11[] = "HTTP/1.1".getBytes();
      InetAddress addr = InetAddress.getByName(hostname);
      HttpRequestMessage requestMessage = new HttpRequestMessage(
          GET,url.getFile().getBytes(),HTTP11);
      ANVLRecord headers = new ANVLRecord();
      headers.addLabelValue("Host", hostname);
     
     
      if(offset != 0) {
        headers.addLabelValue(RANGE_HTTP_HEADER,
            HEADER_BYTES_PREFIX + String.valueOf(offset) +
              HEADER_BYTES_SUFFIX);
      }
      InetSocketAddress sockAddr = new InetSocketAddress(addr,port);
      Socket socket = new Socket();
      socket.setSoTimeout(socketTimeoutMs);
      socket.setReceiveBufferSize(BUF_SIZE);

      socket.connect(sockAddr, connectTimeoutMs);
      OutputStream socketOut = socket.getOutputStream();
      InputStream socketIn = socket.getInputStream();
      socketOut.write(requestMessage.getBytes(true));
      socketOut.write(headers.getUTF8Bytes());
      socketOut.flush();
      HttpResponse response = HttpResponse.load(socketIn);
      String contentType = response.getHeaders().asMap().get("Content-Type");
      if(contentType == null) {
        contentType = "application/unknown";
      }
      String xferEncoding = response.getHeaders().asMap().get("Transfer-Encoding");
     
      if(xferEncoding != null) {
        if(xferEncoding.equals("chunked")) {
          socketIn = new ChunkedInputStream(socketIn);
        }
      }

      ds = new URLDataSource(socketIn,contentType);

    } else {
      // assume a local file path:
      File f = new File(location);
      if(f.isFile() && f.canRead()) {
        long size = f.length();
        if(size < offset) {
          throw new IOException("short file " + location + " cannot" +
              " seek to offset " + offset);
        }
        RandomAccessFile raf = new RandomAccessFile(f,"r");
        raf.seek(offset);
        // BUGBUG: is it compressed?
        ds = new FileDataSource(raf,DEFAULT_CONTENT_TYPE);
       
      } else {
        throw new IOException("No readable file at " + location);
      }
     
    }

    return ds;
  }
 
  private ResourceLocation parseRequest(HttpServletRequest request) {
    ResourceLocation location = null;
   
    String path = request.getRequestURI();
    Matcher fo = FILE_OFFSET_PATTERN.matcher(path);
    if(fo.find()) {
      location = new ResourceLocation(fo.group(1),
          Long.parseLong(fo.group(2)));
    } else {
      fo = FILE_PATTERN.matcher(path);
      if(fo.find()) {
        String rangeHeader = request.getHeader(RANGE_HTTP_HEADER);
        if(rangeHeader != null) {
          if(rangeHeader.startsWith(HEADER_BYTES_PREFIX)) {
            rangeHeader = rangeHeader.substring(
                HEADER_BYTES_PREFIX.length());
            if(rangeHeader.endsWith(HEADER_BYTES_SUFFIX)) {
              rangeHeader = rangeHeader.substring(0,
                  rangeHeader.length() -
                  HEADER_BYTES_SUFFIX.length());
            }
          }
          location = new ResourceLocation(fo.group(1),
              Long.parseLong(rangeHeader));
        } else {
          location = new ResourceLocation(fo.group(1));
        }
      }
    }
    return location;
  }
 
  /**
   * @return the locationDB
   */
  public ResourceFileLocationDB getLocationDB() {
    return locationDB;
  }

  /**
   * @param locationDB the locationDB to set
   */
  public void setLocationDB(ResourceFileLocationDB locationDB) {
    this.locationDB = locationDB;
  }
 
  /**
   * @return the socketTimeoutMs
   */
  public int getSocketTimeoutMs() {
    return socketTimeoutMs;
  }

  /**
   * @param socketTimeoutMs the socketTimeoutMs to set
   */
  public void setSocketTimeoutMs(int socketTimeoutMs) {
    this.socketTimeoutMs = socketTimeoutMs;
  }

  /**
   * @return the connectTimeoutMs
   */
  public int getConnectTimeoutMs() {
    return connectTimeoutMs;
  }

  /**
   * @param connectTimeoutMs the connectTimeoutMs to set
   */
  public void setConnectTimeoutMs(int connectTimeoutMs) {
    this.connectTimeoutMs = connectTimeoutMs;
  }

  private class ResourceLocation {
    private String name = null;
    private long offset = 0;
    public ResourceLocation(String name, long offset) {
      this.name = name;
      this.offset = offset;
    }
    public ResourceLocation(String name) {
      this(name,0);
    }
    public String getName() {
      return name;
    }
    public long getOffset() {
      return offset;
    }
  }
 
  private interface DataSource {
    public void copyTo(OutputStream os) throws IOException;
    public String getContentType();
  }
  private class FileDataSource implements DataSource {
    private RandomAccessFile raf = null;
    private String contentType = null;
    public FileDataSource(RandomAccessFile raf, String contentType) {
      this.raf = raf;
      this.contentType = contentType;
    }
    public String getContentType() {
      return contentType;
    }
    public void copyTo(OutputStream os) throws IOException {
      byte[] buffer = new byte[BUF_SIZE];
      try {
        int r = -1;
        while((r = raf.read(buffer, 0, BUF_SIZE)) != -1) {
          os.write(buffer, 0, r);
        }
      } finally {
        raf.close();
      }
    }
  }
  private class URLDataSource implements DataSource {
    private InputStream is = null;
    private String contentType = null;
    public URLDataSource(InputStream is,String contentType) {
      this.is = is;
      this.contentType = contentType;
    }
    public String getContentType() {
      return contentType;
    }
    public void copyTo(OutputStream os) throws IOException {
      byte[] buffer = new byte[BUF_SIZE];
      try {
        int r = -1;
        while((r = is.read(buffer, 0, BUF_SIZE)) != -1) {
          os.write(buffer, 0, r);
        }
      } finally {
        is.close();
      }
    }
  }
}
TOP

Related Classes of org.archive.wayback.resourcestore.locationdb.FileProxyServlet$FileDataSource

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.