/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. * * * This file incorporates work covered by the following copyright and * permission notice: * * Copyright 2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sun.grizzly.http.jk.common; import com.sun.grizzly.http.jk.core.Msg; import com.sun.grizzly.http.jk.core.MsgContext; import java.io.IOException; import com.sun.grizzly.tcp.OutputBuffer; import com.sun.grizzly.tcp.InputBuffer; import com.sun.grizzly.tcp.Request; import com.sun.grizzly.tcp.Response; import com.sun.grizzly.util.LoggerUtils; import com.sun.grizzly.util.buf.ByteChunk; import com.sun.grizzly.util.buf.MessageBytes; import com.sun.grizzly.util.buf.C2BConverter; import com.sun.grizzly.util.http.HttpMessages; import com.sun.grizzly.util.http.MimeHeaders; import java.util.logging.Level; /** Generic input stream impl on top of ajp */ public class JkInputStream implements InputBuffer, OutputBuffer { private Msg bodyMsg; private Msg outputMsg; private MsgContext mc; // Holds incoming chunks of request body data private MessageBytes bodyBuff = MessageBytes.newInstance(); private MessageBytes tempMB = MessageBytes.newInstance(); private boolean end_of_stream = false; private boolean isEmpty = true; private boolean isFirst = true; private boolean isReplay = false; private boolean isReadRequired = false; static { // Make certain HttpMessages is loaded for SecurityManager try { Class.forName("com.sun.grizzly.util.http.HttpMessages"); } catch (Exception ex) { // ignore } } public JkInputStream(MsgContext context, int bsize) { mc = context; bodyMsg = new MsgAjp(bsize); outputMsg = new MsgAjp(bsize); } /** * @deprecated */ public JkInputStream(MsgContext context) { this(context, 8 * 1024); } // -------------------- Jk specific methods -------------------- /** * Set the flag saying that the server is sending a body */ public void setIsReadRequired(boolean irr) { isReadRequired = irr; } /** * Return the flag saying that the server is sending a body */ public boolean isReadRequired() { return isReadRequired; } /** Must be called before or after each request */ public void recycle() { if (isReadRequired && isFirst) { // The Servlet never read the request body, so we need to junk it try { receive(); } catch (IOException iex) { LoggerUtils.getLogger().log(Level.FINEST, "Error consuming request body", iex); } } end_of_stream = false; isEmpty = true; isFirst = true; isReplay = false; isReadRequired = false; bodyBuff.recycle(); tempMB.recycle(); } public void endMessage() throws IOException { outputMsg.reset(); outputMsg.appendByte(AjpConstants.JK_AJP13_END_RESPONSE); outputMsg.appendByte(1); mc.getSource().send(outputMsg, mc); mc.getSource().flush(outputMsg, mc); } // -------------------- OutputBuffer implementation -------------------- public int doWrite(ByteChunk chunk, Response res) throws IOException { if (!res.isCommitted()) { // Send the connector a request for commit. The connector should // then validate the headers, send them (using sendHeader) and // set the filters accordingly. res.sendHeaders(); } int len = chunk.getLength(); byte buf[] = outputMsg.getBuffer(); // 4 - hardcoded, byte[] marshalling overhead int chunkSize = buf.length - outputMsg.getHeaderLength() - 4; int off = 0; while (len > 0) { int thisTime = len; if (thisTime > chunkSize) { thisTime = chunkSize; } len -= thisTime; outputMsg.reset(); outputMsg.appendByte(AjpConstants.JK_AJP13_SEND_BODY_CHUNK); outputMsg.appendBytes(chunk.getBytes(), chunk.getOffset() + off, thisTime); off += thisTime; mc.getSource().send(outputMsg, mc); } return 0; } public int doRead(ByteChunk responseChunk, Request req) throws IOException { if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) { LoggerUtils.getLogger().log(Level.FINEST, "doRead " + end_of_stream + " " + responseChunk.getOffset() + " " + responseChunk.getLength()); } if (end_of_stream) { return -1; } if (isFirst && isReadRequired) { // Handle special first-body-chunk, but only if httpd expects it. if (!receive()) { return 0; } } else if (isEmpty) { if (!refillReadBuffer()) { return -1; } } ByteChunk bc = bodyBuff.getByteChunk(); responseChunk.setBytes(bc.getBuffer(), bc.getStart(), bc.getLength()); isEmpty = true; return responseChunk.getLength(); } /** Receive a chunk of data. Called to implement the * 'special' packet in ajp13 and to receive the data * after we send a GET_BODY packet */ public boolean receive() throws IOException { isFirst = false; bodyMsg.reset(); int err = mc.getSource().receive(bodyMsg, mc); if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) { LoggerUtils.getLogger().info("Receiving: getting request body chunk " + err + " " + bodyMsg.getLen()); } if (err < 0) { throw new IOException(); } // No data received. if (bodyMsg.getLen() == 0) { // just the header // Don't mark 'end of stream' for the first chunk. // end_of_stream = true; return false; } int blen = bodyMsg.peekInt(); if (blen == 0) { return false; } bodyMsg.getBytes(bodyBuff); isEmpty = false; return true; } /** * Get more request body data from the web server and store it in the * internal buffer. * * @return true if there is more data, false if not. */ private boolean refillReadBuffer() throws IOException { // If the server returns an empty packet, assume that that end of // the stream has been reached (yuck -- fix protocol??). if (isReplay) { end_of_stream = true; // we've read everything there is } if (end_of_stream) { if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) { LoggerUtils.getLogger().log(Level.FINEST, "refillReadBuffer: end of stream "); } return false; } // Why not use outBuf?? bodyMsg.reset(); bodyMsg.appendByte(AjpConstants.JK_AJP13_GET_BODY_CHUNK); bodyMsg.appendInt(AjpConstants.MAX_READ_SIZE); if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) { LoggerUtils.getLogger().log(Level.FINEST, "refillReadBuffer " + Thread.currentThread()); } mc.getSource().send(bodyMsg, mc); mc.getSource().flush(bodyMsg, mc); // Server needs to get it // In JNI mode, response will be in bodyMsg. In TCP mode, response need to be // read boolean moreData = receive(); if (!moreData) { end_of_stream = true; } return moreData; } public void appendHead(Response res) throws IOException { if (LoggerUtils.getLogger().isLoggable(Level.FINEST)) { LoggerUtils.getLogger().log(Level.FINEST, "COMMIT sending headers " + res + " " + res.getMimeHeaders()); } C2BConverter c2b = mc.getConverter(); outputMsg.reset(); outputMsg.appendByte(AjpConstants.JK_AJP13_SEND_HEADERS); outputMsg.appendInt(res.getStatus()); String message = null; // if (com.sun.grizzly.tcp.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER) { message = res.getMessage(); // } if (message == null) { message = HttpMessages.getMessage(res.getStatus()); } else { message = message.replace('\n', ' ').replace('\r', ' '); } tempMB.setString(message); c2b.convert(tempMB); outputMsg.appendBytes(tempMB); // XXX add headers MimeHeaders headers = res.getMimeHeaders(); String contentType = res.getContentType(); if (contentType != null) { headers.setValue("Content-Type").setString(contentType); } String contentLanguage = res.getContentLanguage(); if (contentLanguage != null) { headers.setValue("Content-Language").setString(contentLanguage); } long contentLength = res.getContentLengthLong(); if (contentLength >= 0) { headers.setValue("Content-Length").setLong(contentLength); } int numHeaders = headers.size(); outputMsg.appendInt(numHeaders); for (int i = 0; i < numHeaders; i++) { MessageBytes hN = headers.getName(i); // no header to sc conversion - there's little benefit // on this direction c2b.convert(hN); outputMsg.appendBytes(hN); MessageBytes hV = headers.getValue(i); c2b.convert(hV); outputMsg.appendBytes(hV); } mc.getSource().send(outputMsg, mc); } /** * Set the replay buffer for Form auth */ public void setReplay(ByteChunk replay) { isFirst = false; isEmpty = false; isReplay = true; bodyBuff.setBytes(replay.getBytes(), replay.getStart(), replay.getLength()); } }