Author: costin Date: Tue Mar 25 08:17:03 2008 New Revision: 640852 URL: http://svn.apache.org/viewvc?rev=640852&view=rev Log: A bunch of files to make it easier to load-test, profile, debug coyote. It can also be used to run simple servers with the maximum speed available - no tomcat or servlet engine overhead.
To be useful, a couple of extensions to MessageBytes are needed - to make it easier to use. Some of this is from an old experiment, removed than added back. Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/ClientAbortException.java tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/CoyoteServer.java tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/EchoAdapter.java tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MapperAdapter.java tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageReader.java tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageWriter.java tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/SimpleFileAdapter.java tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticAdapter.java tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticMain.java Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/ClientAbortException.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/ClientAbortException.java?rev=640852&view=auto ============================================================================== --- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/ClientAbortException.java (added) +++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/ClientAbortException.java Tue Mar 25 08:17:03 2008 @@ -0,0 +1,144 @@ +/* + * Copyright 1999,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 org.apache.coyote.adapters; + +import java.io.IOException; + +/** + * Wrap an IOException identifying it as being caused by an abort + * of a request by a remote client. + * + * @author Glenn L. Nielsen + * @version $Revision: 304063 $ $Date: 2005-08-18 06:25:18 -0700 (Thu, 18 Aug 2005) $ + */ + +public final class ClientAbortException extends IOException { + + + //------------------------------------------------------------ Constructors + + + /** + * Construct a new ClientAbortException with no other information. + */ + public ClientAbortException() { + + this(null, null); + + } + + + /** + * Construct a new ClientAbortException for the specified message. + * + * @param message Message describing this exception + */ + public ClientAbortException(String message) { + + this(message, null); + + } + + + /** + * Construct a new ClientAbortException for the specified throwable. + * + * @param throwable Throwable that caused this exception + */ + public ClientAbortException(Throwable throwable) { + + this(null, throwable); + + } + + + /** + * Construct a new ClientAbortException for the specified message + * and throwable. + * + * @param message Message describing this exception + * @param throwable Throwable that caused this exception + */ + public ClientAbortException(String message, Throwable throwable) { + + super(); + this.message = message; + this.throwable = throwable; + + } + + + //------------------------------------------------------ Instance Variables + + + /** + * The error message passed to our constructor (if any) + */ + protected String message = null; + + + /** + * The underlying exception or error passed to our constructor (if any) + */ + protected Throwable throwable = null; + + + //---------------------------------------------------------- Public Methods + + + /** + * Returns the message associated with this exception, if any. + */ + public String getMessage() { + + return (message); + + } + + + /** + * Returns the cause that caused this exception, if any. + */ + public Throwable getCause() { + + return (throwable); + + } + + + /** + * Return a formatted string that describes this exception. + */ + public String toString() { + + StringBuffer sb = new StringBuffer("ClientAbortException: "); + if (message != null) { + sb.append(message); + if (throwable != null) { + sb.append(": "); + } + } + if (throwable != null) { + sb.append(throwable.toString()); + } + return (sb.toString()); + + } + + +} Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/CoyoteServer.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/CoyoteServer.java?rev=640852&view=auto ============================================================================== --- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/CoyoteServer.java (added) +++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/CoyoteServer.java Tue Mar 25 08:17:03 2008 @@ -0,0 +1,136 @@ +package org.apache.coyote.adapters; + +import java.lang.management.ManagementFactory; + +import org.apache.coyote.Adapter; +import org.apache.coyote.ProtocolHandler; +import org.apache.coyote.http11.Http11NioProtocol; +import org.apache.juli.JdkLoggerConfig; +import org.apache.tomcat.util.modeler.Registry; + + +/** + * Simple example of embeding coyote servlet. + * + */ +public class CoyoteServer { + int port = 8800; + String args[]; + + protected ProtocolHandler proto; + + Registry registry; + + protected Adapter adapter; + int maxThreads = 20; + + public CoyoteServer() { + } + + public CoyoteServer(int i) { + port = i; + } + + public CoyoteServer(int i, Adapter adapter) { + port = i; + addAdapter("/", adapter); + } + + public void setArgs(String[] args) { + this.args = args; + } + + /** + * Add an adapter. If more than the 'default' adapter is + * added, a MapperAdapter will be inserted. + * + * @param path Use "/" for the default. + * @param adapter + */ + public void addAdapter(String path, Adapter added) { + if (adapter == null && "/".equals(path)) { + adapter = added; + } else { + if (!(adapter instanceof MapperAdapter)) { + Adapter oldDefault = adapter; + adapter = new MapperAdapter(); + ((MapperAdapter) adapter).setDefaultAdapter(oldDefault); + } + ((MapperAdapter) adapter).getMapper().addWrapper(path, added); + } + } + + /** + */ + public void run() { + init(); + start(); + } + + public void init() { + new JdkLoggerConfig(); + initJMX(); + } + + protected void initAdapters() { + // adapter = ... + // Adapter secondaryadapter = ... + //registry.registerComponent(secondaryadapter, ":name=adapter", null); + } + + public void stop() throws Exception { + proto.destroy(); + } + + /** + * Simple CLI support - arg is a path:className pair. + */ + public void setAdapter(String arg) { + String[] pathClass = arg.split(":", 2); + try { + Class c = Class.forName(pathClass[1]); + Adapter a = (Adapter) c.newInstance(); + addAdapter(pathClass[0],a); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public void setPort() { + this.port = port; + } + + /** + */ + public static ProtocolHandler getDefaultConnector(int port) { + Http11NioProtocol proto = new Http11NioProtocol(); + proto.setCompression("on"); + proto.setCompressionMinSize(32); + proto.setPort(port); + proto.getEndpoint().setDaemon(false); + return proto; + } + + public void start() { + try { + proto = getDefaultConnector(port); + initAdapters(); + registry.registerComponent(adapter, ":name=adapter" + (port), null); + + proto.setAdapter(adapter); + + registry.registerComponent(proto, ":name=ep-" + port, null); + proto.start(); + proto.init(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public void initJMX() { + ManagementFactory.getPlatformMBeanServer(); + registry = Registry.getRegistry(null, null); + + } + +} \ No newline at end of file Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/EchoAdapter.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/EchoAdapter.java?rev=640852&view=auto ============================================================================== --- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/EchoAdapter.java (added) +++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/EchoAdapter.java Tue Mar 25 08:17:03 2008 @@ -0,0 +1,42 @@ +package org.apache.coyote.adapters; + +import java.util.logging.Logger; + +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.http.HttpProcessor; +import org.apache.tomcat.util.net.SocketStatus; + +/** + * Response is plain/text, copy of the received request + */ +public class EchoAdapter implements Adapter { + Logger log = Logger.getLogger("coyote.static"); + + String contentType = "text/plain"; + + + public EchoAdapter() { + } + + public void service(Request req, final Response res) throws Exception { + ByteChunk reqBuf = new ByteChunk(1024); + HttpProcessor.serializeRequest(req, reqBuf); + + res.setStatus(200); + res.setContentLength(reqBuf.getLength()); + res.setContentType(contentType); + + res.sendHeaders(); + + res.doWrite(reqBuf); + } + + public boolean event(Request req, Response res, SocketStatus status) + throws Exception { + return false; + } + +} \ No newline at end of file Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MapperAdapter.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MapperAdapter.java?rev=640852&view=auto ============================================================================== --- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MapperAdapter.java (added) +++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MapperAdapter.java Tue Mar 25 08:17:03 2008 @@ -0,0 +1,214 @@ +package org.apache.coyote.adapters; + +import java.io.IOException; + +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.mapper.Mapper; +import org.apache.tomcat.util.http.mapper.MappingData; +import org.apache.tomcat.util.net.SocketStatus; + +/** + * + */ +public class MapperAdapter implements Adapter { + + private Mapper mapper=new Mapper(); + + public MapperAdapter() { + mapper = new Mapper(); + mapper.setDefaultHostName("localhost"); + mapper.setContext("", new String[] {"index.html"}, + null); + } + + public MapperAdapter(Mapper mapper2) { + mapper = mapper2; + } + + public static void decodeRequest(Request reqB) throws IOException { + MessageBytes decodedURI = reqB.decodedURI(); + decodedURI.duplicate(reqB.requestURI()); + + if (decodedURI.getType() == MessageBytes.T_BYTES) { + // %xx decoding of the URL + reqB.getURLDecoder().convert(decodedURI, false); + // Normalization + if (!normalize(decodedURI)) { + throw new IOException("Error normalizing"); + } + // Character decoding + //convertURI(decodedURI, request); + } else { + // The URL is chars or String, and has been sent using an in-memory + // protocol handler, we have to assume the URL has been properly + // decoded already + decodedURI.toChars(); + } + } + + /** + * Normalize URI. + * <p> + * This method normalizes "\", "//", "/./" and "/../". This method will + * return false when trying to go above the root, or if the URI contains + * a null byte. + * + * @param uriMB URI to be normalized + */ + public static boolean normalize(MessageBytes uriMB) { + + ByteChunk uriBC = uriMB.getByteChunk(); + byte[] b = uriBC.getBytes(); + int start = uriBC.getStart(); + int end = uriBC.getEnd(); + + // URL * is acceptable + if ((end - start == 1) && b[start] == (byte) '*') + return true; + + int pos = 0; + int index = 0; + + // Replace '\' with '/' + // Check for null byte + for (pos = start; pos < end; pos++) { + if (b[pos] == (byte) '\\') + b[pos] = (byte) '/'; + if (b[pos] == (byte) 0) + return false; + } + + // The URL must start with '/' + if (b[start] != (byte) '/') { + return false; + } + + // Replace "//" with "/" + for (pos = start; pos < (end - 1); pos++) { + if (b[pos] == (byte) '/') { + while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) { + copyBytes(b, pos, pos + 1, end - pos - 1); + end--; + } + } + } + + // If the URI ends with "/." or "/..", then we append an extra "/" + // Note: It is possible to extend the URI by 1 without any side effect + // as the next character is a non-significant WS. + if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) { + if ((b[end - 2] == (byte) '/') + || ((b[end - 2] == (byte) '.') + && (b[end - 3] == (byte) '/'))) { + b[end] = (byte) '/'; + end++; + } + } + + uriBC.setEnd(end); + + index = 0; + + // Resolve occurrences of "/./" in the normalized path + while (true) { + index = uriBC.indexOf("/./", 0, 3, index); + if (index < 0) + break; + copyBytes(b, start + index, start + index + 2, + end - start - index - 2); + end = end - 2; + uriBC.setEnd(end); + } + + index = 0; + + // Resolve occurrences of "/../" in the normalized path + while (true) { + index = uriBC.indexOf("/../", 0, 4, index); + if (index < 0) + break; + // Prevent from going outside our context + if (index == 0) + return false; + int index2 = -1; + for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) { + if (b[pos] == (byte) '/') { + index2 = pos; + } + } + copyBytes(b, start + index2, start + index + 3, + end - start - index - 3); + end = end + index2 - index - 3; + uriBC.setEnd(end); + index = index2; + } + + //uriBC.setBytes(b, start, end); + uriBC.setEnd(end); + return true; + + } + + /** + * Copy an array of bytes to a different position. Used during + * normalization. + */ + public static void copyBytes(byte[] b, int dest, int src, int len) { + for (int pos = 0; pos < len; pos++) { + b[pos + dest] = b[pos + src]; + } + } + + + public void service(Request req, final Response res) + throws Exception { + try { + // compute decodedURI - not done by connector + decodeRequest(req); + MappingData mapRes = new MappingData(); + + mapper.map(req.requestURI(), mapRes); + + Adapter h=(Adapter)mapRes.wrapper; + if (h != null) { + h.service( req, res ); + } else { + res.setStatus(404); + } + + } catch( Throwable t ) { + t.printStackTrace(); + } + + // Final processing + MessageWriter.getWriter(req, res, 0).flush(); + res.finish(); + + req.recycle(); + res.recycle(); + + } + + public Mapper getMapper() { + return mapper; + } + + public void setDefaultAdapter(Adapter adapter) { + mapper.addWrapper("/", adapter); + } + + public boolean event(Request req, Response res, boolean error) throws Exception { + // TODO Auto-generated method stub + return false; + } + + public boolean event(Request req, Response res, SocketStatus status) + throws Exception { + return false; + } + +} \ No newline at end of file Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageReader.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageReader.java?rev=640852&view=auto ============================================================================== --- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageReader.java (added) +++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageReader.java Tue Mar 25 08:17:03 2008 @@ -0,0 +1,503 @@ +/* + * Copyright 1999,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 org.apache.coyote.adapters; + +import java.io.IOException; +import java.io.Reader; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.HashMap; + +import org.apache.coyote.Request; +import org.apache.tomcat.util.buf.B2CConverter; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.buf.CharChunk; + +/** + * Refactored from catalina.connector.InputBuffer. Renamed to avoid conflict + * with coyote class. + * + * TODO: move to coyote package. + */ + +/** + * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3 + * OutputBuffer, adapted to handle input instead of output. This allows + * complete recycling of the facade objects (the ServletInputStream and the + * BufferedReader). + * + * @author Remy Maucherat + */ +public class MessageReader extends Reader + implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, + CharChunk.CharOutputChannel { + + + // -------------------------------------------------------------- Constants + + + public static final String DEFAULT_ENCODING = + org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING; + public static final int DEFAULT_BUFFER_SIZE = 8*1024; + + // The buffer can be used for byte[] and char[] reading + // ( this is needed to support ServletInputStream and BufferedReader ) + public final int INITIAL_STATE = 0; + public final int CHAR_STATE = 1; + public final int BYTE_STATE = 2; + + + // ----------------------------------------------------- Instance Variables + + + /** + * The byte buffer. + */ + private ByteChunk bb; + + + /** + * The chunk buffer. + */ + private CharChunk cb; + + + /** + * State of the output buffer. + */ + private int state = 0; + + + /** + * Number of bytes read. + */ + private int bytesRead = 0; + + + /** + * Number of chars read. + */ + private int charsRead = 0; + + + /** + * Flag which indicates if the input buffer is closed. + */ + private boolean closed = false; + + + /** + * Byte chunk used to input bytes. + */ + private ByteChunk inputChunk = new ByteChunk(); + + + /** + * Encoding to use. + */ + private String enc; + + + /** + * Encoder is set. + */ + private boolean gotEnc = false; + + + /** + * List of encoders. + */ + protected HashMap encoders = new HashMap(); + + + /** + * Current byte to char converter. + */ + protected B2CConverter conv; + + + /** + * Associated Coyote request. + */ + private Request coyoteRequest; + + + /** + * Buffer position. + */ + private int markPos = -1; + + + /** + * Buffer size. + */ + private int size = -1; + + + // ----------------------------------------------------------- Constructors + + + /** + * Default constructor. Allocate the buffer with the default buffer size. + */ + public MessageReader() { + + this(DEFAULT_BUFFER_SIZE); + + } + + + /** + * Alternate constructor which allows specifying the initial buffer size. + * + * @param size Buffer size to use + */ + public MessageReader(int size) { + + this.size = size; + bb = new ByteChunk(size); + bb.setLimit(size); + bb.setByteInputChannel(this); + cb = new CharChunk(size); + cb.setLimit(size); + cb.setOptimizedWrite(false); + cb.setCharInputChannel(this); + cb.setCharOutputChannel(this); + + } + + + // ------------------------------------------------------------- Properties + + + /** + * Associated Coyote request. + * + * @param coyoteRequest Associated Coyote request + */ + public void setRequest(Request coyoteRequest) { + this.coyoteRequest = coyoteRequest; + } + + + /** + * Get associated Coyote request. + * + * @return the associated Coyote request + */ + public Request getRequest() { + return this.coyoteRequest; + } + + + // --------------------------------------------------------- Public Methods + + + /** + * Recycle the output buffer. + */ + public void recycle() { + + state = INITIAL_STATE; + bytesRead = 0; + charsRead = 0; + + // If usage of mark made the buffer too big, reallocate it + if (cb.getChars().length > size) { + cb = new CharChunk(size); + cb.setLimit(size); + cb.setCharInputChannel(this); + cb.setCharOutputChannel(this); + } else { + cb.recycle(); + } + markPos = -1; + bb.recycle(); + closed = false; + + if (conv != null) { + conv.recycle(); + } + + gotEnc = false; + enc = null; + + } + + + /** + * Close the input buffer. + * + * @throws IOException An underlying IOException occurred + */ + public void close() + throws IOException { + closed = true; + } + + + public int available() + throws IOException { + if (state == BYTE_STATE) { + return bb.getLength(); + } else if (state == CHAR_STATE) { + return cb.getLength(); + } else { + return 0; + } + } + + + // ------------------------------------------------- Bytes Handling Methods + + + /** + * Reads new bytes in the byte chunk. + * + * @param cbuf Byte buffer to be written to the response + * @param off Offset + * @param len Length + * + * @throws IOException An underlying IOException occurred + */ + public int realReadBytes(byte cbuf[], int off, int len) + throws IOException { + + if (closed) + return -1; + if (coyoteRequest == null) + return -1; + + state = BYTE_STATE; + + int result = coyoteRequest.doRead(bb); + + return result; + + } + + + public int readByte() + throws IOException { + return bb.substract(); + } + + + public int read(byte[] b, int off, int len) + throws IOException { + return bb.substract(b, off, len); + } + + + // ------------------------------------------------- Chars Handling Methods + + + /** + * Since the converter will use append, it is possible to get chars to + * be removed from the buffer for "writing". Since the chars have already + * been read before, they are ignored. If a mark was set, then the + * mark is lost. + */ + public void realWriteChars(char c[], int off, int len) + throws IOException { + markPos = -1; + } + + + public void setEncoding(String s) { + enc = s; + } + + + public int realReadChars(char cbuf[], int off, int len) + throws IOException { + + if (!gotEnc) + setConverter(); + + if (bb.getLength() <= 0) { + int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length); + if (nRead < 0) { + return -1; + } + } + + if (markPos == -1) { + cb.setOffset(0); + cb.setEnd(0); + } + + conv.convert(bb, cb); + bb.setOffset(bb.getEnd()); + state = CHAR_STATE; + + return cb.getLength(); + + } + + + public int read() + throws IOException { + return cb.substract(); + } + + + public int read(char[] cbuf) + throws IOException { + return read(cbuf, 0, cbuf.length); + } + + + public int read(char[] cbuf, int off, int len) + throws IOException { + return cb.substract(cbuf, off, len); + } + + + public long skip(long n) + throws IOException { + + if (n < 0) { + throw new IllegalArgumentException(); + } + + long nRead = 0; + while (nRead < n) { + if (cb.getLength() >= n) { + cb.setOffset(cb.getStart() + (int) n); + nRead = n; + } else { + nRead += cb.getLength(); + cb.setOffset(cb.getEnd()); + int toRead = 0; + if (cb.getChars().length < (n - nRead)) { + toRead = cb.getChars().length; + } else { + toRead = (int) (n - nRead); + } + int nb = realReadChars(cb.getChars(), 0, toRead); + if (nb < 0) + break; + } + } + + return nRead; + + } + + + public boolean ready() + throws IOException { + return (cb.getLength() > 0); + } + + + public boolean markSupported() { + return true; + } + + + public void mark(int readAheadLimit) + throws IOException { + if (cb.getLength() <= 0) { + cb.setOffset(0); + cb.setEnd(0); + } else { + if ((cb.getBuffer().length > (2 * size)) + && (cb.getLength()) < (cb.getStart())) { + System.arraycopy(cb.getBuffer(), cb.getStart(), + cb.getBuffer(), 0, cb.getLength()); + cb.setEnd(cb.getLength()); + cb.setOffset(0); + } + } + int offset = readAheadLimit; + if (offset < size) { + offset = size; + } + cb.setLimit(cb.getStart() + offset); + markPos = cb.getStart(); + } + + + public void reset() + throws IOException { + if (state == CHAR_STATE) { + if (markPos < 0) { + cb.recycle(); + markPos = -1; + throw new IOException(); + } else { + cb.setOffset(markPos); + } + } else { + bb.recycle(); + } + } + + + public void checkConverter() + throws IOException { + + if (!gotEnc) + setConverter(); + + } + + + protected void setConverter() + throws IOException { + + if (coyoteRequest != null) + enc = coyoteRequest.getCharacterEncoding(); + + gotEnc = true; + if (enc == null) + enc = DEFAULT_ENCODING; + conv = (B2CConverter) encoders.get(enc); + if (conv == null) { + if (packageDefinitionEnabled && System.getSecurityManager() != null) { + //SecurityUtil.isPackageProtectionEnabled()){ + try{ + conv = (B2CConverter)AccessController.doPrivileged( + new PrivilegedExceptionAction(){ + + public Object run() throws IOException{ + return new B2CConverter(enc); + } + + } + ); + }catch(PrivilegedActionException ex){ + Exception e = ex.getException(); + if (e instanceof IOException) + throw (IOException)e; + } + } else { + conv = new B2CConverter(enc); + } + encoders.put(enc, conv); + } + + } + + private static boolean packageDefinitionEnabled = + (System.getProperty("package.definition") == null && + System.getProperty("package.access") == null) ? false : true; + +} Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageWriter.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageWriter.java?rev=640852&view=auto ============================================================================== --- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageWriter.java (added) +++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/MessageWriter.java Tue Mar 25 08:17:03 2008 @@ -0,0 +1,680 @@ +/* + * Copyright 1999,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 org.apache.coyote.adapters; + + +import java.io.IOException; +import java.io.Writer; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.HashMap; + +import org.apache.coyote.ActionCode; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.buf.C2BConverter; +import org.apache.tomcat.util.buf.CharChunk; +import org.apache.tomcat.util.buf.MessageBytes; + +/* + * Refactoring: original code in catalina.connector. + * - renamed to OutputWriter to avoid confusion with coyote OutputBuffer + * - + * TODO: move it to coyote, add Response.getWriter + * + */ + +/** + * The buffer used by Tomcat response. This is a derivative of the Tomcat 3.3 + * OutputBuffer, with the removal of some of the state handling (which in + * Coyote is mostly the Processor's responsability). + * + * @author Costin Manolache + * @author Remy Maucherat + */ +public class MessageWriter extends Writer + implements ByteChunk.ByteOutputChannel, CharChunk.CharOutputChannel { + + // used in getWriter, until a method is added to res. + private static final int WRITER_NOTE = 3; + + // -------------------------------------------------------------- Constants + + + public static final String DEFAULT_ENCODING = + org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING; + public static final int DEFAULT_BUFFER_SIZE = 8*1024; + + + // The buffer can be used for byte[] and char[] writing + // ( this is needed to support ServletOutputStream and for + // efficient implementations of templating systems ) + public final int INITIAL_STATE = 0; + public final int CHAR_STATE = 1; + public final int BYTE_STATE = 2; + + + // ----------------------------------------------------- Instance Variables + + + /** + * The byte buffer. + */ + private ByteChunk bb; + + + /** + * The chunk buffer. + */ + private CharChunk cb; + + + /** + * State of the output buffer. + */ + private int state = 0; + + + /** + * Number of bytes written. + */ + private int bytesWritten = 0; + + + /** + * Number of chars written. + */ + private int charsWritten = 0; + + + /** + * Flag which indicates if the output buffer is closed. + */ + private boolean closed = false; + + + /** + * Do a flush on the next operation. + */ + private boolean doFlush = false; + + + /** + * Byte chunk used to output bytes. This is just used to wrap the byte[] + * to match the coyote OutputBuffer interface + */ + private ByteChunk outputChunk = new ByteChunk(); + + + /** + * Encoding to use. + * TODO: isn't it redundant ? enc, gotEnc, conv plus the enc in the bb + */ + private String enc; + + + /** + * Encoder is set. + */ + private boolean gotEnc = false; + + + /** + * List of encoders. The writer is reused - the encoder mapping + * avoids creating expensive objects. In future it'll contain nio.Charsets + */ + protected HashMap encoders = new HashMap(); + + + /** + * Current char to byte converter. TODO: replace with Charset + */ + protected C2BConverter conv; + + + /** + * Associated Coyote response. + */ + private Response coyoteResponse; + + + /** + * Suspended flag. All output bytes will be swallowed if this is true. + */ + private boolean suspended = false; + + + // ----------------------------------------------------------- Constructors + + + /** + * Default constructor. Allocate the buffer with the default buffer size. + */ + public MessageWriter() { + + this(DEFAULT_BUFFER_SIZE); + + } + + + /** + * Alternate constructor which allows specifying the initial buffer size. + * + * @param size Buffer size to use + */ + public MessageWriter(int size) { + + bb = new ByteChunk(size); + bb.setLimit(size); + bb.setByteOutputChannel(this); + cb = new CharChunk(size); + cb.setCharOutputChannel(this); + cb.setLimit(size); + + } + + + // ------------------------------------------------------------- Properties + + + /** + * Associated Coyote response. + * + * @param coyoteResponse Associated Coyote response + */ + public void setResponse(Response coyoteResponse) { + this.coyoteResponse = coyoteResponse; + } + + + /** + * Get associated Coyote response. + * + * @return the associated Coyote response + */ + public Response getResponse() { + return this.coyoteResponse; + } + + + /** + * Is the response output suspended ? + * + * @return suspended flag value + */ + public boolean isSuspended() { + return this.suspended; + } + + + /** + * Set the suspended flag. + * + * @param suspended New suspended flag value + */ + public void setSuspended(boolean suspended) { + this.suspended = suspended; + } + + + // --------------------------------------------------------- Public Methods + + + /** + * Recycle the output buffer. + */ + public void recycle() { + + state = INITIAL_STATE; + bytesWritten = 0; + charsWritten = 0; + + cb.recycle(); + bb.recycle(); + closed = false; + suspended = false; + + if (conv!= null) { + conv.recycle(); + } + + gotEnc = false; + enc = null; + + } + + + /** + * Close the output buffer. This tries to calculate the response size if + * the response has not been committed yet. + * + * @throws IOException An underlying IOException occurred + */ + public void close() + throws IOException { + + if (closed) + return; + if (suspended) + return; + + if ((!coyoteResponse.isCommitted()) + && (coyoteResponse.getContentLengthLong() == -1)) { + // Flushing the char buffer + if (state == CHAR_STATE) { + cb.flushBuffer(); + state = BYTE_STATE; + } + // If this didn't cause a commit of the response, the final content + // length can be calculated + if (!coyoteResponse.isCommitted()) { + coyoteResponse.setContentLength(bb.getLength()); + } + } + + doFlush(false); + closed = true; + + coyoteResponse.finish(); + + } + + + /** + * Flush bytes or chars contained in the buffer. + * + * @throws IOException An underlying IOException occurred + */ + public void flush() + throws IOException { + doFlush(true); + } + + + /** + * Flush bytes or chars contained in the buffer. + * + * @throws IOException An underlying IOException occurred + */ + protected void doFlush(boolean realFlush) + throws IOException { + + if (suspended) + return; + + doFlush = true; + if (state == CHAR_STATE) { + cb.flushBuffer(); + bb.flushBuffer(); + state = BYTE_STATE; + } else if (state == BYTE_STATE) { + bb.flushBuffer(); + } else if (state == INITIAL_STATE) { + // If the buffers are empty, commit the response header + coyoteResponse.sendHeaders(); + } + doFlush = false; + + if (realFlush) { + coyoteResponse.action(ActionCode.ACTION_CLIENT_FLUSH, + coyoteResponse); + // If some exception occurred earlier, or if some IOE occurred + // here, notify the servlet with an IOE + if (coyoteResponse.isExceptionPresent()) { + throw new ClientAbortException + (coyoteResponse.getErrorException()); + } + } + + } + + + // ------------------------------------------------- Bytes Handling Methods + + + /** + * Sends the buffer data to the client output, checking the + * state of Response and calling the right interceptors. + * + * @param buf Byte buffer to be written to the response + * @param off Offset + * @param cnt Length + * + * @throws IOException An underlying IOException occurred + */ + public void realWriteBytes(byte buf[], int off, int cnt) + throws IOException { + + if (closed) + return; + if (coyoteResponse == null) + return; + + // If we really have something to write + if (cnt > 0) { + // real write to the adapter + outputChunk.setBytes(buf, off, cnt); + try { + coyoteResponse.doWrite(outputChunk); + } catch (IOException e) { + // An IOException on a write is almost always due to + // the remote client aborting the request. Wrap this + // so that it can be handled better by the error dispatcher. + throw new ClientAbortException(e); + } + } + + } + + + public void write(byte b[], int off, int len) throws IOException { + + if (suspended) + return; + + if (state == CHAR_STATE) + cb.flushBuffer(); + state = BYTE_STATE; + writeBytes(b, off, len); + + } + + + private void writeBytes(byte b[], int off, int len) + throws IOException { + + if (closed) + return; + + bb.append(b, off, len); + bytesWritten += len; + + // if called from within flush(), then immediately flush + // remaining bytes + if (doFlush) { + bb.flushBuffer(); + } + + } + + + public void writeByte(int b) + throws IOException { + + if (suspended) + return; + + if (state == CHAR_STATE) + cb.flushBuffer(); + state = BYTE_STATE; + + bb.append( (byte)b ); + bytesWritten++; + + } + + + // ------------------------------------------------- Chars Handling Methods + + + public void write(int c) + throws IOException { + + if (suspended) + return; + + state = CHAR_STATE; + + cb.append((char) c); + charsWritten++; + + } + + + public void write(char c[]) + throws IOException { + + if (suspended) + return; + + write(c, 0, c.length); + + } + + + public void write(char c[], int off, int len) + throws IOException { + + if (suspended) + return; + + state = CHAR_STATE; + + cb.append(c, off, len); + charsWritten += len; + + } + + + public void write(StringBuffer sb) + throws IOException { + + if (suspended) + return; + + state = CHAR_STATE; + + int len = sb.length(); + charsWritten += len; + cb.append(sb); + + } + + + /** + * Append a string to the buffer + */ + public void write(String s, int off, int len) + throws IOException { + + if (suspended) + return; + + state=CHAR_STATE; + + charsWritten += len; + if (s==null) + s="null"; + cb.append( s, off, len ); + + } + + + public void write(String s) + throws IOException { + + if (suspended) + return; + + state = CHAR_STATE; + if (s==null) + s="null"; + write(s, 0, s.length()); + + } + + + public void flushChars() + throws IOException { + + cb.flushBuffer(); + state = BYTE_STATE; + + } + + + public boolean flushCharsNeeded() { + return state == CHAR_STATE; + } + + + public void setEncoding(String s) { + enc = s; + } + + + public void realWriteChars(char c[], int off, int len) + throws IOException { + + if (!gotEnc) + setConverter(); + + conv.convert(c, off, len); + conv.flushBuffer(); // ??? + + } + + + public void checkConverter() + throws IOException { + + if (!gotEnc) + setConverter(); + + } + + + protected void setConverter() + throws IOException { + + if (coyoteResponse != null) + enc = coyoteResponse.getCharacterEncoding(); + + gotEnc = true; + if (enc == null) + enc = DEFAULT_ENCODING; + conv = (C2BConverter) encoders.get(enc); + if (conv == null) { + + if (System.getSecurityManager() != null){ + try{ + conv = (C2BConverter)AccessController.doPrivileged( + new PrivilegedExceptionAction(){ + + public Object run() throws IOException{ + return new C2BConverter(bb, enc); + } + + } + ); + }catch(PrivilegedActionException ex){ + Exception e = ex.getException(); + if (e instanceof IOException) + throw (IOException)e; + } + } else { + conv = new C2BConverter(bb, enc); + } + + encoders.put(enc, conv); + + } + } + + + // -------------------- BufferedOutputStream compatibility + + + /** + * Real write - this buffer will be sent to the client + */ + public void flushBytes() + throws IOException { + + bb.flushBuffer(); + + } + + + public int getBytesWritten() { + return bytesWritten; + } + + + public int getCharsWritten() { + return charsWritten; + } + + + public int getContentWritten() { + return bytesWritten + charsWritten; + } + + + /** + * True if this buffer hasn't been used ( since recycle() ) - + * i.e. no chars or bytes have been added to the buffer. + */ + public boolean isNew() { + return (bytesWritten == 0) && (charsWritten == 0); + } + + + public void setBufferSize(int size) { + if (size > bb.getLimit()) {// ?????? + bb.setLimit(size); + } + } + + + public void reset() { + + //count=0; + bb.recycle(); + bytesWritten = 0; + cb.recycle(); + charsWritten = 0; + gotEnc = false; + enc = null; + state = INITIAL_STATE; + } + + + public int getBufferSize() { + return bb.getLimit(); + } + + + public static MessageWriter getWriter(Request req, Response res, int size) + { + MessageWriter out=(MessageWriter)req.getNote(MessageWriter.WRITER_NOTE); + if( out == null ) { + if( size<=0 ) { + out=new MessageWriter(); + } else { + out=new MessageWriter(size); + } + out.setResponse(res); + req.setNote(MessageWriter.WRITER_NOTE, out ); + } + return out; + } + + +} Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/SimpleFileAdapter.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/SimpleFileAdapter.java?rev=640852&view=auto ============================================================================== --- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/SimpleFileAdapter.java (added) +++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/SimpleFileAdapter.java Tue Mar 25 08:17:03 2008 @@ -0,0 +1,138 @@ +package org.apache.coyote.adapters; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.logging.Logger; + +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.buf.C2BConverter; +import org.apache.tomcat.util.net.SocketStatus; + +/** + * Serve a static file. This is the traditional method, a separate adapter could + * use Sendfile. + * + * No fancy things. Not sure if it should have dir support even. + */ +public class SimpleFileAdapter implements Adapter { + Logger log = Logger.getLogger("coyote.file"); + + private String baseDir = "html/"; + + private File baseDirF; + + public SimpleFileAdapter() { + init(); + } + + public void setBaseDir(String s) { + baseDir = s; + } + + public void init() { + baseDirF = new File(baseDir); + try { + baseDir = baseDirF.getCanonicalPath(); + } catch (IOException e) { + } + } + + public void service(Request req, final Response res) throws Exception { + + String uri = req.requestURI().toString(); + if (uri.indexOf("..") >= 0) { + // not supported, too dangerous + // what else to escape ? + log.info("Invalid .. in " + uri); + res.setStatus(404); + return; + } + + // local file + File f = new File(baseDirF, uri); + + // extra check + if (!f.getCanonicalPath().startsWith(baseDir)) { + log.info("File outside basedir " + baseDir + " " + f); + res.setStatus(404); + return; + } + + if (f.isDirectory()) { + // check for index.html, redirect if exists + // list dir if not + + f = new File(f, "index.html"); + } + + if (!f.exists()) { + log.info("File not found " + f); + res.setStatus(404); + return; + } + + res.setStatus(200); + + // TODO: read from a resources in classpath ! + // TODO: refactor to allow sendfile + // TODO: read mime types + + int dot=uri.lastIndexOf("."); + if( dot > 0 ) { + String ext=uri.substring(dot+1); + String ct=getContentType(ext); + if( ct!=null) { + res.setContentType(ct); + } + } + + res.setContentLength(f.length()); + + res.sendHeaders(); + + // not used - writes directly to response + // MessageWriter out = MessageWriter.getWriter(req, res, 0); + + FileInputStream fis = new FileInputStream(f); + byte b[] = new byte[4096]; + ByteChunk mb = new ByteChunk(); + int rd = 0; + while ((rd = fis.read(b)) > 0) { + mb.setBytes(b, 0, rd); + res.doWrite(mb); + } + + } + + static Properties contentTypes=new Properties(); + static { + initContentTypes(); + } + static void initContentTypes() { + contentTypes.put("xhtml", "text/html"); + contentTypes.put("html", "text/html"); + contentTypes.put("txt", "text/plain"); + contentTypes.put("css", "text/css"); + contentTypes.put("xul", "application/vnd.mozilla.xul+xml"); + } + + public String getContentType( String ext ) { + return contentTypes.getProperty( ext, "text/plain" ); + } + + public boolean event(Request req, Response res, boolean error) throws Exception { + // TODO Auto-generated method stub + return false; + } + + public boolean event(Request req, Response res, SocketStatus status) + throws Exception { + return false; + } + +} \ No newline at end of file Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticAdapter.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticAdapter.java?rev=640852&view=auto ============================================================================== --- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticAdapter.java (added) +++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticAdapter.java Tue Mar 25 08:17:03 2008 @@ -0,0 +1,85 @@ +package org.apache.coyote.adapters; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.logging.Logger; + +import org.apache.coyote.ActionCode; +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.net.SocketStatus; + +/** + * Serve a static file. This is the traditional method, a separate adapter could + * use Sendfile. + * + * No fancy things. Not sure if it should have dir support even. + */ +public class StaticAdapter implements Adapter { + Logger log = Logger.getLogger("coyote.static"); + ByteChunk mb = new ByteChunk(); + int len = 4096; + byte[] data; + + boolean chunked = false; + + String contentType = "text/plain"; + + + public StaticAdapter() { + init(); + } + + public StaticAdapter chunked() { + chunked = true; + return this; + } + + public void setFile(String path) { + try { + FileInputStream fis = new FileInputStream(path); + mb.recycle(); + mb.allocate(4096, -1); + + byte b[] = new byte[4096]; + int rd = 0; + while ((rd = fis.read(b)) > 0) { + mb.append(b, 0, rd); + } + len = mb.getLength(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void init() { + data = new byte[len]; + for (int i = 0; i < len; i++) { + data[i] = 'A'; + } + mb.setBytes(data, 0, len); + } + + public void service(Request req, final Response res) throws Exception { + + res.setStatus(200); + if (!chunked) { + res.setContentLength(len); + } + res.setContentType(contentType); + res.sendHeaders(); + if (chunked) { + res.doWrite(mb); + res.action(ActionCode.ACTION_CLIENT_FLUSH, res); + } + res.doWrite(mb); + } + + public boolean event(Request req, Response res, SocketStatus status) + throws Exception { + return false; + } + +} \ No newline at end of file Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticMain.java URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticMain.java?rev=640852&view=auto ============================================================================== --- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticMain.java (added) +++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/coyote/adapters/StaticMain.java Tue Mar 25 08:17:03 2008 @@ -0,0 +1,31 @@ +package org.apache.coyote.adapters; + +import org.apache.tomcat.util.IntrospectionUtils; + + + +/** + * Serve a static resource, all in RAM. This should be the fastest way to + * send data over HTTP with tomcat - no overhead except the coyote layer. + * + * Used to benchmark and for files/resources that are extremely popular. + */ +public class StaticMain extends CoyoteServer { + + public StaticMain() { + } + + protected void initAdapters() { + adapter = new StaticAdapter(); + } + + // ------------------- Main --------------------- + public static void main( String args[]) throws Exception { + StaticMain sa=new StaticMain(); + IntrospectionUtils.processArgs(sa, args); + sa.setArgs(args); + sa.run(); + } + + +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]