Author: costin Date: Sat Nov 5 19:13:06 2005 New Revision: 331062 URL: http://svn.apache.org/viewcvs?rev=331062&view=rev Log: Code for testing ( and using ) coyote standalone.
The adapters are very simple, more as example. The goal is to have a minimal http connector, and play with nio and other apis in a simpler environment About MessageWriter and MessageReader - they are copies of Catalina/connector/OutputBuffer and InputBuffer. Renamed to avoid confusion. Long term, they should be part of o.a.coyote package, there is no dep on catalina and are usefull in in connector. Added: tomcat/sandbox/java/org/apache/coyote/ tomcat/sandbox/java/org/apache/coyote/adapters/ tomcat/sandbox/java/org/apache/coyote/adapters/Counters.java tomcat/sandbox/java/org/apache/coyote/adapters/FileAdapter.java tomcat/sandbox/java/org/apache/coyote/adapters/HelloWorldAdapter.java tomcat/sandbox/java/org/apache/coyote/adapters/JsAdapter.java tomcat/sandbox/java/org/apache/coyote/adapters/Mapper.java tomcat/sandbox/java/org/apache/coyote/standalone/ tomcat/sandbox/java/org/apache/coyote/standalone/ClientAbortException.java tomcat/sandbox/java/org/apache/coyote/standalone/Main.java tomcat/sandbox/java/org/apache/coyote/standalone/MessageReader.java tomcat/sandbox/java/org/apache/coyote/standalone/MessageWriter.java Added: tomcat/sandbox/java/org/apache/coyote/adapters/Counters.java URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/coyote/adapters/Counters.java?rev=331062&view=auto ============================================================================== --- tomcat/sandbox/java/org/apache/coyote/adapters/Counters.java (added) +++ tomcat/sandbox/java/org/apache/coyote/adapters/Counters.java Sat Nov 5 19:13:06 2005 @@ -0,0 +1,78 @@ +package org.apache.coyote.adapters; + +import java.util.List; +import java.util.ArrayList; + +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; + +/** + * Used to collect statistics to evaluate performance of the coyote layer. + * + */ +public class Counters implements Adapter { + + // per thread + public static class CountData { + public long time; + public long requests; + public int exceptions; + } + + // quick hack - need to move the per-thread code from tomcat + List counters=new ArrayList(); + ThreadLocal tl=new ThreadLocal(); + + Adapter next; + + public Counters() { + } + + public void setNext( Adapter adapter ) { + next=adapter; + } + + public Adapter getNext( ) { + return next; + } + + public void service(Request req, final Response res) throws Exception { + long t0=System.currentTimeMillis(); + CountData cnt=(CountData)tl.get(); + if( cnt == null ) { + cnt=new CountData(); + counters.add( cnt ); + tl.set( cnt ); + // TODO: deal with thread death + } + + cnt.requests++; + try { + next.service(req,res); + } catch( Exception ex ) { + cnt.exceptions++; + throw ex; + } finally { + long t1=System.currentTimeMillis(); + cnt.time+=( t1-t0); + } + + } + + /** Returns statistics for the server. + * TODO: make it per thread, agregate all threads + * + * @return + */ + public CountData getCounts() { + CountData total=new CountData(); + for( int i=0; i< counters.size(); i++ ) { + CountData cd=((CountData)counters.get(i)); + total.requests+= cd.requests; + total.time+=cd.time; + total.exceptions+=cd.exceptions; + } + return total; + } +} \ No newline at end of file Added: tomcat/sandbox/java/org/apache/coyote/adapters/FileAdapter.java URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/coyote/adapters/FileAdapter.java?rev=331062&view=auto ============================================================================== --- tomcat/sandbox/java/org/apache/coyote/adapters/FileAdapter.java (added) +++ tomcat/sandbox/java/org/apache/coyote/adapters/FileAdapter.java Sat Nov 5 19:13:06 2005 @@ -0,0 +1,127 @@ +package org.apache.coyote.adapters; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.coyote.standalone.MessageWriter; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.buf.C2BConverter; + +/** + * 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 FileAdapter implements Adapter { + Log log = LogFactory.getLog("coyote.file"); + + private String baseDir = "html/"; + + private File baseDirF; + + public FileAdapter() { + 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("html", "text/html"); + contentTypes.put("txt", "text/plain"); + contentTypes.put("xul", "application/vnd.mozilla.xul+xml"); + } + + public String getContentType( String ext ) { + return contentTypes.getProperty( ext, "text/plain" ); + } + +} \ No newline at end of file Added: tomcat/sandbox/java/org/apache/coyote/adapters/HelloWorldAdapter.java URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/coyote/adapters/HelloWorldAdapter.java?rev=331062&view=auto ============================================================================== --- tomcat/sandbox/java/org/apache/coyote/adapters/HelloWorldAdapter.java (added) +++ tomcat/sandbox/java/org/apache/coyote/adapters/HelloWorldAdapter.java Sat Nov 5 19:13:06 2005 @@ -0,0 +1,15 @@ +package org.apache.coyote.adapters; + +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.coyote.standalone.MessageWriter; + +public class HelloWorldAdapter implements Adapter { + public void service(Request req, Response res) throws Exception { + MessageWriter out=MessageWriter.getWriter(req, res, 0); + res.setContentType("text/html"); + + out.write("<h1>Hello world</h1>"); + } +} Added: tomcat/sandbox/java/org/apache/coyote/adapters/JsAdapter.java URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/coyote/adapters/JsAdapter.java?rev=331062&view=auto ============================================================================== --- tomcat/sandbox/java/org/apache/coyote/adapters/JsAdapter.java (added) +++ tomcat/sandbox/java/org/apache/coyote/adapters/JsAdapter.java Sat Nov 5 19:13:06 2005 @@ -0,0 +1,240 @@ +package org.apache.coyote.adapters; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.coyote.http11.Http11BaseProtocol; +import org.apache.coyote.standalone.Main; +import org.apache.coyote.standalone.MessageWriter; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.EvaluatorException; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.JavaScriptException; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.WrappedException; + +/** + * Will load the 'default.js' and execute init. In init you can set params on + * the server and handler. + * + * Entry points: + * <ul> + * <li>init() - called the first time, can set the port and other + * properties in the "server" object + * <li>initThread() - called per thread. + * <li>service( req, res, out) + * </ul> + * + * Defined objects: + * <ul> + * <li>log - commons logger log for this adapter + * <li>server - the http connector + * <li>jsAdapter, counterAdapter, fileAdapter, mapperAdapter - adapters. + * </ul> + * + * Note: this is just an example, you can extend it or create your own + * with different semantics. After the coyote API is cleaned up and converted + * to NIO - I'll also define a better JS API. + * + */ +public class JsAdapter extends Main implements Adapter { + + // Js file to interpret + static String filename = "js-bin/default.js"; + + // to support reloading of the js file + static long lastModif = 0; + + // Javascript main context and scope + private Context mainCx; + + private Scriptable mainScope; + + private Log log = LogFactory.getLog("js"); + + // we store them per thread. Need a way to manage the tokens + public static final int SCOPE_NOTE = 12; + + public static final int ADAPTER_NOTES = 11; + + boolean reload = true; + + public JsAdapter() { + } + + public boolean getReload() { + return reload; + } + + public void setReload(boolean reload) { + this.reload = reload; + } + + public void service(Request req, final Response res) throws Exception { + // Per thread. + Context cx = (Context) req.getNote(JsAdapter.ADAPTER_NOTES); + Scriptable scope = (Scriptable) req.getNote(JsAdapter.SCOPE_NOTE); + MessageWriter out = MessageWriter.getWriter(req, res, 0); + + if (cx == null) { + cx = Context.enter(); + req.setNote(JsAdapter.ADAPTER_NOTES, cx); + // TODO: exit on thread death + + // Each thread will have an associated context, and an associated + // scope. + // The scope will hold the proxies for req, res and the other + // objects + // Because the req and response never change for a thread - we don't + // need to bind them again. + + scope = cx.newObject(mainScope); + scope.setPrototype(mainScope); + + req.setNote(JsAdapter.SCOPE_NOTE, scope); + + Object fObj = mainScope.get("initThread", mainScope); + if ((fObj instanceof Function)) { + Object functionArgs[] = { req, res, out }; + Function f = (Function) fObj; + Object result = f.call(mainCx, mainScope, mainScope, + functionArgs); + } + } + + // The file was loaded in initJS(), at server startup. We will only + // check if it changed, if we are in devel mode and reload it. + if (reload) { + load(filename); + } + + // Now call the service() js function + Object fObj = ScriptableObject.getProperty(scope, "service"); + if (!(fObj instanceof Function)) { + log.info("service is undefined or not a function. " + fObj); + log.info("Not found in scope... " + scope); + log.info("Parent: " + mainScope.get("service", mainScope)); + fObj = mainScope.get("service", mainScope); + } + + Object functionArgs[] = { req, res, out }; + Function f = (Function) fObj; + Object result = f.call(cx, scope, scope, functionArgs); + + } + + /** + * Initialize. Will run the init() method in the script + * + * @param proto + */ + public void initJS() { + mainCx = Context.enter(); + mainScope = mainCx.initStandardObjects(); + + Object jsOut; + + jsOut = Context.javaToJS(log, mainScope); + ScriptableObject.putProperty(mainScope, "log", jsOut); + + jsOut = Context.javaToJS(proto, mainScope); + ScriptableObject.putProperty(mainScope, "server", jsOut); + + Counters ct = (Counters) proto.getAdapter(); + Mapper mp = (Mapper) ct.getNext(); + FileAdapter fa = (FileAdapter) mp.getDefaultAdapter(); + + jsOut = Context.javaToJS(ct, mainScope); + ScriptableObject.putProperty(mainScope, "countersAdapter", jsOut); + + jsOut = Context.javaToJS(mp, mainScope); + ScriptableObject.putProperty(mainScope, "mapperAdapter", jsOut); + + jsOut = Context.javaToJS(fa, mainScope); + ScriptableObject.putProperty(mainScope, "fileAdapter", jsOut); + + jsOut = Context.javaToJS(this, mainScope); + ScriptableObject.putProperty(mainScope, "jsAdapter", jsOut); + + load(filename); + + Object fObj = mainScope.get("init", mainScope); + if ((fObj instanceof Function)) { + Object functionArgs[] = {}; + Function f = (Function) fObj; + Object result = f.call(mainCx, mainScope, mainScope, functionArgs); + } + } + + /** + * Load a script. + */ + private void load(String filename) { + + FileReader in = null; + try { + File scriptF = new File(filename); + + if (lastModif != 0 && scriptF.lastModified() <= lastModif) { + // already loaded + return; + } + lastModif = scriptF.lastModified(); + in = new FileReader(filename); + } catch (FileNotFoundException ex) { + System.err.println("JS file not found " + ex); + return; + } + + try { + // Here we evalute the entire contents of the file as + // a script. Text is printed only if the print() function + // is called. + Object result = mainCx.evaluateReader(mainScope, in, filename, 1, + null); + } catch (WrappedException we) { + System.err.println(we.getWrappedException().toString()); + we.printStackTrace(); + } catch (EvaluatorException ee) { + System.err.println("js: " + ee.getMessage()); + } catch (JavaScriptException jse) { + System.err.println("js: " + jse.getMessage()); + } catch (IOException ioe) { + System.err.println(ioe.toString()); + } finally { + try { + in.close(); + } catch (IOException ioe) { + System.err.println(ioe.toString()); + } + } + } + + public void setProtocol(Http11BaseProtocol proto) { + this.proto = proto; + } + + // ------------ Example on how to run it -------------------- + + public void init() { + super.init(); + JsAdapter js = this; // new JsAdapter(); + js.setProtocol(proto); + js.initJS(); + mainAdapter.addAdapter("/js-bin", js); + } + + public static void main(String args[]) { + JsAdapter sa = new JsAdapter(); + sa.run(); // in Main - will call init first + } + +} \ No newline at end of file Added: tomcat/sandbox/java/org/apache/coyote/adapters/Mapper.java URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/coyote/adapters/Mapper.java?rev=331062&view=auto ============================================================================== --- tomcat/sandbox/java/org/apache/coyote/adapters/Mapper.java (added) +++ tomcat/sandbox/java/org/apache/coyote/adapters/Mapper.java Sat Nov 5 19:13:06 2005 @@ -0,0 +1,125 @@ +package org.apache.coyote.adapters; + +import java.util.Enumeration; +import java.util.Hashtable; + +import org.apache.coyote.Adapter; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.coyote.http11.Http11BaseProtocol; +import org.apache.coyote.standalone.MessageWriter; +import org.apache.tomcat.util.loader.Loader; +import org.apache.tomcat.util.loader.Repository; + +/** + * Very, very simple mapper for standalone coyote. Used to test and experiment + * various low level changes, or for very simple http servers. + * + * It currently supports only prefix mapping, using the first url component. + */ +public class Mapper implements Adapter { + + // TODO: add extension mappings + // Key = prefix, one level only, value= class name of Adapter + // key starts with a / and has no other / ( /foo - but not /foo/bar ) + Hashtable prefixMap=new Hashtable(); + + String fileAdapterCN="org.apache.coyote.adapters.FileAdapter"; + Adapter defaultAdapter=new FileAdapter(); + + public Mapper() { + } + + public void service(Request req, final Response res) + throws Exception { + try { + String uri=req.requestURI().toString(); + if( uri.equals("/") ) uri="index.html"; + String ctx=""; + String local=uri; + if( uri.length() > 1 ) { + int idx=uri.indexOf('/', 1); + if( idx > 0 ) { + ctx=uri.substring(0, idx); + local=uri.substring( idx ); + } + } + Adapter h=(Adapter)prefixMap.get( ctx ); + if( h != null ) { + h.service( req, res ); + } else { + defaultAdapter.service( req, res ); + } + } catch( Throwable t ) { + t.printStackTrace(); + } + + //out.flushBuffer(); + //out.getByteChunk().flushBuffer(); - part of res.finish() + // final processing + MessageWriter.getWriter(req, res, 0).flush(); + res.finish(); + + req.recycle(); + res.recycle(); + + } + + /** Load the handler table. Just a hack, I'll find a better solution + */ + public void initHandlers() { + + prefixMap=new Hashtable(); + Enumeration keys=System.getProperties().keys(); + + Loader loader=null; + if( loader == null ) { + // Not started from loader, we are embedded - create the loader, so we + // can do reloading. + //LoaderProperties.setPropertiesFile(""); + try { + loader=new Loader(); + ClassLoader myL=this.getClass().getClassLoader(); + loader.setParentClassLoader( myL ); + loader.init(); + } catch( Throwable t ) { + t.printStackTrace(); + } + } + + Repository sR=loader.getRepository("shared"); + // Construct handlers. Handlers will be created, they can get the protocol + // if they need additional init + + while( keys.hasMoreElements()) { + String n=(String)keys.nextElement(); + if( n.startsWith("handler.")) { + String cls=System.getProperty( n ); + String map=n.substring(8); + Adapter hC=null; + try { + // use the loader's server common repository + Class c=sR.getClassLoader().loadClass(cls); + //Class c=Class.forName(cls); + hC=(Adapter)c.newInstance(); + prefixMap.put( map, hC ); + } catch( Throwable t ) { + t.printStackTrace(); + } + } + } + } + + public void addAdapter( String prefix, Adapter adapter ) { + prefixMap.put(prefix, adapter); + } + + public void setDefaultAdapter(Adapter adapter) { + defaultAdapter=adapter; + } + + public Adapter getDefaultAdapter() { + return defaultAdapter; + } + +} \ No newline at end of file Added: tomcat/sandbox/java/org/apache/coyote/standalone/ClientAbortException.java URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/coyote/standalone/ClientAbortException.java?rev=331062&view=auto ============================================================================== --- tomcat/sandbox/java/org/apache/coyote/standalone/ClientAbortException.java (added) +++ tomcat/sandbox/java/org/apache/coyote/standalone/ClientAbortException.java Sat Nov 5 19:13:06 2005 @@ -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.standalone; + +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/java/org/apache/coyote/standalone/Main.java URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/coyote/standalone/Main.java?rev=331062&view=auto ============================================================================== --- tomcat/sandbox/java/org/apache/coyote/standalone/Main.java (added) +++ tomcat/sandbox/java/org/apache/coyote/standalone/Main.java Sat Nov 5 19:13:06 2005 @@ -0,0 +1,66 @@ +package org.apache.coyote.standalone; + +import org.apache.coyote.adapters.Counters; +import org.apache.coyote.adapters.HelloWorldAdapter; +import org.apache.coyote.adapters.Mapper; +import org.apache.coyote.http11.Http11BaseProtocol; + + +/** + * Simple example of embeding coyote. + * + */ +public class Main { + + protected Http11BaseProtocol proto; + protected Mapper mainAdapter; + + public Main() { + } + + public Http11BaseProtocol getProtocol() { + return proto; + } + + public void init() { + proto = new Http11BaseProtocol(); + + mainAdapter = new Mapper(); + mainAdapter.addAdapter("/hello", new HelloWorldAdapter()); + + Counters cnt=new Counters(); + cnt.setNext( mainAdapter ); + + //proto.setAdapter(mainAdapter); + proto.setAdapter(cnt); + } + + /** + */ + public void run() { + init(); + + if( proto.getPort() == 0 ) + proto.setPort(8800); + + try { + proto.init(); + + proto.getThreadPool().setDaemon(false); + + proto.start(); + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + // ------------------- Main --------------------- + public static void main( String args[]) { + Main sa=new Main(); + sa.run(); + } + + +} \ No newline at end of file Added: tomcat/sandbox/java/org/apache/coyote/standalone/MessageReader.java URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/coyote/standalone/MessageReader.java?rev=331062&view=auto ============================================================================== --- tomcat/sandbox/java/org/apache/coyote/standalone/MessageReader.java (added) +++ tomcat/sandbox/java/org/apache/coyote/standalone/MessageReader.java Sat Nov 5 19:13:06 2005 @@ -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.standalone; + +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/java/org/apache/coyote/standalone/MessageWriter.java URL: http://svn.apache.org/viewcvs/tomcat/sandbox/java/org/apache/coyote/standalone/MessageWriter.java?rev=331062&view=auto ============================================================================== --- tomcat/sandbox/java/org/apache/coyote/standalone/MessageWriter.java (added) +++ tomcat/sandbox/java/org/apache/coyote/standalone/MessageWriter.java Sat Nov 5 19:13:06 2005 @@ -0,0 +1,677 @@ +/* + * 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.standalone; + + +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. + */ + private ByteChunk outputChunk = new ByteChunk(); + + + /** + * Encoding to use. + */ + private String enc; + + + /** + * Encoder is set. + */ + private boolean gotEnc = false; + + + /** + * List of encoders. + */ + protected HashMap encoders = new HashMap(); + + + /** + * Current char to byte converter. + */ + 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; + } + + +} --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]