dims 2002/06/07 11:31:58 Modified: java/test/wsdl/multithread MultithreadTestCase.java java/src/org/apache/axis/transport/http SimpleAxisServer.java Added: java/src/org/apache/axis/transport/http SimpleAxisWorker.java Log: Objectives: Improve performance of SimpleAxisServer for more stringent testing in a multi-threaded environment. Steps: - Start a new thread in SimpleAxisServer for each client socket. - Extract code from SimpleAxisServer into SimpleAxisWorker Note: The success ratio of MultithreadTestCase jumped from ~205/400 to ~320/400 on both JDK13 and JDK14 Revision Changes Path 1.8 +10 -11 xml-axis/java/test/wsdl/multithread/MultithreadTestCase.java Index: MultithreadTestCase.java =================================================================== RCS file: /home/cvs/xml-axis/java/test/wsdl/multithread/MultithreadTestCase.java,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- MultithreadTestCase.java 1 Apr 2002 20:12:17 -0000 1.7 +++ MultithreadTestCase.java 7 Jun 2002 18:31:58 -0000 1.8 @@ -1,26 +1,20 @@ package test.wsdl.multithread; -import java.net.ConnectException; - -import java.rmi.RemoteException; - -import javax.xml.rpc.ServiceException; - import junit.framework.AssertionFailedError; import junit.framework.TestCase; - import org.apache.axis.AxisFault; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - +import samples.addr.Address; import samples.addr.AddressBook; -import samples.addr.AddressBookServiceLocator; import samples.addr.AddressBookSOAPBindingStub; -import samples.addr.Address; +import samples.addr.AddressBookServiceLocator; import samples.addr.Phone; import samples.addr.StateType; +import javax.xml.rpc.ServiceException; +import java.net.ConnectException; + /** * This test calls the stub multiple times from multiple threads. Before the * stub was made threadsafe, there was a good chance this test would fail with an @@ -141,5 +135,10 @@ throw error; } } // testMultithreading + + public static void main(String[] args) { + MultithreadTestCase testCase = new MultithreadTestCase("MultithreadTestCase"); + testCase.testMultithreading(); + } } // class MultithreadTestCase 1.61 +43 -655 xml-axis/java/src/org/apache/axis/transport/http/SimpleAxisServer.java Index: SimpleAxisServer.java =================================================================== RCS file: /home/cvs/xml-axis/java/src/org/apache/axis/transport/http/SimpleAxisServer.java,v retrieving revision 1.60 retrieving revision 1.61 diff -u -r1.60 -r1.61 --- SimpleAxisServer.java 4 Jun 2002 01:19:46 -0000 1.60 +++ SimpleAxisServer.java 7 Jun 2002 18:31:58 -0000 1.61 @@ -53,31 +53,16 @@ * <http://www.apache.org/>. */ -package org.apache.axis.transport.http ; +package org.apache.axis.transport.http; -import org.apache.axis.AxisFault; -import org.apache.axis.Constants; -import org.apache.axis.Message; -import org.apache.axis.MessageContext; -import org.apache.axis.encoding.Base64; -import org.apache.axis.message.SOAPEnvelope; -import org.apache.axis.message.SOAPFaultElement; import org.apache.axis.server.AxisServer; import org.apache.axis.session.Session; import org.apache.axis.session.SimpleSession; -import org.apache.axis.utils.Options; import org.apache.axis.utils.JavaUtils; -import org.apache.axis.utils.XMLUtils; - +import org.apache.axis.utils.Options; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.w3c.dom.Document; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InterruptedIOException; -import java.io.OutputStream; import java.net.MalformedURLException; import java.net.ServerSocket; import java.net.Socket; @@ -94,8 +79,7 @@ */ public class SimpleAxisServer implements Runnable { protected static Log log = - LogFactory.getLog(SimpleAxisServer.class.getName()); - + LogFactory.getLog(SimpleAxisServer.class.getName()); // session state. // This table maps session keys (random numbers) to SimpleAxisSession objects. @@ -110,7 +94,26 @@ // Are we doing sessions? // Set this to false if you don't want any session overhead. - public static boolean doSessions = true; + private static boolean doSessions = true; + + protected boolean isSessionUsed() { + return doSessions; + } + + protected Session createSession(String cooky) { + // is there a session already? + Session session = null; + if (sessions.containsKey(cooky)) { + session = (Session) sessions.get(cooky); + } else { + // no session for this cooky, bummer + session = new SimpleSession(); + + // ADD CLEANUP LOGIC HERE if needed + sessions.put(cooky, session); + } + return session; + } // What is our current session index? // This is a monotonically increasing, non-thread-safe integer @@ -119,663 +122,44 @@ // Axis server (shared between instances) private static AxisServer myAxisServer = null; - private static synchronized AxisServer getAxisServer() { + + protected static synchronized AxisServer getAxisServer() { if (myAxisServer == null) { myAxisServer = new AxisServer(); } return myAxisServer; } - // HTTP prefix - private static byte HTTP[] = "HTTP/1.0 ".getBytes(); - - // HTTP status codes - private static byte OK[] = ("200 " + JavaUtils.getMessage("ok00")).getBytes(); - private static byte UNAUTH[] = ("401 " + JavaUtils.getMessage("unauth00")).getBytes(); - private static byte ISE[] = ("500 " + JavaUtils.getMessage("internalError01")).getBytes(); - - // Standard MIME headers for XML payload - private static byte XML_MIME_STUFF[] = - ( "\r\nContent-Type: text/xml; charset=utf-8\r\n" + - "Content-Length: ").getBytes(); - - // Standard MIME headers for HTML payload - private static byte HTML_MIME_STUFF[] = - ( "\r\nContent-Type: text/html; charset=utf-8\r\n" + - "Content-Length: ").getBytes(); - - // Mime/Content separator - private static byte SEPARATOR[] = "\r\n\r\n".getBytes(); - - // Tiddly little response - private static final String responseStr = - "<html><head><title>SimpleAxisServer</title></head>" + - "<body><h1>SimpleAxisServer</h1>" + - JavaUtils.getMessage("reachedServer00") + - "</html>"; - private static byte cannedHTMLResponse[] = responseStr.getBytes(); - - // Axis specific constants - private static String transportName = "SimpleHTTP"; - // are we stopped? // latch to true if stop() is called private boolean stopped = false; /** - * The main workhorse method. - * * Accept requests from a given TCP port and send them through the * Axis engine for processing. */ public void run() { - log.info(JavaUtils.getMessage("start00", "SimpleAxisServer", - new Integer(getServerSocket().getLocalPort()).toString())); - - // create an Axis server - AxisServer engine = getAxisServer(); - //engine.init(); - - // create and initialize a message context - MessageContext msgContext = new MessageContext(engine); - Message requestMsg; - - // Reusuable, buffered, content length controlled, InputStream - NonBlockingBufferedInputStream is = - new NonBlockingBufferedInputStream(); - - // buffers for the headers we care about - StringBuffer soapAction = new StringBuffer(); - StringBuffer httpRequest = new StringBuffer(); - StringBuffer fileName = new StringBuffer(); - StringBuffer cookie = new StringBuffer(); - StringBuffer cookie2 = new StringBuffer(); - StringBuffer authInfo = new StringBuffer(); - StringBuffer contentType= new StringBuffer(); - StringBuffer contentLocation= new StringBuffer(); - - Message responseMsg = null; + log.info(JavaUtils.getMessage("start00", "SimpleAxisServer", + new Integer(getServerSocket().getLocalPort()).toString())); // Accept and process requests from the socket while (!stopped) { Socket socket = null; - - // prepare request (do as much as possible while waiting for the - // next connection). Note the next two statements are commented - // out. Uncomment them if you experience any problems with not - // resetting state between requests: - // msgContext = new MessageContext(); - // requestMsg = new Message("", "String"); try { - msgContext.setTargetService(null); - } catch (AxisFault fault) { - } - msgContext.setResponseMessage(null); - msgContext.reset(); - //msgContext.setProperty("transport", "HTTPTransport"); - msgContext.setTransportName(transportName); - - responseMsg = null; - - try { - try { - socket = serverSocket.accept(); - } catch (IOException ioe) { - break; - } - - // assume the best - byte[] status = OK; - - // assume we're not getting WSDL - boolean doWsdl = false; - - // cookie for this session, if any - String cooky = null; - - try { - // wipe cookies if we're doing sessions - if (doSessions) { - cookie.delete(0, cookie.length()); - cookie2.delete(0, cookie2.length()); - } - authInfo.delete(0, authInfo.length()); - - // read headers - is.setInputStream(socket.getInputStream()); - // parse all headers into hashtable - int contentLength = parseHeaders(is, contentType, - contentLocation, soapAction, - httpRequest, fileName, - cookie, cookie2, authInfo); - is.setContentLength(contentLength); - - int paramIdx = fileName.toString().indexOf('?'); - if (paramIdx != -1) { - // Got params - String params = fileName.substring(paramIdx + 1); - fileName.setLength(paramIdx); - - log.debug(JavaUtils.getMessage("filename00", - fileName.toString())); - log.debug(JavaUtils.getMessage("params00", - params)); - - if ("wsdl".equalsIgnoreCase(params)) - doWsdl = true; - } - - // Real and relative paths are the same for the - // SimpleAxisServer - msgContext.setProperty(Constants.MC_REALPATH, - fileName.toString()); - msgContext.setProperty(Constants.MC_RELATIVE_PATH, - fileName.toString()); - msgContext.setProperty(Constants.MC_JWS_CLASSDIR, - "jwsClasses"); - - String hostname = socket.getInetAddress().getHostName(); - // !!! Fix string concatenation - String url = "http://" + hostname + ":" + - this.getServerSocket().getLocalPort() + "/" + - fileName.toString(); - msgContext.setProperty(MessageContext.TRANS_URL, url); - - String filePart = fileName.toString(); - if (filePart.startsWith("axis/services/")) { - msgContext.setTargetService(filePart.substring(14)); - } - - if (authInfo.length() > 0) { - // Process authentication info - //authInfo = new StringBuffer("dXNlcjE6cGFzczE="); - byte [] decoded = Base64.decode(authInfo.toString()); - StringBuffer userBuf = new StringBuffer(); - StringBuffer pwBuf = new StringBuffer(); - StringBuffer authBuf = userBuf; - for (int i = 0; i < decoded.length; i++) { - if ((char)(decoded[i] & 0x7f) == ':') { - authBuf = pwBuf; - continue; - } - authBuf.append((char)(decoded[i] & 0x7f)); - } - - if (log.isDebugEnabled()) { - log.debug(JavaUtils.getMessage("user00", - userBuf.toString())); - } - - msgContext.setUsername(userBuf.toString()); - msgContext.setPassword(pwBuf.toString()); - } - - // if get, then return simpleton document as response - if (httpRequest.toString().equals("GET")) { - OutputStream out = socket.getOutputStream(); - out.write(HTTP); - out.write(status); - - if (doWsdl) { - engine.generateWSDL(msgContext); - - Document doc = (Document)msgContext.getProperty("WSDL"); - - if (doc != null) { - String response = XMLUtils.DocumentToString(doc); - byte [] respBytes = response.getBytes(); - - out.write(XML_MIME_STUFF); - putInt(out, respBytes.length); - out.write(SEPARATOR); - out.write(respBytes); - out.flush(); - continue; - } - } - - out.write(HTML_MIME_STUFF); - putInt(out, cannedHTMLResponse.length); - out.write(SEPARATOR); - out.write(cannedHTMLResponse); - out.flush(); - continue; - } - - // this may be "" if either SOAPAction: "" or if no SOAPAction at all. - // for now, do not complain if no SOAPAction at all - String soapActionString = soapAction.toString(); - if (soapActionString != null) { - msgContext.setUseSOAPAction(true); - msgContext.setSOAPActionURI(soapActionString); - } - requestMsg = new Message(is, - false, - contentType.toString(), - contentLocation.toString() - ); - msgContext.setRequestMessage(requestMsg); - - // set up session, if any - if (doSessions) { - // did we get a cookie? - if (cookie.length() > 0) { - cooky = cookie.toString().trim(); - } else if (cookie2.length() > 0) { - cooky = cookie2.toString().trim(); - } - - // if cooky is null, cook up a cooky - if (cooky == null) { - // fake one up! - // make it be an arbitrarily increasing number - // (no this is not thread safe because ++ isn't atomic) - int i = sessionIndex++; - cooky = "" + i; - } - - // is there a session already? - Session session = null; - if (sessions.containsKey(cooky)) { - session = (Session)sessions.get(cooky); - } else { - // no session for this cooky, bummer - session = new SimpleSession(); - - // ADD CLEANUP LOGIC HERE if needed - sessions.put(cooky, session); - } - - msgContext.setSession(session); - } - - // invoke the Axis engine - engine.invoke(msgContext); - - // Retrieve the response from Axis - responseMsg = msgContext.getResponseMessage(); - if (responseMsg == null) { - throw new AxisFault(JavaUtils.getMessage("nullResponse00")); - } - - } catch( Exception e ) { - AxisFault af; - if (e instanceof AxisFault) { - af = (AxisFault)e; - log.debug(JavaUtils.getMessage("serverFault00"), af); - - if ("Server.Unauthorized".equals(af.getFaultCode())) { - status = UNAUTH; // SC_UNAUTHORIZED - } else { - status = ISE; // SC_INTERNAL_SERVER_ERROR - } - } else { - status = ISE; // SC_INTERNAL_SERVER_ERROR - af = AxisFault.makeFault(e); - } - - // There may be headers we want to preserve in the - // response message - so if it's there, just add the - // FaultElement to it. Otherwise, make a new one. - responseMsg = msgContext.getResponseMessage(); - if (responseMsg == null) { - responseMsg = new Message(af); - } else { - try { - SOAPEnvelope env = responseMsg.getSOAPEnvelope(); - env.clearBody(); - env.addBodyElement(new SOAPFaultElement((AxisFault)e)); - } catch (AxisFault fault) { - // Should never reach here! - } - } - } - -// byte[] response = (byte[]) responseMsg.getSOAPPartAsBytes(); - - // Send it on its way... - OutputStream out = socket.getOutputStream(); - out.write(HTTP); - out.write(status); - //out.write(XML_MIME_STUFF); - out.write(("\r\n" + HTTPConstants.HEADER_CONTENT_TYPE + ": " + responseMsg.getContentType()).getBytes()); - out.write(("\r\n" + HTTPConstants.HEADER_CONTENT_LENGTH + ": " + responseMsg.getContentLength()).getBytes()); - // putInt(out, response.length); - - if (doSessions && null != cooky && 0 != cooky.trim().length()) { - // write cookie headers, if any - // don't sweat efficiency *too* badly - // optimize at will - StringBuffer cookieOut = new StringBuffer(); - cookieOut.append("\r\nSet-Cookie: ") - .append(cooky) - .append("\r\nSet-Cookie2: ") - .append(cooky); - // OH, THE HUMILITY! yes this is inefficient. - out.write(cookieOut.toString().getBytes()); - } - - out.write(SEPARATOR); - responseMsg.writeTo(out); - // out.write(response); - out.flush(); - - if (msgContext.getProperty(msgContext.QUIT_REQUESTED) != null) { - // why then, quit! - this.stop(); - } - - } catch (InterruptedIOException iie) { - break; + socket = serverSocket.accept(); + } catch (java.io.InterruptedIOException iie) { } catch (Exception e) { log.debug(JavaUtils.getMessage("exception00"), e); - } finally { - try { - if (socket!=null) socket.close(); - } catch (Exception e) { - } - } - } - - log.info(JavaUtils.getMessage("quit00", "SimpleAxisServer")); - } - - // ASCII character mapping to lower case - private static final byte[] toLower = new byte[256]; - - static { - for (int i = 0; i < 256; i++) { - toLower[i] = (byte)i; - } - - for (int lc = 'a'; lc <= 'z'; lc++) { - toLower[lc + 'A' - 'a']=(byte)lc; - } - } - - // mime header for content length - private static final byte lenHeader[] = "content-length: ".getBytes(); - private static final int lenLen = lenHeader.length; - - // mime header for content type - private static final byte typeHeader[] = (HTTPConstants.HEADER_CONTENT_TYPE.toLowerCase() +": ").getBytes(); - private static final int typeLen = typeHeader.length; - - // mime header for content location - private static final byte locationHeader[] = (HTTPConstants.HEADER_CONTENT_LOCATION.toLowerCase()+": ").getBytes(); - private static final int locationLen = locationHeader.length; - - // mime header for soap action - private static final byte actionHeader[] = "soapaction: ".getBytes(); - private static final int actionLen = actionHeader.length; - - // mime header for cookie - private static final byte cookieHeader[] = "cookie: ".getBytes(); - private static final int cookieLen = cookieHeader.length; - - // mime header for cookie2 - private static final byte cookie2Header[] = "cookie2: ".getBytes(); - private static final int cookie2Len = cookie2Header.length; - - // HTTP header for authentication - private static final byte authHeader[] = "authorization: ".getBytes(); - private static final int authLen = authHeader.length; - - // mime header for GET - private static final byte getHeader[] = "GET".getBytes(); - - // mime header for POST - private static final byte postHeader[] = "POST".getBytes(); - - // header ender - private static final byte headerEnder[] = ": ".getBytes(); - - // buffer for IO - private static final int BUFSIZ = 4096; - private byte buf[] = new byte[BUFSIZ]; - - // "Basic" auth string - private static final byte basicAuth[] = "basic ".getBytes(); - - /** - * Read a single line from the input stream - * @param is inputstream to read from - * @param b byte array to read into - * @param off starting offset into the byte array - * @param len maximum number of bytes to read - */ - private int readLine(NonBlockingBufferedInputStream is, byte[] b, int off, int len) - throws IOException - { - int count = 0, c; - - while ((c = is.read()) != -1) { - if(c != '\n' && c != '\r'){ - b[off++] = (byte)c; - count++; - } - if (count == len) break; - if( '\n' == c ){ - int peek= is.peek(); //If the next line begins with tab or space then this is a continuation. - if(peek != ' ' && peek != '\t') break; - } - } - return count > 0 ? count : -1; - } - - /** - * Read all mime headers, returning the value of Content-Length and - * SOAPAction. - * @param is InputStream to read from - * @param contentType The content type. - * @param contentLocation The content location - * @param soapAction StringBuffer to return the soapAction into - * @param httpRequest StringBuffer for GET / POST - * @param cookie first cookie header (if doSessions) - * @param cookie2 second cookie header (if doSessions) - * @return Content-Length - */ - private int parseHeaders(NonBlockingBufferedInputStream is, - StringBuffer contentType, - StringBuffer contentLocation, - StringBuffer soapAction, - StringBuffer httpRequest, - StringBuffer fileName, - StringBuffer cookie, - StringBuffer cookie2, - StringBuffer authInfo) - throws IOException - { - int n; - int len = 0; - - // parse first line as GET or POST - n=this.readLine(is, buf, 0, buf.length); - if (n < 0) { - // nothing! - throw new IOException(JavaUtils.getMessage("unexpectedEOS00")); - } - - // which does it begin with? - httpRequest.delete(0, httpRequest.length()); - fileName.delete(0, fileName.length()); - contentType.delete(0, contentType.length()); - contentLocation.delete(0, contentLocation.length()); - - if (buf[0] == getHeader[0]) { - httpRequest.append("GET"); - for (int i = 0; i < n - 5; i++) { - char c = (char)(buf[i + 5] & 0x7f); - if (c == ' ') - break; - fileName.append(c); - } - log.debug( JavaUtils.getMessage("filename01", "SimpleAxisServer", fileName.toString())); - return 0; - } else if (buf[0] == postHeader[0]) { - httpRequest.append("POST"); - for (int i = 0; i < n - 6; i++) { - char c = (char)(buf[i + 6] & 0x7f); - if (c == ' ') - break; - fileName.append(c); - } - log.debug( JavaUtils.getMessage("filename01", "SimpleAxisServer", fileName.toString())); - } else { - throw new IOException(JavaUtils.getMessage("badRequest00")); - } - - while ((n=readLine(is,buf,0,buf.length)) > 0) { - - if ((n<=2) && (buf[0]=='\n'||buf[0]=='\r') && (len>0)) break; - - // RobJ gutted the previous logic; it was too hard to extend for more headers. - // Now, all it does is search forwards for ": " in the buf, - // then do a length / byte compare. - // Hopefully this is still somewhat efficient (Sam is watching!). - - // First, search forwards for ": " - int endHeaderIndex = 0; - while (endHeaderIndex < n && toLower[buf[endHeaderIndex]] != headerEnder[0]) { - endHeaderIndex++; - } - endHeaderIndex += 2; - // endHeaderIndex now points _just past_ the ": ", and is - // comparable to the various lenLen, actionLen, etc. values - - // convenience; i gets pre-incremented, so initialize it to one less - int i = endHeaderIndex - 1; - - // which header did we find? - if (endHeaderIndex == lenLen && matches(buf, lenHeader)) { - // parse content length - - while ((++i<n) && (buf[i]>='0') && (buf[i]<='9')) { - len = (len*10) + (buf[i]-'0'); - } - - } - else if (endHeaderIndex == actionLen - && matches(buf, actionHeader)) - { - - soapAction.delete(0,soapAction.length()); - // skip initial '"' - i++; - while ((++i<n) && (buf[i]!='"')) { - soapAction.append((char)(buf[i] & 0x7f)); - } - - } - else if (doSessions && endHeaderIndex == cookieLen - && matches(buf, cookieHeader)) - { - - // keep everything up to first ; - while ((++i<n) && (buf[i]!=';') && (buf[i]!='\r') && (buf[i]!='\n')) { - cookie.append((char)(buf[i] & 0x7f)); - } - - } - else if (doSessions && endHeaderIndex == cookie2Len - && matches(buf, cookie2Header)) - { - - // keep everything up to first ; - while ((++i<n) && (buf[i]!=';') && (buf[i]!='\r') && (buf[i]!='\n')) { - cookie2.append((char)(buf[i] & 0x7f)); - } - - } - else if (endHeaderIndex == authLen && matches(buf, authHeader)) { - if (matches(buf, endHeaderIndex, basicAuth)) { - i += basicAuth.length; - while (++i<n && (buf[i]!='\r') && (buf[i]!='\n')) { - if (buf[i]==' ') continue; - authInfo.append((char)(buf[i] & 0x7f)); - } - } else { - throw new IOException( - JavaUtils.getMessage("badAuth00")); - } - } - else if (endHeaderIndex == locationLen && matches(buf, locationHeader)) { - while (++i<n && (buf[i]!='\r') && (buf[i]!='\n')) { - if (buf[i]==' ') continue; - contentLocation.append((char)(buf[i] & 0x7f)); - } - } - else if (endHeaderIndex == typeLen&& matches(buf, typeHeader)) { - while (++i<n && (buf[i]!='\r') && (buf[i]!='\n')) { - if (buf[i]==' ') continue; - contentType.append((char)(buf[i] & 0x7f)); - } - } - - } - return len; - } - - - /** - * does tolower[buf] match the target byte array, up to the target's length? - */ - public boolean matches (byte[] buf, byte[] target) { - for (int i = 0; i < target.length; i++) { - if (toLower[buf[i]] != target[i]) { - return false; + break; } - } - return true; - } - - /** - * Case-insensitive match of a target byte [] to a source byte [], - * starting from a particular offset into the source. - */ - public boolean matches (byte[] buf, int bufIdx, byte[] target) { - for (int i = 0; i < target.length; i++) { - if (toLower[buf[bufIdx + i]] != target[i]) { - return false; + if (socket != null) { + SimpleAxisWorker worker = new SimpleAxisWorker(this, socket); + Thread thread = new Thread(worker); + thread.setDaemon(true); + thread.start(); } } - return true; - } - - - /** - * output an integer into the output stream - * @param out OutputStream to be written to - * @param value Integer value to be written. - */ - private void putInt(OutputStream out, int value) - throws IOException - { - int len = 0; - int offset=buf.length; - - // negative numbers - if (value < 0) { - buf[--offset] = (byte) '-'; - value=-value; - len++; - } - - // zero - if (value == 0) { - buf[--offset] = (byte) '0'; - len++; - } - - // positive numbers - while (value > 0) { - buf[--offset] = (byte)(value%10 + '0'); - value=value/10; - len++; - } - - // write the result - out.write(buf, offset, len); + log.info(JavaUtils.getMessage("quit00", "SimpleAxisServer")); } // per thread socket information @@ -825,6 +209,11 @@ */ public void stop() throws Exception { stopped = true; + try { + serverSocket.close(); + } catch (Exception e) { + e.printStackTrace(); + } if (worker != null) worker.interrupt(); } @@ -848,12 +237,11 @@ int port = opts.getPort(); ServerSocket ss = new ServerSocket(port); sas.setServerSocket(ss); - sas.run(); + sas.start(); } catch (Exception e) { log.error(JavaUtils.getMessage("exception00"), e); return; } - } - + } } 1.1 xml-axis/java/src/org/apache/axis/transport/http/SimpleAxisWorker.java Index: SimpleAxisWorker.java =================================================================== /* * The Apache Software License, Version 1.1 * * * Copyright (c) 2001 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Axis" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.axis.transport.http; import org.apache.axis.AxisFault; import org.apache.axis.Constants; import org.apache.axis.Message; import org.apache.axis.MessageContext; import org.apache.axis.encoding.Base64; import org.apache.axis.message.SOAPEnvelope; import org.apache.axis.message.SOAPFaultElement; import org.apache.axis.server.AxisServer; import org.apache.axis.utils.JavaUtils; import org.apache.axis.utils.XMLUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Document; import java.io.OutputStream; import java.net.Socket; public class SimpleAxisWorker implements Runnable { protected static Log log = LogFactory.getLog(SimpleAxisWorker.class.getName()); private SimpleAxisServer server; private Socket socket; // Axis specific constants private static String transportName = "SimpleHTTP"; // HTTP status codes private static byte OK[] = ("200 " + JavaUtils.getMessage("ok00")).getBytes(); private static byte UNAUTH[] = ("401 " + JavaUtils.getMessage("unauth00")).getBytes(); private static byte ISE[] = ("500 " + JavaUtils.getMessage("internalError01")).getBytes(); // HTTP prefix private static byte HTTP[] = "HTTP/1.0 ".getBytes(); // Standard MIME headers for XML payload private static byte XML_MIME_STUFF[] = ("\r\nContent-Type: text/xml; charset=utf-8\r\n" + "Content-Length: ").getBytes(); // Standard MIME headers for HTML payload private static byte HTML_MIME_STUFF[] = ("\r\nContent-Type: text/html; charset=utf-8\r\n" + "Content-Length: ").getBytes(); // Mime/Content separator private static byte SEPARATOR[] = "\r\n\r\n".getBytes(); // Tiddly little response private static final String responseStr = "<html><head><title>SimpleAxisServer</title></head>" + "<body><h1>SimpleAxisServer</h1>" + JavaUtils.getMessage("reachedServer00") + "</html>"; private static byte cannedHTMLResponse[] = responseStr.getBytes(); // ASCII character mapping to lower case private static final byte[] toLower = new byte[256]; static { for (int i = 0; i < 256; i++) { toLower[i] = (byte) i; } for (int lc = 'a'; lc <= 'z'; lc++) { toLower[lc + 'A' - 'a'] = (byte) lc; } } // buffer for IO private static final int BUFSIZ = 4096; // mime header for content length private static final byte lenHeader[] = "content-length: ".getBytes(); private static final int lenLen = lenHeader.length; // mime header for content type private static final byte typeHeader[] = (HTTPConstants.HEADER_CONTENT_TYPE.toLowerCase() + ": ").getBytes(); private static final int typeLen = typeHeader.length; // mime header for content location private static final byte locationHeader[] = (HTTPConstants.HEADER_CONTENT_LOCATION.toLowerCase() + ": ").getBytes(); private static final int locationLen = locationHeader.length; // mime header for soap action private static final byte actionHeader[] = "soapaction: ".getBytes(); private static final int actionLen = actionHeader.length; // mime header for cookie private static final byte cookieHeader[] = "cookie: ".getBytes(); private static final int cookieLen = cookieHeader.length; // mime header for cookie2 private static final byte cookie2Header[] = "cookie2: ".getBytes(); private static final int cookie2Len = cookie2Header.length; // HTTP header for authentication private static final byte authHeader[] = "authorization: ".getBytes(); private static final int authLen = authHeader.length; // mime header for GET private static final byte getHeader[] = "GET".getBytes(); // mime header for POST private static final byte postHeader[] = "POST".getBytes(); // header ender private static final byte headerEnder[] = ": ".getBytes(); // "Basic" auth string private static final byte basicAuth[] = "basic ".getBytes(); public SimpleAxisWorker(SimpleAxisServer server, Socket socket) { this.server = server; this.socket = socket; } /** * The main workhorse method. */ public void run() { byte buf[] = new byte[BUFSIZ]; // create an Axis server AxisServer engine = server.getAxisServer(); // create and initialize a message context MessageContext msgContext = new MessageContext(engine); Message requestMsg; // Reusuable, buffered, content length controlled, InputStream NonBlockingBufferedInputStream is = new NonBlockingBufferedInputStream(); // buffers for the headers we care about StringBuffer soapAction = new StringBuffer(); StringBuffer httpRequest = new StringBuffer(); StringBuffer fileName = new StringBuffer(); StringBuffer cookie = new StringBuffer(); StringBuffer cookie2 = new StringBuffer(); StringBuffer authInfo = new StringBuffer(); StringBuffer contentType = new StringBuffer(); StringBuffer contentLocation = new StringBuffer(); Message responseMsg = null; // prepare request (do as much as possible while waiting for the // next connection). Note the next two statements are commented // out. Uncomment them if you experience any problems with not // resetting state between requests: // msgContext = new MessageContext(); // requestMsg = new Message("", "String"); try { msgContext.setTargetService(null); } catch (AxisFault fault) { } msgContext.setResponseMessage(null); msgContext.reset(); //msgContext.setProperty("transport", "HTTPTransport"); msgContext.setTransportName(transportName); responseMsg = null; try { // assume the best byte[] status = OK; // assume we're not getting WSDL boolean doWsdl = false; // cookie for this session, if any String cooky = null; try { // wipe cookies if we're doing sessions if (server.isSessionUsed()) { cookie.delete(0, cookie.length()); cookie2.delete(0, cookie2.length()); } authInfo.delete(0, authInfo.length()); // read headers is.setInputStream(socket.getInputStream()); // parse all headers into hashtable int contentLength = parseHeaders(is, buf, contentType, contentLocation, soapAction, httpRequest, fileName, cookie, cookie2, authInfo); is.setContentLength(contentLength); int paramIdx = fileName.toString().indexOf('?'); if (paramIdx != -1) { // Got params String params = fileName.substring(paramIdx + 1); fileName.setLength(paramIdx); log.debug(JavaUtils.getMessage("filename00", fileName.toString())); log.debug(JavaUtils.getMessage("params00", params)); if ("wsdl".equalsIgnoreCase(params)) doWsdl = true; } // Real and relative paths are the same for the // SimpleAxisServer msgContext.setProperty(Constants.MC_REALPATH, fileName.toString()); msgContext.setProperty(Constants.MC_RELATIVE_PATH, fileName.toString()); msgContext.setProperty(Constants.MC_JWS_CLASSDIR, "jwsClasses"); String hostname = socket.getInetAddress().getHostName(); // !!! Fix string concatenation String url = "http://" + hostname + ":" + server.getServerSocket().getLocalPort() + "/" + fileName.toString(); msgContext.setProperty(MessageContext.TRANS_URL, url); String filePart = fileName.toString(); if (filePart.startsWith("axis/services/")) { msgContext.setTargetService(filePart.substring(14)); } if (authInfo.length() > 0) { // Process authentication info //authInfo = new StringBuffer("dXNlcjE6cGFzczE="); byte[] decoded = Base64.decode(authInfo.toString()); StringBuffer userBuf = new StringBuffer(); StringBuffer pwBuf = new StringBuffer(); StringBuffer authBuf = userBuf; for (int i = 0; i < decoded.length; i++) { if ((char) (decoded[i] & 0x7f) == ':') { authBuf = pwBuf; continue; } authBuf.append((char) (decoded[i] & 0x7f)); } if (log.isDebugEnabled()) { log.debug(JavaUtils.getMessage("user00", userBuf.toString())); } msgContext.setUsername(userBuf.toString()); msgContext.setPassword(pwBuf.toString()); } // if get, then return simpleton document as response if (httpRequest.toString().equals("GET")) { OutputStream out = socket.getOutputStream(); out.write(HTTP); out.write(status); if (doWsdl) { engine.generateWSDL(msgContext); Document doc = (Document) msgContext.getProperty("WSDL"); if (doc != null) { String response = XMLUtils.DocumentToString(doc); byte[] respBytes = response.getBytes(); out.write(XML_MIME_STUFF); putInt(buf, out, respBytes.length); out.write(SEPARATOR); out.write(respBytes); out.flush(); return; } } out.write(HTML_MIME_STUFF); putInt(buf, out, cannedHTMLResponse.length); out.write(SEPARATOR); out.write(cannedHTMLResponse); out.flush(); return; } // this may be "" if either SOAPAction: "" or if no SOAPAction at all. // for now, do not complain if no SOAPAction at all String soapActionString = soapAction.toString(); if (soapActionString != null) { msgContext.setUseSOAPAction(true); msgContext.setSOAPActionURI(soapActionString); } requestMsg = new Message(is, false, contentType.toString(), contentLocation.toString() ); msgContext.setRequestMessage(requestMsg); // set up session, if any if (server.isSessionUsed()) { // did we get a cookie? if (cookie.length() > 0) { cooky = cookie.toString().trim(); } else if (cookie2.length() > 0) { cooky = cookie2.toString().trim(); } // if cooky is null, cook up a cooky if (cooky == null) { // fake one up! // make it be an arbitrarily increasing number // (no this is not thread safe because ++ isn't atomic) int i = server.sessionIndex++; cooky = "" + i; } msgContext.setSession(server.createSession(cooky)); } // invoke the Axis engine engine.invoke(msgContext); // Retrieve the response from Axis responseMsg = msgContext.getResponseMessage(); if (responseMsg == null) { throw new AxisFault(JavaUtils.getMessage("nullResponse00")); } } catch (Exception e) { AxisFault af; if (e instanceof AxisFault) { af = (AxisFault) e; log.debug(JavaUtils.getMessage("serverFault00"), af); if ("Server.Unauthorized".equals(af.getFaultCode())) { status = UNAUTH; // SC_UNAUTHORIZED } else { status = ISE; // SC_INTERNAL_SERVER_ERROR } } else { status = ISE; // SC_INTERNAL_SERVER_ERROR af = AxisFault.makeFault(e); } // There may be headers we want to preserve in the // response message - so if it's there, just add the // FaultElement to it. Otherwise, make a new one. responseMsg = msgContext.getResponseMessage(); if (responseMsg == null) { responseMsg = new Message(af); } else { try { SOAPEnvelope env = responseMsg.getSOAPEnvelope(); env.clearBody(); env.addBodyElement(new SOAPFaultElement((AxisFault) e)); } catch (AxisFault fault) { // Should never reach here! } } } // Send it on its way... OutputStream out = socket.getOutputStream(); out.write(HTTP); out.write(status); //out.write(XML_MIME_STUFF); out.write(("\r\n" + HTTPConstants.HEADER_CONTENT_TYPE + ": " + responseMsg.getContentType()).getBytes()); out.write(("\r\n" + HTTPConstants.HEADER_CONTENT_LENGTH + ": " + responseMsg.getContentLength()).getBytes()); // putInt(out, response.length); if (server.isSessionUsed() && null != cooky && 0 != cooky.trim().length()) { // write cookie headers, if any // don't sweat efficiency *too* badly // optimize at will StringBuffer cookieOut = new StringBuffer(); cookieOut.append("\r\nSet-Cookie: ") .append(cooky) .append("\r\nSet-Cookie2: ") .append(cooky); // OH, THE HUMILITY! yes this is inefficient. out.write(cookieOut.toString().getBytes()); } out.write(SEPARATOR); responseMsg.writeTo(out); // out.write(response); out.flush(); if (msgContext.getProperty(msgContext.QUIT_REQUESTED) != null) { // why then, quit! server.stop(); } } catch (Exception e) { log.debug(JavaUtils.getMessage("exception00"), e); } finally { try { if (socket != null) socket.close(); } catch (Exception e) { } } } /** * Read all mime headers, returning the value of Content-Length and * SOAPAction. * @param is InputStream to read from * @param contentType The content type. * @param contentLocation The content location * @param soapAction StringBuffer to return the soapAction into * @param httpRequest StringBuffer for GET / POST * @param cookie first cookie header (if doSessions) * @param cookie2 second cookie header (if doSessions) * @return Content-Length */ private int parseHeaders(NonBlockingBufferedInputStream is, byte buf[], StringBuffer contentType, StringBuffer contentLocation, StringBuffer soapAction, StringBuffer httpRequest, StringBuffer fileName, StringBuffer cookie, StringBuffer cookie2, StringBuffer authInfo) throws java.io.IOException { int n; int len = 0; // parse first line as GET or POST n = this.readLine(is, buf, 0, buf.length); if (n < 0) { // nothing! throw new java.io.IOException(JavaUtils.getMessage("unexpectedEOS00")); } // which does it begin with? httpRequest.delete(0, httpRequest.length()); fileName.delete(0, fileName.length()); contentType.delete(0, contentType.length()); contentLocation.delete(0, contentLocation.length()); if (buf[0] == getHeader[0]) { httpRequest.append("GET"); for (int i = 0; i < n - 5; i++) { char c = (char) (buf[i + 5] & 0x7f); if (c == ' ') break; fileName.append(c); } log.debug(JavaUtils.getMessage("filename01", "SimpleAxisServer", fileName.toString())); return 0; } else if (buf[0] == postHeader[0]) { httpRequest.append("POST"); for (int i = 0; i < n - 6; i++) { char c = (char) (buf[i + 6] & 0x7f); if (c == ' ') break; fileName.append(c); } log.debug(JavaUtils.getMessage("filename01", "SimpleAxisServer", fileName.toString())); } else { throw new java.io.IOException(JavaUtils.getMessage("badRequest00")); } while ((n = readLine(is, buf, 0, buf.length)) > 0) { if ((n <= 2) && (buf[0] == '\n' || buf[0] == '\r') && (len > 0)) break; // RobJ gutted the previous logic; it was too hard to extend for more headers. // Now, all it does is search forwards for ": " in the buf, // then do a length / byte compare. // Hopefully this is still somewhat efficient (Sam is watching!). // First, search forwards for ": " int endHeaderIndex = 0; while (endHeaderIndex < n && toLower[buf[endHeaderIndex]] != headerEnder[0]) { endHeaderIndex++; } endHeaderIndex += 2; // endHeaderIndex now points _just past_ the ": ", and is // comparable to the various lenLen, actionLen, etc. values // convenience; i gets pre-incremented, so initialize it to one less int i = endHeaderIndex - 1; // which header did we find? if (endHeaderIndex == lenLen && matches(buf, lenHeader)) { // parse content length while ((++i < n) && (buf[i] >= '0') && (buf[i] <= '9')) { len = (len * 10) + (buf[i] - '0'); } } else if (endHeaderIndex == actionLen && matches(buf, actionHeader)) { soapAction.delete(0, soapAction.length()); // skip initial '"' i++; while ((++i < n) && (buf[i] != '"')) { soapAction.append((char) (buf[i] & 0x7f)); } } else if (server.isSessionUsed() && endHeaderIndex == cookieLen && matches(buf, cookieHeader)) { // keep everything up to first ; while ((++i < n) && (buf[i] != ';') && (buf[i] != '\r') && (buf[i] != '\n')) { cookie.append((char) (buf[i] & 0x7f)); } } else if (server.isSessionUsed() && endHeaderIndex == cookie2Len && matches(buf, cookie2Header)) { // keep everything up to first ; while ((++i < n) && (buf[i] != ';') && (buf[i] != '\r') && (buf[i] != '\n')) { cookie2.append((char) (buf[i] & 0x7f)); } } else if (endHeaderIndex == authLen && matches(buf, authHeader)) { if (matches(buf, endHeaderIndex, basicAuth)) { i += basicAuth.length; while (++i < n && (buf[i] != '\r') && (buf[i] != '\n')) { if (buf[i] == ' ') continue; authInfo.append((char) (buf[i] & 0x7f)); } } else { throw new java.io.IOException( JavaUtils.getMessage("badAuth00")); } } else if (endHeaderIndex == locationLen && matches(buf, locationHeader)) { while (++i < n && (buf[i] != '\r') && (buf[i] != '\n')) { if (buf[i] == ' ') continue; contentLocation.append((char) (buf[i] & 0x7f)); } } else if (endHeaderIndex == typeLen && matches(buf, typeHeader)) { while (++i < n && (buf[i] != '\r') && (buf[i] != '\n')) { if (buf[i] == ' ') continue; contentType.append((char) (buf[i] & 0x7f)); } } } return len; } /** * does tolower[buf] match the target byte array, up to the target's length? */ public boolean matches(byte[] buf, byte[] target) { for (int i = 0; i < target.length; i++) { if (toLower[buf[i]] != target[i]) { return false; } } return true; } /** * Case-insensitive match of a target byte [] to a source byte [], * starting from a particular offset into the source. */ public boolean matches(byte[] buf, int bufIdx, byte[] target) { for (int i = 0; i < target.length; i++) { if (toLower[buf[bufIdx + i]] != target[i]) { return false; } } return true; } /** * output an integer into the output stream * @param out OutputStream to be written to * @param value Integer value to be written. */ private void putInt(byte buf[], OutputStream out, int value) throws java.io.IOException { int len = 0; int offset = buf.length; // negative numbers if (value < 0) { buf[--offset] = (byte) '-'; value = -value; len++; } // zero if (value == 0) { buf[--offset] = (byte) '0'; len++; } // positive numbers while (value > 0) { buf[--offset] = (byte) (value % 10 + '0'); value = value / 10; len++; } // write the result out.write(buf, offset, len); } /** * Read a single line from the input stream * @param is inputstream to read from * @param b byte array to read into * @param off starting offset into the byte array * @param len maximum number of bytes to read */ private int readLine(NonBlockingBufferedInputStream is, byte[] b, int off, int len) throws java.io.IOException { int count = 0, c; while ((c = is.read()) != -1) { if (c != '\n' && c != '\r') { b[off++] = (byte) c; count++; } if (count == len) break; if ('\n' == c) { int peek = is.peek(); //If the next line begins with tab or space then this is a continuation. if (peek != ' ' && peek != '\t') break; } } return count > 0 ? count : -1; } }