/* * Copyright (c) 1998-2011 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.ProtocolConnection; import com.caucho.network.listen.SocketLink; import com.caucho.server.cluster.Server; import com.caucho.server.dispatch.Invocation; import com.caucho.server.http.AbstractHttpRequest; import com.caucho.server.http.AbstractResponseStream; import com.caucho.server.http.HttpBufferStore; 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. * * <p>Packets are straightforward: * <pre>code l2 l1 l0 contents</pre> * Where code is the code of the packet and l2-0 give 12 bits of length. * * <p>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. * * <p>The overriding protocol is controlled by requests from the * frontend server. * * <p>A ping request: * <pre> * Frontend Backend * CSE_PING * CSE_END * CSE_END * <pre> * * <p>A GET request: * <pre> * Frontend Backend * CSE_METHOD * ... * CSE_HEADER/CSE_VALUE * CSE_END * CSE_DATA * CSE_DATA * CSE_END * <pre> * * <p>Short POST: * <pre> * Frontend Backend * CSE_METHOD * ... * CSE_HEADER/CSE_VALUE * CSE_DATA * CSE_END * CSE_DATA * CSE_DATA * CSE_END * <pre> * * <p>Long POST: * <pre> * 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 * <pre> * */ 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 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 boolean _isHmtpRequest; private HmuxProtocol _hmuxProtocol; private ErrorPageManager _errorManager; public HmuxRequest(Server 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() || _isHmtpRequest; } @Override public boolean handleRequest() throws IOException { try { if (_isHmtpRequest) { return _hmtpRequest.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. * * <p>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 { HttpBufferStore httpBuffer = getServer().allocateHttpBuffer(); startRequest(httpBuffer); startInvocation(); return handleInvocation(); } finally { if (! _hasRequest) getResponse().setHeaderWritten(true); if (! _isHmtpRequest) { finishInvocation(); } try { // server/0190 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 (! _isHmtpRequest) { 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(); Server 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"); 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", 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}); request.setAttribute(com.caucho.security.AbstractLogin.LOGIN_USER_NAME, ((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(); _isHmtpRequest = false; } /** * Clears variables at the start of a new request. */ @Override protected void startRequest(HttpBufferStore httpBuffer) throws IOException { super.startRequest(httpBuffer); _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(); Server 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_NAME, 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"); _isHmtpRequest = true; 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 { HmuxExtension ext = _hmuxProtocol.getExtension(value); if (ext != null) { if (log.isLoggable(Level.FINE)) log.fine(dbgId() + (char) code + "-r: extension " + ext); _filter.setClientClosed(true); result = ext.handleRequest(this, is, _rawWrite); } 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<CharSegment> 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<String> getHeaderNames() { HashSet<String> names = new HashSet<String>(); 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 } // 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 { flushNextBuffer(); _rawWrite.flush(); } protected void flushNextBuffer() throws IOException { WriteStream next = _rawWrite; if (log.isLoggable(Level.FINE)) log.fine(dbgId() + "flush()"); int startOffset = _bufferStartOffset; if (startOffset > 0) { _bufferStartOffset = 0; if (startOffset == next.getBufferOffset()) { next.setBufferOffset(startOffset - 3); } else { // illegal state because the data isn't filled throw new IllegalStateException(); } } } 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() { _isHmtpRequest = false; _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; 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; 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; } 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(); } } }