Java tutorial
/* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package org.apache.jk.common; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.CharConversionException; import java.net.InetAddress; import java.util.Properties; import org.apache.commons.modeler.Registry; import org.apache.coyote.Request; import org.apache.coyote.RequestGroupInfo; import org.apache.coyote.RequestInfo; import org.apache.coyote.Response; import org.apache.jk.core.JkHandler; import org.apache.jk.core.Msg; import org.apache.jk.core.MsgContext; import org.apache.jk.core.WorkerEnv; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.CharChunk; import org.apache.tomcat.util.buf.HexUtils; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.net.SSLSupport; import org.apache.tomcat.util.threads.ThreadWithAttributes; /** * Handle messages related with basic request information. * * This object can handle the following incoming messages: * - "FORWARD_REQUEST" input message ( sent when a request is passed from the * web server ) * - "RECEIVE_BODY_CHUNK" input ( sent by container to pass more body, in * response to GET_BODY_CHUNK ) * * It can handle the following outgoing messages: * - SEND_HEADERS. Pass the status code and headers. * - SEND_BODY_CHUNK. Send a chunk of body * - GET_BODY_CHUNK. Request a chunk of body data * - END_RESPONSE. Notify the end of a request processing. * * @author Henri Gomez [hgomez@apache.org] * @author Dan Milstein [danmil@shore.net] * @author Keith Wannamaker [Keith@Wannamaker.org] * @author Costin Manolache */ public class HandlerRequest extends JkHandler { private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory .getLog(HandlerRequest.class); // XXX Will move to a registry system. // Prefix codes for message types from server to container public static final byte JK_AJP13_FORWARD_REQUEST = 2; public static final byte JK_AJP13_SHUTDOWN = 7; public static final byte JK_AJP13_PING_REQUEST = 8; public static final byte JK_AJP13_CPING_REQUEST = 10; // Prefix codes for message types from container to server public static final byte JK_AJP13_SEND_BODY_CHUNK = 3; public static final byte JK_AJP13_SEND_HEADERS = 4; public static final byte JK_AJP13_END_RESPONSE = 5; public static final byte JK_AJP13_GET_BODY_CHUNK = 6; public static final byte JK_AJP13_CPONG_REPLY = 9; // Integer codes for common response header strings public static final int SC_RESP_CONTENT_TYPE = 0xA001; public static final int SC_RESP_CONTENT_LANGUAGE = 0xA002; public static final int SC_RESP_CONTENT_LENGTH = 0xA003; public static final int SC_RESP_DATE = 0xA004; public static final int SC_RESP_LAST_MODIFIED = 0xA005; public static final int SC_RESP_LOCATION = 0xA006; public static final int SC_RESP_SET_COOKIE = 0xA007; public static final int SC_RESP_SET_COOKIE2 = 0xA008; public static final int SC_RESP_SERVLET_ENGINE = 0xA009; public static final int SC_RESP_STATUS = 0xA00A; public static final int SC_RESP_WWW_AUTHENTICATE = 0xA00B; // Integer codes for common (optional) request attribute names public static final byte SC_A_CONTEXT = 1; // XXX Unused public static final byte SC_A_SERVLET_PATH = 2; // XXX Unused public static final byte SC_A_REMOTE_USER = 3; public static final byte SC_A_AUTH_TYPE = 4; public static final byte SC_A_QUERY_STRING = 5; public static final byte SC_A_JVM_ROUTE = 6; public static final byte SC_A_SSL_CERT = 7; public static final byte SC_A_SSL_CIPHER = 8; public static final byte SC_A_SSL_SESSION = 9; public static final byte SC_A_SSL_KEYSIZE = 11; public static final byte SC_A_SECRET = 12; // Used for attributes which are not in the list above public static final byte SC_A_REQ_ATTRIBUTE = 10; // Terminates list of attributes public static final byte SC_A_ARE_DONE = (byte) 0xFF; // Translates integer codes to names of HTTP methods public static final String[] methodTransArray = { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", "ACL", "REPORT", "VERSION-CONTROL", "CHECKIN", "CHECKOUT", "UNCHECKOUT", "SEARCH", "MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY" }; // id's for common request headers public static final int SC_REQ_ACCEPT = 1; public static final int SC_REQ_ACCEPT_CHARSET = 2; public static final int SC_REQ_ACCEPT_ENCODING = 3; public static final int SC_REQ_ACCEPT_LANGUAGE = 4; public static final int SC_REQ_AUTHORIZATION = 5; public static final int SC_REQ_CONNECTION = 6; public static final int SC_REQ_CONTENT_TYPE = 7; public static final int SC_REQ_CONTENT_LENGTH = 8; public static final int SC_REQ_COOKIE = 9; public static final int SC_REQ_COOKIE2 = 10; public static final int SC_REQ_HOST = 11; public static final int SC_REQ_PRAGMA = 12; public static final int SC_REQ_REFERER = 13; public static final int SC_REQ_USER_AGENT = 14; // AJP14 new header public static final byte SC_A_SSL_KEY_SIZE = 11; // XXX ??? // Translates integer codes to request header names public static final String[] headerTransArray = { "accept", "accept-charset", "accept-encoding", "accept-language", "authorization", "connection", "content-type", "content-length", "cookie", "cookie2", "host", "pragma", "referer", "user-agent" }; /* * Note for Host parsing. */ public static final int HOSTBUFFER = 10; HandlerDispatch dispatch; String ajpidDir = "conf"; public HandlerRequest() { } public void init() { dispatch = (HandlerDispatch) wEnv.getHandler("dispatch"); if (dispatch != null) { // register incoming message handlers dispatch.registerMessageType(JK_AJP13_FORWARD_REQUEST, "JK_AJP13_FORWARD_REQUEST", this, null); // 2 dispatch.registerMessageType(JK_AJP13_SHUTDOWN, "JK_AJP13_SHUTDOWN", this, null); // 7 dispatch.registerMessageType(JK_AJP13_CPING_REQUEST, "JK_AJP13_CPING_REQUEST", this, null); // 10 // register outgoing messages handler dispatch.registerMessageType(JK_AJP13_SEND_BODY_CHUNK, // 3 "JK_AJP13_SEND_BODY_CHUNK", this, null); } bodyNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "jkInputStream"); tmpBufNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "tmpBuf"); secretNote = wEnv.getNoteId(WorkerEnv.ENDPOINT_NOTE, "secret"); if (next == null) next = wEnv.getHandler("container"); if (log.isDebugEnabled()) log.debug("Container handler " + next + " " + next.getName() + " " + next.getClass().getName()); // should happen on start() generateAjp13Id(); } public void setSecret(String s) { requiredSecret = s; } public void setUseSecret(boolean b) { requiredSecret = Double.toString(Math.random()); } public void setDecodedUri(boolean b) { decoded = b; } public boolean isTomcatAuthentication() { return tomcatAuthentication; } public void setTomcatAuthentication(boolean newTomcatAuthentication) { tomcatAuthentication = newTomcatAuthentication; } public void setAjpidDir(String path) { if ("".equals(path)) path = null; ajpidDir = path; } // -------------------- Ajp13.id -------------------- private void generateAjp13Id() { int portInt = 8009; // tcpCon.getPort(); InetAddress address = null; // tcpCon.getAddress(); if (requiredSecret == null) return; File f1 = new File(wEnv.getJkHome()); File f2 = new File(f1, "conf"); if (!f2.exists()) { log.error("No conf dir for ajp13.id " + f2); return; } File sf = new File(f2, "ajp13.id"); if (log.isDebugEnabled()) log.debug("Using stop file: " + sf); try { Properties props = new Properties(); props.put("port", Integer.toString(portInt)); if (address != null) { props.put("address", address.getHostAddress()); } if (requiredSecret != null) { props.put("secret", requiredSecret); } FileOutputStream stopF = new FileOutputStream(sf); props.save(stopF, "Automatically generated, don't edit"); } catch (IOException ex) { log.debug("Can't create stop file: " + sf); ex.printStackTrace(); } } // -------------------- Incoming message -------------------- String requiredSecret = null; int bodyNote; int tmpBufNote; int secretNote; boolean decoded = true; boolean tomcatAuthentication = true; public int invoke(Msg msg, MsgContext ep) throws IOException { int type = msg.getByte(); ThreadWithAttributes twa = null; if (Thread.currentThread() instanceof ThreadWithAttributes) { twa = (ThreadWithAttributes) Thread.currentThread(); } Object control = ep.getControl(); MessageBytes tmpMB = (MessageBytes) ep.getNote(tmpBufNote); if (tmpMB == null) { tmpMB = new MessageBytes(); ep.setNote(tmpBufNote, tmpMB); } if (log.isDebugEnabled()) log.debug("Handling " + type); switch (type) { case JK_AJP13_FORWARD_REQUEST: try { if (twa != null) { twa.setCurrentStage(control, "JkDecode"); } decodeRequest(msg, ep, tmpMB); if (twa != null) { twa.setCurrentStage(control, "JkService"); twa.setParam(control, ((Request) ep.getRequest()).unparsedURI()); } } catch (Exception ex) { log.error("Error decoding request ", ex); msg.dump("Incomming message"); return ERROR; } if (requiredSecret != null) { String epSecret = (String) ep.getNote(secretNote); if (epSecret == null || !requiredSecret.equals(epSecret)) return ERROR; } /* XXX it should be computed from request, by workerEnv */ if (log.isDebugEnabled()) log.debug("Calling next " + next.getName() + " " + next.getClass().getName()); int err = next.invoke(msg, ep); if (twa != null) { twa.setCurrentStage(control, "JkDone"); } if (log.isDebugEnabled()) log.debug("Invoke returned " + err); return err; case JK_AJP13_SHUTDOWN: String epSecret = null; if (msg.getLen() > 3) { // we have a secret msg.getBytes(tmpMB); epSecret = tmpMB.toString(); } if (requiredSecret != null && requiredSecret.equals(epSecret)) { if (log.isDebugEnabled()) log.debug("Received wrong secret, no shutdown "); return ERROR; } // XXX add isSameAddress check JkHandler ch = ep.getSource(); if (ch instanceof ChannelSocket) { if (!((ChannelSocket) ch).isSameAddress(ep)) { log.error("Shutdown request not from 'same address' "); return ERROR; } } // forward to the default handler - it'll do the shutdown next.invoke(msg, ep); log.info("Exiting"); System.exit(0); return OK; // We got a PING REQUEST, quickly respond with a PONG case JK_AJP13_CPING_REQUEST: msg.reset(); msg.appendByte(JK_AJP13_CPONG_REPLY); ep.setType(JkHandler.HANDLE_SEND_PACKET); ep.getSource().invoke(msg, ep); return OK; default: System.err.println("Unknown message " + type); msg.dump("Unknown message"); } return OK; } static int count = 0; RequestGroupInfo global = null; private int decodeRequest(Msg msg, MsgContext ep, MessageBytes tmpMB) throws IOException { // FORWARD_REQUEST handler Request req = (Request) ep.getRequest(); if (req == null) { req = new Request(); Response res = new Response(); req.setResponse(res); ep.setRequest(req); if (this.getDomain() != null) { try { if (global == null) { global = new RequestGroupInfo(); Registry.getRegistry().registerComponent(global, getDomain(), "GlobalRequestProcessor", "type=GlobalRequestProcessor,name=jk"); } RequestInfo rp = req.getRequestProcessor(); rp.setGlobalProcessor(global); Registry.getRegistry().registerComponent(rp, getDomain(), "RequestProcessor", "type=RequestProcessor,name=JkRequest" + count++); } catch (Exception ex) { log.warn("Error registering request"); } } } MessageBytes tmpMB2 = (MessageBytes) req.getNote(WorkerEnv.SSL_CERT_NOTE); if (tmpMB2 != null) { tmpMB2.recycle(); } req.setStartTime(System.currentTimeMillis()); JkInputStream jkBody = (JkInputStream) ep.getNote(bodyNote); if (jkBody == null) { jkBody = new JkInputStream(); jkBody.setMsgContext(ep); ep.setNote(bodyNote, jkBody); } jkBody.recycle(); // Translate the HTTP method code to a String. byte methodCode = msg.getByte(); String mName = methodTransArray[(int) methodCode - 1]; req.method().setString(mName); msg.getBytes(req.protocol()); msg.getBytes(req.requestURI()); msg.getBytes(req.remoteAddr()); msg.getBytes(req.remoteHost()); msg.getBytes(req.localName()); req.setLocalPort(msg.getInt()); boolean isSSL = msg.getByte() != 0; if (isSSL) { // XXX req.setSecure( true ); req.scheme().setString("https"); } decodeHeaders(ep, msg, req, tmpMB); decodeAttributes(ep, msg, req, tmpMB); // if(req.getSecure() ) { // req.setScheme(req.SCHEME_HTTPS); // } MessageBytes valueMB = req.getMimeHeaders().getValue("host"); parseHost(valueMB, req); // set cookies on request now that we have all headers req.getCookies().setHeaders(req.getMimeHeaders()); // Check to see if there should be a body packet coming along // immediately after int cl = req.getContentLength(); if (cl > 0) { jkBody.setContentLength(cl); jkBody.receive(); } if (log.isTraceEnabled()) { log.trace(req.toString()); } return OK; } private int decodeAttributes(MsgContext ep, Msg msg, Request req, MessageBytes tmpMB) { boolean moreAttr = true; while (moreAttr) { byte attributeCode = msg.getByte(); if (attributeCode == SC_A_ARE_DONE) return 200; /* Special case ( XXX in future API make it separate type !) */ if (attributeCode == SC_A_SSL_KEY_SIZE) { // Bug 1326: it's an Integer. req.setAttribute(SSLSupport.KEY_SIZE_KEY, new Integer(msg.getInt())); //Integer.toString(msg.getInt())); } if (attributeCode == SC_A_REQ_ATTRIBUTE) { // 2 strings ???... msg.getBytes(tmpMB); String n = tmpMB.toString(); msg.getBytes(tmpMB); String v = tmpMB.toString(); req.setAttribute(n, v); } // 1 string attributes switch (attributeCode) { case SC_A_CONTEXT: msg.getBytes(tmpMB); // nothing break; case SC_A_SERVLET_PATH: msg.getBytes(tmpMB); // nothing break; case SC_A_REMOTE_USER: if (tomcatAuthentication) { // ignore server msg.getBytes(tmpMB); } else { msg.getBytes(req.getRemoteUser()); } break; case SC_A_AUTH_TYPE: msg.getBytes(req.getAuthType()); break; case SC_A_QUERY_STRING: msg.getBytes(req.queryString()); break; case SC_A_JVM_ROUTE: msg.getBytes(req.instanceId()); break; case SC_A_SSL_CERT: req.scheme().setString("https"); // Transform the string into certificate. MessageBytes tmpMB2 = (MessageBytes) req.getNote(WorkerEnv.SSL_CERT_NOTE); if (tmpMB2 == null) { tmpMB2 = new MessageBytes(); req.setNote(WorkerEnv.SSL_CERT_NOTE, tmpMB2); } // SSL certificate extraction is costy, moved to JkCoyoteHandler msg.getBytes(tmpMB2); break; case SC_A_SSL_CIPHER: req.scheme().setString("https"); msg.getBytes(tmpMB); req.setAttribute(SSLSupport.CIPHER_SUITE_KEY, tmpMB.toString()); break; case SC_A_SSL_SESSION: req.scheme().setString("https"); msg.getBytes(tmpMB); req.setAttribute(SSLSupport.SESSION_ID_KEY, tmpMB.toString()); break; case SC_A_SECRET: msg.getBytes(tmpMB); String secret = tmpMB.toString(); log.info("Secret: " + secret); // endpoint note ep.setNote(secretNote, secret); break; default: break; // ignore, we don't know about it - backward compat } } return 200; } private void decodeHeaders(MsgContext ep, Msg msg, Request req, MessageBytes tmpMB) { // Decode headers MimeHeaders headers = req.getMimeHeaders(); int hCount = msg.getInt(); for (int i = 0; i < hCount; i++) { String hName = null; // Header names are encoded as either an integer code starting // with 0xA0, or as a normal string (in which case the first // two bytes are the length). int isc = msg.peekInt(); int hId = isc & 0xFF; MessageBytes vMB = null; isc &= 0xFF00; if (0xA000 == isc) { msg.getInt(); // To advance the read position hName = headerTransArray[hId - 1]; vMB = headers.addValue(hName); } else { // reset hId -- if the header currently being read // happens to be 7 or 8 bytes long, the code below // will think it's the content-type header or the // content-length header - SC_REQ_CONTENT_TYPE=7, // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected // behaviour. see bug 5861 for more information. hId = -1; msg.getBytes(tmpMB); ByteChunk bc = tmpMB.getByteChunk(); //hName=tmpMB.toString(); // vMB=headers.addValue( hName ); vMB = headers.addValue(bc.getBuffer(), bc.getStart(), bc.getLength()); } msg.getBytes(vMB); if (hId == SC_REQ_CONTENT_LENGTH || tmpMB.equalsIgnoreCase("Content-Length")) { // just read the content-length header, so set it int contentLength = (vMB == null) ? -1 : vMB.getInt(); req.setContentLength(contentLength); } else if (hId == SC_REQ_CONTENT_TYPE || tmpMB.equalsIgnoreCase("Content-Type")) { // just read the content-type header, so set it ByteChunk bchunk = vMB.getByteChunk(); req.contentType().setBytes(bchunk.getBytes(), bchunk.getOffset(), bchunk.getLength()); } } } /** * Parse host. */ private void parseHost(MessageBytes valueMB, Request request) throws IOException { if (valueMB == null || valueMB.isNull()) { // HTTP/1.0 // Default is what the socket tells us. Overriden if a host is // found/parsed request.setServerPort(request.getLocalPort()); request.serverName().duplicate(request.localName()); return; } ByteChunk valueBC = valueMB.getByteChunk(); byte[] valueB = valueBC.getBytes(); int valueL = valueBC.getLength(); int valueS = valueBC.getStart(); int colonPos = -1; CharChunk hostNameC = (CharChunk) request.getNote(HOSTBUFFER); if (hostNameC == null) { hostNameC = new CharChunk(valueL); request.setNote(HOSTBUFFER, hostNameC); } hostNameC.recycle(); boolean ipv6 = (valueB[valueS] == '['); boolean bracketClosed = false; for (int i = 0; i < valueL; i++) { char b = (char) valueB[i + valueS]; hostNameC.append(b); if (b == ']') { bracketClosed = true; } else if (b == ':') { if (!ipv6 || bracketClosed) { colonPos = i; break; } } } if (colonPos < 0) { if (request.scheme().equalsIgnoreCase("https")) { // 80 - Default HTTTP port request.setServerPort(443); } else { // 443 - Default HTTPS port request.setServerPort(80); } request.serverName().setChars(hostNameC.getChars(), hostNameC.getStart(), hostNameC.getLength()); } else { request.serverName().setChars(hostNameC.getChars(), hostNameC.getStart(), colonPos); int port = 0; int mult = 1; for (int i = valueL - 1; i > colonPos; i--) { int charValue = HexUtils.DEC[(int) valueB[i + valueS]]; if (charValue == -1) { // Invalid character throw new CharConversionException("Invalid char in port: " + valueB[i + valueS]); } port = port + (charValue * mult); mult = 10 * mult; } request.setServerPort(port); } } }