Package com.caucho.vfs

Source Code of com.caucho.vfs.HttpStream

/*
* Copyright (c) 1998-2010 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT.  See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
*   Free Software Foundation, Inc.
*   59 Temple Place, Suite 330
*   Boston, MA 02111-1307  USA
*
* @author Scott Ferguson
*/

package com.caucho.vfs;

import com.caucho.util.Alarm;
import com.caucho.util.CharBuffer;
import com.caucho.util.L10N;
import com.caucho.util.Log;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Underlying stream handling HTTP requests.
*/
class HttpStream extends StreamImpl {
  private static final L10N L = new L10N(HttpStream.class);
 
  private static final Logger log
    = Logger.getLogger(HttpStream.class.getName());
  // reserved headers that should not be passed to the HTTP server
  private static HashMap<String,String> _reserved;

  private static final Object LOCK = new Object();
 
  // Saved keepalive stream for a new request.
  private static HttpStream _savedStream;
  // Time the stream was saved
  private static long _saveTime;
 
  private long _socketTimeout = 30000L;

  private boolean _isSSL;

  private Socket _s;
  private InputStream _is;
  private OutputStream _os;
  private ReadStream _rs;
  private WriteStream _ws;

  // The server's host name
  private String _host;
  // The server's port
  private int _port;

  private String _virtualHost;

  // the method
  private String _method;
  // true for a HEAD stream
  private boolean _isHead;
  // true for a POST stream
  private boolean _isPost;
 
  // true for an HTTP 1.1 client
  private boolean _isHttp11 = true;

  // buffer containing the POST data
  private MemoryStream _tempStream;

  // true if keepalive is allowed
  private boolean _isKeepalive = true;
  // true after the request has been sent
  private boolean _didGet;
  // content length from the returned response
  private int _contentLength;
  // true if the response was chunked
  private boolean _isChunked;
  // length of the current chunk, -1 on eof
  private int _chunkLength;
  // the request is done
  private boolean _isRequestDone;

  private HashMap<String,Object> _attributes;

  // Used to read unread bytes on recycle.
  private byte []_tempBuffer;

  /**
   * Create a new HTTP stream.
   */
  private HttpStream(Path path, String host, int port, Socket s)
    throws IOException
  {
    _s = s;

    _host = host;
    _port = port;
   
    _is = _s.getInputStream();
    _os = _s.getOutputStream();

    _ws = VfsStream.openWrite(_os);
    _rs = VfsStream.openRead(_is, _ws);

    _attributes = new HashMap<String,Object>();

    init(path);
  }

  /**
   * Opens a new HTTP stream for reading, i.e. a GET request.
   *
   * @param path the URL for the stream
   *
   * @return the opened stream
   */
  static HttpStreamWrapper openRead(HttpPath path) throws IOException
  {
    HttpStream stream = createStream(path);
    stream._isPost = false;

    return new HttpStreamWrapper(stream);
  }

  /**
   * Opens a new HTTP stream for reading and writing, i.e. a POST request.
   *
   * @param path the URL for the stream
   *
   * @return the opened stream
   */
  static HttpStreamWrapper openReadWrite(HttpPath path) throws IOException
  {
    HttpStream stream = createStream(path);
    stream._isPost = true;

    return new HttpStreamWrapper(stream);
  }

  /**
   * Creates a new HTTP stream.  If there is a saved connection to
   * the same host, use it.
   *
   * @param path the URL for the stream
   *
   * @return the opened stream
   */
  static private HttpStream createStream(HttpPath path) throws IOException
  {
    String host = path.getHost();
    int port = path.getPort();

    HttpStream stream = null;
    long streamTime = 0;
    synchronized (LOCK) {
      if (_savedStream != null
          && host.equals(_savedStream.getHost())
          && port == _savedStream.getPort()) {
        stream = _savedStream;
        streamTime = _saveTime;
        _savedStream = null;
      }
    }

    if (stream == null) {
    }
    // if the stream is still valid, use it
    else if (Alarm.getCurrentTime() < streamTime + 5000) {
      stream.init(path);
      return stream;
    }
    // if the stream has timed out, close it
    else {
      try {
        stream._isKeepalive = false;
        stream.close();
      } catch (IOException e) {
        log.log(Level.FINE, e.toString(), e);
      }
    }

    Socket s;

    try {
      s = new Socket(host, port);
     
      if (path instanceof HttpsPath) {
        SSLContext context = SSLContext.getInstance("TLS");

  javax.net.ssl.TrustManager tm =
    new javax.net.ssl.X509TrustManager() {
      public java.security.cert.X509Certificate[]
        getAcceptedIssuers() {
        return null;
      }
      public void checkClientTrusted(
             java.security.cert.X509Certificate[] cert, String foo) {
      }
      public void checkServerTrusted(
             java.security.cert.X509Certificate[] cert, String foo) {
      }
    };

     
        context.init(null, new javax.net.ssl.TrustManager[] { tm }, null);
        SSLSocketFactory factory = context.getSocketFactory();

        s = factory.createSocket(s, host, port, true);
      }
    } catch (ConnectException e) {
      throw new ConnectException(path.getURL() + ": " + e.getMessage());
    } catch (Exception e) {
      throw new ConnectException(path.getURL() + ": " + e.toString());
    }

    int socketTimeout = 300 * 1000;

    try {
      s.setSoTimeout(socketTimeout);
    } catch (Exception e) {
    }
         
    return new HttpStream(path, host, port, s);
  }

  /**
   * Initializes the stream for the next request.
   */
  private void init(Path path)
  {
    _contentLength = -1;
    _isChunked = false;
    _isRequestDone = false;
    _didGet = false;
    _isPost = false;
    _isHead = false;
    _method = null;
    _attributes.clear();
   
    setPath(path);

    if (path instanceof HttpPath)
      _virtualHost = ((HttpPath) path).getVirtualHost();
  }

  /**
   * Set if this should be an SSL connection.
   */
  public void setSSL(boolean isSSL)
  {
    _isSSL = isSSL;
  }

  /**
   * Set if this should be an SSL connection.
   */
  public boolean isSSL()
  {
    return _isSSL;
  }

  /**
   * Sets the method
   */
  public void setMethod(String method)
  {
    _method = method;
  }

  /**
   * Sets true if we're only interested in the head.
   */
  public void setHead(boolean isHead)
  {
    _isHead = isHead;
  }

  /**
   * Returns the stream's host.
   */
  public String getHost()
  {
    return _host;
  }

  /**
   * Returns the stream's port.
   */
  public int getPort()
  {
    return _port;
  }
 
  /**
   * Sets the http version.
   */
  public void setHttp10()
  {
    _isHttp11 = false;
  }
 
  /**
   * Sets the http version.
   */
  public void setHttp11()
  {
    _isHttp11 = true;
  }
 
  /**
   * Returns a header from the response returned from the HTTP server.
   *
   * @param name name of the header
   * @return the header value.
   */
  public Object getAttribute(String name)
    throws IOException
  {
    if (! _didGet)
      getConnInput();
   
    return _attributes.get(name.toLowerCase());
  }

  /**
   * Returns an iterator of the returned header names.
   */
  public Iterator getAttributeNames()
    throws IOException
  {
    if (! _didGet)
      getConnInput();

    return _attributes.keySet().iterator();
  }

  /**
   * Sets a header for the request.
   */
  public void setAttribute(String name, Object value)
  {
    if (name.equals("method"))
      setMethod((String) value);
    else if (name.equals("socket-timeout")) {
      if (value instanceof Integer) {
        int socketTimeout = ((Integer) value).intValue();

        if (socketTimeout > 0) {
          try {
            if (_s != null)
              _s.setSoTimeout(socketTimeout);
          } catch (Exception e) {

          }
        }
      }
    }
    else {
      Object oldValue = _attributes.put(name.toLowerCase(), value);

      if (oldValue instanceof String[]) {
  String []old = (String []) oldValue;
  String []newValue = new String[old.length + 1];
  System.arraycopy(old, 0, newValue, 0, old.length);
  newValue[old.length] = String.valueOf(value);
  _attributes.put(name.toLowerCase(), newValue);
      }
      else if (oldValue != null) {
  String []newValue = new String[] { String.valueOf(oldValue),
             String.valueOf(value) };
  _attributes.put(name.toLowerCase(), newValue);
      }
    }
  }

  /**
   * Remove a header for the request.
   */
  public void removeAttribute(String name)
  {
    _attributes.remove(name.toLowerCase());
  }

  /**
   * Sets the timeout.
   */
  public void setSocketTimeout(long timeout)
    throws SocketException
  {
    if (_s != null)
      _s.setSoTimeout((int) timeout);
  }

  /**
   * The stream is always writable (?)
   */
  public boolean canWrite()
  {
    return true;
  }

  /**
   * Writes a buffer to the underlying stream.
   *
   * @param buffer the byte array to write.
   * @param offset the offset into the byte array.
   * @param length the number of bytes to write.
   * @param isEnd true when the write is flushing a close.
   */
  public void write(byte []buf, int offset, int length, boolean isEnd)
    throws IOException
  {
    if (! _isPost)
      return;

    if (_tempStream == null)
      _tempStream = new MemoryStream();

    _tempStream.write(buf, offset, length, isEnd);
  }

  /**
   * The stream is readable.
   */
  public boolean canRead()
  {
    return true;
  }

  /**
   * Read data from the connection.  If the request hasn't yet been sent
   * to the server, send it.
   */
  public int read(byte []buf, int offset, int length) throws IOException
  {
    try {
      return readInt(buf, offset, length);
    } catch (IOException e) {
      _isKeepalive = false;
      throw e;
    } catch (RuntimeException e) {
      _isKeepalive = false;
      throw e;
    }
  }
 
  /**
   * Read data from the connection.  If the request hasn't yet been sent
   * to the server, send it.
   */
  public int readInt(byte []buf, int offset, int length) throws IOException
  {
    if (! _didGet)
      getConnInput();

    if (_isRequestDone)
      return -1;

    try {
      int len = length;

      if (_isChunked) {
  if (_chunkLength == 0) {
    int ch;

    for (ch = _rs.read();
         ch >= 0 && (ch == '\r' || ch == '\n' || ch == ' ');
         ch = _rs.read()) {
    }
       
    for (; ch >= 0 && ch != '\n'; ch = _rs.read()) {
      if (ch >= '0' && ch <= '9')
        _chunkLength = 16 * _chunkLength + ch - '0';
      else if (ch >= 'a' && ch <= 'f')
        _chunkLength = 16 * _chunkLength + ch - 'a' + 10;
      else if (ch >= 'A' && ch <= 'F')
        _chunkLength = 16 * _chunkLength + ch - 'A' + 10;
    }

    if (_chunkLength == 0) {
      _isRequestDone = true;
      return -1;
    }
  }
  else if (_chunkLength < 0)
    return -1;
     
  if (_chunkLength < len)
    len = _chunkLength;
      }
      else if (_contentLength < 0) {
      }
      else if (_contentLength == 0) {
  _isRequestDone = true;
  return -1;
      }
      else if (_contentLength < len)
  len = _contentLength;

      len = _rs.read(buf, offset, len);

      if (len < 0) {
      }
      else if (_isChunked)
  _chunkLength -= len;
      else if (_contentLength > 0)
  _contentLength -= len;

      return len;
    } catch (IOException e) {
      _isKeepalive = false;
      throw e;
    } catch (RuntimeException e) {
      _isKeepalive = false;
      throw e;
    }
  }

  /**
   * Sends the request and initializes the response.
   */
  private void getConnInput() throws IOException
  {
    if (_didGet)
      return;

    try {
      getConnInputImpl();
    } catch (IOException e) {
      _isKeepalive = false;
      throw e;
    } catch (RuntimeException e) {
      _isKeepalive = false;
      throw e;
    }
  }

  /**
   * Send the request to the server, wait for the response and parse
   * the headers.
   */
  private void getConnInputImpl() throws IOException
  {
    if (_didGet)
      return;

    _didGet = true;
   
    if (log.isLoggable(Level.FINER))
      log.finer(this + " connect " + _method + " post=" + _isPost);

    if (_method != null) {
      _ws.print(_method);
      _ws.print(' ');
    }
    else if (_isPost)
      _ws.print("POST ");
    else if (_isHead)
      _ws.print("HEAD ");
    else
      _ws.print("GET ");

    // Not splitting query? Also fullpath?
    _ws.print(_path.getPath());

    if (_path.getQuery() != null) {
      _ws.print("?");
      _ws.print(_path.getQuery());
    }
   
    if (_isHttp11) {
      _ws.print(" HTTP/1.1\r\n");
      Object host = getAttribute("host");
      _ws.print("Host: ");
      if (host != null) {
        _ws.print(host);
      }
      else if (_virtualHost != null)
        _ws.print(_virtualHost);
      else {
        _ws.print(_path.getHost());
        if (_path.getPort() != 80) {
      _ws.print(":");
      _ws.print(String.valueOf(_path.getPort()));
        }
      }
    }
    else
      _ws.print(" HTTP/1.0\r\n");
   
    _ws.print("\r\n");
   
    Object userAgent = getAttribute("User-Agent");
    if (userAgent == null)
      _ws.print("User-Agent: Mozilla/4.0 (compatible; Resin 1.0; JDK)\r\n");
    else
      _ws.print("User-Agent: " + userAgent + "\r\n");
    Iterator iter = getAttributeNames();
    while (iter.hasNext()) {
      String name = (String) iter.next();
      if (_reserved.get(name.toLowerCase()) == null) {
  Object value = getAttribute(name);
  if (value instanceof String[]) {
    String []values = (String []) value;
    for (int i = 0; i < values.length; i++) {
      _ws.print(name + ": " + values[i] + "\r\n");
    }
  }
  else
    _ws.print(name + ": " + value + "\r\n");
      }
    }
    if (! _isKeepalive)
      _ws.print("Connection: close\r\n");
   
    if (_isPost) {
      int writeLength = 0;
      if (_tempStream != null)
        writeLength = _tempStream.getLength();
     
      Object contentLength = getAttribute("Content-Length");

      if (contentLength != null) {
        long len = 0;
       
        if (contentLength instanceof Number)
          len = ((Number) contentLength).longValue();
        else {
          String lenStr = contentLength.toString().trim();
         
          for (int i = 0; i < lenStr.length(); i++) {
            char ch = lenStr.charAt(i);
           
            if ('0' <= ch && ch <= '9')
              len = len * 10 + ch - '0';
            else
              break;
          }
        }
       
  // server/1963
        if (len != writeLength) {
    throw new IOException(L.l("Content-Length={0} but only received {1}",
            len, "" + writeLength));
  }
      }

      _ws.print("Content-Length: " + writeLength);
      _ws.print("\r\n");
    }
    _ws.print("\r\n");

    if (_isPost) {
      MemoryStream tempStream = _tempStream;
      _tempStream = null;
      if (tempStream != null) {
  tempStream.writeToStream(_ws);
  tempStream.destroy();
      }
    }

    _attributes.clear();

    parseHeaders();

    if (_isHead)
      _isRequestDone = true;
  }

  /**
   * Parse the headers returned from the server.
   */
  private void parseHeaders() throws IOException
  {
    CharBuffer line = new CharBuffer();

    // Skip blank lines
    int count = 0;
    do {
      line.clear();
      if (! _rs.readln(line)) {
        _isKeepalive = false;
        return;
      }
    } while (line.length() == 0 && ++count < 10);

    if (line.length() == 0) {
      _isKeepalive = false;
      return;
    }

    if (line.startsWith("HTTP/1.1 100")) {
      count = 100;
      do {
  line.clear();
  if (! _rs.readln(line)) {
    _isKeepalive = false;
    return;
  }
      } while (line.length() != 0 && count-- > 0);
     
      count = 100;
      do {
  line.clear();
  if (! _rs.readln(line)) {
    _isKeepalive = false;
    return;
  }
      } while (line.length() == 0 && count-- > 0);
    }

    if (line.length() == 0) {
      _isKeepalive = false;
      return;
    }

    int i = 0;
    for (i = 0; i < line.length() && line.charAt(i) != ' '; i++) {
    }
   
    for (; i < line.length() && line.charAt(i) == ' '; i++) {
    }
   
    int status = 0;
    for (; i < line.length(); i++) {
      char ch = line.charAt(i);
      if (ch >= '0' && ch <= '9')
        status = 10 * status + ch - '0';
      else
        break;
    }

    if (status != 200)
      _isKeepalive = false;
    else if (! line.startsWith("HTTP/1.1 "))
      _isKeepalive = false;

    _attributes.put("status", String.valueOf(status));
    _attributes.put("status-message", line.toString());

    CharBuffer key = new CharBuffer();
    while (true) {
      line.clear();
      if (! _rs.readln(line) || line.length() == 0)
    break;

      int lineLength = line.length();
     
      for (i = 0;
           i < lineLength && Character.isWhitespace(line.charAt(i));
           i++) {
      }

      key.clear();
      for (;
           i < lineLength && ! Character.isWhitespace(line.charAt(i))
             && line.charAt(i) != ':';
           i++) {
        key.append((char) line.charAt(i));
      }

      for (;
           i < lineLength && Character.isWhitespace(line.charAt(i));
           i++) {
      }

      if (key.length() == 0 || lineLength <= i || line.charAt(i) != ':')
        continue;

      for (i++;
           i < lineLength && Character.isWhitespace(line.charAt(i));
           i++) {
      }

      key.toLowerCase();
      String value = line.substring(i);

      if (log.isLoggable(Level.FINE))
        log.fine(key + ": " + value);
     
      if (key.matchesIgnoreCase("content-length")) {
        _contentLength = Integer.parseInt(value.trim());
      }
      else if (key.matchesIgnoreCase("connection")
               && value.equalsIgnoreCase("close")) {
        _isKeepalive = false;
      }
      else if (key.matchesIgnoreCase("transfer-encoding")
               && value.equalsIgnoreCase("chunked")) {
       
        _isChunked = true;
        _chunkLength = 0;
      }

      String keyString = key.toLowerCase().toString();
     
      String oldValue = (String) _attributes.put(keyString, value);

      if (oldValue != null) {
        value = oldValue + '\n' + value;
        _attributes.put(keyString, value);
      }
    }
  }

  /**
   * Returns the bytes still available.
   */
  public int getAvailable() throws IOException
  {
    if (! _didGet)
      getConnInput();

    // php/164q
    if (_isRequestDone)
      return 0;
    else if (_contentLength > 0)
      return _contentLength;
    else
      return _rs.getAvailable();
  }

  /**
   * Close the connection.
   */
  public void close() throws IOException
  {
    if (_isKeepalive) {
      // If recycling, read any unread data
      if (! _didGet)
        getConnInput();

      if (! _isRequestDone) {
        if (_tempBuffer == null)
          _tempBuffer = new byte[256];

        try {
          while (read(_tempBuffer, 0, _tempBuffer.length) > 0) {
          }
        } catch (IOException e) {
          _isKeepalive = false;
        }
      }
    }

    if (com.caucho.util.Alarm.isTest())
      _isKeepalive = false; // XXX:
   
    if (_isKeepalive) {
      HttpStream oldSaved;
      long now = Alarm.getCurrentTime();
      synchronized (LOCK) {
        oldSaved = _savedStream;
        _savedStream = this;
        _saveTime = now;
      }

      if (oldSaved != null && oldSaved != this) {
        oldSaved._isKeepalive = false;
        oldSaved.close();
      }

      return;
    }

    try {
      try {
        if (_ws != null)
          _ws.close();
      } catch (Throwable e) {
      }
      _ws = null;

      try {
        if (_rs != null)
          _rs.close();
      } catch (Throwable e) {
      }
      _rs = null;

      try {
        if (_os != null)
          _os.close();
      } catch (Throwable e) {
      }
      _os = null;

      try {
        if (_is != null)
          _is.close();
      } catch (Throwable e) {
      }
      _is = null;
    } finally {
      if (_s != null)
  _s.close();
      _s = null;
    }
  }

  @Override
  public String toString()
  {
    return getClass().getSimpleName() + "[" + _s + "]";
  }

  static {
    _reserved = new HashMap<String,String>();
    _reserved.put("user-agent", "");
    _reserved.put("content-length", "");
    //_reserved.put("content-encoding", "");
    _reserved.put("connection", "");
    //_reserved.put("host", "");
  }
}
TOP

Related Classes of com.caucho.vfs.HttpStream

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.