*applause* :)
> -----Original Message----- > From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED]] > Sent: Friday, June 07, 2002 2:32 PM > To: [EMAIL PROTECTED] > Subject: cvs commit: xml-axis/java/src/org/apache/axis/transport/http > SimpleAxisWorker.java SimpleAxisServer.java > > > 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/MultithreadTestC > ase.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/Sim > pleAxisServer.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; > } > } > > > >