All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.caucho.server.hmux.HmuxRequest Maven / Gradle / Ivy

/*
 * Copyright (c) 1998-2018 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.server.hmux;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.caucho.cloud.bam.BamSystem;
import com.caucho.cloud.hmtp.HmtpRequest;
import com.caucho.network.listen.Protocol;
import com.caucho.network.listen.ProtocolConnection;
import com.caucho.network.listen.SocketLink;
import com.caucho.server.cluster.ServletService;
import com.caucho.server.dispatch.Invocation;
import com.caucho.server.http.AbstractHttpRequest;
import com.caucho.server.http.AbstractResponseStream;
import com.caucho.server.http.HttpServletRequestImpl;
import com.caucho.server.http.HttpServletResponseImpl;
import com.caucho.server.webapp.ErrorPageManager;
import com.caucho.util.ByteBuffer;
import com.caucho.util.CharBuffer;
import com.caucho.util.CharSegment;
import com.caucho.vfs.ClientDisconnectException;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.StreamImpl;
import com.caucho.vfs.WriteStream;

/**
 * Handles requests from a remote dispatcher.  For example, mod_caucho
 * and the IIS plugin use this protocol to talk to the backend.
 *
 * 

Packets are straightforward: *

code l2 l1 l0 contents
* Where code is the code of the packet and l2-0 give 12 bits of length. * *

The protocol is designed to allow pipelining and buffering whenever * possible. So most commands are not acked. Data from the frontend (POST) * does need acks to prevent deadlocks at either end while waiting * for new data. * *

The overriding protocol is controlled by requests from the * frontend server. * *

A ping request: *

 * Frontend       Backend
 * CSE_PING
 * CSE_END
 *                CSE_END
 * 
 *
 * 

A GET request: *

 * Frontend       Backend
 * CSE_METHOD
 * ...
 * CSE_HEADER/CSE_VALUE
 * CSE_END
 *                CSE_DATA
 *                CSE_DATA
 *                CSE_END
 * 
 *
 * 

Short POST: *

 * Frontend       Backend
 * CSE_METHOD
 * ...
 * CSE_HEADER/CSE_VALUE
 * CSE_DATA
 * CSE_END
 *                CSE_DATA
 *                CSE_DATA
 *                CSE_END
 * 
 *
 * 

Long POST: *

 * Frontend       Backend
 * CSE_METHOD
 * ...
 * CSE_HEADER/CSE_VALUE
 * CSE_DATA
 *                CSE_DATA (optional)
 * CSE_DATA
 *                CSE_ACK
 *                CSE_DATA (optional)
 * CSE_DATA
 *                CSE_ACK
 * CSE_END
 *                CSE_DATA
 *                CSE_END
 * 
 *
 */
public class HmuxRequest extends AbstractHttpRequest
  implements ProtocolConnection
{
  private static final Logger log
    = Logger.getLogger(HmuxRequest.class.getName());

  // HMUX channel control codes
  public static final int HMUX_CHANNEL =        'C';
  public static final int HMUX_ACK =            'A';
  public static final int HMUX_ERROR =          'E';
  public static final int HMUX_YIELD =          'Y';
  public static final int HMUX_QUIT =           'Q';
  public static final int HMUX_EXIT =           'X';

  public static final int HMUX_DATA =           'D';
  public static final int HMUX_URI =            'U';
  public static final int HMUX_STRING =         'S';
  public static final int HMUX_HEADER =         'H';
  public static final int HMUX_BINARY =         'B';
  public static final int HMUX_PROTOCOL =       'P';
  public static final int HMUX_META_HEADER =    'M';

  // The following are HTTP codes
  public static final int CSE_NULL =            '?';
  public static final int CSE_PATH_INFO =       'b';
  public static final int CSE_PROTOCOL =        'c';
  public static final int CSE_REMOTE_USER =     'd';
  public static final int CSE_QUERY_STRING =    'e';
  public static final int HMUX_FLUSH  =         'f';
  public static final int CSE_SERVER_PORT =     'g';
  public static final int CSE_REMOTE_HOST =     'h';
  public static final int CSE_REMOTE_ADDR =     'i';
  public static final int CSE_REMOTE_PORT =     'j';
  public static final int CSE_REAL_PATH =       'k';
  public static final int CSE_SCRIPT_FILENAME = 'l';
  public static final int HMUX_METHOD =         'm';
  public static final int CSE_AUTH_TYPE =       'n';
  public static final int CSE_URI =             'o';
  public static final int CSE_CONTENT_LENGTH =  'p';
  public static final int CSE_CONTENT_TYPE =    'q';
  public static final int CSE_IS_SECURE =       'r';
  public static final int HMUX_STATUS =         's';
  public static final int CSE_CLIENT_CERT =     't';
  public static final int CSE_SERVER_TYPE =     'u';
  public static final int HMUX_SERVER_NAME =    'v';

  public static final int CSE_SEND_HEADER =     'G';

  public static final int CSE_FLUSH =           'F';
  public static final int CSE_KEEPALIVE =       'K';
  public static final int CSE_END =             'Z';

  // other, specialized protocols
  public static final int CSE_QUERY =           'Q';

  public static final int HMUX_TO_UNIDIR_HMTP = '7';
  public static final int HMUX_SWITCH_TO_HMTP = '8';
  public static final int HMUX_HMTP_OK        = '9';

  // public static final int HMUX_CLUSTER_PROTOCOL = 0x101;
  public static final int HMUX_DISPATCH_PROTOCOL = 0x102;
  public static final int HMUX_JMS_PROTOCOL = 0x103;
  public static final int HMUX_HTTP_PROXY_PROTOCOL = 0x104;

  public enum ProtocolResult {
    QUIT,
    EXIT,
    YIELD
  };

  static final int HTTP_0_9 = 0x0009;
  static final int HTTP_1_0 = 0x0100;
  static final int HTTP_1_1 = 0x0101;

  private static final int HEADER_CAPACITY = 256;

  static final CharBuffer _getCb = new CharBuffer("GET");
  static final CharBuffer _headCb = new CharBuffer("HEAD");
  static final CharBuffer _postCb = new CharBuffer("POST");

  private final CharBuffer _method;   // "GET"
  private String _methodString;       // "GET"
  // private CharBuffer scheme;       // "http:"
  private CharBuffer _host;            // www.caucho.com
  private ByteBuffer _uri;             // "/path/test.jsp/Junk"
  private CharBuffer _protocol;        // "HTTP/1.0"
  private int _version;

  private CharBuffer _remoteAddr;
  private CharBuffer _remoteHost;
  private CharBuffer _serverName;
  private CharBuffer _serverPort;
  private CharBuffer _remotePort;

  private boolean _isSecure;
  private ByteBuffer _clientCert;

  private CharBuffer []_headerKeys;
  private CharBuffer []_headerValues;
  private int _headerSize;

  private int _serverType;

  // write stream from the connection
  private WriteStream _rawWrite;
  private int _bufferStartOffset;

  // StreamImpl to break reads and writes to the underlying protocol
  private ServletFilter _filter;
  private int _pendingData;

  private CharBuffer _cb1;
  private CharBuffer _cb2;
  private boolean _hasRequest;

  private final HmuxDispatchRequest _dispatchRequest;

  private HmtpRequest _hmtpRequest;

  private ProtocolConnection _subProtocol;

  private HmuxProtocol _hmuxProtocol;
  private ErrorPageManager _errorManager;

  public HmuxRequest(ServletService server,
                     SocketLink conn,
                     HmuxProtocol protocol)
  {
    super(server, conn);

    _errorManager = new ErrorPageManager(server);
    _hmuxProtocol = protocol;

    _rawWrite = conn.getWriteStream();

    // XXX: response.setIgnoreClientDisconnect(server.getIgnoreClientDisconnect());

    _dispatchRequest = new HmuxDispatchRequest(this);

    _uri = new ByteBuffer();

    _method = new CharBuffer();
    _host = new CharBuffer();
    _protocol = new CharBuffer();

    _headerKeys = new CharBuffer[HEADER_CAPACITY];
    _headerValues = new CharBuffer[_headerKeys.length];
    for (int i = 0; i < _headerKeys.length; i++) {
      _headerKeys[i] = new CharBuffer();
      _headerValues[i] = new CharBuffer();
    }

    _remoteHost = new CharBuffer();
    _remoteAddr = new CharBuffer();
    _serverName = new CharBuffer();
    _serverPort = new CharBuffer();
    _remotePort = new CharBuffer();

    _clientCert = new ByteBuffer();

    _cb1 = new CharBuffer();
    _cb2 = new CharBuffer();

    _filter = new ServletFilter();

    BamSystem bamService = BamSystem.getCurrent();

    _hmtpRequest = new HmtpRequest(conn, bamService);
  }

  @Override
  public HmuxResponse createResponse()
  {
    return new HmuxResponse(this, getConnection().getWriteStream());
  }

  @Override
  public boolean isWaitForRead()
  {
    return true;
  }

  /**
   * Returns true if a valid HTTP request has started.
   */
  @Override
  public boolean hasRequest()
  {
    return _hasRequest;
  }

  @Override
  public boolean isSuspend()
  {
    return super.isSuspend() || _subProtocol != null;
  }

  @Override
  public boolean handleRequest()
    throws IOException
  {
    try {
      ProtocolConnection subProtocol = _subProtocol;

      if (subProtocol != null) {
        return subProtocol.handleRequest();
      }
      else {
        return handleRequestImpl();
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (IOException e) {
      throw e;
    }
  }

  /**
   * Handles a new request.  Initializes the protocol handler and
   * the request streams.
   *
   * 

Note: ClientDisconnectException must be rethrown to * the caller. */ private boolean handleRequestImpl() throws IOException { // XXX: should be moved to TcpConnection Thread thread = Thread.currentThread(); thread.setContextClassLoader(getServer().getClassLoader()); if (log.isLoggable(Level.FINE)) log.fine(dbgId() + "start request"); _filter.init(this, getRawRead(), getRawWrite()); try { startRequest(); startInvocation(); return handleInvocation(); } finally { if (! _hasRequest) { getResponse().setHeaderWritten(true); } if (_subProtocol == null) { finishInvocation(); } try { // server/0190, server/1ld7 if (! isSuspend()) { finishRequest(); } } catch (ClientDisconnectException e) { throw e; } catch (Exception e) { killKeepalive("hmux finishRequest exception " + e); log.log(Level.FINE, dbgId() + e, e); } // cluster/6610 - hmtp mode doesn't close the stream. if (_subProtocol == null && ! isSuspend()) { try { ReadStream is = getReadStream(); is.setDisableClose(false); is.close(); } catch (Exception e) { killKeepalive("hmux close exception: " + e); log.log(Level.FINE, dbgId() + e, e); } } } } private boolean handleInvocation() throws IOException { try { _hasRequest = false; if (! scanHeaders() || ! getConnection().isPortActive()) { _hasRequest = false; killKeepalive("hmux scanHeaders failure"); return false; } else if (! _hasRequest) { return true; } } catch (InterruptedIOException e) { killKeepalive("hmux parse header exception: "+ e); log.fine(dbgId() + "interrupted keepalive"); return false; } if (_isSecure) getClientCertificate(); _hasRequest = true; // setStartDate(); ServletService server = getServer(); if (server == null || server.isDestroyed()) { log.fine(dbgId() + "server is closed"); ReadStream is = getRawRead(); try { is.setDisableClose(false); is.close(); } catch (Exception e) { } return false; } _filter.setPending(_pendingData); try { if (_method.getLength() == 0) throw new RuntimeException("HTTP protocol exception, expected method"); if (! getServer().waitForActive(5000)) { return sendBusyResponse(); } Invocation invocation; invocation = getInvocation(getHost(), _uri.getBuffer(), _uri.getLength()); getRequestFacade().setInvocation(invocation); startInvocation(); if (getServer().isPreview() && ! "resin.admin".equals(getHost())) { return sendBusyResponse(); } invocation.service(getRequestFacade(), getResponseFacade()); } catch (ClientDisconnectException e) { throw e; } catch (Throwable e) { log.log(Level.FINER, e.toString(), e); try { _errorManager.sendServletError(e, getRequestFacade(), getResponseFacade()); } catch (ClientDisconnectException e1) { throw e1; } catch (Exception e1) { log.log(Level.FINE, e1.toString(), e1); } } return true; } /** * Initialize the read stream from the raw stream. */ @Override public boolean initStream(ReadStream readStream, ReadStream rawStream) throws IOException { readStream.init(_filter, null); return true; } private void getClientCertificate() { HttpServletRequestImpl request = getRequestFacade(); String cipher = getHeader("SSL_CIPHER"); if (cipher == null) cipher = getHeader("HTTPS_CIPHER"); if (cipher != null) request.setAttribute("javax.servlet.request.cipher_suite", cipher); String keySize = getHeader("SSL_CIPHER_USEKEYSIZE"); if (keySize == null) keySize = getHeader("SSL_SECRETKEYSIZE"); if (keySize != null) request.setAttribute("javax.servlet.request.key_size", new Integer(keySize)); if (_clientCert.size() == 0) return; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); InputStream is = _clientCert.createInputStream(); X509Certificate cert = (X509Certificate) cf.generateCertificate(is); is.close(); request.setAttribute("javax.servlet.request.X509Certificate", new X509Certificate[]{cert}); /* #6424 request.setAttribute(com.caucho.security.AbstractLogin.LOGIN_USER, ((X509Certificate) cert).getSubjectDN()); */ } catch (Exception e) { log.log(Level.FINE, e.toString(), e); } } protected boolean checkLogin() { return true; } @Override public void onStartConnection() { super.onStartConnection(); _hmtpRequest.onStartConnection(); _subProtocol = null; } /** * Clears variables at the start of a new request. */ @Override protected void startRequest() throws IOException { super.startRequest(); _method.clear(); _methodString = null; _protocol.clear(); _version = 0; _uri.clear(); _host.clear(); _headerSize = 0; _remoteHost.clear(); _remoteAddr.clear(); _serverName.clear(); _serverPort.clear(); _remotePort.clear(); _clientCert.clear(); _pendingData = 0; _bufferStartOffset = 0; _isSecure = getConnection().isSecure(); } /** * Sends busy response for preview mode. */ private boolean sendBusyResponse() throws IOException { HttpServletResponseImpl response = getResponseFacade(); response.sendError(503); return true; } /** * Fills request parameters from the stream. */ private boolean scanHeaders() throws IOException { boolean isLoggable = log.isLoggable(Level.FINE); ReadStream is = getRawRead(); WriteStream os = getRawWrite(); CharBuffer cb = getCharBuffer(); int code; int len; while (true) { _rawWrite.flush(); code = is.read(); ServletService server = getServer(); if (server == null || server.isDestroyed()) { log.fine(dbgId() + " request after server close"); killKeepalive("after server close"); return false; } switch (code) { case -1: if (isLoggable) log.fine(dbgId() + "r: end of file"); _filter.setClientClosed(true); killKeepalive("hmux end of file"); return false; case HMUX_CHANNEL: int channel = (is.read() << 8) + is.read(); if (isLoggable) log.fine(dbgId() + "channel-r " + channel); break; case HMUX_YIELD: if (log.isLoggable(Level.FINER)) log.finer(dbgId() + (char) code + "-r: yield"); os.write(HMUX_ACK); os.write(0); os.write(0); os.flush(); break; case HMUX_QUIT: if (isLoggable) log.fine(dbgId() + (char) code + "-r: end of request"); return true; case HMUX_EXIT: if (isLoggable) log.fine(dbgId() + (char) code + "-r: end of socket"); killKeepalive("hmux exit"); return true; case HMUX_PROTOCOL: len = (is.read() << 8) + is.read(); if (len != 4) { log.fine(dbgId() + (char) code + "-r: protocol length (" + len + ") must be 4."); killKeepalive("hmux protocol"); return false; } int value = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + (is.read())); dispatchProtocol(is, code, value); return true; case HMUX_URI: len = (is.read() << 8) + is.read(); _uri.setLength(len); is.readAll(_uri.getBuffer(), 0, len); if (isLoggable) log.fine(dbgId() + (char) code + ":uri " + _uri); _hasRequest = true; break; case HMUX_METHOD: len = (is.read() << 8) + is.read(); is.readAll(_method, len); if (isLoggable) log.fine(dbgId() + (char) code + ":method " + _method); break; case CSE_REAL_PATH: len = (is.read() << 8) + is.read(); _cb1.clear(); is.readAll(_cb1, len); code = is.read(); if (code != HMUX_STRING) throw new IOException("protocol expected HMUX_STRING"); _cb2.clear(); is.readAll(_cb2, readLength(is)); //http.setRealPath(cb1.toString(), cb2.toString()); if (isLoggable) log.fine(dbgId() + (char) code + " " + _cb1.toString() + "->" + _cb2.toString()); //throw new RuntimeException(); break; case CSE_REMOTE_HOST: len = (is.read() << 8) + is.read(); is.readAll(_remoteHost, len); if (isLoggable) log.fine(dbgId() + (char) code + " " + _remoteHost); break; case CSE_REMOTE_ADDR: len = (is.read() << 8) + is.read(); is.readAll(_remoteAddr, len); if (isLoggable) log.fine(dbgId() + (char) code + " " + _remoteAddr); break; case HMUX_SERVER_NAME: len = (is.read() << 8) + is.read(); is.readAll(_serverName, len); if (isLoggable) log.fine(dbgId() + (char) code + " server-host: " + _serverName); break; case CSE_REMOTE_PORT: len = (is.read() << 8) + is.read(); is.readAll(_remotePort, len); if (isLoggable) log.fine(dbgId() + (char) code + " remote-port: " + _remotePort); break; case CSE_SERVER_PORT: len = (is.read() << 8) + is.read(); is.readAll(_serverPort, len); if (isLoggable) log.fine(dbgId() + (char) code + " server-port: " + _serverPort); break; case CSE_QUERY_STRING: len = (is.read() << 8) + is.read(); if (len > 0) { _uri.add('?'); _uri.ensureCapacity(_uri.getLength() + len); is.readAll(_uri.getBuffer(), _uri.getLength(), len); _uri.setLength(_uri.getLength() + len); } break; case CSE_PROTOCOL: len = (is.read() << 8) + is.read(); is.readAll(_protocol, len); if (isLoggable) log.fine(dbgId() + (char) code + " protocol: " + _protocol); for (int i = 0; i < len; i++) { char ch = _protocol.charAt(i); if ('0' <= ch && ch <= '9') _version = 16 * _version + ch - '0'; else if (ch == '.') _version = 16 * _version; } break; case HMUX_HEADER: len = (is.read() << 8) + is.read(); int headerSize = _headerSize; CharBuffer key = _headerKeys[headerSize]; key.clear(); CharBuffer valueCb = _headerValues[headerSize]; valueCb.clear(); is.readAll(key, len); code = is.read(); if (code != HMUX_STRING) throw new IOException("protocol expected HMUX_STRING at " + (char) code); is.readAll(valueCb, readLength(is)); if (isLoggable) log.fine(dbgId() + "H " + key + "=" + valueCb); if (addHeaderInt(key.getBuffer(), 0, key.length(), valueCb)) { _headerSize++; } break; case CSE_CONTENT_LENGTH: len = (is.read() << 8) + is.read(); if (_headerKeys.length <= _headerSize) resizeHeaders(); _headerKeys[_headerSize].clear(); _headerKeys[_headerSize].append("Content-Length"); _headerValues[_headerSize].clear(); is.readAll(_headerValues[_headerSize], len); setContentLength(_headerValues[_headerSize]); if (isLoggable) log.fine(dbgId() + (char) code + " content-length=" + _headerValues[_headerSize]); _headerSize++; break; case CSE_CONTENT_TYPE: len = (is.read() << 8) + is.read(); if (_headerKeys.length <= _headerSize) resizeHeaders(); _headerKeys[_headerSize].clear(); _headerKeys[_headerSize].append("Content-Type"); _headerValues[_headerSize].clear(); is.readAll(_headerValues[_headerSize], len); if (isLoggable) log.fine(dbgId() + (char) code + " content-type=" + _headerValues[_headerSize]); _headerSize++; break; case CSE_IS_SECURE: len = (is.read() << 8) + is.read(); _isSecure = true; if (isLoggable) log.fine(dbgId() + "secure"); is.skip(len); break; case CSE_CLIENT_CERT: len = (is.read() << 8) + is.read(); _clientCert.clear(); _clientCert.setLength(len); is.readAll(_clientCert.getBuffer(), 0, len); if (isLoggable) log.fine(dbgId() + (char) code + " cert=" + _clientCert + " len:" + len); break; case CSE_SERVER_TYPE: len = (is.read() << 8) + is.read(); _cb1.clear(); is.readAll(_cb1, len); if (isLoggable) log.fine(dbgId() + (char) code + " server=" + _cb1); if (_cb1.length() > 0) _serverType = _cb1.charAt(0); break; case CSE_REMOTE_USER: len = (is.read() << 8) + is.read(); cb.clear(); is.readAll(cb, len); if (isLoggable) log.fine(dbgId() + (char) code + " " + cb); getRequestFacade().setAttribute(com.caucho.security.AbstractLogin.LOGIN_USER, new com.caucho.security.BasicPrincipal(cb.toString())); break; case HMUX_DATA: len = (is.read() << 8) + is.read(); _pendingData = len; if (isLoggable) log.fine(dbgId() + (char) code + " post-data: " + len); if (len > 0) { return true; } break; case HMUX_TO_UNIDIR_HMTP: case HMUX_SWITCH_TO_HMTP: { if (_hasRequest) throw new IllegalStateException(); is.unread(); if (isLoggable) log.fine(dbgId() + (char) code + "-r switch-to-hmtp"); _subProtocol = _hmtpRequest; boolean result = _hmtpRequest.handleRequest(); return result; } case ' ': case '\n': // skip for debugging break; default: { int d1 = is.read(); int d2 = is.read(); if (d2 < 0) { if (isLoggable) log.fine(dbgId() + "r: unexpected end of file"); killKeepalive("hmux data end of file"); return false; } len = (d1 << 8) + d2; if (isLoggable) log.fine(dbgId() + (char) code + " " + len); is.skip(len); break; } } } } private void dispatchProtocol(ReadStream is, int code, int value) throws IOException { int result = HMUX_EXIT; boolean isKeepalive = false; if (value == HMUX_DISPATCH_PROTOCOL) { if (log.isLoggable(Level.FINE)) log.fine(dbgId() + (char) code + "-r: dispatch protocol"); _filter.setClientClosed(true); isKeepalive = _dispatchRequest.handleRequest(is, _rawWrite); if (isKeepalive) result = HMUX_QUIT; else result = HMUX_EXIT; } else { Protocol ext = _hmuxProtocol.getExtension(value); if (ext != null) { if (log.isLoggable(Level.FINE)) log.fine(dbgId() + (char) code + "-r: extension " + ext); _filter.setClientClosed(true); _subProtocol = ext.createConnection(getTcpSocketLink()); _subProtocol.handleRequest(); return; } else { log.fine(dbgId() + (char) code + "-r: unknown protocol (" + value + ")"); result = HMUX_EXIT; } } if (result == HMUX_YIELD) { _rawWrite.write(HMUX_ACK); _rawWrite.write(0); _rawWrite.write(0); _rawWrite.flush(); return; // XXX: } else { if (result == HMUX_QUIT && ! isKeepalive()) result = HMUX_EXIT; if (result == HMUX_QUIT) { _rawWrite.write(HMUX_QUIT); _rawWrite.flush(); } else { killKeepalive("hmux failed result: " + (char) result); _rawWrite.write(HMUX_EXIT); _rawWrite.close(); } } } private void resizeHeaders() { CharBuffer []newKeys = new CharBuffer[_headerSize * 2]; CharBuffer []newValues = new CharBuffer[_headerSize * 2]; for (int i = 0; i < _headerSize; i++) { newKeys[i] = _headerKeys[i]; newValues[i] = _headerValues[i]; } for (int i = _headerSize; i < newKeys.length; i++) { newKeys[i] = new CharBuffer(); newValues[i] = new CharBuffer(); } _headerKeys = newKeys; _headerValues = newValues; } private int readLength(ReadStream is) throws IOException { return ((is.read() << 8) + is.read()); } void writeFlush() throws IOException { WriteStream out = _rawWrite; synchronized (out) { out.flush(); } } /** * Returns the header. */ @Override public String getMethod() { if (_methodString == null) { CharSegment cb = getMethodBuffer(); if (cb.length() == 0) { _methodString = "GET"; return _methodString; } switch (cb.charAt(0)) { case 'G': _methodString = cb.equals(_getCb) ? "GET" : cb.toString(); break; case 'H': _methodString = cb.equals(_headCb) ? "HEAD" : cb.toString(); break; case 'P': _methodString = cb.equals(_postCb) ? "POST" : cb.toString(); break; default: _methodString = cb.toString(); } } return _methodString; } public CharSegment getMethodBuffer() { return _method; } /** * Returns a char buffer containing the host. */ @Override protected CharBuffer getHost() { if (_host.length() > 0) return _host; _host.append(_serverName); _host.toLowerCase(); return _host; } public final byte []getUriBuffer() { return _uri.getBuffer(); } public final int getUriLength() { return _uri.getLength(); } /** * Returns the protocol. */ public String getProtocol() { return _protocol.toString(); } public CharSegment getProtocolBuffer() { return _protocol; } final int getVersion() { return _version; } /** * Returns true if the request is secure. */ @Override public boolean isSecure() { return super.isSecure() || _isSecure; } /** * Returns the header. */ public String getHeader(String key) { CharSegment buf = getHeaderBuffer(key); if (buf != null) return buf.toString(); else return null; } @Override public CharSegment getHeaderBuffer(String key) { for (int i = 0; i < _headerSize; i++) { CharBuffer test = _headerKeys[i]; if (test.equalsIgnoreCase(key)) return _headerValues[i]; } return null; } public CharSegment getHeaderBuffer(char []buf, int length) { for (int i = 0; i < _headerSize; i++) { CharBuffer test = _headerKeys[i]; if (test.length() != length) continue; char []keyBuf = test.getBuffer(); int j; for (j = 0; j < length; j++) { char a = buf[j]; char b = keyBuf[j]; if (a == b) continue; if (a >= 'A' && a <= 'Z') a += 'a' - 'A'; if (b >= 'A' && b <= 'Z') b += 'a' - 'A'; if (a != b) break; } if (j == length) return _headerValues[i]; } return null; } @Override public void setHeader(String key, String value) { if (_headerKeys.length <= _headerSize) resizeHeaders(); _headerKeys[_headerSize].clear(); _headerKeys[_headerSize].append(key); _headerValues[_headerSize].clear(); _headerValues[_headerSize].append(value); _headerSize++; } @Override public void getHeaderBuffers(String key, ArrayList values) { CharBuffer cb = getCharBuffer(); cb.clear(); cb.append(key); int size = _headerSize; for (int i = 0; i < size; i++) { CharBuffer test = _headerKeys[i]; if (test.equalsIgnoreCase(cb)) values.add(_headerValues[i]); } } public Enumeration getHeaderNames() { HashSet names = new HashSet(); for (int i = 0; i < _headerSize; i++) names.add(_headerKeys[i].toString()); return Collections.enumeration(names); } /** * Returns the URI for the request, special casing the IIS issues. * Because IIS already escapes the URI before sending it, the URI * needs to be re-escaped. */ @Override public String getRequestURI() { if (_serverType == 'R') return super.getRequestURI(); String _rawURI = super.getRequestURI(); if (_rawURI == null) return null; CharBuffer cb = CharBuffer.allocate(); for (int i = 0; i < _rawURI.length(); i++) { char ch = _rawURI.charAt(i); if (ch <= ' ' || ch >= 0x80 || ch == '%') { addHex(cb, ch); } else cb.append(ch); } return cb.close(); } /** * Adds a hex escape. * * @param cb the char buffer containing the escape. * @param ch the character to be escaped. */ private void addHex(CharBuffer cb, int ch) { cb.append('%'); int d = (ch >> 4) & 0xf; if (d < 10) cb.append((char) ('0' + d)); else cb.append((char) ('a' + d - 10)); d = ch & 0xf; if (d < 10) cb.append((char) ('0' + d)); else cb.append((char) ('a' + d - 10)); } /** * Returns the server name. */ @Override public String getServerName() { CharBuffer host = getHost(); if (host == null) { InetAddress addr = getConnection().getRemoteAddress(); return addr.getHostName(); } int p = host.indexOf(':'); if (p >= 0) return host.substring(0, p); else return host.toString(); } @Override public int getServerPort() { int len = _serverPort.length(); int port = 0; for (int i = 0; i < len; i++) { char ch = _serverPort.charAt(i); port = 10 * port + ch - '0'; } return port; } @Override public String getRemoteAddr() { return _remoteAddr.toString(); } public void getRemoteAddr(CharBuffer cb) { cb.append(_remoteAddr); } @Override public int printRemoteAddr(byte []buffer, int offset) throws IOException { char []buf = _remoteAddr.getBuffer(); int len = _remoteAddr.getLength(); for (int i = 0; i < len; i++) buffer[offset + i] = (byte) buf[i]; return offset + len; } @Override public String getRemoteHost() { return _remoteHost.toString(); } /** * Called for a connection: close */ @Override protected void handleConnectionClose() { // ignore for hmux } @Override public void finishRequest() throws IOException { try { super.finishRequest(); } finally { _filter.close(); } } // Response data void writeStatus(CharBuffer message) throws IOException { writeString(HMUX_STATUS, message); } /** * Complete sending of all headers. */ void sendHeader() throws IOException { writeString(CSE_SEND_HEADER, ""); } /** * Writes a header to the plugin. * * @param key the header's key * @param value the header's value */ void writeHeader(String key, String value) throws IOException { writeString(HMUX_HEADER, key); writeString(HMUX_STRING, value); } /** * Writes a header to the plugin. * * @param key the header's key * @param value the header's value */ void writeHeader(String key, CharBuffer value) throws IOException { writeString(HMUX_HEADER, key); writeString(HMUX_STRING, value); } void flushResponseBuffer() throws IOException { HttpServletRequestImpl request = getRequestFacade(); if (request != null) { AbstractResponseStream stream = request.getResponse().getResponseStream(); stream.flushNext(); } } // // HmuxResponseStream methods // protected byte []getNextBuffer() { return _rawWrite.getBuffer(); } protected int getNextStartOffset() { if (_bufferStartOffset == 0) { int bufferLength = _rawWrite.getBuffer().length; int startOffset = _rawWrite.getBufferOffset() + 3; if (bufferLength <= startOffset) { try { _rawWrite.flush(); } catch (IOException e) { log.log(Level.FINE, e.toString(), e); } startOffset = _rawWrite.getBufferOffset() + 3; } _rawWrite.setBufferOffset(startOffset); _bufferStartOffset = startOffset; } return _bufferStartOffset; } protected int getNextBufferOffset() throws IOException { if (_bufferStartOffset == 0) { int bufferLength = _rawWrite.getBuffer().length; int startOffset = _rawWrite.getBufferOffset() + 3; if (bufferLength <= startOffset) { _rawWrite.flush(); startOffset = _rawWrite.getBufferOffset() + 3; } _rawWrite.setBufferOffset(startOffset); _bufferStartOffset = startOffset; } return _rawWrite.getBufferOffset(); } protected void setNextBufferOffset(int offset) throws IOException { offset = fillDataBuffer(offset); _rawWrite.setBufferOffset(offset); } protected byte []writeNextBuffer(int offset) throws IOException { offset = fillDataBuffer(offset); return _rawWrite.nextBuffer(offset); } protected void flushNext() throws IOException { if (flushNextBuffer()) { // server/26u2 _rawWrite.write(HMUX_FLUSH); _rawWrite.write(0); _rawWrite.write(0); if (log.isLoggable(Level.FINE)) { log.fine(dbgId() + "flush-w()"); } } _rawWrite.flush(); } protected final boolean flushNextBuffer() throws IOException { WriteStream next = _rawWrite; int startOffset = _bufferStartOffset; if (startOffset > 0) { _bufferStartOffset = 0; if (startOffset == next.getBufferOffset()) { next.setBufferOffset(startOffset - 3); if (startOffset == 3) { return false; } } else { // illegal state because the data isn't filled throw new IllegalStateException(); } return true; } else { return false; } } protected void writeTail() throws IOException { WriteStream next = _rawWrite; int offset = next.getBufferOffset(); offset = fillDataBuffer(offset); // server/26a6 // Use setBufferOffset because nextBuffer would // force an early flush next.setBufferOffset(offset); } private int fillDataBuffer(int offset) throws IOException { // server/2642 int bufferStart = _bufferStartOffset; if (bufferStart == 0) return offset; byte []buffer = _rawWrite.getBuffer(); // if (bufferStart > 0 && offset == buffer.length) { int length = offset - bufferStart; _bufferStartOffset = 0; // server/26a0 if (length == 0) { offset = bufferStart - 3; } else { buffer[bufferStart - 3] = (byte) HmuxRequest.HMUX_DATA; buffer[bufferStart - 2] = (byte) (length >> 8); buffer[bufferStart - 1] = (byte) (length); if (log.isLoggable(Level.FINE)) log.fine(dbgId() + (char) HmuxRequest.HMUX_DATA + "-w(" + length + ")"); } _bufferStartOffset = 0; return offset; } void writeString(int code, String value) throws IOException { int len = value.length(); WriteStream os = _rawWrite; os.write(code); os.write(len >> 8); os.write(len); os.print(value); if (log.isLoggable(Level.FINE)) log.fine(dbgId() + (char)code + "-w " + value); } void writeString(int code, CharBuffer cb) throws IOException { int len = cb.length(); WriteStream os = _rawWrite; os.write(code); os.write(len >> 8); os.write(len); os.print(cb.getBuffer(), 0, len); if (log.isLoggable(Level.FINE)) log.fine(dbgId() + (char)code + "-w: " + cb); } /** * Close when the socket closes. */ @Override public void onCloseConnection() { _subProtocol = null; _hmtpRequest.onCloseConnection(); super.onCloseConnection(); } protected String getRequestId() { String id = getServer().getServerId(); if (id.equals("")) return "server-" + getConnection().getId(); else return "server-" + id + ":" + getConnection().getId(); } @Override public final String dbgId() { String id = getServer().getServerId(); if (id.equals("")) return "Hmux[" + getConnection().getId() + "] "; else return "Hmux[" + id + ":" + getConnection().getId() + "] "; } @Override public String toString() { return "HmuxRequest" + dbgId(); } /** * Implements the protocol for data reads and writes. Data from the * web server to the JVM must be acked, except for the first data. * Data back to the web server needs no ack. */ static class ServletFilter extends StreamImpl { HmuxRequest _request; ReadStream _is; WriteStream _os; byte []_buffer = new byte[16]; int _pendingData; boolean _needsAck; boolean _isClosed; boolean _isClientClosed; ServletFilter() { } void init(HmuxRequest request, ReadStream nextRead, WriteStream nextWrite) { _request = request; _is = nextRead; _os = nextWrite; _pendingData = 0; _isClosed = false; _isClientClosed = false; _needsAck = false; } void setPending(int pendingData) { _pendingData = pendingData; } void setClientClosed(boolean isClientClosed) { _isClientClosed = isClientClosed; } @Override public boolean canRead() { return true; } @Override public int getAvailable() { return _pendingData; } /** * Reads available data. If the data needs an ack, then do so. */ @Override public int read(byte []buf, int offset, int length) throws IOException { int sublen = _pendingData; ReadStream is = _is; if (sublen <= 0) return -1; if (length < sublen) sublen = length; _os.flush(); int readLen = is.read(buf, offset, sublen); _pendingData -= readLen; if (log.isLoggable(Level.FINEST)) log.finest(new String(buf, offset, readLen)); while (_pendingData == 0) { _os.flush(); int code = is.read(); switch (code) { case HMUX_DATA: { int len = (is.read() << 8) + is.read(); if (log.isLoggable(Level.FINE)) log.fine(_request.dbgId() + "D-r:post-data " + len); _pendingData = len; if (len > 0) return readLen; break; } case HMUX_QUIT: { if (log.isLoggable(Level.FINE)) log.fine(_request.dbgId() + "Q-r:quit"); return readLen; } case HMUX_EXIT: { if (log.isLoggable(Level.FINE)) log.fine(_request.dbgId() + "X-r:exit"); _request.killKeepalive("hmux request exit"); return readLen; } case HMUX_YIELD: { _request.flushResponseBuffer(); _os.write(HMUX_ACK); _os.write(0); _os.write(0); _os.flush(); if (log.isLoggable(Level.FINE)) { log.fine(_request.dbgId() + "Y-r:yield"); log.fine(_request.dbgId() + "A-w:ack"); } break; } case HMUX_CHANNEL: { int channel = (is.read() << 8) + is.read(); if (log.isLoggable(Level.FINE)) log.fine(_request.dbgId() + "channel-r " + channel); break; } case ' ': case '\n': break; default: if (code < 0) { _request.killKeepalive("hmux request end-of-file"); return readLen; } else { _request.killKeepalive("hmux unknown request: " + (char) code); int len = (is.read() << 8) + is.read(); if (log.isLoggable(Level.FINE)) log.fine(_request.dbgId() + "unknown-r '" + (char) code + "' " + len); is.skip(len); } } } return readLen; } @Override public boolean canWrite() { return true; } /** * Send data back to the web server */ @Override public void write(byte []buf, int offset, int length, boolean isEnd) throws IOException { _request.flushResponseBuffer(); if (log.isLoggable(Level.FINE)) { log.fine(_request.dbgId() + (char) HMUX_DATA + ":data " + length); if (log.isLoggable(Level.FINEST)) log.finest(_request.dbgId() + "data <" + new String(buf, offset, length) + ">"); } byte []tempBuf = _buffer; Thread.dumpStack(); while (length > 0) { int sublen = length; if (32 * 1024 < sublen) sublen = 32 * 1024; // The 3 bytes are already allocated by setPrefixWrite tempBuf[0] = HMUX_DATA; tempBuf[1] = (byte) (sublen >> 8); tempBuf[2] = (byte) sublen; _os.write(tempBuf, 0, 3); _os.write(buf, offset, sublen); length -= sublen; offset += sublen; } } @Override public void flush() throws IOException { if (! _request._hasRequest) { return; } System.out.println("HMUX_FLUSH:"); if (log.isLoggable(Level.FINE)) { log.fine(_request.dbgId() + (char) HMUX_FLUSH + "-w:flush"); } _os.write(HMUX_FLUSH); _os.write(0); _os.write(0); _os.flush(); } @Override public void close() throws IOException { if (_isClosed) return; _isClosed = true; if (_pendingData > 0) { _is.skip(_pendingData); _pendingData = 0; } HmuxRequest request = _request; if (request == null) { return; } boolean keepalive = request.isKeepalive(); if (! _isClientClosed) { if (log.isLoggable(Level.FINE)) { if (keepalive) log.fine(request.dbgId() + (char) HMUX_QUIT + "-w: quit channel"); else log.fine(request.dbgId() + (char) HMUX_EXIT + "-w: exit socket"); } if (keepalive) _os.write(HMUX_QUIT); else _os.write(HMUX_EXIT); } if (keepalive) _os.flush(); else _os.close(); //nextRead.close(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy