diff --git a/.gitignore b/.gitignore index 56b34575555..11c793e1afe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,33 @@ -target/ +# eclipse .classpath .project .settings + +# maven +target/ */src/main/java/META-INF/ -.pmd + +# common junk *.log *.swp *.diff *.patch + +# intellij *.iml *.ipr *.iws .idea/ + +# Mac filesystem dust +/.DS_Store + +# pmd +.pmdruleset +.pmd + +# netbeans +/nbproject + +# vim +.*.sw[a-p] diff --git a/VERSION.txt b/VERSION.txt index 1e784b9d04b..8d3535289ea 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,5 +1,11 @@ jetty-8.0.2-SNAPSHOT + +jetty-7.5.2-SNAPSHOT + + 358121 Implement new UTF8 Algorithm to UTF8Appendable.java + + 353839 ajp component error when upload file + + JETTY-1378 new system property to for the use of the JDTCompiler when using the latest jsp-impl + jetty-8.0.1.v20110908 - 08 September 2011 + 350634 Added Resource.newResource(File) + 356190 fix monodb tests for changed test api @@ -12,12 +18,25 @@ jetty-8.0.1.v20110908 - 08 September 2011 + 356823 correctly decode close codes. Send not utf-8 close code. + 357058 Acceptor thread blocking +jetty-7.5.1.v20110908 - 08 September 2011 + + 350634 Added Resource.newResource(File) + + 356190 fix monodb tests for changed test api + + 356428 removed timed waits from test + + 356693 reduce visibility to webapp of websocket implementations + + 356695 jetty server jars are provided for websockets + + 356726 Instead of the sessionDestroyed called sessionCreated after + invalidate session + + 356751 Add null protection to ServletContextHandler.doStop + + 356823 correctly decode close codes. Send not utf-8 close code. + + 357058 Acceptor thread blocking + jetty-8.0.0.v20110901 - 01 September 2011 + 352565 cookie httponly flag ignored + 353073 better warnings + 353285 ServletSecurity annotation ignored + 356421 Upgraded websocket to draft 13 support + jetty-7.5.0.v20110901 - 01 September 2011 + 356421 Upgraded websocket to draft 13 support + 353073 better warnings diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java index 117dbf6dd83..328e98449d7 100644 --- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java +++ b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java @@ -33,6 +33,7 @@ import org.eclipse.jetty.server.handler.RequestLogHandler; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; +import org.eclipse.jetty.server.ssl.SslSocketConnector; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -87,9 +88,15 @@ public class LikeJettyXml "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA" }); + cf.setProtocol("TLSv1.1"); + cf.addExcludeProtocols(new String[]{"TLSv1","SSLv3"}); ssl_connector.setStatsOn(true); server.addConnector(ssl_connector); + ssl_connector.open(); + + + Ajp13SocketConnector ajp = new Ajp13SocketConnector(); ajp.setPort(8009); server.addConnector(ajp); diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java index 7224824cb3e..33c00e7a69b 100644 --- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java +++ b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/SecuredHelloHandler.java @@ -29,7 +29,7 @@ public class SecuredHelloHandler { public static void main(String[] args) throws Exception { - Server server = new Server(0); + Server server = new Server(8080); LoginService loginService = new HashLoginService("MyRealm","src/test/resources/realm.properties"); server.addBean(loginService); diff --git a/jetty-aggregate/jetty-all-server/pom.xml b/jetty-aggregate/jetty-all-server/pom.xml index df8ea420d5c..979ded7460d 100644 --- a/jetty-aggregate/jetty-all-server/pom.xml +++ b/jetty-aggregate/jetty-all-server/pom.xml @@ -25,7 +25,7 @@ unpack-dependencies - META-INF/**,org/eclipse/** + META-INF/**,org/eclipse/**,org/apache/jasper/compiler/** **/MANIFEST.MF,javax/** ${project.build.directory}/classes false diff --git a/jetty-aggregate/jetty-all/pom.xml b/jetty-aggregate/jetty-all/pom.xml index 3965d02c98f..e7eaf855935 100644 --- a/jetty-aggregate/jetty-all/pom.xml +++ b/jetty-aggregate/jetty-all/pom.xml @@ -22,7 +22,7 @@ unpack-dependencies - META-INF/**,org/eclipse/** + META-INF/**,org/eclipse/**,org/apache/jasper/compiler/* **/MANIFEST.MF,javax/** ${project.build.directory}/classes false @@ -75,6 +75,19 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + javadoc-jar + compile + + jar + + + + diff --git a/jetty-aggregate/jetty-webapp/pom.xml b/jetty-aggregate/jetty-webapp/pom.xml index f278dff7ee9..82107590aa0 100644 --- a/jetty-aggregate/jetty-webapp/pom.xml +++ b/jetty-aggregate/jetty-webapp/pom.xml @@ -22,7 +22,7 @@ unpack-dependencies - META-INF/**,org/eclipse/** + META-INF/**,org/eclipse/**,org/apache/jasper/compiler/** **/MANIFEST.MF,javax/** ${project.build.directory}/classes false diff --git a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java index 488af7401a3..dd05df0df91 100644 --- a/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java +++ b/jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java @@ -44,38 +44,38 @@ public class Ajp13Generator extends AbstractGenerator static { byte[] xA001 = - { (byte) 0xA0, (byte) 0x01 }; + { (byte)0xA0, (byte)0x01 }; byte[] xA002 = - { (byte) 0xA0, (byte) 0x02 }; + { (byte)0xA0, (byte)0x02 }; byte[] xA003 = - { (byte) 0xA0, (byte) 0x03 }; + { (byte)0xA0, (byte)0x03 }; byte[] xA004 = - { (byte) 0xA0, (byte) 0x04 }; + { (byte)0xA0, (byte)0x04 }; byte[] xA005 = - { (byte) 0xA0, (byte) 0x05 }; + { (byte)0xA0, (byte)0x05 }; byte[] xA006 = - { (byte) 0xA0, (byte) 0x06 }; + { (byte)0xA0, (byte)0x06 }; byte[] xA007 = - { (byte) 0xA0, (byte) 0x07 }; + { (byte)0xA0, (byte)0x07 }; byte[] xA008 = - { (byte) 0xA0, (byte) 0x08 }; + { (byte)0xA0, (byte)0x08 }; byte[] xA009 = - { (byte) 0xA0, (byte) 0x09 }; + { (byte)0xA0, (byte)0x09 }; byte[] xA00A = - { (byte) 0xA0, (byte) 0x0A }; + { (byte)0xA0, (byte)0x0A }; byte[] xA00B = - { (byte) 0xA0, (byte) 0x0B }; - __headerHash.put("Content-Type", xA001); - __headerHash.put("Content-Language", xA002); - __headerHash.put("Content-Length", xA003); - __headerHash.put("Date", xA004); - __headerHash.put("Last-Modified", xA005); - __headerHash.put("Location", xA006); - __headerHash.put("Set-Cookie", xA007); - __headerHash.put("Set-Cookie2", xA008); - __headerHash.put("Servlet-Engine", xA009); - __headerHash.put("Status", xA00A); - __headerHash.put("WWW-Authenticate", xA00B); + { (byte)0xA0, (byte)0x0B }; + __headerHash.put("Content-Type",xA001); + __headerHash.put("Content-Language",xA002); + __headerHash.put("Content-Length",xA003); + __headerHash.put("Date",xA004); + __headerHash.put("Last-Modified",xA005); + __headerHash.put("Location",xA006); + __headerHash.put("Set-Cookie",xA007); + __headerHash.put("Set-Cookie2",xA008); + __headerHash.put("Servlet-Engine",xA009); + __headerHash.put("Status",xA00A); + __headerHash.put("WWW-Authenticate",xA00B); } @@ -83,7 +83,7 @@ public class Ajp13Generator extends AbstractGenerator // 0, 1 ajp int 1 packet length // 9 CPONG response Code private static final byte[] AJP13_CPONG_RESPONSE = - { 'A', 'B', 0, 1, 9}; + { 'A', 'B', 0, 1, 9 }; private static final byte[] AJP13_END_RESPONSE = { 'A', 'B', 0, 2, 5, 1 }; @@ -114,7 +114,7 @@ public class Ajp13Generator extends AbstractGenerator /* ------------------------------------------------------------ */ public Ajp13Generator(Buffers buffers, EndPoint io) { - super(buffers, io); + super(buffers,io); } /* ------------------------------------------------------------ */ @@ -130,6 +130,7 @@ public class Ajp13Generator extends AbstractGenerator { return true; } + /* ------------------------------------------------------------ */ @Override public void reset(boolean returnBuffers) @@ -140,9 +141,7 @@ public class Ajp13Generator extends AbstractGenerator _needMore = false; _expectMore = false; _bufferPrepared = false; - _last=false; - - + _last = false; _state = STATE_HEADER; @@ -159,11 +158,9 @@ public class Ajp13Generator extends AbstractGenerator _noContent = false; _persistent = true; - - - _header = null; // Buffer for HTTP header (and maybe small _content) - _buffer = null; // Buffer for copy of passed _content - _content = null; // Buffer passed to addContent + _header = null; // Buffer for HTTP header (and maybe small _content) + _buffer = null; // Buffer for copy of passed _content + _content = null; // Buffer passed to addContent } @@ -175,13 +172,13 @@ public class Ajp13Generator extends AbstractGenerator { initContent(); } - catch(IOException e) + catch (IOException e) { throw new RuntimeException(e); } - return super.getContentBufferSize()-7; + return super.getContentBufferSize() - 7; } - + /* ------------------------------------------------------------ */ @Override public void increaseContentBufferSize(int contentBufferSize) @@ -196,11 +193,9 @@ public class Ajp13Generator extends AbstractGenerator * @param content * @param last * @throws IllegalArgumentException - * if content is - * {@link Buffer#isImmutable immutable}. + * if content is {@link Buffer#isImmutable immutable}. * @throws IllegalStateException - * If the request is not expecting any more content, or if the - * buffers are full and cannot be flushed. + * If the request is not expecting any more content, or if the buffers are full and cannot be flushed. * @throws IOException * if there is a problem flushing the buffers. */ @@ -217,13 +212,13 @@ public class Ajp13Generator extends AbstractGenerator if (_last || _state == STATE_END) { - LOG.debug("Ignoring extra content {}", content); + LOG.debug("Ignoring extra content {}",content); content.clear(); return; } _last = last; - if(!_endp.isOpen()) + if (!_endp.isOpen()) { _state = STATE_END; return; @@ -289,8 +284,7 @@ public class Ajp13Generator extends AbstractGenerator if (_last || _state == STATE_END) throw new IllegalStateException("Closed"); - - if(!_endp.isOpen()) + if (!_endp.isOpen()) { _state = STATE_END; return false; @@ -322,8 +316,7 @@ public class Ajp13Generator extends AbstractGenerator /* ------------------------------------------------------------ */ /** - * Prepare buffer for unchecked writes. Prepare the generator buffer to - * receive unchecked writes + * Prepare buffer for unchecked writes. Prepare the generator buffer to receive unchecked writes * * @return the available space in the buffer. * @throws IOException @@ -337,8 +330,7 @@ public class Ajp13Generator extends AbstractGenerator if (_last || _state == STATE_END) throw new IllegalStateException("Closed"); - - if(!_endp.isOpen()) + if (!_endp.isOpen()) { _state = STATE_END; return -1; @@ -377,8 +369,8 @@ public class Ajp13Generator extends AbstractGenerator _last = _last | allContentAdded; boolean has_server = false; - if (_persistent==null) - _persistent=(_version > HttpVersions.HTTP_1_0_ORDINAL); + if (_persistent == null) + _persistent = (_version > HttpVersions.HTTP_1_0_ORDINAL); // get a header buffer if (_header == null) @@ -390,13 +382,13 @@ public class Ajp13Generator extends AbstractGenerator try { // start the header - _buffer.put((byte) 'A'); - _buffer.put((byte) 'B'); + _buffer.put((byte)'A'); + _buffer.put((byte)'B'); addInt(0); - _buffer.put((byte) 0x4); + _buffer.put((byte)0x4); addInt(_status); if (_reason == null) - _reason=HttpGenerator.getReasonBuffer(_status); + _reason = HttpGenerator.getReasonBuffer(_status); if (_reason == null) _reason = new ByteArrayBuffer(Integer.toString(_status)); addBuffer(_reason); @@ -407,7 +399,6 @@ public class Ajp13Generator extends AbstractGenerator _content = null; } - // allocate 2 bytes for number of headers int field_index = _buffer.putIndex(); addInt(0); @@ -417,15 +408,15 @@ public class Ajp13Generator extends AbstractGenerator if (fields != null) { // Add headers - int s=fields.size(); - for (int f=0;f 0) ? 4 : 0) | ((_buffer != null && _buffer.length() > 0) ? 2 : 0); - + int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0); switch (to_flush) { - case 7: - throw new IllegalStateException(); // should - // never - // happen! - case 6: - len = _endp.flush(_header, _buffer, null); + case 7: + throw new IllegalStateException(); // should + // never + // happen! + case 6: + len = _endp.flush(_header,_buffer,null); - break; - case 5: - throw new IllegalStateException(); // should - // never - // happen! - case 4: - len = _endp.flush(_header); - break; - case 3: - throw new IllegalStateException(); // should - // never - // happen! - case 2: - len = _endp.flush(_buffer); + break; + case 5: + throw new IllegalStateException(); // should + // never + // happen! + case 4: + len = _endp.flush(_header); + break; + case 3: + throw new IllegalStateException(); // should + // never + // happen! + case 2: + len = _endp.flush(_buffer); - break; - case 1: - throw new IllegalStateException(); // should - // never - // happen! - case 0: - { - // Nothing more we can write now. - if (_header != null) - _header.clear(); - - _bufferPrepared = false; - - if (_buffer != null) + break; + case 1: + throw new IllegalStateException(); // should + // never + // happen! + case 0: { - _buffer.clear(); + // Nothing more we can write now. + if (_header != null) + _header.clear(); - // reserve some space for the - // header - _buffer.setPutIndex(7); - _buffer.setGetIndex(7); + _bufferPrepared = false; - // Special case handling for - // small left over buffer from - // an addContent that caused a - // buffer flush. - if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) + if (_buffer != null) { + _buffer.clear(); + + // reserve some space for the + // header + _buffer.setPutIndex(7); + _buffer.setGetIndex(7); + + // Special case handling for + // small left over buffer from + // an addContent that caused a + // buffer flush. + if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) + { + + _buffer.put(_content); + _content.clear(); + _content = null; + break Flushing; + } + + } + + // Are we completely finished for now? + if (!_expectMore && !_needEOC && (_content == null || _content.length() == 0)) + { + if (_state == STATE_FLUSHING) + _state = STATE_END; + + // if (_state == STATE_END) + // { + // _endp.close(); + // } + // - _buffer.put(_content); - _content.clear(); - _content = null; break Flushing; } + // Try to prepare more to write. + prepareBuffers(); } - - - - // Are we completely finished for now? - if (!_expectMore && !_needEOC && (_content == null || _content.length() == 0)) - { - if (_state == STATE_FLUSHING) - _state = STATE_END; - -// if (_state == STATE_END) -// { -// _endp.close(); -// } -// - - break Flushing; - } - - // Try to prepare more to write. - prepareBuffers(); - } } // If we failed to flush anything twice in a row @@ -631,7 +618,7 @@ public class Ajp13Generator extends AbstractGenerator catch (IOException e) { LOG.ignore(e); - throw (e instanceof EofException) ? e : new EofException(e); + throw (e instanceof EofException)?e:new EofException(e); } } @@ -680,14 +667,14 @@ public class Ajp13Generator extends AbstractGenerator { _bufferPrepared = true; - _buffer.put((byte) 0); + _buffer.put((byte)0); int put = _buffer.putIndex(); _buffer.setGetIndex(0); _buffer.setPutIndex(0); - _buffer.put((byte) 'A'); - _buffer.put((byte) 'B'); + _buffer.put((byte)'A'); + _buffer.put((byte)'B'); addInt(payloadSize + 4); - _buffer.put((byte) 3); + _buffer.put((byte)3); addInt(payloadSize); _buffer.setPutIndex(put); } @@ -759,15 +746,15 @@ public class Ajp13Generator extends AbstractGenerator /* ------------------------------------------------------------ */ private void addInt(int i) { - _buffer.put((byte) ((i >> 8) & 0xFF)); - _buffer.put((byte) (i & 0xFF)); + _buffer.put((byte)((i >> 8) & 0xFF)); + _buffer.put((byte)(i & 0xFF)); } /* ------------------------------------------------------------ */ private void addInt(int startIndex, int i) { - _buffer.poke(startIndex, (byte) ((i >> 8) & 0xFF)); - _buffer.poke((startIndex + 1), (byte) (i & 0xFF)); + _buffer.poke(startIndex,(byte)((i >> 8) & 0xFF)); + _buffer.poke((startIndex + 1),(byte)(i & 0xFF)); } /* ------------------------------------------------------------ */ @@ -786,7 +773,7 @@ public class Ajp13Generator extends AbstractGenerator addInt(b.length); _buffer.put(b); - _buffer.put((byte) 0); + _buffer.put((byte)0); } /* ------------------------------------------------------------ */ @@ -800,15 +787,14 @@ public class Ajp13Generator extends AbstractGenerator addInt(b.length()); _buffer.put(b); - _buffer.put((byte) 0); + _buffer.put((byte)0); } /* ------------------------------------------------------------ */ public void getBodyChunk() throws IOException { - _needMore = true; - _expectMore = true; - flushBuffer(); + ByteArrayBuffer bf = new ByteArrayBuffer(AJP13_MORE_CONTENT); + _endp.flush(bf); } /* ------------------------------------------------------------ */ @@ -818,7 +804,6 @@ public class Ajp13Generator extends AbstractGenerator _expectMore = false; } - /* ------------------------------------------------------------ */ public void sendCPong() throws IOException { @@ -831,13 +816,11 @@ public class Ajp13Generator extends AbstractGenerator { _endp.flush(buff); } - while(buff.length() >0); + while (buff.length() > 0); _buffers.returnBuffer(buff); reset(true); } - - } diff --git a/jetty-annotations/src/main/config/etc/jetty-annotations.xml b/jetty-annotations/src/main/config/etc/jetty-annotations.xml index b27f9443062..7aa719d7ca6 100644 --- a/jetty-annotations/src/main/config/etc/jetty-annotations.xml +++ b/jetty-annotations/src/main/config/etc/jetty-annotations.xml @@ -16,7 +16,6 @@ org.eclipse.jetty.webapp.FragmentConfiguration org.eclipse.jetty.annotations.AnnotationConfiguration org.eclipse.jetty.webapp.JettyWebXmlConfiguration - org.eclipse.jetty.annotations.ContainerInitializerConfiguration diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java index 5b5cd62811c..dc5bea6cb7e 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java @@ -18,9 +18,11 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; +import java.util.Set; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; +import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; @@ -47,6 +49,8 @@ public class AnnotationConfiguration extends AbstractConfiguration { private static final Logger LOG = Log.getLogger(AnnotationConfiguration.class); public static final String CLASS_INHERITANCE_MAP = "org.eclipse.jetty.classInheritanceMap"; + public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers"; + public void preConfigure(final WebAppContext context) throws Exception { @@ -58,45 +62,46 @@ public class AnnotationConfiguration extends AbstractConfiguration boolean metadataComplete = context.getMetaData().isMetaDataComplete(); context.addDecorator(new AnnotationDecorator(context)); - if (metadataComplete) - { - //Never scan any jars or classes for annotations if metadata is complete - if (LOG.isDebugEnabled()) LOG.debug("Metadata-complete==true, not processing annotations for context "+context); - return; - } - else - { - //Only scan jars and classes if metadata is not complete and the web app is version 3.0, or - //a 2.5 version webapp that has specifically asked to discover annotations - if (LOG.isDebugEnabled()) LOG.debug("parsing annotations"); - - AnnotationParser parser = createAnnotationParser(); - //Discoverable annotations - those that you have to look for without loading a class - parser.registerAnnotationHandler("javax.servlet.annotation.WebServlet", new WebServletAnnotationHandler(context)); - parser.registerAnnotationHandler("javax.servlet.annotation.WebFilter", new WebFilterAnnotationHandler(context)); - parser.registerAnnotationHandler("javax.servlet.annotation.WebListener", new WebListenerAnnotationHandler(context)); - ClassInheritanceHandler classHandler = new ClassInheritanceHandler(); - parser.registerClassHandler(classHandler); - registerServletContainerInitializerAnnotationHandlers(context, parser); - - if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered()) - { - if (LOG.isDebugEnabled()) LOG.debug("Scanning all classses for annotations: webxmlVersion="+context.getServletContext().getEffectiveMajorVersion()+" configurationDiscovered="+context.isConfigurationDiscovered()); - parseContainerPath(context, parser); - //email from Rajiv Mordani jsrs 315 7 April 2010 - // If there is a then the ordering should be - // WEB-INF/classes the order of the declared elements + others. - // In case there is no others then it is - // WEB-INF/classes + order of the elements. - parseWebInfClasses(context, parser); - parseWebInfLib (context, parser); - } - - //save the type inheritance map created by the parser for later reference - context.setAttribute(CLASS_INHERITANCE_MAP, classHandler.getMap()); - } + + //Even if metadata is complete, we still need to scan for ServletContainerInitializers - if there are any + AnnotationParser parser = null; + if (!metadataComplete) + { + //If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations + if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered()) + { + parser = createAnnotationParser(); + parser.registerAnnotationHandler("javax.servlet.annotation.WebServlet", new WebServletAnnotationHandler(context)); + parser.registerAnnotationHandler("javax.servlet.annotation.WebFilter", new WebFilterAnnotationHandler(context)); + parser.registerAnnotationHandler("javax.servlet.annotation.WebListener", new WebListenerAnnotationHandler(context)); + } + } + else + if (LOG.isDebugEnabled()) LOG.debug("Metadata-complete==true, not processing discoverable servlet annotations for context "+context); + + + + //Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the + //classes so we can call their onStartup() methods correctly + List nonExcludedInitializers = getNonExcludedInitializers(context); + parser = registerServletContainerInitializerAnnotationHandlers(context, parser, nonExcludedInitializers); + + if (parser != null) + { + if (LOG.isDebugEnabled()) LOG.debug("Scanning all classses for annotations: webxmlVersion="+context.getServletContext().getEffectiveMajorVersion()+" configurationDiscovered="+context.isConfigurationDiscovered()); + parseContainerPath(context, parser); + //email from Rajiv Mordani jsrs 315 7 April 2010 + // If there is a then the ordering should be + // WEB-INF/classes the order of the declared elements + others. + // In case there is no others then it is + // WEB-INF/classes + order of the elements. + parseWebInfClasses(context, parser); + parseWebInfLib (context, parser); + } } + + /** * @return a new AnnotationParser. This method can be overridden to use a different impleemntation of * the AnnotationParser. Note that this is considered internal API. @@ -112,10 +117,13 @@ public class AnnotationConfiguration extends AbstractConfiguration context.addDecorator(new AnnotationDecorator(context)); } + - public void registerServletContainerInitializerAnnotationHandlers (WebAppContext context, AnnotationParser parser) + + public AnnotationParser registerServletContainerInitializerAnnotationHandlers (WebAppContext context, AnnotationParser parser, List scis) throws Exception { + //TODO verify my interpretation of the spec. That is, that metadata-complete has nothing //to do with finding the ServletContainerInitializers, classes designated to be of interest to them, //or even calling them on startup. @@ -125,46 +133,73 @@ public class AnnotationConfiguration extends AbstractConfiguration //that will record the classes that have that annotation. //If it is NOT an annotation, then we will interrogate the type hierarchy discovered during //parsing later on to find the applicable classes. - ArrayList initializers = new ArrayList(); - context.setAttribute(ContainerInitializerConfiguration.CONTAINER_INITIALIZERS, initializers); - //We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect - ServiceLoader loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class, context.getClassLoader()); - - if (loadedInitializers != null) + if (scis == null || scis.isEmpty()) + return parser; // nothing to do + + ServletContainerInitializerListener listener = new ServletContainerInitializerListener(); + listener.setWebAppContext(context); + context.addEventListener(listener); + + //may need to add a listener + + ArrayList initializers = new ArrayList(); + context.setAttribute(CONTAINER_INITIALIZERS, initializers); + + for (ServletContainerInitializer service : scis) { - for (ServletContainerInitializer service : loadedInitializers) + HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class); + ContainerInitializer initializer = new ContainerInitializer(); + initializer.setTarget(service); + initializers.add(initializer); + if (annotation != null) { - if (!isFromExcludedJar(context, service)) - { - HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class); - ContainerInitializer initializer = new ContainerInitializer(); - initializer.setTarget(service); - initializers.add(initializer); - if (annotation != null) + //There is a HandlesTypes annotation on the on the ServletContainerInitializer + Class[] classes = annotation.value(); + if (classes != null) + { + initializer.setInterestedTypes(classes); + + //We need to create a parser if we haven't already + if (parser == null) + parser = createAnnotationParser(); + + //If we haven't already done so, we need to register a handler that will + //process the whole class hierarchy + if (context.getAttribute(CLASS_INHERITANCE_MAP) == null) { - Class[] classes = annotation.value(); - if (classes != null) - { - initializer.setInterestedTypes(classes); - for (Class c: classes) - { - if (c.isAnnotation()) - { - if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName()); - parser.registerAnnotationHandler(c.getName(), new ContainerInitializerAnnotationHandler(initializer, c)); - } - } - } - else - if (LOG.isDebugEnabled()) LOG.debug("No classes in HandlesTypes on initializer "+service.getClass()); + ClassInheritanceHandler classHandler = new ClassInheritanceHandler(); + context.setAttribute(CLASS_INHERITANCE_MAP, classHandler.getMap()); + parser.registerClassHandler(classHandler); + } + + for (Class c: classes) + { + //The value of one of the HandlesTypes classes is actually an Annotation itself so + //register a handler for it + if (c.isAnnotation()) + { + if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName()); + + parser.registerAnnotationHandler(c.getName(), new ContainerInitializerAnnotationHandler(initializer, c)); + } } - else - if (LOG.isDebugEnabled()) LOG.debug("No annotation on initializer "+service.getClass()); } + else + if (LOG.isDebugEnabled()) LOG.debug("No classes in HandlesTypes on initializer "+service.getClass()); } + else + if (LOG.isDebugEnabled()) LOG.debug("No annotation on initializer "+service.getClass()); } + + //return the parser in case we lazily created it + return parser; } + + + + + /** * Check to see if the ServletContainerIntializer loaded via the ServiceLoader came @@ -206,6 +241,30 @@ public class AnnotationConfiguration extends AbstractConfiguration return !found; } + + + public List getNonExcludedInitializers (WebAppContext context) + throws Exception + { + List nonExcludedInitializers = new ArrayList(); + + //We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect + ServiceLoader loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class, context.getClassLoader()); + + if (loadedInitializers != null) + { + for (ServletContainerInitializer service : loadedInitializers) + { + if (!isFromExcludedJar(context, service)) + nonExcludedInitializers.add(service); + } + } + return nonExcludedInitializers; + } + + + + public void parseContainerPath (final WebAppContext context, final AnnotationParser parser) throws Exception { diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java similarity index 67% rename from jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerConfiguration.java rename to jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java index 91960ffcfd4..e3cac47aaa3 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ContainerInitializerConfiguration.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java @@ -1,3 +1,17 @@ +package org.eclipse.jetty.annotations; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.plus.annotation.ContainerInitializer; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.webapp.WebAppContext; + // ======================================================================== // Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ @@ -11,37 +25,28 @@ // You may elect to redistribute this code under either of these licenses. // ======================================================================== - -package org.eclipse.jetty.annotations; - - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.eclipse.jetty.plus.annotation.ContainerInitializer; -import org.eclipse.jetty.util.MultiMap; -import org.eclipse.jetty.webapp.AbstractConfiguration; -import org.eclipse.jetty.webapp.Configuration; -import org.eclipse.jetty.webapp.WebAppContext; - /** - * ContainerInitializerConfiguration + * ServletContainerInitializerListener + * * - * Apply the ServletContainerInitializers. */ -public class ContainerInitializerConfiguration extends AbstractConfiguration +public class ServletContainerInitializerListener implements ServletContextListener { - public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers"; - - public void preConfigure(WebAppContext context) throws Exception - { + WebAppContext _context = null; + + + public void setWebAppContext (WebAppContext context) + { + _context = context; } - public void configure(WebAppContext context) throws Exception + /** + * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent) + */ + public void contextInitialized(ServletContextEvent sce) { - List initializers = (List)context.getAttribute(CONTAINER_INITIALIZERS); - MultiMap classMap = (MultiMap)context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP); + List initializers = (List)_context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS); + MultiMap classMap = (MultiMap)_context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP); if (initializers != null) { @@ -58,9 +63,12 @@ public class ContainerInitializerConfiguration extends AbstractConfiguration //add the class with the annotation i.addApplicableTypeName(name); //add the classes that inherit the annotation - List implementsOrExtends = (List)classMap.getValues(name); - if (implementsOrExtends != null && !implementsOrExtends.isEmpty()) - addInheritedTypes(classMap, i, implementsOrExtends); + if (classMap != null) + { + List implementsOrExtends = (List)classMap.getValues(name); + if (implementsOrExtends != null && !implementsOrExtends.isEmpty()) + addInheritedTypes(classMap, i, implementsOrExtends); + } } } @@ -75,29 +83,33 @@ public class ContainerInitializerConfiguration extends AbstractConfiguration { //add the classes that implement or extend the class. //TODO but not including the class itself? - List implementsOrExtends = (List)classMap.getValues(c.getName()); - if (implementsOrExtends != null && !implementsOrExtends.isEmpty()) - addInheritedTypes(classMap, i, implementsOrExtends); + if (classMap != null) + { + List implementsOrExtends = (List)classMap.getValues(c.getName()); + if (implementsOrExtends != null && !implementsOrExtends.isEmpty()) + addInheritedTypes(classMap, i, implementsOrExtends); + } } } } + //instantiate ServletContainerInitializers, call doStart - i.callStartup(context); + try + { + i.callStartup(_context); + } + catch (Exception e) + { + //OK, how do I throw an exception such that it really stops the startup sequence? + e.printStackTrace(); + } } //TODO Email from Jan Luehe 18 August: after all ServletContainerInitializers have been //called, need to check to see if there are any ServletRegistrations remaining //that are "preliminary" and fail the deployment if so. - } - } - - public void postConfigure(WebAppContext context) throws Exception - { - - } - - public void deconfigure(WebAppContext context) throws Exception - { + } + } @@ -114,5 +126,15 @@ public class ContainerInitializerConfiguration extends AbstractConfiguration addInheritedTypes (classMap, initializer, implementsOrExtends); } } - + + + /** + * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent) + */ + public void contextDestroyed(ServletContextEvent sce) + { + // TODO Auto-generated method stub + + } + } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 85b16242c91..31b27424dc9 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -178,10 +178,19 @@ public class HttpClient extends HttpBuffers implements Attributes, Dumpable /* ------------------------------------------------------------ */ /** - * @return the threadPool + * @return the threadpool */ public ThreadPool getThreadPool() { + if (_threadPool==null) + { + QueuedThreadPool pool = new QueuedThreadPool(); + pool.setMaxThreads(16); + pool.setDaemon(true); + pool.setName("HttpClient"); + _threadPool = pool; + } + return _threadPool; } @@ -420,13 +429,7 @@ public class HttpClient extends HttpBuffers implements Attributes, Dumpable _idleTimeoutQ.setNow(); if (_threadPool == null) - { - QueuedThreadPool pool = new QueuedThreadPool(); - pool.setMaxThreads(16); - pool.setDaemon(true); - pool.setName("HttpClient"); - _threadPool = pool; - } + getThreadPool(); if (_threadPool instanceof LifeCycle) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index 7dbd756f07f..34cc9e499a6 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -73,7 +73,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable HttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp) { super(endp); - + _generator = new HttpGenerator(requestBuffers,endp); _parser = new HttpParser(responseBuffers,endp,new Handler()); } @@ -277,6 +277,9 @@ public class HttpConnection extends AbstractConnection implements Dumpable { long filled = _parser.parseAvailable(); io += filled; + + if (_parser.isIdle() && (_endp.isInputShutdown() || !_endp.isOpen())) + throw new EOFException(); } if (io > 0) @@ -353,16 +356,34 @@ public class HttpConnection extends AbstractConnection implements Dumpable complete = true; } } + + // if the endpoint is closed, but the parser incomplete + if (!_endp.isOpen() && !(_parser.isComplete()||_parser.isIdle())) + { + // we wont be called again so let the parser see the close + complete=true; + _parser.parseAvailable(); + // TODO should not need this + if (!(_parser.isComplete()||_parser.isIdle())) + { + LOG.warn("Incomplete {} {}",_parser,_endp); + if (_exchange!=null && !_exchange.isDone()) + { + _exchange.setStatus(HttpExchange.STATUS_EXCEPTED); + _exchange.getEventListener().onException(new EOFException("Incomplete")); + } + } + } } - if (_generator.isComplete() && !_parser.isComplete()) + if (_endp.isInputShutdown() && !_parser.isComplete() && !_parser.isIdle()) { - if (!_endp.isOpen() || _endp.isInputShutdown()) + if (_exchange!=null && !_exchange.isDone()) { - complete=true; - close=true; - close(); + _exchange.setStatus(HttpExchange.STATUS_EXCEPTED); + _exchange.getEventListener().onException(new EOFException("Incomplete")); } + _endp.close(); } if (complete || failed) @@ -431,9 +452,9 @@ public class HttpConnection extends AbstractConnection implements Dumpable finally { _parser.returnBuffers(); - + // Do we have more stuff to write? - if (!_generator.isComplete() && _generator.getBytesBuffered()>0 && _endp instanceof AsyncEndPoint) + if (!_generator.isComplete() && _generator.getBytesBuffered()>0 && _endp.isOpen() && _endp instanceof AsyncEndPoint) { // Assume we are write blocked! ((AsyncEndPoint)_endp).scheduleWrite(); @@ -549,6 +570,8 @@ public class HttpConnection extends AbstractConnection implements Dumpable private boolean shouldClose() { + if (_endp.isInputShutdown()) + return true; if (_connectionHeader!=null) { if (HttpHeaderValues.CLOSE_BUFFER.equals(_connectionHeader)) @@ -669,6 +692,9 @@ public class HttpConnection extends AbstractConnection implements Dumpable case HttpExchange.STATUS_EXCEPTED: case HttpExchange.STATUS_EXPIRED: break; + case HttpExchange.STATUS_PARSING_CONTENT: + if (_endp.isInputShutdown() && _parser.isState(HttpParser.STATE_EOF_CONTENT)) + break; default: String exch= exchange.toString(); String reason = _endp.isOpen()?(_endp.isInputShutdown()?"half closed: ":"local close: "):"closed: "; @@ -724,7 +750,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable } } } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.util.component.Dumpable#dump() @@ -746,7 +772,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable AggregateLifeCycle.dump(out,indent,Collections.singletonList(_endp)); } } - + /* ------------------------------------------------------------ */ private class ConnectionIdleTask extends Timeout.Task { @@ -761,14 +787,14 @@ public class HttpConnection extends AbstractConnection implements Dumpable } } } - - + + /* ------------------------------------------------------------ */ private class NonFinalResponseListener implements HttpEventListener { final HttpExchange _exchange; final HttpEventListener _next; - + /* ------------------------------------------------------------ */ public NonFinalResponseListener(HttpExchange exchange) { @@ -813,7 +839,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable { _exchange.setEventListener(_next); _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE); - _parser.reset(); + _parser.reset(); } /* ------------------------------------------------------------ */ diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index 6d243383db4..9d7723b2b1b 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java @@ -584,7 +584,7 @@ public class HttpDestination implements Dumpable @Override public synchronized String toString() { - return "HttpDestination@" + hashCode() + "//" + _address.getHost() + ":" + _address.getPort() + "(" + _connections.size() + "," + _idle.size() + "," + _queue.size() + ")"; + return String.format("HttpDestination@%x//%s:%d(%d/%d,%d,%d/%d)%n",hashCode(),_address.getHost(),_address.getPort(),_connections.size(),_maxConnections,_idle.size(),_queue.size(),_maxQueueSize); } public synchronized String toDetailString() diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java index e97cc611654..6840adcabd2 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java @@ -35,7 +35,9 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Timeout; /** - *

An HTTP client API that encapsulates an exchange (a request and its response) with a HTTP server.

+ *

+ * An HTTP client API that encapsulates an exchange (a request and its response) with a HTTP server. + *

* * This object encapsulates: *
    @@ -48,23 +50,25 @@ import org.eclipse.jetty.util.thread.Timeout; *
  • The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)} *
* - *

The HttpExchange class is intended to be used by a developer wishing to have close asynchronous - * interaction with the the exchange.
- * Typically a developer will extend the HttpExchange class with a derived - * class that overrides some or all of the onXxx callbacks.
- * There are also some predefined HttpExchange subtypes that can be used as a basis, - * see {@link org.eclipse.jetty.client.ContentExchange} and {@link org.eclipse.jetty.client.CachedExchange}.

+ *

+ * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous interaction with the the exchange.
+ * Typically a developer will extend the HttpExchange class with a derived class that overrides some or all of the onXxx callbacks.
+ * There are also some predefined HttpExchange subtypes that can be used as a basis, see {@link org.eclipse.jetty.client.ContentExchange} and + * {@link org.eclipse.jetty.client.CachedExchange}. + *

* - *

Typically the HttpExchange is passed to the {@link HttpClient#send(HttpExchange)} method, which in - * turn selects a {@link HttpDestination} and calls its {@link HttpDestination#send(HttpExchange)}, which - * then creates or selects a {@link HttpConnection} and calls its {@link HttpConnection#send(HttpExchange)}. - * A developer may wish to directly call send on the destination or connection if they wish to bypass - * some handling provided (eg Cookie handling in the HttpDestination).

+ *

+ * Typically the HttpExchange is passed to the {@link HttpClient#send(HttpExchange)} method, which in turn selects a {@link HttpDestination} and calls its + * {@link HttpDestination#send(HttpExchange)}, which then creates or selects a {@link HttpConnection} and calls its {@link HttpConnection#send(HttpExchange)}. A + * developer may wish to directly call send on the destination or connection if they wish to bypass some handling provided (eg Cookie handling in the + * HttpDestination). + *

* - *

In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed - * pipeline request, authentication retry or redirection). In such cases, the HttpClient and/or HttpDestination - * may insert their own HttpExchangeListener to intercept and filter the call backs intended for the - * HttpExchange.

+ *

+ * In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed pipeline request, authentication retry or redirection). + * In such cases, the HttpClient and/or HttpDestination may insert their own HttpExchangeListener to intercept and filter the call backs intended for the + * HttpExchange. + *

*/ public class HttpExchange { @@ -106,9 +110,10 @@ public class HttpExchange // a timeout for this exchange private long _timeout = -1; private volatile Timeout.Task _timeoutTask; - - private long _lastStateChange=-1; + private long _lastStateChange=System.currentTimeMillis(); private long _sent=-1; + private int _lastState=-1; + private int _lastStatePeriod=-1; boolean _onRequestCompleteDone; boolean _onResponseCompleteDone; @@ -131,8 +136,10 @@ public class HttpExchange } /** - * @param status the status to wait for - * @throws InterruptedException if the waiting thread is interrupted + * @param status + * the status to wait for + * @throws InterruptedException + * if the waiting thread is interrupted * @deprecated Use {@link #waitForDone()} instead */ @Deprecated @@ -142,21 +149,17 @@ public class HttpExchange } /** - * Wait until the exchange is "done". - * Done is defined as when a final state has been passed to the - * HttpExchange via the associated onXxx call. Note that an - * exchange can transit a final state when being used as part - * of a dialog (eg {@link SecurityListener}. Done status - * is thus defined as:
-     *   done == onConnectionFailed
-     *        || onException
-     *        || onExpire
-     *        || onRequestComplete && onResponseComplete
+     * Wait until the exchange is "done". Done is defined as when a final state has been passed to the HttpExchange via the associated onXxx call. Note that an
+     * exchange can transit a final state when being used as part of a dialog (eg {@link SecurityListener}. Done status is thus defined as:
+     *
+     * 
+     * done == onConnectionFailed || onException || onExpire || onRequestComplete && onResponseComplete
      * 
+ * * @return the done status * @throws InterruptedException */ - public int waitForDone () throws InterruptedException + public int waitForDone() throws InterruptedException { synchronized (this) { @@ -170,12 +173,12 @@ public class HttpExchange { // TODO - this should do a cancel and wakeup everybody that was waiting. // might need a version number concept - synchronized(this) + synchronized (this) { - _timeoutTask=null; - _onRequestCompleteDone=false; - _onResponseCompleteDone=false; - _onDone=false; + _timeoutTask = null; + _onRequestCompleteDone = false; + _onResponseCompleteDone = false; + _onDone = false; setStatus(STATUS_START); } } @@ -186,13 +189,16 @@ public class HttpExchange { int oldStatus = _status.get(); boolean set = false; - if (oldStatus!=newStatus) + if (oldStatus != newStatus) { - _lastStateChange=System.currentTimeMillis(); + long now = System.currentTimeMillis(); + _lastStatePeriod=(int)(now-_lastStateChange); + _lastState=oldStatus; + _lastStateChange=now; if (newStatus==STATUS_SENDING_REQUEST) _sent=_lastStateChange; } - + // State machine: from which old status you can go into which new status switch (oldStatus) { @@ -204,7 +210,10 @@ public class HttpExchange case STATUS_WAITING_FOR_COMMIT: case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); + break; + case STATUS_EXPIRED: + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -214,11 +223,10 @@ public class HttpExchange case STATUS_WAITING_FOR_COMMIT: case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -228,11 +236,10 @@ public class HttpExchange case STATUS_SENDING_REQUEST: case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -240,16 +247,15 @@ public class HttpExchange switch (newStatus) { case STATUS_WAITING_FOR_RESPONSE: - if (set=_status.compareAndSet(oldStatus,newStatus)) + if (set = _status.compareAndSet(oldStatus,newStatus)) getEventListener().onRequestCommitted(); break; case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -259,11 +265,10 @@ public class HttpExchange case STATUS_PARSING_HEADERS: case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -271,16 +276,15 @@ public class HttpExchange switch (newStatus) { case STATUS_PARSING_CONTENT: - if (set=_status.compareAndSet(oldStatus,newStatus)) + if (set = _status.compareAndSet(oldStatus,newStatus)) getEventListener().onResponseHeaderComplete(); break; case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -288,16 +292,15 @@ public class HttpExchange switch (newStatus) { case STATUS_COMPLETED: - if (set=_status.compareAndSet(oldStatus,newStatus)) + if (set = _status.compareAndSet(oldStatus,newStatus)) getEventListener().onResponseComplete(); break; case STATUS_CANCELLING: case STATUS_EXCEPTED: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_EXPIRED: - if (set=_status.compareAndSet(oldStatus,newStatus)) - getEventListener().onExpire(); + set = setStatusExpired(newStatus,oldStatus); break; } break; @@ -307,12 +310,12 @@ public class HttpExchange case STATUS_START: case STATUS_EXCEPTED: case STATUS_WAITING_FOR_RESPONSE: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; case STATUS_CANCELLING: case STATUS_EXPIRED: // Don't change the status, it's too late - set=true; + set = true; break; } break; @@ -321,12 +324,12 @@ public class HttpExchange { case STATUS_EXCEPTED: case STATUS_CANCELLED: - if (set=_status.compareAndSet(oldStatus,newStatus)) + if (set = _status.compareAndSet(oldStatus,newStatus)) done(); break; default: // Ignore other statuses, we're cancelling - set=true; + set = true; break; } break; @@ -336,10 +339,10 @@ public class HttpExchange switch (newStatus) { case STATUS_START: - set=_status.compareAndSet(oldStatus,newStatus); + set = _status.compareAndSet(oldStatus,newStatus); break; default: - set=true; + set = true; break; } break; @@ -357,6 +360,14 @@ public class HttpExchange } } + private boolean setStatusExpired(int newStatus, int oldStatus) + { + boolean set; + if (set = _status.compareAndSet(oldStatus,newStatus)) + getEventListener().onExpire(); + return set; + } + public boolean isDone() { synchronized (this) @@ -369,7 +380,7 @@ public class HttpExchange * @deprecated */ @Deprecated - public boolean isDone (int status) + public boolean isDone(int status) { return isDone(); } @@ -381,10 +392,10 @@ public class HttpExchange public void setEventListener(HttpEventListener listener) { - _listener=listener; + _listener = listener; } - public void setTimeout( long timeout ) + public void setTimeout(long timeout) { _timeout = timeout; } @@ -395,7 +406,8 @@ public class HttpExchange } /** - * @param url an absolute URL (for example 'http://localhost/foo/bar?a=1') + * @param url + * an absolute URL (for example 'http://localhost/foo/bar?a=1') */ public void setURL(String url) { @@ -403,7 +415,8 @@ public class HttpExchange } /** - * @param address the address of the server + * @param address + * the address of the server */ public void setAddress(Address address) { @@ -421,8 +434,7 @@ public class HttpExchange /** * the local address used by the connection * - * Note: this method will not be populated unless the exchange - * has been executed by the HttpClient + * Note: this method will not be populated unless the exchange has been executed by the HttpClient * * @return the local address used for the running of the exchange if available, null otherwise. */ @@ -432,15 +444,17 @@ public class HttpExchange } /** - * @param scheme the scheme of the URL (for example 'http') + * @param scheme + * the scheme of the URL (for example 'http') */ public void setScheme(Buffer scheme) { _scheme = scheme; } - + /** - * @param scheme the scheme of the URL (for example 'http') + * @param scheme + * the scheme of the URL (for example 'http') */ public void setScheme(String scheme) { @@ -464,7 +478,8 @@ public class HttpExchange } /** - * @param version the HTTP protocol version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1 + * @param version + * the HTTP protocol version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1 */ public void setVersion(int version) { @@ -472,7 +487,8 @@ public class HttpExchange } /** - * @param version the HTTP protocol version as string + * @param version + * the HTTP protocol version as string */ public void setVersion(String version) { @@ -493,7 +509,8 @@ public class HttpExchange } /** - * @param method the HTTP method (for example 'GET') + * @param method + * the HTTP method (for example 'GET') */ public void setMethod(String method) { @@ -528,9 +545,10 @@ public class HttpExchange } /** - * Set the request URI + * Set the request URI * - * @param uri new request URI + * @param uri + * new request URI * @see #setRequestURI(String) * @deprecated */ @@ -541,36 +559,41 @@ public class HttpExchange } /** - * Set the request URI + * Set the request URI * * Per RFC 2616 sec5, Request-URI = "*" | absoluteURI | abs_path | authority
- * where:

- * "*" - request applies to server itself
+ * where:
+ *
+ * "*" - request applies to server itself
* absoluteURI - required for proxy requests, e.g. http://localhost:8080/context
- * (this form is generated automatically by HttpClient)
- * abs_path - used for most methods, e.g. /context
- * authority - used for CONNECT method only, e.g. localhost:8080
+ * (this form is generated automatically by HttpClient)
+ * abs_path - used for most methods, e.g. /context
+ * authority - used for CONNECT method only, e.g. localhost:8080
*
* For complete definition of URI components, see RFC 2396 sec3.
- * - * @param uri new request URI + * + * @param uri + * new request URI */ public void setRequestURI(String uri) { _uri = uri; } - + /* ------------------------------------------------------------ */ /** - * @param uri an absolute URI (for example 'http://localhost/foo/bar?a=1') + * @param uri + * an absolute URI (for example 'http://localhost/foo/bar?a=1') */ public void setURI(URI uri) { if (!uri.isAbsolute()) - throw new IllegalArgumentException("!Absolute URI: "+uri); - + throw new IllegalArgumentException("!Absolute URI: " + uri); + if (uri.isOpaque()) - throw new IllegalArgumentException("Opaque URI: "+uri); + throw new IllegalArgumentException("Opaque URI: " + uri); + + LOG.debug("URI = {}",uri.toASCIIString()); String scheme = uri.getScheme(); int port = uri.getPort(); @@ -582,13 +605,16 @@ public class HttpExchange HttpURI httpUri = new HttpURI(uri); String completePath = httpUri.getCompletePath(); - setRequestURI(completePath==null ? "/" : completePath); + setRequestURI(completePath == null?"/":completePath); } /** * Adds the specified request header - * @param name the header name - * @param value the header value + * + * @param name + * the header name + * @param value + * the header value */ public void addRequestHeader(String name, String value) { @@ -597,8 +623,11 @@ public class HttpExchange /** * Adds the specified request header - * @param name the header name - * @param value the header value + * + * @param name + * the header name + * @param value + * the header value */ public void addRequestHeader(Buffer name, Buffer value) { @@ -607,30 +636,37 @@ public class HttpExchange /** * Sets the specified request header - * @param name the header name - * @param value the header value + * + * @param name + * the header name + * @param value + * the header value */ public void setRequestHeader(String name, String value) { - getRequestFields().put(name, value); + getRequestFields().put(name,value); } /** * Sets the specified request header - * @param name the header name - * @param value the header value + * + * @param name + * the header name + * @param value + * the header value */ public void setRequestHeader(Buffer name, Buffer value) { - getRequestFields().put(name, value); + getRequestFields().put(name,value); } /** - * @param value the content type of the request + * @param value + * the content type of the request */ public void setRequestContentType(String value) { - getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER, value); + getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value); } /** @@ -642,7 +678,8 @@ public class HttpExchange } /** - * @param requestContent the request content + * @param requestContent + * the request content */ public void setRequestContent(Buffer requestContent) { @@ -650,7 +687,8 @@ public class HttpExchange } /** - * @param stream the request content as a stream + * @param stream + * the request content as a stream */ public void setRequestContentSource(InputStream stream) { @@ -708,7 +746,8 @@ public class HttpExchange } /** - * @param retryStatus whether a retry will be attempted or not + * @param retryStatus + * whether a retry will be attempted or not */ public void setRetryStatus(boolean retryStatus) { @@ -716,13 +755,10 @@ public class HttpExchange } /** - * Initiates the cancelling of this exchange. - * The status of the exchange is set to {@link #STATUS_CANCELLING}. - * Cancelling the exchange is an asynchronous operation with respect to the request/response, - * and as such checking the request/response status of a cancelled exchange may return undefined results - * (for example it may have only some of the response headers being sent by the server). - * The cancelling of the exchange is completed when the exchange status (see {@link #getStatus()}) is - * {@link #STATUS_CANCELLED}, and this can be waited using {@link #waitForDone()}. + * Initiates the cancelling of this exchange. The status of the exchange is set to {@link #STATUS_CANCELLING}. Cancelling the exchange is an asynchronous + * operation with respect to the request/response, and as such checking the request/response status of a cancelled exchange may return undefined results + * (for example it may have only some of the response headers being sent by the server). The cancelling of the exchange is completed when the exchange + * status (see {@link #getStatus()}) is {@link #STATUS_CANCELLED}, and this can be waited using {@link #waitForDone()}. */ public void cancel() { @@ -732,10 +768,10 @@ public class HttpExchange private void done() { - synchronized(this) + synchronized (this) { disassociate(); - _onDone=true; + _onDone = true; notifyAll(); } } @@ -764,8 +800,8 @@ public class HttpExchange void associate(HttpConnection connection) { - if (connection.getEndPoint().getLocalHost() != null) - _localAddress = new Address(connection.getEndPoint().getLocalHost(), connection.getEndPoint().getLocalPort()); + if (connection.getEndPoint().getLocalHost() != null) + _localAddress = new Address(connection.getEndPoint().getLocalHost(),connection.getEndPoint().getLocalPort()); _connection = connection; if (getStatus() == STATUS_CANCELLING) @@ -789,33 +825,60 @@ public class HttpExchange public static String toState(int s) { String state; - switch(s) + switch (s) { - case STATUS_START: state="START"; break; - case STATUS_WAITING_FOR_CONNECTION: state="CONNECTING"; break; - case STATUS_WAITING_FOR_COMMIT: state="CONNECTED"; break; - case STATUS_SENDING_REQUEST: state="SENDING"; break; - case STATUS_WAITING_FOR_RESPONSE: state="WAITING"; break; - case STATUS_PARSING_HEADERS: state="HEADERS"; break; - case STATUS_PARSING_CONTENT: state="CONTENT"; break; - case STATUS_COMPLETED: state="COMPLETED"; break; - case STATUS_EXPIRED: state="EXPIRED"; break; - case STATUS_EXCEPTED: state="EXCEPTED"; break; - case STATUS_CANCELLING: state="CANCELLING"; break; - case STATUS_CANCELLED: state="CANCELLED"; break; - default: state="UNKNOWN"; + case STATUS_START: + state = "START"; + break; + case STATUS_WAITING_FOR_CONNECTION: + state = "CONNECTING"; + break; + case STATUS_WAITING_FOR_COMMIT: + state = "CONNECTED"; + break; + case STATUS_SENDING_REQUEST: + state = "SENDING"; + break; + case STATUS_WAITING_FOR_RESPONSE: + state = "WAITING"; + break; + case STATUS_PARSING_HEADERS: + state = "HEADERS"; + break; + case STATUS_PARSING_CONTENT: + state = "CONTENT"; + break; + case STATUS_COMPLETED: + state = "COMPLETED"; + break; + case STATUS_EXPIRED: + state = "EXPIRED"; + break; + case STATUS_EXCEPTED: + state = "EXCEPTED"; + break; + case STATUS_CANCELLING: + state = "CANCELLING"; + break; + case STATUS_CANCELLED: + state = "CANCELLED"; + break; + default: + state = "UNKNOWN"; } return state; } - + @Override public String toString() { String state=toState(getStatus()); long now=System.currentTimeMillis(); long forMs = now -_lastStateChange; - String s= String.format("%s@%x=%s//%s%s#%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,state,forMs); - if (getStatus()>=STATUS_SENDING_REQUEST) + String s= _lastState>=0 + ?String.format("%s@%x=%s//%s%s#%s(%dms)->%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,toState(_lastState),_lastStatePeriod,state,forMs) + :String.format("%s@%x=%s//%s%s#%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,state,forMs); + if (getStatus()>=STATUS_SENDING_REQUEST && _sent>0) s+="sent="+(now-_sent)+"ms"; return s; } @@ -828,79 +891,93 @@ public class HttpExchange } /** - * Callback called when the request headers have been sent to the server. - * This implementation does nothing. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the request headers have been sent to the server. This implementation does nothing. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onRequestCommitted() throws IOException { } /** - * Callback called when the request and its body have been sent to the server. - * This implementation does nothing. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the request and its body have been sent to the server. This implementation does nothing. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onRequestComplete() throws IOException { } /** - * Callback called when a response status line has been received from the server. - * This implementation does nothing. - * @param version the HTTP version - * @param status the HTTP status code - * @param reason the HTTP status reason string - * @throws IOException allowed to be thrown by overriding code + * Callback called when a response status line has been received from the server. This implementation does nothing. + * + * @param version + * the HTTP version + * @param status + * the HTTP status code + * @param reason + * the HTTP status reason string + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException { } /** - * Callback called for each response header received from the server. - * This implementation does nothing. - * @param name the header name - * @param value the header value - * @throws IOException allowed to be thrown by overriding code + * Callback called for each response header received from the server. This implementation does nothing. + * + * @param name + * the header name + * @param value + * the header value + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseHeader(Buffer name, Buffer value) throws IOException { } /** - * Callback called when the response headers have been completely received from the server. - * This implementation does nothing. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the response headers have been completely received from the server. This implementation does nothing. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseHeaderComplete() throws IOException { } /** - * Callback called for each chunk of the response content received from the server. - * This implementation does nothing. - * @param content the buffer holding the content chunk - * @throws IOException allowed to be thrown by overriding code + * Callback called for each chunk of the response content received from the server. This implementation does nothing. + * + * @param content + * the buffer holding the content chunk + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseContent(Buffer content) throws IOException { } /** - * Callback called when the entire response has been received from the server - * This implementation does nothing. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the entire response has been received from the server This implementation does nothing. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onResponseComplete() throws IOException { } /** - * Callback called when an exception was thrown during an attempt to establish the connection - * with the server (for example the server is not listening). + * Callback called when an exception was thrown during an attempt to establish the connection with the server (for example the server is not listening). * This implementation logs a warning. - * @param x the exception thrown attempting to establish the connection with the server + * + * @param x + * the exception thrown attempting to establish the connection with the server */ protected void onConnectionFailed(Throwable x) { @@ -908,9 +985,10 @@ public class HttpExchange } /** - * Callback called when any other exception occurs during the handling of this exchange. - * This implementation logs a warning. - * @param x the exception thrown during the handling of this exchange + * Callback called when any other exception occurs during the handling of this exchange. This implementation logs a warning. + * + * @param x + * the exception thrown during the handling of this exchange */ protected void onException(Throwable x) { @@ -918,8 +996,7 @@ public class HttpExchange } /** - * Callback called when no response has been received within the timeout. - * This implementation logs a warning. + * Callback called when no response has been received within the timeout. This implementation logs a warning. */ protected void onExpire() { @@ -927,9 +1004,10 @@ public class HttpExchange } /** - * Callback called when the request is retried (due to failures or authentication). - * Implementations must reset any consumable content that needs to be sent. - * @throws IOException allowed to be thrown by overriding code + * Callback called when the request is retried (due to failures or authentication). Implementations must reset any consumable content that needs to be sent. + * + * @throws IOException + * allowed to be thrown by overriding code */ protected void onRetry() throws IOException { @@ -948,8 +1026,7 @@ public class HttpExchange } /** - * @return true if the exchange should have listeners configured for it by the destination, - * false if this is being managed elsewhere + * @return true if the exchange should have listeners configured for it by the destination, false if this is being managed elsewhere * @see #setConfigureListeners(boolean) */ public boolean configureListeners() @@ -958,7 +1035,8 @@ public class HttpExchange } /** - * @param autoConfigure whether the listeners are configured by the destination or elsewhere + * @param autoConfigure + * whether the listeners are configured by the destination or elsewhere */ public void setConfigureListeners(boolean autoConfigure) { @@ -981,7 +1059,7 @@ public class HttpExchange HttpClient httpClient = destination.getHttpClient(); long timeout = getTimeout(); if (timeout > 0) - httpClient.schedule(_timeoutTask, timeout); + httpClient.schedule(_timeoutTask,timeout); else httpClient.schedule(_timeoutTask); } @@ -1045,7 +1123,7 @@ public class HttpExchange } finally { - synchronized(HttpExchange.this) + synchronized (HttpExchange.this) { _onRequestCompleteDone = true; // Member _onDone may already be true, for example @@ -1066,7 +1144,7 @@ public class HttpExchange } finally { - synchronized(HttpExchange.this) + synchronized (HttpExchange.this) { _onResponseCompleteDone = true; // Member _onDone may already be true, for example @@ -1101,7 +1179,7 @@ public class HttpExchange public void onRetry() { - HttpExchange.this.setRetryStatus( true ); + HttpExchange.this.setRetryStatus(true); try { HttpExchange.this.onRetry(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java index b46d2bcc4fa..00bc4595432 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectListener.java @@ -110,25 +110,49 @@ public class RedirectListener extends HttpEventListenerWrapper if (_location != null) { if (_location.indexOf("://")>0) + { _exchange.setURL(_location); + } else + { _exchange.setRequestURI(_location); + } // destination may have changed - HttpDestination destination=_destination.getHttpClient().getDestination(_exchange.getAddress(),HttpSchemes.HTTPS.equals(String.valueOf(_exchange.getScheme()))); - + boolean isHttps = HttpSchemes.HTTPS.equals(String.valueOf(_exchange.getScheme())); + HttpDestination destination=_destination.getHttpClient().getDestination(_exchange.getAddress(),isHttps); + if (_destination==destination) + { _destination.resend(_exchange); + } else { // unwrap to find ultimate listener. HttpEventListener listener=this; while(listener instanceof HttpEventListenerWrapper) + { listener=((HttpEventListenerWrapper)listener).getEventListener(); + } + //reset the listener _exchange.getEventListener().onRetry(); _exchange.reset(); _exchange.setEventListener(listener); + + // Set the new Host header + Address address = _exchange.getAddress(); + int port = address.getPort(); + StringBuilder hostHeader = new StringBuilder( 64 ); + hostHeader.append( address.getHost() ); + if( !( ( port == 80 && !isHttps ) || ( port == 443 && isHttps ) ) ) + { + hostHeader.append( ':' ); + hostHeader.append( port ); + } + + _exchange.setRequestHeader( HttpHeaders.HOST, hostHeader.toString() ); + destination.send(_exchange); } @@ -156,5 +180,28 @@ public class RedirectListener extends HttpEventListenerWrapper super.onRetry(); } -} + /** + * Delegate failed connection + */ + @Override + public void onConnectionFailed( Throwable ex ) + { + setDelegatingRequests(true); + setDelegatingResponses(true); + + super.onConnectionFailed( ex ); + } + + /** + * Delegate onException + */ + @Override + public void onException( Throwable ex ) + { + setDelegatingRequests(true); + setDelegatingResponses(true); + + super.onException( ex ); + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java index 333c4166ba4..fd37190a2b5 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java @@ -15,10 +15,13 @@ package org.eclipse.jetty.client; import java.io.IOException; import java.net.SocketTimeoutException; +import java.net.UnknownHostException; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.nio.channels.UnresolvedAddressException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; + import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; @@ -48,7 +51,6 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector private final HttpClient _httpClient; private final Manager _selectorManager=new Manager(); private final Map _connectingChannels = new ConcurrentHashMap(); - private SSLContext _sslContext; private Buffers _sslBuffers; /** @@ -89,31 +91,34 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector public void startConnection( HttpDestination destination ) throws IOException { + SocketChannel channel = null; try { - SocketChannel channel = SocketChannel.open(); + channel = SocketChannel.open(); Address address = destination.isProxied() ? destination.getProxy() : destination.getAddress(); channel.socket().setTcpNoDelay(true); if (_httpClient.isConnectBlocking()) { - channel.socket().connect(address.toSocketAddress(), _httpClient.getConnectTimeout()); - channel.configureBlocking(false); - _selectorManager.register( channel, destination ); + channel.socket().connect(address.toSocketAddress(), _httpClient.getConnectTimeout()); + channel.configureBlocking(false); + _selectorManager.register( channel, destination ); } else { - channel.configureBlocking( false ); - channel.connect(address.toSocketAddress()); - _selectorManager.register( channel, destination ); - ConnectTimeout connectTimeout = new ConnectTimeout(channel, destination); + channel.configureBlocking(false); + channel.connect(address.toSocketAddress()); + _selectorManager.register(channel,destination); + ConnectTimeout connectTimeout = new ConnectTimeout(channel,destination); _httpClient.schedule(connectTimeout,_httpClient.getConnectTimeout()); - _connectingChannels.put(channel, connectTimeout); + _connectingChannels.put(channel,connectTimeout); } } catch(IOException ex) { + if (channel != null) + channel.close(); destination.onConnectionFailed(ex); } } @@ -194,19 +199,16 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector private synchronized SSLEngine newSslEngine(SocketChannel channel) throws IOException { SslContextFactory sslContextFactory = _httpClient.getSslContextFactory(); - if (_sslContext == null) - _sslContext = sslContextFactory.getSslContext(); - SSLEngine sslEngine; - if (channel != null && sslContextFactory.isSessionCachingEnabled()) + if (channel != null) { String peerHost = channel.socket().getInetAddress().getHostAddress(); int peerPort = channel.socket().getPort(); - sslEngine = _sslContext.createSSLEngine(peerHost, peerPort); + sslEngine = sslContextFactory.newSslEngine(peerHost, peerPort); } else { - sslEngine = _sslContext.createSSLEngine(); + sslEngine = sslContextFactory.newSslEngine(); } sslEngine.setUseClientMode(true); sslEngine.beginHandshake(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java b/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java index 226cd644ffd..031c0cb5bb3 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/SocketConnector.java @@ -45,18 +45,9 @@ class SocketConnector extends AbstractLifeCycle implements HttpClient.Connector public void startConnection(final HttpDestination destination) throws IOException { - Socket socket=null; - - if ( destination.isSecure() ) - { - SSLContext sslContext = _httpClient.getSSLContext(); - socket = sslContext.getSocketFactory().createSocket(); - } - else - { - LOG.debug("Using Regular Socket"); - socket = SocketFactory.getDefault().createSocket(); - } + Socket socket= destination.isSecure() + ?_httpClient.getSslContextFactory().newSslSocket() + :SocketFactory.getDefault().createSocket(); socket.setSoTimeout(0); socket.setTcpNoDelay(true); @@ -97,6 +88,17 @@ class SocketConnector extends AbstractLifeCycle implements HttpClient.Connector destination.onException(e); } } + finally + { + try + { + destination.returnConnection(connection,true); + } + catch (IOException e) + { + LOG.debug(e); + } + } } }); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java index 2cd06974593..9f5bbaa95cb 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractConnectionTest.java @@ -14,9 +14,9 @@ package org.eclipse.jetty.client; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; @@ -25,6 +25,9 @@ import java.util.concurrent.TimeUnit; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * @version $Revision$ $Date$ */ @@ -37,7 +40,7 @@ public abstract class AbstractConnectionTest // httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); return httpClient; } - + @Test public void testServerClosedConnection() throws Exception { @@ -57,6 +60,19 @@ public abstract class AbstractConnectionTest httpClient.send(exchange); Socket remote = serverSocket.accept(); + + // HttpClient.send() above is async, so if we write the response immediately + // there is a chance that it arrives before the request is being sent, so we + // read the request before sending the response to avoid the race + InputStream input = remote.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line; + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + OutputStream output = remote.getOutputStream(); output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); output.write("Content-Length: 0\r\n".getBytes("UTF-8")); @@ -80,6 +96,15 @@ public abstract class AbstractConnectionTest httpClient.send(exchange); remote = serverSocket.accept(); + + input = remote.getInputStream(); + reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + output = remote.getOutputStream(); output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); output.write("Content-Length: 0\r\n".getBytes("UTF-8")); @@ -94,6 +119,105 @@ public abstract class AbstractConnectionTest } } + @Test + public void testServerClosedIncomplete() throws Exception + { + ServerSocket serverSocket = new ServerSocket(); + serverSocket.bind(null); + int port=serverSocket.getLocalPort(); + + HttpClient httpClient = newHttpClient(); + httpClient.setMaxConnectionsPerAddress(1); + httpClient.start(); + try + { + CountDownLatch latch = new CountDownLatch(1); + HttpExchange exchange = new ConnectionExchange(latch); + exchange.setAddress(new Address("localhost", port)); + exchange.setRequestURI("/"); + httpClient.send(exchange); + + Socket remote = serverSocket.accept(); + + // HttpClient.send() above is async, so if we write the response immediately + // there is a chance that it arrives before the request is being sent, so we + // read the request before sending the response to avoid the race + InputStream input = remote.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line; + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + + OutputStream output = remote.getOutputStream(); + output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); + output.write("Content-Length: 10\r\n".getBytes("UTF-8")); + output.write("\r\n".getBytes("UTF-8")); + output.flush(); + + remote.close(); + + assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone()); + + } + finally + { + httpClient.stop(); + } + } + + @Test + public void testServerHalfClosedIncomplete() throws Exception + { + ServerSocket serverSocket = new ServerSocket(); + serverSocket.bind(null); + int port=serverSocket.getLocalPort(); + + HttpClient httpClient = newHttpClient(); + httpClient.setIdleTimeout(10000); + httpClient.setMaxConnectionsPerAddress(1); + httpClient.start(); + try + { + CountDownLatch latch = new CountDownLatch(1); + HttpExchange exchange = new ConnectionExchange(latch); + exchange.setAddress(new Address("localhost", port)); + exchange.setRequestURI("/"); + httpClient.send(exchange); + + Socket remote = serverSocket.accept(); + + // HttpClient.send() above is async, so if we write the response immediately + // there is a chance that it arrives before the request is being sent, so we + // read the request before sending the response to avoid the race + InputStream input = remote.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line; + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + + OutputStream output = remote.getOutputStream(); + output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); + output.write("Content-Length: 10\r\n".getBytes("UTF-8")); + output.write("\r\n".getBytes("UTF-8")); + output.flush(); + + remote.shutdownOutput(); + + assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone()); + + } + finally + { + httpClient.stop(); + } + } + @Test public void testConnectionFailed() throws Exception { @@ -262,16 +386,16 @@ public abstract class AbstractConnectionTest } } - private class ConnectionExchange extends HttpExchange + protected class ConnectionExchange extends HttpExchange { private final CountDownLatch latch; - private ConnectionExchange() + protected ConnectionExchange() { this.latch = null; } - private ConnectionExchange(CountDownLatch latch) + protected ConnectionExchange(CountDownLatch latch) { this.latch = latch; } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java index cb5812780a2..c1d5c3da2c1 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java @@ -14,9 +14,7 @@ package org.eclipse.jetty.client; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.io.IOException; import java.net.SocketTimeoutException; @@ -34,7 +32,6 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.StdErrLog; import org.junit.After; import org.junit.Before; @@ -45,8 +42,6 @@ import org.junit.Test; */ public abstract class AbstractHttpExchangeCancelTest { - private static final Logger LOG = Log.getLogger(AbstractHttpExchangeCancelTest.class); - private Server server; private Connector connector; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java index 75dc89a1ed3..7dbbefd7e5b 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSelectConnectionTest.java @@ -22,4 +22,12 @@ public class AsyncSelectConnectionTest extends AbstractConnectionTest httpClient.setConnectBlocking(false); return httpClient; } + + @Override + public void testServerHalfClosedIncomplete() throws Exception + { + super.testServerHalfClosedIncomplete(); + } + + } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java index f5ac43dc403..2c878e273d5 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AsyncSslHttpExchangeTest.java @@ -13,19 +13,23 @@ package org.eclipse.jetty.client; +import org.eclipse.jetty.client.helperClasses.AsyncSslServerAndClientCreator; +import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + public class AsyncSslHttpExchangeTest extends SslHttpExchangeTest { - @Override - public void setUp() throws Exception + private static ServerAndClientCreator serverAndClientCreator = new AsyncSslServerAndClientCreator(); + + @Before + public void setUpOnce() throws Exception { _scheme="https"; - startServer(); - _httpClient=new HttpClient(); - _httpClient.setIdleTimeout(2000); - _httpClient.setTimeout(2500); - _httpClient.setConnectTimeout(1000); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - _httpClient.setMaxConnectionsPerAddress(2); - _httpClient.start(); + _server = serverAndClientCreator.createServer(); + _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000); + _port = _server.getConnectors()[0].getLocalPort(); } + } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java index 445947e3534..27af22c21ed 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ExternalKeyStoreAsyncSslHttpExchangeTest.java @@ -13,28 +13,23 @@ package org.eclipse.jetty.client; -import java.io.FileInputStream; - -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.client.helperClasses.ExternalKeyStoreAsyncSslServerAndClientCreator; +import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator; +import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; public class ExternalKeyStoreAsyncSslHttpExchangeTest extends SslHttpExchangeTest { - @Override - public void setUp() throws Exception + private static ServerAndClientCreator serverAndClientCreator = new ExternalKeyStoreAsyncSslServerAndClientCreator(); + + @Before + public void setUpOnce() throws Exception { - _scheme = "https"; - startServer(); - _httpClient = new HttpClient(); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - _httpClient.setMaxConnectionsPerAddress(2); - - String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); - - _httpClient.setKeyStoreInputStream(new FileInputStream(keystore)); - _httpClient.setKeyStorePassword("storepwd"); - _httpClient.setKeyManagerPassword("keypwd"); - _httpClient.start(); + _scheme="https"; + _server = serverAndClientCreator.createServer(); + _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000); + _port = _server.getConnectors()[0].getLocalPort(); } @Override diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAsserts.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAsserts.java new file mode 100644 index 00000000000..e0410057571 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAsserts.java @@ -0,0 +1,29 @@ +package org.eclipse.jetty.client; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.jetty.http.HttpFields; +import org.junit.Assert; + +public final class HttpAsserts +{ + public static void assertContainsHeaderKey(String expectedKey, HttpFields headers) + { + if (headers.containsKey(expectedKey)) + { + return; + } + List names = Collections.list(headers.getFieldNames()); + StringBuilder err = new StringBuilder(); + err.append("Missing expected header key [").append(expectedKey); + err.append("] (of ").append(names.size()).append(" header fields)"); + for (int i = 0; i < names.size(); i++) + { + String value = headers.getStringField(names.get(i)); + err.append("\n").append(i).append("] ").append(names.get(i)); + err.append(": ").append(value); + } + Assert.fail(err.toString()); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java index 09de4467204..3b84bfec975 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpExchangeTest.java @@ -13,14 +13,9 @@ package org.eclipse.jetty.client; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.matchers.JUnitMatchers.containsString; +import static org.junit.Assert.*; +import static org.junit.matchers.JUnitMatchers.*; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -30,13 +25,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - +import org.eclipse.jetty.client.helperClasses.HttpServerAndClientCreator; +import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator; import org.eclipse.jetty.client.security.ProxyAuthorization; import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.Buffer; @@ -44,17 +36,13 @@ import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.nio.DirectNIOBuffer; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.toolchain.test.Stress; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; /* ------------------------------------------------------------ */ @@ -63,37 +51,44 @@ import org.junit.Test; */ public class HttpExchangeTest { - private static final Logger LOG = Log.getLogger(HttpExchangeTest.class); - - protected int _maxConnectionsPerAddress = 2; - protected String _scheme = "http"; - protected Server _server; - protected int _port; - protected HttpClient _httpClient; - protected Connector _connector; - protected AtomicInteger _count = new AtomicInteger(); + final static boolean verbose=false; + protected static int _maxConnectionsPerAddress = 2; + protected static String _scheme = "http"; + protected static Server _server; + protected static int _port; + protected static HttpClient _httpClient; + protected static AtomicInteger _count = new AtomicInteger(); + protected static ServerAndClientCreator serverAndClientCreator = new HttpServerAndClientCreator(); + + protected static URI getBaseURI() + { + return URI.create(_scheme + "://localhost:" + _port + "/"); + } /* ------------------------------------------------------------ */ + // TODO work out why BeforeClass does not work here? @Before - public void setUp() throws Exception + public void setUpOnce() throws Exception { - startServer(); - _httpClient=new HttpClient(); - _httpClient.setIdleTimeout(3000); - _httpClient.setTimeout(3500); - _httpClient.setConnectTimeout(2000); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - _httpClient.setMaxConnectionsPerAddress(_maxConnectionsPerAddress); - _httpClient.start(); + _scheme = "http"; + _server = serverAndClientCreator.createServer(); + _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000); + _port = _server.getConnectors()[0].getLocalPort(); } /* ------------------------------------------------------------ */ @After - public void tearDown() throws Exception + public void tearDownOnce() throws Exception { _httpClient.stop(); - Thread.sleep(500); - stopServer(); + long startTime = System.currentTimeMillis(); + while (!_httpClient.getState().equals(AbstractLifeCycle.STOPPED)) + { + if (System.currentTimeMillis() - startTime > 1000) + break; + Thread.sleep(5); + } + _server.stop(); } /* ------------------------------------------------------------ */ @@ -110,7 +105,9 @@ public class HttpExchangeTest { sender(1,false); sender(1,true); - + sender(10,false); + sender(10,true); + if (Stress.isEnabled()) { sender(100,false); @@ -118,67 +115,72 @@ public class HttpExchangeTest sender(10000,false); sender(10000,true); } - else - { - sender(10,false); - sender(10,true); - } } /* ------------------------------------------------------------ */ /** * Test sending data through the exchange. - * + * * @throws IOException */ - public void sender(final int nb,final boolean close) throws Exception + public void sender(final int nb, final boolean close) throws Exception { _count.set(0); - final CountDownLatch complete=new CountDownLatch(nb); - final CountDownLatch latch=new CountDownLatch(nb); + final CountDownLatch complete = new CountDownLatch(nb); + final AtomicInteger allcontent = new AtomicInteger(nb); HttpExchange[] httpExchange = new HttpExchange[nb]; - long start=System.currentTimeMillis(); - for (int i=0; i "+len); } /* ------------------------------------------------------------ */ @Override protected void onResponseComplete() { - result="complete"; - if (len==2009) - latch.countDown(); + if (verbose) + System.err.println("] == "+len+" "+complete.getCount()+"/"+nb); + result = "complete"; + if (len == 2009) + allcontent.decrementAndGet(); else - { - System.err.println(n+" ONLY "+len); - } + System.err.println(n + " ONLY " + len+ "/2009"); complete.countDown(); } @@ -207,9 +211,11 @@ public class HttpExchangeTest @Override protected void onConnectionFailed(Throwable ex) { + if (verbose) + System.err.println("] "+ex); complete.countDown(); - result="failed"; - System.err.println(n+" FAILED "+ex); + result = "failed"; + System.err.println(n + " FAILED " + ex); super.onConnectionFailed(ex); } @@ -217,9 +223,11 @@ public class HttpExchangeTest @Override protected void onException(Throwable ex) { + if (verbose) + System.err.println("] "+ex); complete.countDown(); - result="excepted"; - System.err.println(n+" EXCEPTED "+ex); + result = "excepted"; + System.err.println(n + " EXCEPTED " + ex); super.onException(ex); } @@ -227,9 +235,11 @@ public class HttpExchangeTest @Override protected void onExpire() { + if (verbose) + System.err.println("] expired"); complete.countDown(); - result="expired"; - System.err.println(n+" EXPIRED "+len); + result = "expired"; + System.err.println(n + " EXPIRED " + len); super.onExpire(); } @@ -237,29 +247,24 @@ public class HttpExchangeTest @Override public String toString() { - return n+" "+result+" "+len; + return n+"/"+result+"/"+len+"/"+super.toString(); } }; - httpExchange[n].setURL(_scheme+"://localhost:"+_port+"/"+n); + httpExchange[n].setURI(getBaseURI().resolve("/" + n)); httpExchange[n].addRequestHeader("arbitrary","value"); if (close) httpExchange[n].setRequestHeader("Connection","close"); _httpClient.send(httpExchange[n]); } - - assertTrue(complete.await(45,TimeUnit.SECONDS)); - - long elapsed=System.currentTimeMillis()-start; - // make windows-friendly ... System.currentTimeMillis() on windows is dope! - /* - if(elapsed>0) - System.err.println(nb+"/"+_count+" c="+close+" rate="+(nb*1000/elapsed)); - */ + if (!complete.await(2,TimeUnit.SECONDS)) + System.err.println(_httpClient.dump()); - assertEquals("nb="+nb+" close="+close,0,latch.getCount()); + assertTrue(complete.await(20,TimeUnit.SECONDS)); + + assertEquals("nb="+nb+" close="+close,0,allcontent.get()); } /* ------------------------------------------------------------ */ @@ -269,7 +274,7 @@ public class HttpExchangeTest for (int i=0;i<20;i++) { ContentExchange httpExchange=new ContentExchange(); - httpExchange.setURI(new URI(_scheme, null, "localhost", _port, null, null, null)); + httpExchange.setURI(getBaseURI()); httpExchange.setMethod(HttpMethods.POST); httpExchange.setRequestContent(new ByteArrayBuffer("")); _httpClient.send(httpExchange); @@ -288,12 +293,14 @@ public class HttpExchangeTest for (int i=0;i<10;i++) { ContentExchange httpExchange=new ContentExchange(); - httpExchange.setURI(new URI(_scheme, null, "localhost", _port, "/", "i="+i, null)); + URI uri = getBaseURI().resolve("?i=" + i); + httpExchange.setURI(uri); httpExchange.setMethod(HttpMethods.GET); _httpClient.send(httpExchange); int status = httpExchange.waitForDone(); //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED); String result=httpExchange.getResponseContent(); + assertNotNull("Should have received response content", result); assertEquals("i="+i,0,result.indexOf("")); assertEquals("i="+i,result.length()-10,result.indexOf("")); assertEquals(HttpExchange.STATUS_COMPLETED, status); @@ -308,17 +315,16 @@ public class HttpExchangeTest for (int i=0;i<10;i++) { ContentExchange httpExchange=new ContentExchange(); - httpExchange.setURL(_scheme+"://localhost:"+_port+"/?i="+i); + URI uri = getBaseURI().resolve("?i=" + i); + httpExchange.setURI(uri); httpExchange.setMethod(HttpMethods.GET); _httpClient.send(httpExchange); int status = httpExchange.waitForDone(); assertNotNull(httpExchange.getLocalAddress()); - //System.out.println("Local Address: " + httpExchange.getLocalAddress()); - - //httpExchange.waitForStatus(HttpExchange.STATUS_COMPLETED); String result=httpExchange.getResponseContent(); + assertNotNull("Should have received response content", result); assertEquals("i="+i,0,result.indexOf("")); assertEquals("i="+i,result.length()-10,result.indexOf("")); assertEquals(HttpExchange.STATUS_COMPLETED, status); @@ -355,7 +361,7 @@ public class HttpExchangeTest throwable.set(x); } }; - httpExchange.setURL(_scheme+"://localhost:"+_port+"/"); + httpExchange.setURI(getBaseURI()); httpExchange.setMethod("SLEEP"); _httpClient.send(httpExchange); new Thread() @@ -374,6 +380,7 @@ public class HttpExchangeTest System.err.println(throwable.get()); assertTrue(throwable.get().toString().indexOf("close")>=0); assertEquals(HttpExchange.STATUS_EXCEPTED, status); + _httpClient.start(); } /* ------------------------------------------------------------ */ @@ -381,7 +388,54 @@ public class HttpExchangeTest public void testBigPostWithContentExchange() throws Exception { int size =32; - ContentExchange httpExchange=new ContentExchange(); + ContentExchange httpExchange=new ContentExchange() + { + int total; + + @Override + protected synchronized void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + if (verbose) + System.err.println("] "+version+" "+status+" "+reason); + super.onResponseStatus(version,status,reason); + } + + @Override + protected synchronized void onResponseHeader(Buffer name, Buffer value) throws IOException + { + if (verbose) + System.err.println("] "+name+": "+value); + super.onResponseHeader(name,value); + } + + @Override + protected synchronized void onResponseContent(Buffer content) throws IOException + { + if (verbose) + { + total+=content.length(); + System.err.println("] "+content.length()+" -> "+total); + } + super.onResponseContent(content); + } + + @Override + protected void onRequestComplete() throws IOException + { + if (verbose) + System.err.println("] =="); + super.onRequestComplete(); + } + + @Override + protected void onResponseHeaderComplete() throws IOException + { + if (verbose) + System.err.println("] --"); + super.onResponseHeaderComplete(); + } + + }; Buffer babuf = new ByteArrayBuffer(size*36*1024); Buffer niobuf = new DirectNIOBuffer(size*36*1024); @@ -394,28 +448,27 @@ public class HttpExchangeTest niobuf.put(bytes); } - httpExchange.setURL(_scheme+"://localhost:"+_port+"/"); + httpExchange.setURI(getBaseURI()); httpExchange.setMethod(HttpMethods.POST); httpExchange.setRequestContentType("application/data"); httpExchange.setRequestContent(babuf); _httpClient.send(httpExchange); int status = httpExchange.waitForDone(); - assertEquals(HttpExchange.STATUS_COMPLETED,status); String result=httpExchange.getResponseContent(); assertEquals(babuf.length(),result.length()); httpExchange.reset(); - httpExchange.setURL(_scheme+"://localhost:"+_port+"/"); + httpExchange.setURI(getBaseURI()); httpExchange.setMethod(HttpMethods.POST); httpExchange.setRequestContentType("application/data"); httpExchange.setRequestContent(niobuf); _httpClient.send(httpExchange); status = httpExchange.waitForDone(); + assertEquals(HttpExchange.STATUS_COMPLETED, status); result=httpExchange.getResponseContent(); assertEquals(niobuf.length(),result.length()); - assertEquals(HttpExchange.STATUS_COMPLETED, status); } /* ------------------------------------------------------------ */ @@ -426,7 +479,7 @@ public class HttpExchangeTest { }; - httpExchange.setURL(_scheme+"://localhost:"+_port); + httpExchange.setURI(getBaseURI()); httpExchange.setMethod(HttpMethods.POST); final String data="012345678901234567890123456789012345678901234567890123456789"; @@ -447,7 +500,7 @@ public class HttpExchangeTest @Override public int read(byte[] b, int off, int len) throws IOException { - if (_index>=data.length()) + if (_index >= data.length()) return -1; try @@ -458,28 +511,28 @@ public class HttpExchangeTest { e.printStackTrace(); } - - int l=0; - while (l<5 && _index=0) + while ((len = in.read(buffer)) >= 0) { out.write(buffer,0,len); } } catch (EofException e) { - System.err.println("HttpExchangeTest#copyStream: "+e); + System.err.println("HttpExchangeTest#copyStream: " + e); } catch (IOException e) { e.printStackTrace(); } } - - /* ------------------------------------------------------------ */ - protected void newServer() throws Exception - { - _server=new Server(); - _server.setGracefulShutdown(500); - _connector=new SelectChannelConnector(); - - _connector.setMaxIdleTime(3000000); - - _connector.setPort(0); - _server.setConnectors(new Connector[] { _connector }); - } - - /* ------------------------------------------------------------ */ - protected void startServer() throws Exception - { - newServer(); - _server.setHandler(new AbstractHandler() - { - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - int i=0; - try - { - baseRequest.setHandled(true); - response.setStatus(200); - _count.incrementAndGet(); - - if (request.getServerName().equals("jetty.eclipse.org")) - { - response.getOutputStream().println("Proxy request: "+request.getRequestURL()); - response.getOutputStream().println(request.getHeader(HttpHeaders.PROXY_AUTHORIZATION)); - } - else if (request.getMethod().equalsIgnoreCase("GET")) - { - response.getOutputStream().println(""); - for (; i<100; i++) - { - response.getOutputStream().println(" "+i+""); - } - else if (request.getMethod().equalsIgnoreCase("OPTIONS")) - { - if ("*".equals(target)) - { - response.setContentLength(0); - response.setHeader("Allow","GET,HEAD,POST,PUT,DELETE,MOVE,OPTIONS,TRACE"); - } - } - else if (request.getMethod().equalsIgnoreCase("SLEEP")) - { - Thread.sleep(10000); - } - else - { - response.setContentType(request.getContentType()); - int size=request.getContentLength(); - ByteArrayOutputStream bout = new ByteArrayOutputStream(size>0?size:32768); - IO.copy(request.getInputStream(),bout); - response.getOutputStream().write(bout.toByteArray()); - } - } - catch(InterruptedException e) - { - LOG.debug(e); - } - catch(IOException e) - { - e.printStackTrace(); - throw e; - } - catch(Throwable e) - { - e.printStackTrace(); - throw new ServletException(e); - } - finally - { - } - } - }); - _server.start(); - _port=_connector.getLocalPort(); - } - - /* ------------------------------------------------------------ */ - private void stopServer() throws Exception - { - _server.stop(); - } - } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java index 2970de67aa9..4438463ce2d 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SocketConnectionTest.java @@ -13,6 +13,16 @@ package org.eclipse.jetty.client; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; + +import static org.junit.Assert.assertEquals; + public class SocketConnectionTest extends AbstractConnectionTest { protected HttpClient newHttpClient() @@ -21,10 +31,61 @@ public class SocketConnectionTest extends AbstractConnectionTest httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); return httpClient; } - + @Override - public void testServerClosedConnection() + public void testServerClosedConnection() throws Exception { - // TODO work out why this does not work + // Differently from the SelectConnector, the SocketConnector cannot detect server closes. + // Therefore, upon a second send, the exchange will fail. + // Applications needs to retry it explicitly. + + ServerSocket serverSocket = new ServerSocket(); + serverSocket.bind(null); + int port=serverSocket.getLocalPort(); + + HttpClient httpClient = this.newHttpClient(); + httpClient.setMaxConnectionsPerAddress(1); + httpClient.start(); + try + { + CountDownLatch latch = new CountDownLatch(1); + HttpExchange exchange = new ConnectionExchange(latch); + exchange.setAddress(new Address("localhost", port)); + exchange.setRequestURI("/"); + httpClient.send(exchange); + + Socket remote = serverSocket.accept(); + + // HttpClient.send() above is async, so if we write the response immediately + // there is a chance that it arrives before the request is being sent, so we + // read the request before sending the response to avoid the race + InputStream input = remote.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + String line; + while ((line = reader.readLine()) != null) + { + if (line.length() == 0) + break; + } + + OutputStream output = remote.getOutputStream(); + output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8")); + output.write("Content-Length: 0\r\n".getBytes("UTF-8")); + output.write("\r\n".getBytes("UTF-8")); + output.flush(); + + assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone()); + + remote.close(); + + exchange.reset(); + httpClient.send(exchange); + + assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone()); + } + finally + { + httpClient.stop(); + } } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java index 6ea97bc5aa4..8ec13c1ef21 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslHttpExchangeTest.java @@ -13,67 +13,36 @@ package org.eclipse.jetty.client; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.*; -import org.eclipse.jetty.http.ssl.SslContextFactory; +import org.eclipse.jetty.client.helperClasses.ServerAndClientCreator; +import org.eclipse.jetty.client.helperClasses.SslServerAndClientCreator; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ssl.SslSocketConnector; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.Stress; import org.junit.Assume; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; /** * Functional testing for HttpExchange. - * - * - * */ public class SslHttpExchangeTest extends HttpExchangeTest { + protected static ServerAndClientCreator serverAndClientCreator = new SslServerAndClientCreator(); + /* ------------------------------------------------------------ */ @Before - @Override - public void setUp() throws Exception + public void setUpOnce() throws Exception { _scheme="https"; - startServer(); - _httpClient=new HttpClient(); - _httpClient.setIdleTimeout(2000); - _httpClient.setTimeout(2500); - _httpClient.setConnectTimeout(1000); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - _httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); - _httpClient.setMaxConnectionsPerAddress(2); - _httpClient.start(); + _server = serverAndClientCreator.createServer(); + _httpClient = serverAndClientCreator.createClient(3000L,3500L,2000); + Connector[] connectors = _server.getConnectors(); + _port = connectors[0].getLocalPort(); } - /* ------------------------------------------------------------ */ - @Override - protected void newServer() - { - _server = new Server(); - //SslSelectChannelConnector connector = new SslSelectChannelConnector(); - SslSocketConnector connector = new SslSocketConnector(); - - String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); - - connector.setPort(0); - SslContextFactory cf = connector.getSslContextFactory(); - cf.setKeyStore(keystore); - cf.setKeyStorePassword("storepwd"); - cf.setKeyManagerPassword("keypwd"); - connector.setAllowRenegotiate(true); - - _server.setConnectors(new Connector[] - { connector }); - _connector=connector; - } - /* ------------------------------------------------------------ */ private void IgnoreTestOnBuggyIBM() { diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AbstractSslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AbstractSslServerAndClientCreator.java new file mode 100644 index 00000000000..d7df2a39168 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AbstractSslServerAndClientCreator.java @@ -0,0 +1,57 @@ +// ======================================================================== +// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.client.helperClasses; + +import org.eclipse.jetty.http.ssl.SslContextFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; +import org.eclipse.jetty.server.ssl.SslSocketConnector; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + + +/* ------------------------------------------------------------ */ +/** + */ +public abstract class AbstractSslServerAndClientCreator implements ServerAndClientCreator +{ + private static final Logger LOG = Log.getLogger(AbstractSslServerAndClientCreator.class); + + /* ------------------------------------------------------------ */ + public Server createServer() throws Exception + { + Server server = new Server(); + //SslSelectChannelConnector connector = new SslSelectChannelConnector(); + SslSocketConnector connector = new SslSocketConnector(); + + String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); + + connector.setPort(0); + SslContextFactory cf = connector.getSslContextFactory(); + cf.setKeyStore(keystore); + cf.setKeyStorePassword("storepwd"); + cf.setKeyManagerPassword("keypwd"); + connector.setAllowRenegotiate(true); + + server.setConnectors(new Connector[]{ connector }); + server.setHandler(new GenericServerHandler()); + server.start(); + return server; + } + + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AsyncSslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AsyncSslServerAndClientCreator.java new file mode 100644 index 00000000000..6d08d216f90 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/AsyncSslServerAndClientCreator.java @@ -0,0 +1,26 @@ +package org.eclipse.jetty.client.helperClasses; + +import java.io.FileInputStream; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; + +public class AsyncSslServerAndClientCreator extends AbstractSslServerAndClientCreator implements ServerAndClientCreator +{ + + /* ------------------------------------------------------------ */ + public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + httpClient.setMaxConnectionsPerAddress(2); + + String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); + httpClient.setKeyStoreInputStream(new FileInputStream(keystore)); + httpClient.setKeyStorePassword("storepwd"); + httpClient.setKeyManagerPassword("keypwd"); + httpClient.start(); + return httpClient; + } + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ExternalKeyStoreAsyncSslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ExternalKeyStoreAsyncSslServerAndClientCreator.java new file mode 100644 index 00000000000..584f4b1a64b --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ExternalKeyStoreAsyncSslServerAndClientCreator.java @@ -0,0 +1,27 @@ +package org.eclipse.jetty.client.helperClasses; + +import java.io.FileInputStream; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; + +public class ExternalKeyStoreAsyncSslServerAndClientCreator extends AbstractSslServerAndClientCreator implements ServerAndClientCreator +{ + + public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + httpClient.setMaxConnectionsPerAddress(2); + + String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); + + httpClient.setKeyStoreInputStream(new FileInputStream(keystore)); + httpClient.setKeyStorePassword("storepwd"); + httpClient.setKeyManagerPassword("keypwd"); + httpClient.start(); + return httpClient; + } + + +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/GenericServerHandler.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/GenericServerHandler.java new file mode 100644 index 00000000000..82dbdb35c47 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/GenericServerHandler.java @@ -0,0 +1,84 @@ +package org.eclipse.jetty.client.helperClasses; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Generic Server Handler used for various client tests. + */ +public class GenericServerHandler extends AbstractHandler +{ + private static final Logger LOG = Log.getLogger(GenericServerHandler.class); + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + int i = 0; + try + { + baseRequest.setHandled(true); + response.setStatus(200); + + if (request.getServerName().equals("jetty.eclipse.org")) + { + response.getOutputStream().println("Proxy request: " + request.getRequestURL()); + response.getOutputStream().println(request.getHeader(HttpHeaders.PROXY_AUTHORIZATION)); + } + else if (request.getMethod().equalsIgnoreCase("GET")) + { + response.getOutputStream().println(""); + for (; i < 100; i++) + { + response.getOutputStream().println(" " + i + ""); + } + else if (request.getMethod().equalsIgnoreCase("OPTIONS")) + { + if ("*".equals(target)) + { + response.setContentLength(0); + response.setHeader("Allow","GET,HEAD,POST,PUT,DELETE,MOVE,OPTIONS,TRACE"); + } + } + else if (request.getMethod().equalsIgnoreCase("SLEEP")) + { + Thread.sleep(10000); + } + else + { + response.setContentType(request.getContentType()); + int size = request.getContentLength(); + ByteArrayOutputStream bout = new ByteArrayOutputStream(size > 0?size:32768); + IO.copy(request.getInputStream(),bout); + response.getOutputStream().write(bout.toByteArray()); + } + } + catch (InterruptedException e) + { + LOG.debug(e); + } + catch (IOException e) + { + LOG.warn(e); + throw e; + } + catch (Throwable e) + { + LOG.warn(e); + throw new ServletException(e); + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/HttpServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/HttpServerAndClientCreator.java new file mode 100644 index 00000000000..d9a2f1d3eef --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/HttpServerAndClientCreator.java @@ -0,0 +1,36 @@ +package org.eclipse.jetty.client.helperClasses; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.nio.SelectChannelConnector; + +public class HttpServerAndClientCreator implements ServerAndClientCreator +{ + public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setIdleTimeout(idleTimeout); + httpClient.setTimeout(timeout); + httpClient.setConnectTimeout(connectTimeout); + httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + httpClient.setMaxConnectionsPerAddress(2); + httpClient.start(); + return httpClient; + } + + public Server createServer() throws Exception + { + Server _server = new Server(); + _server.setGracefulShutdown(500); + Connector _connector = new SelectChannelConnector(); + + _connector.setMaxIdleTime(3000000); + + _connector.setPort(0); + _server.setConnectors(new Connector[]{ _connector }); + _server.setHandler(new GenericServerHandler()); + _server.start(); + return _server; + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ServerAndClientCreator.java new file mode 100644 index 00000000000..015006f6802 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/ServerAndClientCreator.java @@ -0,0 +1,11 @@ +package org.eclipse.jetty.client.helperClasses; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.server.Server; + +public interface ServerAndClientCreator +{ + Server createServer() throws Exception; + + HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception; +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/SslServerAndClientCreator.java b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/SslServerAndClientCreator.java new file mode 100644 index 00000000000..d0e0ad0a115 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/helperClasses/SslServerAndClientCreator.java @@ -0,0 +1,19 @@ +package org.eclipse.jetty.client.helperClasses; + +import org.eclipse.jetty.client.HttpClient; + +public class SslServerAndClientCreator extends AbstractSslServerAndClientCreator implements ServerAndClientCreator +{ + + public HttpClient createClient(long idleTimeout, long timeout, int connectTimeout) throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setIdleTimeout(idleTimeout); + httpClient.setTimeout(timeout); + httpClient.setConnectTimeout(connectTimeout); + httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET); + httpClient.setMaxConnectionsPerAddress(2); + httpClient.start(); + return httpClient; + } +} diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index 46cad3e98eb..e49d6d28c9c 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -155,157 +155,6 @@ maven-dependency-plugin - - unpack - generate-resources - - unpack - - - - - org.eclipse.jetty - jetty-rewrite - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-ajp - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - test-jetty-webapp - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-jmx - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-util - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-webapp - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-deploy - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-plus - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-server - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-security - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-policy - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-monitor - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-overlay-deployer - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - org.eclipse.jetty - jetty-annotations - ${project.version} - config - jar - true - ** - ${assembly-directory} - - - - copy generate-resources @@ -324,187 +173,6 @@ ${assembly-directory}/ VERSION.txt - - org.eclipse.jetty - jetty-util - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-io - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-http - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-server - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-security - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-servlet - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-servlets - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-xml - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-webapp - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - - org.eclipse.jetty - jetty-deploy - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-jmx - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-rewrite - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-ajp - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-annotations - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-client - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-jndi - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-policy - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-monitor - ${project.version} - jar - true - ** - ${assembly-directory}/lib/monitor - - - org.eclipse.jetty - jetty-plus - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-continuation - ${project.version} - jar - true - ** - ${assembly-directory}/lib - org.eclipse.jetty test-jetty-webapp @@ -525,36 +193,77 @@ ${assembly-directory} start.jar - - org.eclipse.jetty - jetty-overlay-deployer - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-overlay-deployer - ${project.version} - jar - true - ** - ${assembly-directory}/lib - - - org.eclipse.jetty - jetty-websocket - ${project.version} - jar - true - ** - ${assembly-directory}/lib - + + copy-lib-deps + generate-resources + + copy-dependencies + + + org.eclipse.jetty + jetty-start,jetty-monitor,jetty-jsp-2.1 + jar + ${assembly-directory}/lib + + + + copy-lib-jsp-deps + generate-resources + + copy-dependencies + + + org.eclipse.jetty,org.glassfish.web + jetty-jsp-2.1,jsp-impl + jar + ${assembly-directory}/lib/jsp + + + + copy-lib-monitor-deps + generate-resources + + copy-dependencies + + + org.eclipse.jetty + jetty-monitor + jar + true + ${assembly-directory}/lib/monitor + + + + unpack-config-deps + generate-resources + + unpack-dependencies + + + org.eclipse.jetty + config + false + ${assembly-directory} + + + + unpack-javadoc + generate-resources + + unpack-dependencies + + + + org.eclipse.jetty.aggregate + jetty-all + javadoc + true + ${assembly-directory}/javadoc + + @@ -663,12 +372,12 @@ org.eclipse.jetty - jetty-overlay-deployer + jetty-servlets ${project.version} org.eclipse.jetty - jetty-overlay-deployer + jetty-monitor ${project.version} @@ -676,5 +385,17 @@ jetty-websocket ${project.version} + + org.eclipse.jetty + jetty-overlay-deployer + ${project.version} + + + org.eclipse.jetty.aggregate + jetty-all + javadoc + jar + ${project.version} + diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java index 50f58ae63f1..6901fe1f63f 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java @@ -483,8 +483,13 @@ public abstract class AbstractGenerator implements Generator { if (close) _persistent=false; - if (!isCommitted()) + if (isCommitted()) { + LOG.debug("sendError on committed: {} {}",code,reason); + } + else + { + LOG.debug("sendError: {} {}",code,reason); setResponse(code, reason); if (content != null) { diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index aecc18b069c..572a0390379 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -4,15 +4,16 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.http; +import java.io.EOFException; import java.io.IOException; import org.eclipse.jetty.io.Buffer; @@ -67,7 +68,7 @@ public class HttpParser implements Parser private String _multiLineValue; private int _responseStatus; // If >0 then we are parsing a response private boolean _forceContentBuffer; - + /* ------------------------------------------------------------------------------- */ protected final View _contentView=new View(); // View of the content in the buffer for {@link Input} protected int _state=STATE_START; @@ -78,7 +79,7 @@ public class HttpParser implements Parser protected int _chunkLength; protected int _chunkPosition; private boolean _headResponse; - + /* ------------------------------------------------------------------------------- */ /** * Constructor. @@ -103,7 +104,7 @@ public class HttpParser implements Parser /* ------------------------------------------------------------------------------- */ /** * Constructor. - * @param buffers the buffers to use + * @param buffers the buffers to use * @param endp the endpoint * @param handler the even handler */ @@ -134,7 +135,7 @@ public class HttpParser implements Parser { _headResponse=head; } - + /* ------------------------------------------------------------------------------- */ public int getState() { @@ -170,7 +171,7 @@ public class HttpParser implements Parser { return isState(STATE_END); } - + /* ------------------------------------------------------------ */ public boolean isMoreInBuffer() throws IOException @@ -203,11 +204,11 @@ public class HttpParser implements Parser if (parseNext()<0) return; } - + /* ------------------------------------------------------------------------------- */ /** * Parse until END state. - * This method will parse any remaining content in the current buffer. It does not care about the + * This method will parse any remaining content in the current buffer. It does not care about the * {@link #getState current state} of the parser. * @see #parse * @see #parseNext @@ -216,7 +217,7 @@ public class HttpParser implements Parser { int progress = parseNext(); int total=progress>0?1:0; - + // continue parsing while (!isComplete() && _buffer!=null && _buffer.length()>0) { @@ -237,9 +238,9 @@ public class HttpParser implements Parser { int progress=0; - if (_state == STATE_END) + if (_state == STATE_END) return 0; - + if (_buffer==null) { if (_header == null) @@ -252,23 +253,33 @@ public class HttpParser implements Parser _tok0.setPutIndex(_tok0.getIndex()); _tok1.setPutIndex(_tok1.getIndex()); } - - + + if (_state == STATE_CONTENT && _contentPosition == _contentLength) { _state=STATE_END; _handler.messageComplete(_contentPosition); return 1; } - + int length=_buffer.length(); - + // Fill buffer if we can if (length == 0) { - long filled=fill(); - - if (filled < 0) + long filled=-1; + IOException ex=null; + try + { + filled=fill(); + } + catch(IOException e) + { + LOG.debug(this.toString(),e); + ex=e; + } + + if (filled < 0 || _endp.isInputShutdown()) { if (_headResponse && _state>STATE_END) { @@ -284,19 +295,25 @@ public class HttpParser implements Parser Buffer chunk=_buffer.get(_buffer.length()); _contentPosition += chunk.length(); _contentView.update(chunk); - _handler.content(chunk); // May recurse here + _handler.content(chunk); // May recurse here } _state=STATE_END; _handler.messageComplete(_contentPosition); return 1; } - + + if (ex!=null) + throw ex; + + if (!isComplete() && !isIdle()) + throw new EOFException(); + return -1; } length=_buffer.length(); } - + // EventHandler header byte ch; byte[] array=_buffer.array(); @@ -308,16 +325,16 @@ public class HttpParser implements Parser progress++; last=_state; } - + ch=_buffer.get(); - + if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED) { _eol=HttpTokens.LINE_FEED; continue; } _eol=0; - + switch (_state) { case STATE_START: @@ -464,22 +481,22 @@ public class HttpParser implements Parser _state=STATE_HEADER_VALUE; break; } - + default: { // handler last header if any if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null) { - + Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0); _cached=null; Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue); - + int ho=HttpHeaders.CACHE.getOrdinal(header); if (ho >= 0) { - int vo; - + int vo; + switch (ho) { case HttpHeaders.CONTENT_LENGTH_ORDINAL: @@ -498,7 +515,7 @@ public class HttpParser implements Parser _contentLength=HttpTokens.NO_CONTENT; } break; - + case HttpHeaders.TRANSFER_ENCODING_ORDINAL: value=HttpHeaderValues.CACHE.lookup(value); vo=HttpHeaderValues.CACHE.getOrdinal(value); @@ -509,22 +526,22 @@ public class HttpParser implements Parser String c=value.toString(StringUtil.__ISO_8859_1); if (c.endsWith(HttpHeaderValues.CHUNKED)) _contentLength=HttpTokens.CHUNKED_CONTENT; - + else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0) throw new HttpException(400,null); } break; } } - + _handler.parsedHeader(header, value); _tok0.setPutIndex(_tok0.getIndex()); _tok1.setPutIndex(_tok1.getIndex()); _multiLineValue=null; } _buffer.setMarkIndex(-1); - - + + // now handle ch if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) { @@ -544,7 +561,7 @@ public class HttpParser implements Parser _eol=ch; if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) _eol=_buffer.get(); - + // We convert _contentLength to an int for this switch statement because // we don't care about the amount of data available just whether there is some. switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength) @@ -553,19 +570,19 @@ public class HttpParser implements Parser _state=STATE_EOF_CONTENT; _handler.headerComplete(); // May recurse here ! break; - + case HttpTokens.CHUNKED_CONTENT: _state=STATE_CHUNKED_CONTENT; _handler.headerComplete(); // May recurse here ! break; - + case HttpTokens.NO_CONTENT: _state=STATE_END; returnBuffers(); - _handler.headerComplete(); + _handler.headerComplete(); _handler.messageComplete(_contentPosition); break; - + default: _state=STATE_CONTENT; _handler.headerComplete(); // May recurse here ! @@ -579,7 +596,7 @@ public class HttpParser implements Parser _length=1; _buffer.mark(); _state=STATE_HEADER_NAME; - + // try cached name! if (array!=null) { @@ -592,10 +609,10 @@ public class HttpParser implements Parser length=_buffer.length(); } } - } + } } } - + break; case STATE_HEADER_NAME: @@ -617,16 +634,16 @@ public class HttpParser implements Parser case HttpTokens.SPACE: case HttpTokens.TAB: break; - default: + default: { _cached=null; - if (_length == -1) + if (_length == -1) _buffer.mark(); _length=_buffer.getIndex() - _buffer.markIndex(); - _state=STATE_HEADER_IN_NAME; + _state=STATE_HEADER_IN_NAME; } } - + break; case STATE_HEADER_IN_NAME: @@ -682,11 +699,11 @@ public class HttpParser implements Parser break; default: { - if (_length == -1) + if (_length == -1) _buffer.mark(); _length=_buffer.getIndex() - _buffer.markIndex(); _state=STATE_HEADER_IN_VALUE; - } + } } break; @@ -720,9 +737,9 @@ public class HttpParser implements Parser break; } } // end of HEADER states loop - + // ========================== - + // Handle HEAD response if (_responseStatus>0 && _headResponse) { @@ -731,10 +748,10 @@ public class HttpParser implements Parser } // ========================== - + // Handle _content length=_buffer.length(); - Buffer chunk; + Buffer chunk; last=_state; while (_state > STATE_END && length > 0) { @@ -743,7 +760,7 @@ public class HttpParser implements Parser progress++; last=_state; } - + if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED) { _eol=_buffer.get(); @@ -757,11 +774,11 @@ public class HttpParser implements Parser chunk=_buffer.get(_buffer.length()); _contentPosition += chunk.length(); _contentView.update(chunk); - _handler.content(chunk); // May recurse here + _handler.content(chunk); // May recurse here // TODO adjust the _buffer to keep unconsumed content return 1; - case STATE_CONTENT: + case STATE_CONTENT: { long remaining=_contentLength - _contentPosition; if (remaining == 0) @@ -770,24 +787,24 @@ public class HttpParser implements Parser _handler.messageComplete(_contentPosition); return 1; } - - if (length > remaining) + + if (length > remaining) { // We can cast reamining to an int as we know that it is smaller than - // or equal to length which is already an int. + // or equal to length which is already an int. length=(int)remaining; } - + chunk=_buffer.get(length); _contentPosition += chunk.length(); _contentView.update(chunk); - _handler.content(chunk); // May recurse here - + _handler.content(chunk); // May recurse here + if(_contentPosition == _contentLength) { _state=STATE_END; _handler.messageComplete(_contentPosition); - } + } // TODO adjust the _buffer to keep unconsumed content return 1; } @@ -814,7 +831,7 @@ public class HttpParser implements Parser if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) { _eol=ch; - + if (_chunkLength == 0) { if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED) @@ -858,8 +875,8 @@ public class HttpParser implements Parser } break; } - - case STATE_CHUNK: + + case STATE_CHUNK: { int remaining=_chunkLength - _chunkPosition; if (remaining == 0) @@ -867,13 +884,13 @@ public class HttpParser implements Parser _state=STATE_CHUNKED_CONTENT; break; } - else if (length > remaining) + else if (length > remaining) length=remaining; chunk=_buffer.get(length); _contentPosition += chunk.length(); _chunkPosition += chunk.length(); _contentView.update(chunk); - _handler.content(chunk); // May recurse here + _handler.content(chunk); // May recurse here // TODO adjust the _buffer to keep unconsumed content return 1; } @@ -881,13 +898,13 @@ public class HttpParser implements Parser length=_buffer.length(); } - + return progress; } /* ------------------------------------------------------------------------------- */ /** fill the buffers from the endpoint - * + * */ public long fill() throws IOException { @@ -898,14 +915,14 @@ public class HttpParser implements Parser _tok0=new View.CaseInsensitive(_buffer); _tok1=new View.CaseInsensitive(_buffer); } - + // Is there unconsumed content in body buffer if (_state>STATE_END && _buffer==_header && _header!=null && !_header.hasContent() && _body!=null && _body.hasContent()) { _buffer=_body; return _buffer.length(); } - + // Shall we switch to a body buffer? if (_buffer==_header && _state>STATE_END && _header.length()==0 && (_forceContentBuffer || (_contentLength-_contentPosition)>_header.capacity()) && (_body!=null||_buffers!=null)) { @@ -913,20 +930,20 @@ public class HttpParser implements Parser _body=_buffers.getBuffer(); _buffer=_body; } - + // Do we have somewhere to fill from? if (_endp != null ) { // Shall we compact the body? - if (_buffer==_body || _state>STATE_END) + if (_buffer==_body || _state>STATE_END) { _buffer.compact(); } - + // Are we full? - if (_buffer.space() == 0) - throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head")); - + if (_buffer.space() == 0) + throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head")); + try { return _endp.fill(_buffer); @@ -943,7 +960,7 @@ public class HttpParser implements Parser /* ------------------------------------------------------------------------------- */ /** Skip any CRLFs in buffers - * + * */ public void skipCRLF() { @@ -971,12 +988,12 @@ public class HttpParser implements Parser else break; } - + } - + /* ------------------------------------------------------------------------------- */ public void reset() - { + { // reset state _contentView.setGetIndex(_contentView.putIndex()); _state=STATE_START; @@ -1026,12 +1043,12 @@ public class HttpParser implements Parser public void returnBuffers() { if (_body!=null && !_body.hasContent() && _body.markIndex()==-1 && _buffers!=null) - { + { if (_buffer==_body) _buffer=_header; if (_buffers!=null) _buffers.returnBuffer(_body); - _body=null; + _body=null; } if (_header!=null && !_header.hasContent() && _header.markIndex()==-1 && _buffers!=null) @@ -1042,7 +1059,7 @@ public class HttpParser implements Parser _header=null; } } - + /* ------------------------------------------------------------------------------- */ public void setState(int state) { @@ -1055,13 +1072,13 @@ public class HttpParser implements Parser { return "state=" + _state + " length=" + _length + " buf=" + buf.hashCode(); } - + /* ------------------------------------------------------------------------------- */ @Override public String toString() { return "state=" + _state + " length=" + _length + " len=" + _contentLength; - } + } /* ------------------------------------------------------------ */ public Buffer getHeaderBuffer() @@ -1072,7 +1089,7 @@ public class HttpParser implements Parser } return _header; } - + /* ------------------------------------------------------------ */ public Buffer getBodyBuffer() { @@ -1086,20 +1103,20 @@ public class HttpParser implements Parser public void setForceContentBuffer(boolean force) { _forceContentBuffer=force; - } - + } + /* ------------------------------------------------------------ */ public Buffer blockForContent(long maxIdleTime) throws IOException { if (_contentView.length()>0) return _contentView; - if (getState() <= HttpParser.STATE_END) + if (getState() <= HttpParser.STATE_END) return null; - + try { parseNext(); - + // parse until some progress is made (or IOException thrown for timeout) while(_contentView.length() == 0 && !isState(HttpParser.STATE_END) && _endp!=null && _endp.isOpen()) { @@ -1123,9 +1140,9 @@ public class HttpParser implements Parser _endp.close(); throw e; } - - return _contentView.length()>0?_contentView:null; - } + + return _contentView.length()>0?_contentView:null; + } /* ------------------------------------------------------------ */ /* (non-Javadoc) @@ -1143,11 +1160,11 @@ public class HttpParser implements Parser return 0; } - + parseNext(); return _contentView==null?0:_contentView.length(); } - + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ @@ -1175,7 +1192,7 @@ public class HttpParser implements Parser */ public abstract void startRequest(Buffer method, Buffer url, Buffer version) throws IOException; - + /** * This is the method called by parser when the HTTP request line is parsed */ @@ -1185,5 +1202,5 @@ public class HttpParser implements Parser - + } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java index 7e24daa3ebf..19da9e00104 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java @@ -4,11 +4,11 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.http; @@ -34,7 +34,7 @@ import org.eclipse.jetty.util.URIUtil; * /foo/bar - an exact path specification. * /foo/* - a prefix path specification (must end '/*'). * *.ext - a suffix path specification. - * / - the default path specification. + * / - the default path specification. *
* Matching is performed in the following order *
  • Exact match. @@ -43,24 +43,24 @@ import org.eclipse.jetty.util.URIUtil; *
  • default. * * Multiple path specifications can be mapped by providing a list of - * specifications. By default this class uses characters ":," as path - * separators, unless configured differently by calling the static - * method @see PathMap#setPathSpecSeparators(String) + * specifications. By default this class uses characters ":," as path + * separators, unless configured differently by calling the static + * method @see PathMap#setPathSpecSeparators(String) *

    * Special characters within paths such as '?� and ';' are not treated specially - * as it is assumed they would have been either encoded in the original URL or + * as it is assumed they would have been either encoded in the original URL or * stripped from the path. *

    * This class is not synchronized. If concurrent modifications are * possible then it should be synchronized at a higher level. * - * + * */ public class PathMap extends HashMap implements Externalizable { /* ------------------------------------------------------------ */ private static String __pathSpecSeparators = ":,"; - + /* ------------------------------------------------------------ */ /** Set the path spec separator. * Multiple path specification may be included in a single string @@ -72,7 +72,7 @@ public class PathMap extends HashMap implements Externalizable { __pathSpecSeparators=s; } - + /* --------------------------------------------------------------- */ final StringMap _prefixMap=new StringMap(); final StringMap _suffixMap=new StringMap(); @@ -83,7 +83,7 @@ public class PathMap extends HashMap implements Externalizable Entry _default=null; final Set _entrySet; boolean _nodefault=false; - + /* --------------------------------------------------------------- */ /** Construct empty PathMap. */ @@ -102,7 +102,7 @@ public class PathMap extends HashMap implements Externalizable _entrySet=entrySet(); _nodefault=nodefault; } - + /* --------------------------------------------------------------- */ /** Construct empty PathMap. */ @@ -111,7 +111,7 @@ public class PathMap extends HashMap implements Externalizable super (capacity); _entrySet=entrySet(); } - + /* --------------------------------------------------------------- */ /** Construct from dictionary PathMap. */ @@ -120,7 +120,7 @@ public class PathMap extends HashMap implements Externalizable putAll(m); _entrySet=entrySet(); } - + /* ------------------------------------------------------------ */ public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException @@ -128,7 +128,7 @@ public class PathMap extends HashMap implements Externalizable HashMap map = new HashMap(this); out.writeObject(map); } - + /* ------------------------------------------------------------ */ public void readExternal(java.io.ObjectInput in) throws java.io.IOException, ClassNotFoundException @@ -136,7 +136,7 @@ public class PathMap extends HashMap implements Externalizable HashMap map = (HashMap)in.readObject(); this.putAll(map); } - + /* --------------------------------------------------------------- */ /** Add a single path match to the PathMap. * @param pathSpec The path specification, or comma separated list of @@ -148,16 +148,16 @@ public class PathMap extends HashMap implements Externalizable { StringTokenizer tok = new StringTokenizer(pathSpec.toString(),__pathSpecSeparators); Object old =null; - + while (tok.hasMoreTokens()) { String spec=tok.nextToken(); - + if (!spec.startsWith("/") && !spec.startsWith("*.")) throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'"); - + old = super.put(spec,object); - + // Make entry that was just created. Entry entry = new Entry(spec,object); @@ -176,7 +176,7 @@ public class PathMap extends HashMap implements Externalizable else if (spec.startsWith("*.")) _suffixMap.put(spec.substring(2),entry); else if (spec.equals(URIUtil.SLASH)) - { + { if (_nodefault) _exactMap.put(spec,entry); else @@ -193,7 +193,7 @@ public class PathMap extends HashMap implements Externalizable } } } - + return old; } @@ -209,8 +209,8 @@ public class PathMap extends HashMap implements Externalizable return entry.getValue(); return null; } - - + + /* --------------------------------------------------------------- */ /** Get the entry mapped by the best specification. * @param path the path. @@ -222,14 +222,14 @@ public class PathMap extends HashMap implements Externalizable if (path==null) return null; - - int l=path.length(); + + int l=path.length(); // try exact match entry=_exactMap.getEntry(path,0,l); if (entry!=null) return (Entry) entry.getValue(); - + // prefix search int i=l; while((i=path.lastIndexOf('/',i-1))>=0) @@ -238,11 +238,11 @@ public class PathMap extends HashMap implements Externalizable if (entry!=null) return (Entry) entry.getValue(); } - + // Prefix Default if (_prefixDefault!=null) return _prefixDefault; - + // Extension search i=0; while ((i=path.indexOf('.',i+1))>0) @@ -250,12 +250,12 @@ public class PathMap extends HashMap implements Externalizable entry=_suffixMap.getEntry(path,i+1,l-i-1); if (entry!=null) return (Entry) entry.getValue(); - } - + } + // Default return _default; } - + /* --------------------------------------------------------------- */ /** Get all entries matched by the path. * Best match first. @@ -263,20 +263,20 @@ public class PathMap extends HashMap implements Externalizable * @return LazyList of Map.Entry instances key=pathSpec */ public Object getLazyMatches(String path) - { + { Map.Entry entry; Object entries=null; if (path==null) return LazyList.getList(entries); - + int l=path.length(); // try exact match entry=_exactMap.getEntry(path,0,l); if (entry!=null) entries=LazyList.add(entries,entry.getValue()); - + // prefix search int i=l-1; while((i=path.lastIndexOf('/',i-1))>=0) @@ -285,11 +285,11 @@ public class PathMap extends HashMap implements Externalizable if (entry!=null) entries=LazyList.add(entries,entry.getValue()); } - + // Prefix Default if (_prefixDefault!=null) entries=LazyList.add(entries,_prefixDefault); - + // Extension search i=0; while ((i=path.indexOf('.',i+1))>0) @@ -305,13 +305,13 @@ public class PathMap extends HashMap implements Externalizable // Optimization for just the default if (entries==null) return _defaultSingletonList; - + entries=LazyList.add(entries,_default); } - + return entries; } - + /* --------------------------------------------------------------- */ /** Get all entries matched by the path. * Best match first. @@ -319,7 +319,7 @@ public class PathMap extends HashMap implements Externalizable * @return List of Map.Entry instances key=pathSpec */ public List getMatches(String path) - { + { return LazyList.getList(getLazyMatches(path)); } @@ -330,12 +330,12 @@ public class PathMap extends HashMap implements Externalizable * @return Whether the PathMap contains any entries that match this */ public boolean containsMatch(String path) - { + { Entry match = getMatch(path); return match!=null && !match.equals(_default); } - /* --------------------------------------------------------------- */ + /* --------------------------------------------------------------- */ @Override public Object remove(Object pathSpec) { @@ -362,7 +362,7 @@ public class PathMap extends HashMap implements Externalizable } return super.remove(pathSpec); } - + /* --------------------------------------------------------------- */ @Override public void clear() @@ -374,7 +374,7 @@ public class PathMap extends HashMap implements Externalizable _defaultSingletonList=null; super.clear(); } - + /* --------------------------------------------------------------- */ /** * @return true if match. @@ -397,7 +397,7 @@ public class PathMap extends HashMap implements Externalizable { if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path)) return true; - + if(isPathWildcardMatch(pathSpec, path)) return true; } @@ -419,24 +419,24 @@ public class PathMap extends HashMap implements Externalizable } return false; } - - + + /* --------------------------------------------------------------- */ /** Return the portion of a path that matches a path spec. * @return null if no match at all. */ public static String pathMatch(String pathSpec, String path) - { + { char c = pathSpec.charAt(0); - + if (c=='/') { if (pathSpec.length()==1) return path; - + if (pathSpec.equals(path)) return path; - + if (isPathWildcardMatch(pathSpec, path)) return path.substring(0,pathSpec.length()-2); } @@ -448,7 +448,7 @@ public class PathMap extends HashMap implements Externalizable } return null; } - + /* --------------------------------------------------------------- */ /** Return the portion of a path that is after a path spec. * @return The path info string @@ -456,12 +456,12 @@ public class PathMap extends HashMap implements Externalizable public static String pathInfo(String pathSpec, String path) { char c = pathSpec.charAt(0); - + if (c=='/') { if (pathSpec.length()==1) return null; - + boolean wildcard = isPathWildcardMatch(pathSpec, path); // handle the case where pathSpec uses a wildcard and path info is "/*" @@ -474,7 +474,7 @@ public class PathMap extends HashMap implements Externalizable return null; return path.substring(pathSpec.length()-2); } - } + } return null; } @@ -484,7 +484,7 @@ public class PathMap extends HashMap implements Externalizable * @param base The base the path is relative to. * @param pathSpec The spec of the path segment to ignore. * @param path the additional path - * @return base plus path with pathspec removed + * @return base plus path with pathspec removed */ public static String relativePath(String base, String pathSpec, @@ -508,7 +508,7 @@ public class PathMap extends HashMap implements Externalizable path = base + URIUtil.SLASH + info; return path; } - + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ @@ -516,7 +516,7 @@ public class PathMap extends HashMap implements Externalizable { private final Object key; private final Object value; - private String mapped; + private String mapped; private transient String string; Entry(Object key, Object value) @@ -529,7 +529,7 @@ public class PathMap extends HashMap implements Externalizable { return key; } - + public Object getValue() { return value; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java b/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java index 52cecaaad1b..90227c88073 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java @@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.InetAddress; import java.security.InvalidParameterException; import java.security.KeyStore; import java.security.SecureRandom; @@ -42,7 +43,10 @@ import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509KeyManager; @@ -51,6 +55,8 @@ import javax.net.ssl.X509TrustManager; import org.eclipse.jetty.http.security.Password; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.security.CertificateUtils; import org.eclipse.jetty.util.security.CertificateValidator; @@ -65,6 +71,8 @@ import org.eclipse.jetty.util.security.CertificateValidator; */ public class SslContextFactory extends AbstractLifeCycle { + private static final Logger LOG = Log.getLogger(SslContextFactory.class); + public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM = (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ? "SunX509" : Security.getProperty("ssl.KeyManagerFactory.algorithm")); @@ -82,8 +90,14 @@ public class SslContextFactory extends AbstractLifeCycle /** String name of keystore password property. */ public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password"; + /** Excluded protocols. */ + private final Set _excludeProtocols = new HashSet(); + // private final Set _excludeProtocols = new HashSet(Collections.singleton("SSLv2Hello")); + /** Included protocols. */ + private Set _includeProtocols = null; + /** Excluded cipher suites. */ - private Set _excludeCipherSuites = null; + private final Set _excludeCipherSuites = new HashSet(); /** Included cipher suites. */ private Set _includeCipherSuites = null; @@ -196,6 +210,7 @@ public class SslContextFactory extends AbstractLifeCycle if (_keyStoreInputStream == null && _keyStorePath == null && _trustStoreInputStream == null && _trustStorePath == null ) { + LOG.debug("No keystore or trust store configured. ACCEPTING UNTRUSTED CERTIFICATES!!!!!"); // Create a trust manager that does not validate certificate chains TrustManager trustAllCerts = new X509TrustManager() { @@ -218,11 +233,115 @@ public class SslContextFactory extends AbstractLifeCycle } else { - createSSLContext(); + // verify that keystore and truststore + // parameters are set up correctly + try + { + checkKeyStore(); + } + catch(IllegalStateException e) + { + LOG.ignore(e); + } + + KeyStore keyStore = loadKeyStore(); + KeyStore trustStore = loadTrustStore(); + + Collection crls = loadCRL(_crlPath); + + if (_validateCerts && keyStore != null) + { + if (_certAlias == null) + { + List aliases = Collections.list(keyStore.aliases()); + _certAlias = aliases.size() == 1 ? aliases.get(0) : null; + } + + Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias); + if (cert == null) + { + throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias)); + } + + CertificateValidator validator = new CertificateValidator(trustStore, crls); + validator.setMaxCertPathLength(_maxCertPathLength); + validator.setEnableCRLDP(_enableCRLDP); + validator.setEnableOCSP(_enableOCSP); + validator.setOcspResponderURL(_ocspResponderURL); + validator.validate(keyStore, cert); + } + + KeyManager[] keyManagers = getKeyManagers(keyStore); + TrustManager[] trustManagers = getTrustManagers(trustStore,crls); + + SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm); + _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider); + _context.init(keyManagers,trustManagers,secureRandom); + + SSLEngine engine=newSslEngine(); + LOG.info("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols())); + LOG.debug("Enabled Ciphers {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites())); } } } + /* ------------------------------------------------------------ */ + /** + * @return The array of protocol names to exclude from + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public String[] getExcludeProtocols() + { + return _excludeProtocols.toArray(new String[_excludeProtocols.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param Protocols + * The array of protocol names to exclude from + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public void setExcludeProtocols(String... protocols) + { + checkNotStarted(); + + _excludeProtocols.clear(); + _excludeProtocols.addAll(Arrays.asList(protocols)); + } + + /* ------------------------------------------------------------ */ + /** + * @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])} + */ + public void addExcludeProtocols(String... protocol) + { + checkNotStarted(); + _excludeProtocols.addAll(Arrays.asList(protocol)); + } + + /* ------------------------------------------------------------ */ + /** + * @return The array of protocol names to include in + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public String[] getIncludeProtocols() + { + return _includeProtocols.toArray(new String[_includeProtocols.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * @param Protocols + * The array of protocol names to include in + * {@link SSLEngine#setEnabledProtocols(String[])} + */ + public void setIncludeProtocols(String... protocols) + { + checkNotStarted(); + + _includeProtocols = new HashSet(Arrays.asList(protocols)); + } + /* ------------------------------------------------------------ */ /** * @return The array of cipher suite names to exclude from @@ -239,11 +358,21 @@ public class SslContextFactory extends AbstractLifeCycle * The array of cipher suite names to exclude from * {@link SSLEngine#setEnabledCipherSuites(String[])} */ - public void setExcludeCipherSuites(String[] cipherSuites) + public void setExcludeCipherSuites(String... cipherSuites) { - checkStarted(); - - _excludeCipherSuites = new HashSet(Arrays.asList(cipherSuites)); + checkNotStarted(); + _excludeCipherSuites.clear(); + _excludeCipherSuites.addAll(Arrays.asList(cipherSuites)); + } + + /* ------------------------------------------------------------ */ + /** + * @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])} + */ + public void addExcludeCipherSuites(String... cipher) + { + checkNotStarted(); + _excludeCipherSuites.addAll(Arrays.asList(cipher)); } /* ------------------------------------------------------------ */ @@ -262,9 +391,9 @@ public class SslContextFactory extends AbstractLifeCycle * The array of cipher suite names to include in * {@link SSLEngine#setEnabledCipherSuites(String[])} */ - public void setIncludeCipherSuites(String[] cipherSuites) + public void setIncludeCipherSuites(String... cipherSuites) { - checkStarted(); + checkNotStarted(); _includeCipherSuites = new HashSet(Arrays.asList(cipherSuites)); } @@ -285,7 +414,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setKeyStore(String keyStorePath) { - checkStarted(); + checkNotStarted(); _keyStorePath = keyStorePath; } @@ -306,7 +435,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setKeyStoreProvider(String keyStoreProvider) { - checkStarted(); + checkNotStarted(); _keyStoreProvider = keyStoreProvider; } @@ -327,7 +456,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setKeyStoreType(String keyStoreType) { - checkStarted(); + checkNotStarted(); _keyStoreType = keyStoreType; } @@ -341,7 +470,7 @@ public class SslContextFactory extends AbstractLifeCycle @Deprecated public InputStream getKeyStoreInputStream() { - checkConfig(); + checkKeyStore(); return _keyStoreInputStream; } @@ -355,7 +484,7 @@ public class SslContextFactory extends AbstractLifeCycle @Deprecated public void setKeyStoreInputStream(InputStream keyStoreInputStream) { - checkStarted(); + checkNotStarted(); _keyStoreInputStream = keyStoreInputStream; } @@ -376,7 +505,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setCertAlias(String certAlias) { - checkStarted(); + checkNotStarted(); _certAlias = certAlias; } @@ -397,7 +526,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setTrustStore(String trustStorePath) { - checkStarted(); + checkNotStarted(); _trustStorePath = trustStorePath; } @@ -418,7 +547,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setTrustStoreProvider(String trustStoreProvider) { - checkStarted(); + checkNotStarted(); _trustStoreProvider = trustStoreProvider; } @@ -439,7 +568,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setTrustStoreType(String trustStoreType) { - checkStarted(); + checkNotStarted(); _trustStoreType = trustStoreType; } @@ -453,7 +582,7 @@ public class SslContextFactory extends AbstractLifeCycle @Deprecated public InputStream getTrustStoreInputStream() { - checkConfig(); + checkKeyStore(); return _trustStoreInputStream; } @@ -467,7 +596,7 @@ public class SslContextFactory extends AbstractLifeCycle @Deprecated public void setTrustStoreInputStream(InputStream trustStoreInputStream) { - checkStarted(); + checkNotStarted(); _trustStoreInputStream = trustStoreInputStream; } @@ -490,7 +619,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setNeedClientAuth(boolean needClientAuth) { - checkStarted(); + checkNotStarted(); _needClientAuth = needClientAuth; } @@ -513,7 +642,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setWantClientAuth(boolean wantClientAuth) { - checkStarted(); + checkNotStarted(); _wantClientAuth = wantClientAuth; } @@ -545,7 +674,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setValidateCerts(boolean validateCerts) { - checkStarted(); + checkNotStarted(); _validateCerts = validateCerts; } @@ -566,7 +695,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setValidatePeerCerts(boolean validatePeerCerts) { - checkStarted(); + checkNotStarted(); _validatePeerCerts = validatePeerCerts; } @@ -593,7 +722,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setAllowRenegotiate(boolean allowRenegotiate) { - checkStarted(); + checkNotStarted(); _allowRenegotiate = allowRenegotiate; } @@ -605,7 +734,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setKeyStorePassword(String password) { - checkStarted(); + checkNotStarted(); _keyStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null); } @@ -617,7 +746,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setKeyManagerPassword(String password) { - checkStarted(); + checkNotStarted(); _keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null); } @@ -629,7 +758,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setTrustStorePassword(String password) { - checkStarted(); + checkNotStarted(); _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null); } @@ -652,7 +781,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setProvider(String provider) { - checkStarted(); + checkNotStarted(); _sslProvider = provider; } @@ -675,7 +804,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setProtocol(String protocol) { - checkStarted(); + checkNotStarted(); _sslProtocol = protocol; } @@ -700,7 +829,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setSecureRandomAlgorithm(String algorithm) { - checkStarted(); + checkNotStarted(); _secureRandomAlgorithm = algorithm; } @@ -721,7 +850,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setSslKeyManagerFactoryAlgorithm(String algorithm) { - checkStarted(); + checkNotStarted(); _keyManagerFactoryAlgorithm = algorithm; } @@ -742,7 +871,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setTrustManagerFactoryAlgorithm(String algorithm) { - checkStarted(); + checkNotStarted(); _trustManagerFactoryAlgorithm = algorithm; } @@ -763,7 +892,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setCrlPath(String crlPath) { - checkStarted(); + checkNotStarted(); _crlPath = crlPath; } @@ -786,7 +915,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setMaxCertPathLength(int maxCertPathLength) { - checkStarted(); + checkNotStarted(); _maxCertPathLength = maxCertPathLength; } @@ -797,6 +926,8 @@ public class SslContextFactory extends AbstractLifeCycle */ public SSLContext getSslContext() { + if (!isStarted()) + throw new IllegalStateException(getState()); return _context; } @@ -807,60 +938,11 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setSslContext(SSLContext sslContext) { - checkStarted(); + checkNotStarted(); _context = sslContext; } - /* ------------------------------------------------------------ */ - /** - * @throws Exception - */ - protected void createSSLContext() throws Exception - { - // verify that keystore and truststore - // parameters are set up correctly - checkConfig(); - - KeyStore keyStore = loadKeyStore(); - KeyStore trustStore = loadTrustStore(); - - Collection crls = loadCRL(_crlPath); - - if (_validateCerts && keyStore != null) - { - if (_certAlias == null) - { - List aliases = Collections.list(keyStore.aliases()); - _certAlias = aliases.size() == 1 ? aliases.get(0) : null; - } - - Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias); - if (cert == null) - { - throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias)); - } - - CertificateValidator validator = new CertificateValidator(trustStore, crls); - validator.setMaxCertPathLength(_maxCertPathLength); - validator.setEnableCRLDP(_enableCRLDP); - validator.setEnableOCSP(_enableOCSP); - validator.setOcspResponderURL(_ocspResponderURL); - validator.validate(keyStore, cert); - } - - KeyManager[] keyManagers = getKeyManagers(keyStore); - TrustManager[] trustManagers = getTrustManagers(trustStore,crls); - - SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm); - _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider); - _context.init(keyManagers,trustManagers,secureRandom); - - SSLSessionContext sslSessionContext = _context.getServerSessionContext(); - sslSessionContext.setSessionCacheSize(_sslSessionCacheSize); - sslSessionContext.setSessionTimeout(_sslSessionTimeout); - } - /* ------------------------------------------------------------ */ /** * Override this method to provide alternate way to load a keystore. @@ -1014,33 +1096,27 @@ public class SslContextFactory extends AbstractLifeCycle /* ------------------------------------------------------------ */ /** - * Check configuration. Ensures that if keystore has been + * Check KetyStore Configuration. Ensures that if keystore has been * configured but there's no truststore, that keystore is * used as truststore. - * @return true SslContextFactory configuration can be used in server connector. + * @throws IllegalStateException if SslContextFactory configuration can't be used. */ - public boolean checkConfig() + public void checkKeyStore() { - boolean check = true; if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null) + throw new IllegalStateException("SSL doesn't have a valid keystore"); + + // if the keystore has been configured but there is no + // truststore configured, use the keystore as the truststore + if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null) { - // configuration doesn't have a valid keystore - check = false; - } - else - { - // if the keystore has been configured but there is no - // truststore configured, use the keystore as the truststore - if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null) - { - _trustStore = _keyStore; - _trustStorePath = _keyStorePath; - _trustStoreInputStream = _keyStoreInputStream; - _trustStoreType = _keyStoreType; - _trustStoreProvider = _keyStoreProvider; - _trustStorePassword = _keyStorePassword; - _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm; - } + _trustStore = _keyStore; + _trustStorePath = _keyStorePath; + _trustStoreInputStream = _keyStoreInputStream; + _trustStoreType = _keyStoreType; + _trustStoreProvider = _keyStoreProvider; + _trustStorePassword = _keyStorePassword; + _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm; } // It's the same stream we cannot read it twice, so read it once in memory @@ -1057,11 +1133,9 @@ public class SslContextFactory extends AbstractLifeCycle } catch (Exception ex) { - throw new RuntimeException(ex); + throw new IllegalStateException(ex); } } - - return check; } /* ------------------------------------------------------------ */ @@ -1073,57 +1147,68 @@ public class SslContextFactory extends AbstractLifeCycle * @param supportedCipherSuites Array of supported cipher suites * @return Array of cipher suites to enable */ - public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites) + public String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols) { - Set selectedCipherSuites = null; - if (enabledCipherSuites != null) + Set selected_protocols = new HashSet(); + + // Set the starting protocols - either from the included or enabled list + if (_includeProtocols!=null) { - selectedCipherSuites = new HashSet(Arrays.asList(enabledCipherSuites)); + // Use only the supported included protocols + for (String protocol : supportedProtocols) + if (_includeProtocols.contains(protocol)) + selected_protocols.add(protocol); } else + selected_protocols.addAll(Arrays.asList(enabledProtocols)); + + + // Remove any excluded protocols + if (_excludeProtocols != null) + selected_protocols.removeAll(_excludeProtocols); + + return selected_protocols.toArray(new String[selected_protocols.size()]); + } + + /* ------------------------------------------------------------ */ + /** + * Select cipher suites to be used by the connector + * based on configured inclusion and exclusion lists + * as well as enabled and supported cipher suite lists. + * @param enabledCipherSuites Array of enabled cipher suites + * @param supportedCipherSuites Array of supported cipher suites + * @return Array of cipher suites to enable + */ + public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites) + { + Set selected_ciphers = new HashSet(); + + // Set the starting ciphers - either from the included or enabled list + if (_includeCipherSuites!=null) { - selectedCipherSuites = new HashSet(); + // Use only the supported included ciphers + for (String cipherSuite : supportedCipherSuites) + if (_includeCipherSuites.contains(cipherSuite)) + selected_ciphers.add(cipherSuite); } - - if ((supportedCipherSuites != null && supportedCipherSuites.length > 0) && - (_includeCipherSuites != null && _includeCipherSuites.size() > 0)) - { - Set supportedCSList = new HashSet(Arrays.asList(supportedCipherSuites)); - - for (String cipherName : _includeCipherSuites) - { - if ((!selectedCipherSuites.contains(cipherName)) && - supportedCSList.contains(cipherName)) - { - selectedCipherSuites.add(cipherName); - } - } - } - - if (_excludeCipherSuites != null && _excludeCipherSuites.size() > 0) - { - for (String cipherName : _excludeCipherSuites) - { - if (selectedCipherSuites.contains(cipherName)) - { - selectedCipherSuites.remove(cipherName); - } - } - } - - return selectedCipherSuites.toArray(new String[selectedCipherSuites.size()]); + else + selected_ciphers.addAll(Arrays.asList(enabledCipherSuites)); + + + // Remove any excluded ciphers + if (_excludeCipherSuites != null) + selected_ciphers.removeAll(_excludeCipherSuites); + return selected_ciphers.toArray(new String[selected_ciphers.size()]); } /* ------------------------------------------------------------ */ /** * Check if the lifecycle has been started and throw runtime exception */ - protected void checkStarted() + protected void checkNotStarted() { if (isStarted()) - { - throw new IllegalStateException("Cannot modify configuration after SslContextFactory was started"); - } + throw new IllegalStateException("Cannot modify configuration when "+getState()); } /* ------------------------------------------------------------ */ @@ -1141,7 +1226,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setEnableCRLDP(boolean enableCRLDP) { - checkStarted(); + checkNotStarted(); _enableCRLDP = enableCRLDP; } @@ -1161,7 +1246,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setEnableOCSP(boolean enableOCSP) { - checkStarted(); + checkNotStarted(); _enableOCSP = enableOCSP; } @@ -1181,7 +1266,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setOcspResponderURL(String ocspResponderURL) { - checkStarted(); + checkNotStarted(); _ocspResponderURL = ocspResponderURL; } @@ -1192,7 +1277,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setKeyStore(KeyStore keyStore) { - checkStarted(); + checkNotStarted(); _keyStore = keyStore; } @@ -1203,7 +1288,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setTrustStore(KeyStore trustStore) { - checkStarted(); + checkNotStarted(); _trustStore = trustStore; } @@ -1214,7 +1299,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setKeyStoreResource(Resource resource) { - checkStarted(); + checkNotStarted(); try { @@ -1233,7 +1318,7 @@ public class SslContextFactory extends AbstractLifeCycle */ public void setTrustStore(Resource resource) { - checkStarted(); + checkNotStarted(); try { @@ -1299,4 +1384,83 @@ public class SslContextFactory extends AbstractLifeCycle { _sslSessionTimeout = sslSessionTimeout; } + + + /* ------------------------------------------------------------ */ + public SSLServerSocket newSslServerSocket(String host,int port,int backlog) throws IOException + { + SSLServerSocketFactory factory = _context.getServerSocketFactory(); + + SSLServerSocket socket = + (SSLServerSocket) (host==null ? + factory.createServerSocket(port,backlog): + factory.createServerSocket(port,backlog,InetAddress.getByName(host))); + + if (getWantClientAuth()) + socket.setWantClientAuth(getWantClientAuth()); + if (getNeedClientAuth()) + socket.setNeedClientAuth(getNeedClientAuth()); + + socket.setEnabledCipherSuites(selectCipherSuites( + socket.getEnabledCipherSuites(), + socket.getSupportedCipherSuites())); + socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols())); + + return socket; + } + + /* ------------------------------------------------------------ */ + public SSLSocket newSslSocket() throws IOException + { + SSLSocketFactory factory = _context.getSocketFactory(); + + SSLSocket socket = (SSLSocket)factory.createSocket(); + + if (getWantClientAuth()) + socket.setWantClientAuth(getWantClientAuth()); + if (getNeedClientAuth()) + socket.setNeedClientAuth(getNeedClientAuth()); + + socket.setEnabledCipherSuites(selectCipherSuites( + socket.getEnabledCipherSuites(), + socket.getSupportedCipherSuites())); + socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols())); + + return socket; + } + + /* ------------------------------------------------------------ */ + public SSLEngine newSslEngine(String host,int port) + { + SSLEngine sslEngine=isSessionCachingEnabled() + ?_context.createSSLEngine(host, port) + :_context.createSSLEngine(); + + customize(sslEngine); + return sslEngine; + } + + /* ------------------------------------------------------------ */ + public SSLEngine newSslEngine() + { + SSLEngine sslEngine=_context.createSSLEngine(); + customize(sslEngine); + return sslEngine; + } + + /* ------------------------------------------------------------ */ + public void customize(SSLEngine sslEngine) + { + if (getWantClientAuth()) + sslEngine.setWantClientAuth(getWantClientAuth()); + if (getNeedClientAuth()) + sslEngine.setNeedClientAuth(getNeedClientAuth()); + + sslEngine.setEnabledCipherSuites(selectCipherSuites( + sslEngine.getEnabledCipherSuites(), + sslEngine.getSupportedCipherSuites())); + + sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols())); + } + } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java index 21919c5fe12..6940991f387 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java @@ -47,6 +47,7 @@ public interface EndPoint * The buffer may chose to do a compact before filling. * @return an int value indicating the number of bytes * filled or -1 if EOF is reached. + * @throws EofException If input is shutdown or the endpoint is closed. */ int fill(Buffer buffer) throws IOException; @@ -59,6 +60,7 @@ public interface EndPoint * * @param buffer The buffer to flush. This buffers getIndex is updated. * @return the number of bytes written + * @throws EofException If the endpoint is closed or output is shutdown. */ int flush(Buffer buffer) throws IOException; @@ -157,7 +159,7 @@ public interface EndPoint /* ------------------------------------------------------------ */ /** Flush any buffered output. * May fail to write all data if endpoint is non-blocking - * @throws IOException + * @throws EofException If the endpoint is closed or output is shutdown. */ public void flush() throws IOException; diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java index e9e7d5e00ca..f749f0ba417 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/SocketEndPoint.java @@ -4,11 +4,11 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.io.bio; @@ -17,6 +17,7 @@ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; +import javax.net.ssl.SSLSocket; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; @@ -34,13 +35,13 @@ public class SocketEndPoint extends StreamEndPoint final Socket _socket; final InetSocketAddress _local; final InetSocketAddress _remote; - + /* ------------------------------------------------------------ */ /** - * + * */ public SocketEndPoint(Socket socket) - throws IOException + throws IOException { super(socket.getInputStream(),socket.getOutputStream()); _socket=socket; @@ -48,13 +49,13 @@ public class SocketEndPoint extends StreamEndPoint _remote=(InetSocketAddress)_socket.getRemoteSocketAddress(); super.setMaxIdleTime(_socket.getSoTimeout()); } - + /* ------------------------------------------------------------ */ /** - * + * */ protected SocketEndPoint(Socket socket, int maxIdleTime) - throws IOException + throws IOException { super(socket.getInputStream(),socket.getOutputStream()); _socket=socket; @@ -63,7 +64,7 @@ public class SocketEndPoint extends StreamEndPoint _socket.setSoTimeout(maxIdleTime>0?maxIdleTime:0); super.setMaxIdleTime(maxIdleTime); } - + /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.eclipse.io.BufferIO#isClosed() @@ -73,19 +74,19 @@ public class SocketEndPoint extends StreamEndPoint { return super.isOpen() && _socket!=null && !_socket.isClosed(); } - + /* ------------------------------------------------------------ */ @Override public boolean isInputShutdown() { - return !super.isOpen() || _socket!=null && _socket.isInputShutdown(); + return !isOpen() || super.isInputShutdown(); } - + /* ------------------------------------------------------------ */ @Override public boolean isOutputShutdown() { - return !super.isOpen() || _socket!=null && _socket.isOutputShutdown(); + return !isOpen() || super.isOutputShutdown(); } /* ------------------------------------------------------------ */ @@ -94,9 +95,13 @@ public class SocketEndPoint extends StreamEndPoint */ @Override public void shutdownOutput() throws IOException - { - if (!_socket.isClosed() && !_socket.isOutputShutdown()) - _socket.shutdownOutput(); + { + if (!isOutputShutdown()) + { + super.shutdownOutput(); + if (!(_socket instanceof SSLSocket)) + _socket.shutdownOutput(); + } } @@ -106,11 +111,15 @@ public class SocketEndPoint extends StreamEndPoint */ @Override public void shutdownInput() throws IOException - { - if (!_socket.isClosed() && !_socket.isInputShutdown()) - _socket.shutdownInput(); + { + if (!isInputShutdown()) + { + super.shutdownInput(); + if (!(_socket instanceof SSLSocket)) + _socket.shutdownInput(); + } } - + /* ------------------------------------------------------------ */ /* (non-Javadoc) * @see org.eclipse.io.BufferIO#close() @@ -122,10 +131,10 @@ public class SocketEndPoint extends StreamEndPoint _in=null; _out=null; } - + /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getLocalAddr() */ @Override @@ -133,12 +142,12 @@ public class SocketEndPoint extends StreamEndPoint { if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) return StringUtil.ALL_INTERFACES; - + return _local.getAddress().getHostAddress(); } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getLocalHost() */ @Override @@ -146,12 +155,12 @@ public class SocketEndPoint extends StreamEndPoint { if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress()) return StringUtil.ALL_INTERFACES; - + return _local.getAddress().getCanonicalHostName(); } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getLocalPort() */ @Override @@ -163,7 +172,7 @@ public class SocketEndPoint extends StreamEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getRemoteAddr() */ @Override @@ -176,7 +185,7 @@ public class SocketEndPoint extends StreamEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getRemoteHost() */ @Override @@ -188,7 +197,7 @@ public class SocketEndPoint extends StreamEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getRemotePort() */ @Override @@ -200,7 +209,7 @@ public class SocketEndPoint extends StreamEndPoint } /* ------------------------------------------------------------ */ - /* + /* * @see org.eclipse.io.EndPoint#getConnection() */ @Override @@ -237,5 +246,5 @@ public class SocketEndPoint extends StreamEndPoint _socket.close(); } } - + } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java index fde7272eb77..da1f58b007f 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/bio/StreamEndPoint.java @@ -22,17 +22,13 @@ import java.net.SocketTimeoutException; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.EndPoint; -/** - * - * - * To change the template for this generated type comment go to - * Window - Preferences - Java - Code Generation - Code and Comments - */ public class StreamEndPoint implements EndPoint { InputStream _in; OutputStream _out; int _maxIdleTime; + boolean _ishut; + boolean _oshut; /** * @@ -75,23 +71,33 @@ public class StreamEndPoint implements EndPoint } public void shutdownOutput() throws IOException - { + { + if (_oshut) + return; + _oshut = true; + if (_out!=null) + _out.close(); } - + public boolean isInputShutdown() { - return !isOpen(); + return _ishut; } public void shutdownInput() throws IOException - { + { + if (_ishut) + return; + _ishut = true; + if (_in!=null) + _in.close(); } - + public boolean isOutputShutdown() { - return !isOpen(); + return _oshut; } - + /* * @see org.eclipse.io.BufferIO#close() */ @@ -107,35 +113,43 @@ public class StreamEndPoint implements EndPoint protected void idleExpired() throws IOException { - _in.close(); + if (_in!=null) + _in.close(); } - + /* (non-Javadoc) * @see org.eclipse.io.BufferIO#fill(org.eclipse.io.Buffer) */ public int fill(Buffer buffer) throws IOException { - // TODO handle null array() if (_in==null) return 0; - int space=buffer.space(); - if (space<=0) - { - if (buffer.hasContent()) - return 0; - throw new IOException("FULL"); - } + int space=buffer.space(); + if (space<=0) + { + if (buffer.hasContent()) + return 0; + throw new IOException("FULL"); + } - try - { - return buffer.readFrom(_in,space); - } - catch(SocketTimeoutException e) - { - idleExpired(); - return -1; - } + try + { + int read=buffer.readFrom(_in, space); + if (read<0 && isOpen()) + { + if (!isInputShutdown()) + shutdownInput(); + else if (isOutputShutdown()) + close(); + } + return read; + } + catch(SocketTimeoutException e) + { + idleExpired(); + return -1; + } } /* (non-Javadoc) @@ -143,7 +157,6 @@ public class StreamEndPoint implements EndPoint */ public int flush(Buffer buffer) throws IOException { - // TODO handle null array() if (_out==null) return -1; int length=buffer.length(); @@ -313,13 +326,13 @@ public class StreamEndPoint implements EndPoint { return false; } - + /* ------------------------------------------------------------ */ public int getMaxIdleTime() { return _maxIdleTime; } - + /* ------------------------------------------------------------ */ public void setMaxIdleTime(int timeMs) throws IOException { diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java index 839996dbb52..9670613eede 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java @@ -109,9 +109,12 @@ public class ChannelEndPoint implements EndPoint if (_channel.isOpen() && _channel instanceof SocketChannel) { Socket socket= ((SocketChannel)_channel).socket(); - if (!socket.isClosed()&&!socket.isInputShutdown()) + if (!socket.isClosed()) { - socket.shutdownInput(); + if(socket.isOutputShutdown()) + socket.close(); + else if (!socket.isInputShutdown()) + socket.shutdownInput(); } } } @@ -124,9 +127,12 @@ public class ChannelEndPoint implements EndPoint if (_channel.isOpen() && _channel instanceof SocketChannel) { Socket socket= ((SocketChannel)_channel).socket(); - if (!socket.isClosed()&&!socket.isOutputShutdown()) + if (!socket.isClosed()) { - socket.shutdownOutput(); + if (socket.isInputShutdown()) + socket.close(); + else if (!socket.isOutputShutdown()) + socket.shutdownOutput(); } } } @@ -160,8 +166,8 @@ public class ChannelEndPoint implements EndPoint { final NIOBuffer nbuf = (NIOBuffer)buf; final ByteBuffer bbuf=nbuf.getByteBuffer(); - //noinspection SynchronizationOnLocalVariableOrMethodParameter + //noinspection SynchronizationOnLocalVariableOrMethodParameter try { synchronized(bbuf) @@ -178,13 +184,17 @@ public class ChannelEndPoint implements EndPoint } } - if (len<0 && isOpen() && !isInputShutdown()) + if (len<0 && isOpen()) { - shutdownInput(); + if (!isInputShutdown()) + shutdownInput(); + else if (isOutputShutdown()) + _channel.close(); } } catch (IOException x) { + LOG.debug(x); try { close(); @@ -196,7 +206,6 @@ public class ChannelEndPoint implements EndPoint if (len>0) throw x; - LOG.ignore(x); len=-1; } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java index fd5413ff3e0..a318543f647 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java @@ -34,27 +34,48 @@ import org.eclipse.jetty.util.log.Logger; */ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPoint, ConnectedEndPoint { - public static final Logger __log=Log.getLogger("org.eclipse.jetty.io.nio"); + public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio"); private final SelectorManager.SelectSet _selectSet; private final SelectorManager _manager; + private SelectionKey _key; private final Runnable _handler = new Runnable() { public void run() { handle(); } }; + /** The desired value for {@link SelectionKey#interestOps()} */ + private int _interestOps; + + /** + * The connection instance is the handler for any IO activity on the endpoint. + * There is a different type of connection for HTTP, AJP, WebSocket and + * ProxyConnect. The connection may change for an SCEP as it is upgraded + * from HTTP to proxy connect or websocket. + */ private volatile Connection _connection; + + /** true if a thread has been dispatched to handle this endpoint */ private boolean _dispatched = false; + + /** true if a non IO dispatch (eg async resume) is outstanding */ private boolean _redispatched = false; + + /** true if the last write operation succeed and wrote all offered bytes */ private volatile boolean _writable = true; - private SelectionKey _key; - private int _interestOps; + + /** True if a thread has is blocked in {@link #blockReadable(long)} */ private boolean _readBlocked; - private boolean _writeBlocked; - private boolean _open; - private volatile long _idleTimestamp; + /** True if a thread has is blocked in {@link #blockWritable(long)} */ + private boolean _writeBlocked; + + /** true if {@link SelectSet#destroyEndPoint(SelectChannelEndPoint)} has not been called */ + private boolean _open; + + private volatile long _idleTimestamp; + /* ------------------------------------------------------------ */ public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key, int maxIdleTime) throws IOException @@ -90,6 +111,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo scheduleIdle(); } + /* ------------------------------------------------------------ */ public SelectionKey getSelectionKey() { @@ -205,7 +227,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo if(!dispatched) { _dispatched = false; - __log.warn("Dispatched Failed! "+this+" to "+_manager); + LOG.warn("Dispatched Failed! "+this+" to "+_manager); updateKey(); } } @@ -250,7 +272,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo public void checkIdleTimestamp(long now) { long idleTimestamp=_idleTimestamp; - if (idleTimestamp!=0 && _maxIdleTime>0 && now>(idleTimestamp+_maxIdleTime)) + if (!getChannel().isOpen() || idleTimestamp!=0 && _maxIdleTime>0 && now>(idleTimestamp+_maxIdleTime)) idleExpired(); } @@ -260,6 +282,15 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo _connection.idleExpired(); } + /* ------------------------------------------------------------ */ + /** + * @return True if the endpoint has produced/consumed bytes itself (non application data). + */ + public boolean isProgressing() + { + return false; + } + /* ------------------------------------------------------------ */ /* */ @@ -340,7 +371,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (InterruptedException e) { - __log.warn(e); + LOG.warn(e); } finally { @@ -385,7 +416,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (InterruptedException e) { - __log.warn(e); + LOG.warn(e); } finally { @@ -398,7 +429,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo catch(Throwable e) { // TODO remove this if it finds nothing - __log.warn(e); + LOG.warn(e); if (e instanceof RuntimeException) throw (RuntimeException)e; if (e instanceof Error) @@ -414,10 +445,20 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } return true; } + + /* ------------------------------------------------------------ */ + /* short cut for busyselectChannelServerTest */ + public void clearWritable() + { + _writable=false; + } /* ------------------------------------------------------------ */ public void scheduleWrite() { + if (_writable==true) + LOG.debug("Required scheduleWrite {}",this); + _writable=false; updateKey(); } @@ -445,7 +486,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo catch(Exception e) { _key=null; - __log.ignore(e); + LOG.ignore(e); } } @@ -483,7 +524,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (Exception e) { - __log.ignore(e); + LOG.ignore(e); if (_key!=null && _key.isValid()) { _key.cancel(); @@ -520,9 +561,9 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo cancelIdle(); if (_open) { + _open=false; _selectSet.destroyEndPoint(this); } - _open=false; _key = null; } } @@ -545,7 +586,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo final Connection next = _connection.handle(); if (next!=_connection) { - __log.debug("{} replaced {}",next,_connection); + LOG.debug("{} replaced {}",next,_connection); _connection=next; continue; } @@ -554,26 +595,26 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (ClosedChannelException e) { - __log.ignore(e); + LOG.ignore(e); } catch (EofException e) { - __log.debug("EOF", e); - try{close();} - catch(IOException e2){__log.ignore(e2);} + LOG.debug("EOF", e); + try{getChannel().close();} + catch(IOException e2){LOG.ignore(e2);} } catch (IOException e) { - __log.warn(e.toString()); - __log.debug(e); - try{close();} - catch(IOException e2){__log.ignore(e2);} + LOG.warn(e.toString()); + LOG.debug(e); + try{getChannel().close();} + catch(IOException e2){LOG.ignore(e2);} } catch (Throwable e) { - __log.warn("handle failed", e); - try{close();} - catch(IOException e2){__log.ignore(e2);} + LOG.warn("handle failed", e); + try{getChannel().close();} + catch(IOException e2){LOG.ignore(e2);} } dispatched=!undispatch(); } @@ -585,7 +626,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo dispatched=!undispatch(); while (dispatched) { - __log.warn("SCEP.run() finally DISPATCHED"); + LOG.warn("SCEP.run() finally DISPATCHED"); dispatched=!undispatch(); } } @@ -605,7 +646,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo } catch (IOException e) { - __log.ignore(e); + LOG.ignore(e); } finally { @@ -620,7 +661,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo synchronized(this) { return "SCEP@" + hashCode() + _channel+ - "[d=" + _dispatched + ",io=" + _interestOps+ + "[o="+isOpen()+" d=" + _dispatched + ",io=" + _interestOps+ ",w=" + _writable + ",rb=" + _readBlocked + ",wb=" + _writeBlocked + "]"; } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java index 5b80a68d9df..1b8218832a4 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectorManager.java @@ -4,11 +4,11 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.io.nio; @@ -30,7 +30,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - import org.eclipse.jetty.io.ConnectedEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; @@ -49,21 +48,16 @@ import org.eclipse.jetty.util.thread.Timeout.Task; * The Selector Manager manages and number of SelectSets to allow * NIO scheduling to scale to large numbers of connections. *

    - * This class works around a number of know JVM bugs. For details - * see http://wiki.eclipse.org/Jetty/Feature/JVM_NIO_Bug */ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable { public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio"); - - // TODO Tune these by approx system speed. - private static final int __JVMBUG_THRESHHOLD=Integer.getInteger("org.eclipse.jetty.io.nio.JVMBUG_THRESHHOLD",0).intValue(); + private static final int __MONITOR_PERIOD=Integer.getInteger("org.eclipse.jetty.io.nio.MONITOR_PERIOD",1000).intValue(); private static final int __MAX_SELECTS=Integer.getInteger("org.eclipse.jetty.io.nio.MAX_SELECTS",25000).intValue(); private static final int __BUSY_PAUSE=Integer.getInteger("org.eclipse.jetty.io.nio.BUSY_PAUSE",50).intValue(); - private static final int __BUSY_KEY=Integer.getInteger("org.eclipse.jetty.io.nio.BUSY_KEY",-1).intValue(); private static final int __IDLE_TICK=Integer.getInteger("org.eclipse.jetty.io.nio.IDLE_TICK",400).intValue(); - + private int _maxIdleTime; private int _lowResourcesMaxIdleTime; private long _lowResourcesConnections; @@ -72,7 +66,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa private volatile int _set; private boolean _deferringInterestedOps0=true; private int _selectorPriorityDelta=0; - + /* ------------------------------------------------------------ */ /** * @param maxIdleTime The maximum period in milli seconds that a connection may be idle before it is closed. @@ -82,18 +76,18 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _maxIdleTime=(int)maxIdleTime; } - + /* ------------------------------------------------------------ */ /** * @param selectSets number of select sets to create */ public void setSelectSets(int selectSets) { - long lrc = _lowResourcesConnections * _selectSets; + long lrc = _lowResourcesConnections * _selectSets; _selectSets=selectSets; _lowResourcesConnections=lrc/_selectSets; } - + /* ------------------------------------------------------------ */ /** * @return the max idle time @@ -102,7 +96,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { return _maxIdleTime; } - + /* ------------------------------------------------------------ */ /** * @return the number of select sets in use @@ -114,14 +108,14 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa /* ------------------------------------------------------------ */ /** - * @param i + * @param i * @return The select set */ public SelectSet getSelectSet(int i) { return _selectSet[i]; } - + /* ------------------------------------------------------------ */ /** Register a channel * @param channel @@ -132,8 +126,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa // The ++ increment here is not atomic, but it does not matter. // so long as the value changes sometimes, then connections will // be distributed over the available sets. - - int s=_set++; + + int s=_set++; s=s%_selectSets; SelectSet[] sets=_selectSet; if (sets!=null) @@ -144,7 +138,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } } - + /* ------------------------------------------------------------ */ /** Register a channel * @param channel @@ -154,8 +148,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa // The ++ increment here is not atomic, but it does not matter. // so long as the value changes sometimes, then connections will // be distributed over the available sets. - - int s=_set++; + + int s=_set++; s=s%_selectSets; SelectSet[] sets=_selectSet; if (sets!=null) @@ -165,14 +159,14 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa set.wakeup(); } } - + /* ------------------------------------------------------------ */ /** Register a {@link ServerSocketChannel} * @param acceptChannel */ public void register(ServerSocketChannel acceptChannel) { - int s=_set++; + int s=_set++; s=s%_selectSets; SelectSet set=_selectSet[s]; set.addChange(acceptChannel); @@ -196,8 +190,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _selectorPriorityDelta=delta; } - - + + /* ------------------------------------------------------------ */ /** * @return the lowResourcesConnections @@ -237,7 +231,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _lowResourcesMaxIdleTime=(int)lowResourcesMaxIdleTime; } - + /* ------------------------------------------------------------------------------- */ public abstract boolean dispatch(Runnable task); @@ -254,7 +248,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa _selectSet[i]= new SelectSet(i); super.doStart(); - + // start a thread to Select for (int i=0;i _changes = new ConcurrentLinkedQueue(); - + private volatile Selector _selector; - + private volatile Thread _selecting; - private int _jvmBug; - private int _selects; - private long _monitorStart; + private int _busySelects; private long _monitorNext; private boolean _pausing; - private SelectionKey _busyKey; - private int _busyKeyCount; - private long _log; - private int _paused; - private int _jvmFix0; - private int _jvmFix1; - private int _jvmFix2; + private boolean _paused; private volatile long _idleTick; private ConcurrentMap _endPoints = new ConcurrentHashMap(); - + /* ------------------------------------------------------------ */ SelectSet(int acceptorID) throws Exception { @@ -416,11 +402,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa // create a selector; _selector = Selector.open(); - _monitorStart=System.currentTimeMillis(); - _monitorNext=_monitorStart+__MONITOR_PERIOD; - _log=_monitorStart+60000; + _monitorNext=System.currentTimeMillis()+__MONITOR_PERIOD; } - + /* ------------------------------------------------------------ */ public void addChange(Object change) { @@ -429,7 +413,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa /* ------------------------------------------------------------ */ public void addChange(SelectableChannel channel, Object att) - { + { if (att==null) addChange(channel); else if (att instanceof EndPoint) @@ -437,11 +421,11 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa else addChange(new ChannelAndAttachment(channel,att)); } - + /* ------------------------------------------------------------ */ /** * Select and dispatch tasks found from changes and the selector. - * + * * @throws IOException */ public void doSelect() throws IOException @@ -450,6 +434,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _selecting=Thread.currentThread(); final Selector selector=_selector; + // Stopped concurrently ? + if (selector == null) + return; // Make any key changes required Object change; @@ -458,7 +445,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { Channel ch=null; SelectionKey key=null; - + try { if (change instanceof EndPoint) @@ -475,7 +462,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa final SelectableChannel channel=asc._channel; ch=channel; final Object att = asc._attachment; - + if ((channel instanceof SocketChannel) && ((SocketChannel)channel).isConnected()) { key = channel.register(selector,SelectionKey.OP_READ,att); @@ -517,7 +504,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { if (e instanceof ThreadDeath) throw (ThreadDeath)e; - + if (isRunning()) LOG.warn(e); else @@ -525,7 +512,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa try { - ch.close(); + if (ch!=null) + ch.close(); } catch(IOException e2) { @@ -537,10 +525,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa // Do and instant select to see if any connections can be handled. int selected=selector.selectNow(); - _selects++; long now=System.currentTimeMillis(); - + // if no immediate things to do if (selected==0 && selector.selectedKeys().isEmpty()) { @@ -562,7 +549,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa _timeout.setNow(now); long to_next_timeout=_timeout.getTimeToNext(); - long wait = _changes.size()==0?__IDLE_TICK:0L; + long wait = _changes.size()==0?__IDLE_TICK:0L; if (wait > 0 && to_next_timeout >= 0 && wait > to_next_timeout) wait = to_next_timeout; @@ -571,24 +558,48 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { long before=now; selected=selector.select(wait); - _selects++; now = System.currentTimeMillis(); _timeout.setNow(now); - - if (__JVMBUG_THRESHHOLD>0) - checkJvmBugs(before, now, wait, selected); + + // If we are monitoring for busy selector + // and this select did not wait more than 1ms + if (__MONITOR_PERIOD>0 && now-before <=1) + { + // count this as a busy select and if there have been too many this monitor cycle + if (++_busySelects>__MAX_SELECTS) + { + // Start injecting pauses + _pausing=true; + + // if this is the first pause + if (!_paused) + { + // Log and dump some status + _paused=true; + LOG.warn("Selector {} is too busy, pausing!",this); + final SelectSet set = this; + SelectorManager.this.dispatch( + new Runnable(){ + public void run() + { + System.err.println(set+":\n"+set.dump()); + } + }); + } + } + } } } - + // have we been destroyed while sleeping if (_selector==null || !selector.isOpen()) return; // Look for things to do for (SelectionKey key: selector.selectedKeys()) - { + { SocketChannel channel=null; - + try { if (!key.isValid()) @@ -641,7 +652,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa SelectChannelEndPoint endpoint = createEndPoint(channel,key); key.attach(endpoint); if (key.isReadable()) - endpoint.schedule(); + endpoint.schedule(); } key = null; } @@ -665,15 +676,15 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { LOG.debug(e2); } - + if (key != null && !(key.channel() instanceof ServerSocketChannel) && key.isValid()) key.cancel(); } } - + // Everything always handled selector.selectedKeys().clear(); - + now=System.currentTimeMillis(); _timeout.setNow(now); Task task = _timeout.expired(); @@ -688,11 +699,11 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa if (now-_idleTick>__IDLE_TICK) { _idleTick=now; - + final long idle_now=((_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections)) ?(now+_maxIdleTime-_lowResourcesMaxIdleTime) :now; - + dispatch(new Runnable() { public void run() @@ -703,6 +714,16 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } } }); + + } + + // Reset busy select monitor counts + if (__MONITOR_PERIOD>0 && now>_monitorNext) + { + _busySelects=0; + _pausing=false; + _monitorNext=now+__MONITOR_PERIOD; + } } catch (ClosedSelectorException e) @@ -721,130 +742,10 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa _selecting=null; } } - + + /* ------------------------------------------------------------ */ - private void checkJvmBugs(long before, long now, long wait, int selected) - throws IOException - { - Selector selector = _selector; - if (selector==null) - return; - - // Look for JVM bugs over a monitor period. - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933 - // http://bugs.sun.com/view_bug.do?bug_id=6693490 - if (now>_monitorNext) - { - _selects=(int)(_selects*__MONITOR_PERIOD/(now-_monitorStart)); - _pausing=_selects>__MAX_SELECTS; - if (_pausing) - _paused++; - - _selects=0; - _jvmBug=0; - _monitorStart=now; - _monitorNext=now+__MONITOR_PERIOD; - } - - if (now>_log) - { - if (_paused>0) - LOG.debug(this+" Busy selector - injecting delay "+_paused+" times"); - - if (_jvmFix2>0) - LOG.debug(this+" JVM BUG(s) - injecting delay"+_jvmFix2+" times"); - - if (_jvmFix1>0) - LOG.debug(this+" JVM BUG(s) - recreating selector "+_jvmFix1+" times, cancelled keys "+_jvmFix0+" times"); - - else if(LOG.isDebugEnabled() && _jvmFix0>0) - LOG.debug(this+" JVM BUG(s) - cancelled keys "+_jvmFix0+" times"); - _paused=0; - _jvmFix2=0; - _jvmFix1=0; - _jvmFix0=0; - _log=now+60000; - } - - // If we see signature of possible JVM bug, increment count. - if (selected==0 && wait>10 && (now-before)<(wait/2)) - { - // Increment bug count and try a work around - _jvmBug++; - if (_jvmBug>(__JVMBUG_THRESHHOLD)) - { - try - { - if (_jvmBug==__JVMBUG_THRESHHOLD+1) - _jvmFix2++; - - Thread.sleep(__BUSY_PAUSE); // pause to avoid busy loop - } - catch(InterruptedException e) - { - LOG.ignore(e); - } - } - else if (_jvmBug==__JVMBUG_THRESHHOLD) - { - renewSelector(); - } - else if (_jvmBug%32==31) // heuristic attempt to cancel key 31,63,95,... loops - { - // Cancel keys with 0 interested ops - int cancelled=0; - for (SelectionKey k: selector.keys()) - { - if (k.isValid()&&k.interestOps()==0) - { - k.cancel(); - cancelled++; - } - } - if (cancelled>0) - _jvmFix0++; - - return; - } - } - else if (__BUSY_KEY>0 && selected==1 && _selects>__MAX_SELECTS) - { - // Look for busy key - SelectionKey busy = selector.selectedKeys().iterator().next(); - if (busy==_busyKey) - { - if (++_busyKeyCount>__BUSY_KEY && !(busy.channel() instanceof ServerSocketChannel)) - { - final SelectChannelEndPoint endpoint = (SelectChannelEndPoint)busy.attachment(); - LOG.warn("Busy Key "+busy.channel()+" "+endpoint); - busy.cancel(); - if (endpoint!=null) - { - dispatch(new Runnable() - { - public void run() - { - try - { - endpoint.close(); - } - catch (IOException e) - { - LOG.ignore(e); - } - } - }); - } - } - } - else - _busyKeyCount=0; - _busyKey=busy; - } - } - - /* ------------------------------------------------------------ */ - private void renewSelector() + private void renewSelector() { try { @@ -876,7 +777,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa throw new RuntimeException("recreating selector",e); } } - + /* ------------------------------------------------------------ */ public SelectorManager getManager() { @@ -891,9 +792,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa /* ------------------------------------------------------------ */ /** - * @param task The task to timeout. If it implements Runnable, then + * @param task The task to timeout. If it implements Runnable, then * expired will be called from a dispatched thread. - * + * * @param timeoutMs */ public void scheduleTimeout(Timeout.Task task, long timeoutMs) @@ -902,7 +803,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa throw new IllegalArgumentException("!Runnable"); _timeout.schedule(task, timeoutMs); } - + /* ------------------------------------------------------------ */ public void cancelTimeout(Timeout.Task task) { @@ -927,23 +828,24 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa renewSelector(); } }); - + renewSelector(); } } - + /* ------------------------------------------------------------ */ private SelectChannelEndPoint createEndPoint(SocketChannel channel, SelectionKey sKey) throws IOException { SelectChannelEndPoint endp = newEndPoint(channel,this,sKey); - endPointOpened(endp); + endPointOpened(endp); _endPoints.put(endp,this); return endp; } - + /* ------------------------------------------------------------ */ public void destroyEndPoint(SelectChannelEndPoint endp) { + LOG.debug("destroyEndPoint {}",endp); _endPoints.remove(endp); endPointClosed(endp); } @@ -953,11 +855,11 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { return _selector; } - + /* ------------------------------------------------------------ */ void stop() throws Exception { - // Spin for a while waiting for selector to complete + // Spin for a while waiting for selector to complete // to avoid unneccessary closed channel exceptions try { @@ -994,8 +896,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } } } - - + + _timeout.cancelAll(); try { @@ -1006,7 +908,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa catch (IOException e) { LOG.ignore(e); - } + } _selector=null; } } @@ -1021,9 +923,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa public void dump(Appendable out, String indent) throws IOException { out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_setID)).append("\n"); - + Thread selecting = _selecting; - + Object where = "not selecting"; StackTraceElement[] trace =selecting==null?null:selecting.getStackTrace(); if (trace!=null) @@ -1037,28 +939,32 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } Selector selector=_selector; - final ArrayList dump = new ArrayList(selector.keys().size()*2); - dump.add(where); - - final CountDownLatch latch = new CountDownLatch(1); - - addChange(new Runnable(){ - public void run() + if (selector!=null) + { + final ArrayList dump = new ArrayList(selector.keys().size()*2); + dump.add(where); + + final CountDownLatch latch = new CountDownLatch(1); + + addChange(new ChangeTask() { - dumpKeyState(dump); - latch.countDown(); + public void run() + { + dumpKeyState(dump); + latch.countDown(); + } + }); + + try + { + latch.await(5,TimeUnit.SECONDS); } - }); - - try - { - latch.await(5,TimeUnit.SECONDS); + catch(InterruptedException e) + { + LOG.ignore(e); + } + AggregateLifeCycle.dump(out,indent,dump); } - catch(InterruptedException e) - { - LOG.ignore(e); - } - AggregateLifeCycle.dump(out,indent,dump); } /* ------------------------------------------------------------ */ @@ -1081,7 +987,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { final SelectableChannel _channel; final Object _attachment; - + public ChannelAndAttachment(SelectableChannel channel, Object attachment) { super(); @@ -1101,12 +1007,12 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { _deferringInterestedOps0 = deferringInterestedOps0; } - + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ private interface ChangeTask extends Runnable {} - + } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslSelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslSelectChannelEndPoint.java index 02f91fb8e5f..d0b9bfb9ae5 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslSelectChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslSelectChannelEndPoint.java @@ -41,11 +41,10 @@ import org.eclipse.jetty.util.log.Logger; */ public class SslSelectChannelEndPoint extends SelectChannelEndPoint { - private static final Logger LOG = Log.getLogger(SslSelectChannelEndPoint.class); + public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio").getLogger("ssl"); - public static final Logger __log=Log.getLogger("org.eclipse.jetty.io.nio").getLogger("ssl"); - - private static final ByteBuffer[] __NO_BUFFERS={}; + private static final Buffer __EMPTY_BUFFER=new DirectNIOBuffer(0); + private static final ByteBuffer __ZERO_BUFFER=ByteBuffer.allocate(0); private final Buffers _buffers; @@ -54,15 +53,13 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint private volatile NIOBuffer _inNIOBuffer; private volatile NIOBuffer _outNIOBuffer; - private final ByteBuffer[] _gather=new ByteBuffer[2]; - private boolean _closing=false; private SSLEngineResult _result; - private boolean _handshook=false; - private boolean _allowRenegotiate=false; + private volatile boolean _handshook=false; + private boolean _allowRenegotiate=true; - private final boolean _debug = __log.isDebugEnabled(); // snapshot debug status for optimizer + private volatile boolean _debug = LOG.isDebugEnabled(); // snapshot debug status for optimizer /* ------------------------------------------------------------ */ public SslSelectChannelEndPoint(Buffers buffers,SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, SSLEngine engine, int maxIdleTime) @@ -75,7 +72,7 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint _engine=engine; _session=engine.getSession(); - if (_debug) __log.debug(_session+" channel="+channel); + if (_debug) LOG.debug(_session+" channel="+channel); } /* ------------------------------------------------------------ */ @@ -89,17 +86,15 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint _engine=engine; _session=engine.getSession(); - if (_debug) __log.debug(_session+" channel="+channel); + if (_debug) LOG.debug(_session+" channel="+channel); } - int _outCount; /* ------------------------------------------------------------ */ private void needOutBuffer() { synchronized (this) { - _outCount++; if (_outNIOBuffer==null) _outNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize()); } @@ -110,22 +105,19 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint { synchronized (this) { - if (--_outCount<=0 && _outNIOBuffer!=null && _outNIOBuffer.length()==0) + if (_outNIOBuffer!=null && _outNIOBuffer.length()==0) { _buffers.returnBuffer(_outNIOBuffer); _outNIOBuffer=null; - _outCount=0; } } } - int _inCount; /* ------------------------------------------------------------ */ private void needInBuffer() { synchronized (this) { - _inCount++; if(_inNIOBuffer==null) _inNIOBuffer=(NIOBuffer)_buffers.getBuffer(_session.getPacketBufferSize()); } @@ -136,15 +128,25 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint { synchronized (this) { - if (--_inCount<=0 &&_inNIOBuffer!=null && _inNIOBuffer.length()==0) + if (_inNIOBuffer!=null && _inNIOBuffer.length()==0) { _buffers.returnBuffer(_inNIOBuffer); _inNIOBuffer=null; - _inCount=0; } } } + /* ------------------------------------------------------------ */ + /** + * @return True if the endpoint has produced/consumed bytes itself (non application data). + */ + public boolean isProgressing() + { + SSLEngineResult result = _result; + _result=null; + return result!=null && (result.bytesConsumed()>0 || result.bytesProduced()>0); + } + /* ------------------------------------------------------------ */ /** * @return True if SSL re-negotiation is allowed (default false) @@ -186,138 +188,171 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint @Override public void shutdownOutput() throws IOException { - try - { - sslClose(); - } - finally - { - super.shutdownOutput(); - } + LOG.debug("{} shutdownOutput",_session); + // All SSL closes should be graceful, as it is more secure. + // So normal SSL close can be used here. + close(); } /* ------------------------------------------------------------ */ - protected void sslClose() throws IOException + private int process(ByteBuffer inBBuf, Buffer outBuf) throws IOException { - if (_closing) - return; - _closing=true; + if (_debug) + LOG.debug("{} process closing={} in={} out={}",_session,_closing,inBBuf,outBuf); - // TODO - this really should not be done in a loop here - but with async callbacks. - long end=System.currentTimeMillis()+getMaxIdleTime(); - try + // If there is no place to put incoming application data, + if (inBBuf==null) { - while (isOpen() && isBufferingOutput()&& System.currentTimeMillis()0) - flush(); - _outNIOBuffer.compact(); - int put=_outNIOBuffer.putIndex(); - out_buffer.position(put); - _result=null; - _result=_engine.wrap(__NO_BUFFERS,out_buffer); - if (_debug) __log.debug(_session+" close wrap "+_result); - _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); - } - catch(SSLException e) - { - super.close(); - throw e; - } - finally - { - out_buffer.position(0); - freeOutBuffer(); - } + int c=wrap(outBuf); + progress=c>0||_result.bytesProduced()>0||_result.bytesConsumed()>0; - break; + if (c>0) + sent+=c; + else if (c<0 && sent==0) + sent=-1; } + + // Try unwrapping some application data + if (inBBuf.remaining()>0 && _inNIOBuffer!=null && _inNIOBuffer.hasContent()) + { + int space=inBBuf.remaining(); + progress|=unwrap(inBBuf); + received+=space-inBBuf.remaining(); + } + break; + + + case NEED_TASK: + { + // A task needs to be run, so run it! + Runnable task; + while ((task=_engine.getDelegatedTask())!=null) + { + progress=true; + task.run(); + } + + // Detect SUN JVM Bug!!! + if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && + _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && sent==0) + { + // This should be NEED_WRAP + // The fix simply detects the signature of the bug and then close the connection (fail-fast) so that ff3 will delegate to using SSL instead of TLS. + // This is a jvm bug on java1.6 where the SSLEngine expects more data from the initial handshake when the client(ff3-tls) already had given it. + // See http://jira.codehaus.org/browse/JETTY-567 for more details + if (_debug) LOG.warn("{} JETTY-567",_session); + return -1; + } + break; + } + + case NEED_WRAP: + { + checkRenegotiate(); + + // The SSL needs to send some handshake data to the other side + int c=0; + if (outBuf!=null && outBuf.hasContent()) + c=wrap(outBuf); + else + c=wrap(__EMPTY_BUFFER); + + progress=_result.bytesProduced()>0||_result.bytesConsumed()>0; + if (c>0) + sent+=c; + else if (c<0 && sent==0) + sent=-1; + break; + } + + case NEED_UNWRAP: + { + checkRenegotiate(); + + // Need more data to be unwrapped so try another call to unwrap + progress|=unwrap(inBBuf); + if (_closing) + inBBuf.clear(); + break; } } + + if (_debug) LOG.debug("{} progress {}",_session,progress); } - catch (ThreadDeath x) - { - super.close(); - throw x; - } - catch (Throwable x) - { - LOG.debug(x); - super.close(); - } + + if (_debug) LOG.debug("{} received {} sent {}",_session,received,sent); + + freeInBuffer(); + return (received<0||sent<0)?-1:(received+sent); } + + /* ------------------------------------------------------------ */ @Override public void close() throws IOException { + if (_closing) + return; + + _closing=true; + LOG.debug("{} close",_session); try { - sslClose(); + _engine.closeOutbound(); + process(null,null); + } + catch (IOException e) + { + // We could not write the SSL close message because the + // socket was already closed, nothing more we can do. + LOG.ignore(e); } finally { @@ -332,149 +367,29 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint */ @Override public int fill(Buffer buffer) throws IOException - { + { + _debug=LOG.isDebugEnabled(); + LOG.debug("{} fill",_session); // This end point only works on NIO buffer type (director // or indirect), so extract the NIO buffer that is wrapped // by the passed jetty Buffer. - final ByteBuffer bbuf=extractInputBuffer(buffer); + ByteBuffer bbuf=((NIOBuffer)buffer).getByteBuffer(); + // remember the original size of the unencrypted buffer int size=buffer.length(); - HandshakeStatus initialStatus = _engine.getHandshakeStatus(); - //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (bbuf) { + bbuf.position(buffer.putIndex()); try { // Call the SSLEngine unwrap method to process data in // the inBuffer. If there is no data in the inBuffer, then // super.fill is called to read encrypted bytes. unwrap(bbuf); - - // Loop through the SSL engine state machine - - int wraps=0; - loop: while (true) - { - // If we have encrypted data in output buffer - if (isBufferingOutput()) - { - // we must flush it, as the other end might be - // waiting for that outgoing data before sending - // more incoming data - flush(); - - // If we were unable to flush all the data, then - // we should break the loop and wait for the call - // back to handle when the SelectSet detects that - // the channel is writable again. - if (isBufferingOutput()) - break loop; - } - - // handle the current hand share status - switch(_engine.getHandshakeStatus()) - { - case FINISHED: - case NOT_HANDSHAKING: - // If we are closing, then unwrap must have CLOSED result, - // so return -1 to signal upwards - if (_closing) - return -1; - - // otherwise we break loop with the data we have unwrapped. - break loop; - - case NEED_UNWRAP: - checkRenegotiate(); - // Need more data to be unwrapped so try another call to unwrap - if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) - { - // If the unwrap call did not make any progress and we are still in - // NEED_UNWRAP, then we should break the loop and wait for more data to - // arrive. - break loop; - } - // progress was made so continue the loop. - break; - - case NEED_TASK: - { - // A task needs to be run, so run it! - - Runnable task; - while ((task=_engine.getDelegatedTask())!=null) - { - task.run(); - } - - // Detect SUN JVM Bug!!! - if(initialStatus==HandshakeStatus.NOT_HANDSHAKING && - _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP && wraps==0) - { - // This should be NEED_WRAP - // The fix simply detects the signature of the bug and then close the connection (fail-fast) so that ff3 will delegate to using SSL instead of TLS. - // This is a jvm bug on java1.6 where the SSLEngine expects more data from the initial handshake when the client(ff3-tls) already had given it. - // See http://jira.codehaus.org/browse/JETTY-567 for more details - if (_debug) __log.warn(_session+" JETTY-567"); - return -1; - } - break; - } - - case NEED_WRAP: - { - checkRenegotiate(); - // The SSL needs to send some handshake data to the other side, - // so let fill become a flush for a little bit. - wraps++; - needOutBuffer(); - ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); - synchronized(out_buffer) - { - try - { - // call wrap with empty application buffers, so it can - // generate required handshake messages into _outNIOBuffer - _outNIOBuffer.compact(); - int put=_outNIOBuffer.putIndex(); - out_buffer.position(); - _result=null; - _result=_engine.wrap(__NO_BUFFERS,out_buffer); - if (_debug) __log.debug(_session+" fill wrap "+_result); - switch(_result.getStatus()) - { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - LOG.warn("wrap {}",_result); - _closing=true; - break; - case CLOSED: - _closing=true; - break; - } - _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); - } - catch(SSLException e) - { - super.close(); - throw e; - } - finally - { - out_buffer.position(0); - } - } - - // flush the encrypted outNIOBuffer - flush(); - freeOutBuffer(); - - break; - } - } - } + process(bbuf,null); } finally { @@ -482,20 +397,22 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint buffer.setPutIndex(bbuf.position()); bbuf.position(0); } - - // return the number of unencrypted bytes filled. - int filled=buffer.length()-size; - if (filled>0) - _handshook=true; - return filled; } + // return the number of unencrypted bytes filled. + int filled=buffer.length()-size; + if (filled==0 && (isInputShutdown() || !isOpen())) + return -1; + + return filled; } /* ------------------------------------------------------------ */ @Override public int flush(Buffer buffer) throws IOException { - return flush(buffer,null,null); + _debug=LOG.isDebugEnabled(); + LOG.debug("{} flush1",_session); + return process(null,buffer); } @@ -505,162 +422,57 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint @Override public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException { - int consumed=0; - int available=header==null?0:header.length(); - if (buffer!=null) - available+=buffer.length(); + _debug=LOG.isDebugEnabled(); + LOG.debug("{} flush3",_session); - needOutBuffer(); - ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); - loop: while (true) + int len=0; + int flushed=0; + if (header!=null && header.hasContent()) { - if (_outNIOBuffer.length()>0) - { - flush(); - if (isBufferingOutput()) - break loop; - } - - switch(_engine.getHandshakeStatus()) - { - case FINISHED: - case NOT_HANDSHAKING: - if (_closing || available==0) - { - if (consumed==0) - consumed= -1; - break loop; - } - - int c; - if (header!=null && header.length()>0) - { - if (buffer!=null && buffer.length()>0) - c=wrap(header,buffer); - else - c=wrap(header); - } - else - c=wrap(buffer); - - - if (c>0) - { - _handshook=true; - consumed+=c; - available-=c; - } - else if (c<0) - { - if (consumed==0) - consumed=-1; - break loop; - } - - break; - - case NEED_UNWRAP: - checkRenegotiate(); - Buffer buf =_buffers.getBuffer(_engine.getSession().getApplicationBufferSize()); - try - { - ByteBuffer bbuf = ((NIOBuffer)buf).getByteBuffer(); - if (!unwrap(bbuf) && _engine.getHandshakeStatus()==HandshakeStatus.NEED_UNWRAP) - { - break loop; - } - } - finally - { - _buffers.returnBuffer(buf); - } - - break; - - case NEED_TASK: - { - Runnable task; - while ((task=_engine.getDelegatedTask())!=null) - { - task.run(); - } - break; - } - - case NEED_WRAP: - { - checkRenegotiate(); - synchronized(out_buffer) - { - try - { - _outNIOBuffer.compact(); - int put=_outNIOBuffer.putIndex(); - out_buffer.position(); - _result=null; - _result=_engine.wrap(__NO_BUFFERS,out_buffer); - if (_debug) __log.debug(_session+" flush wrap "+_result); - switch(_result.getStatus()) - { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - LOG.warn("unwrap {}",_result); - _closing=true; - break; - case CLOSED: - _closing=true; - break; - } - _outNIOBuffer.setPutIndex(put+_result.bytesProduced()); - } - catch(SSLException e) - { - super.close(); - throw e; - } - finally - { - out_buffer.position(0); - } - } - - flush(); - if (isBufferingOutput()) - break loop; - - break; - } - } + len=header.length(); + flushed=flush(header); + } + if (flushed==len && buffer!=null && buffer.hasContent()) + { + int f=flush(buffer); + if (f>=0) + flushed+=f; + else if (flushed==0) + flushed=-1; } - freeOutBuffer(); - return consumed; + return flushed; } /* ------------------------------------------------------------ */ @Override public void flush() throws IOException { - if (_outNIOBuffer==null) - return; + LOG.debug("{} flush",_session); + if (!isOpen()) + throw new EofException(); - int len=_outNIOBuffer.length(); if (isBufferingOutput()) { int flushed=super.flush(_outNIOBuffer); - if (_debug) __log.debug(_session+" Flushed "+flushed+"/"+len); - if (isBufferingOutput()) + if (_debug) + LOG.debug("{} flushed={} left={}",_session,flushed,_outNIOBuffer.length()); + } + else if (_engine.isOutboundDone() && super.isOpen()) + { + if (_debug) + LOG.debug("{} flush shutdownOutput",_session); + try { - // Try again after yield.... cheaper than a reschedule. - Thread.yield(); - flushed=super.flush(_outNIOBuffer); - if (_debug) __log.debug(_session+" flushed "+flushed+"/"+len); + super.shutdownOutput(); } - else if (_closing && !_engine.isOutboundDone()) + catch(IOException e) { - _engine.closeOutbound(); + LOG.ignore(e); } } + + freeOutBuffer(); } /* ------------------------------------------------------------ */ @@ -668,21 +480,11 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint { if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen()) { - LOG.warn("SSL renegotiate denied: "+_channel); + LOG.warn("SSL renegotiate denied: {}",_channel); super.close(); } } - /* ------------------------------------------------------------ */ - private ByteBuffer extractInputBuffer(Buffer buffer) - { - assert buffer instanceof NIOBuffer; - NIOBuffer nbuf=(NIOBuffer)buffer; - ByteBuffer bbuf=nbuf.getByteBuffer(); - bbuf.position(buffer.putIndex()); - return bbuf; - } - /* ------------------------------------------------------------ */ /** * @return true if progress is made @@ -692,46 +494,30 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint needInBuffer(); ByteBuffer in_buffer=_inNIOBuffer.getByteBuffer(); - if (_inNIOBuffer.hasContent()) - _inNIOBuffer.compact(); - else - _inNIOBuffer.clear(); + _inNIOBuffer.compact(); int total_filled=0; boolean remoteClosed = false; + + LOG.debug("{} unwrap space={} open={}",_session,_inNIOBuffer.space(),super.isOpen()); + // loop filling as much encrypted data as we can into the buffer while (_inNIOBuffer.space()>0 && super.isOpen()) { - try - { - int filled=super.fill(_inNIOBuffer); - if (_debug) __log.debug(_session+" unwrap filled "+filled); - if (filled < 0) - remoteClosed = true; - // break the loop if no progress is made (we have read everything there is to read) - if (filled<=0) - break; - total_filled+=filled; - } - catch(IOException e) - { - if (_inNIOBuffer.length()==0) - { - freeInBuffer(); - if (_outNIOBuffer!=null) - { - _outNIOBuffer.clear(); - freeOutBuffer(); - } - throw e; - } + int filled=super.fill(_inNIOBuffer); + if (_debug) LOG.debug("{} filled {}",_session,filled); + if (filled < 0) + remoteClosed = true; + // break the loop if no progress is made (we have read everything there is to read) + if (filled<=0) break; - } + total_filled+=filled; } // If we have no progress and no data if (total_filled==0 && _inNIOBuffer.length()==0) { + // Do we need to close? if (isOpen() && remoteClosed) { try @@ -746,9 +532,6 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint } } - freeInBuffer(); - freeOutBuffer(); - if (!isOpen()) throw new EofException(); @@ -765,15 +548,16 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint // Do the unwrap _result=_engine.unwrap(in_buffer,buffer); - if (_debug) __log.debug(_session+" unwrap unwrap "+_result); + if (!_handshook && _result.getHandshakeStatus()==SSLEngineResult.HandshakeStatus.FINISHED) + _handshook=true; + if (_debug) LOG.debug("{} unwrap {}",_session,_result); // skip the bytes consumed _inNIOBuffer.skip(_result.bytesConsumed()); } catch(SSLException e) { - LOG.warn(getRemoteAddr() + ":" + getRemotePort() + " " + e); - freeOutBuffer(); + LOG.warn(getRemoteAddr() + ":" + getRemotePort() + " ",e); super.close(); throw e; } @@ -782,21 +566,21 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint // reset the buffer so it can be managed by the _inNIOBuffer again. in_buffer.position(0); in_buffer.limit(in_buffer.capacity()); - freeInBuffer(); } // handle the unwrap results switch(_result.getStatus()) { case BUFFER_OVERFLOW: - throw new IllegalStateException(_result.toString()+" "+buffer.position()+" "+buffer.limit()); + LOG.debug("{} unwrap overflow",_session); + return false; case BUFFER_UNDERFLOW: // Not enough data, // If we are closed, we will never get more, so EOF // else return and we will be tried again // later when more data arriving causes another dispatch. - if (LOG.isDebugEnabled()) LOG.debug("unwrap {}",_result); + if (LOG.isDebugEnabled()) LOG.debug("{} unwrap {}",_session,_result); if(!isOpen()) { _inNIOBuffer.clear(); @@ -810,16 +594,17 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint _closing=true; // return true is some bytes somewhere were moved about. return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; + case OK: // return true is some bytes somewhere were moved about. return total_filled>0 ||_result.bytesConsumed()>0 || _result.bytesProduced()>0; + default: - LOG.warn("unwrap "+_result); + LOG.warn("{} unwrap default: {}",_session,_result); throw new IOException(_result.toString()); } } - /* ------------------------------------------------------------ */ private ByteBuffer extractOutputBuffer(Buffer buffer) { @@ -829,155 +614,61 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint return ByteBuffer.wrap(buffer.array()); } - /* ------------------------------------------------------------ */ - private int wrap(final Buffer header, final Buffer buffer) throws IOException - { - _gather[0]=extractOutputBuffer(header); - - synchronized(_gather[0]) - { - _gather[0].position(header.getIndex()); - _gather[0].limit(header.putIndex()); - - _gather[1]=extractOutputBuffer(buffer); - - synchronized(_gather[1]) - { - _gather[1].position(buffer.getIndex()); - _gather[1].limit(buffer.putIndex()); - - needOutBuffer(); - ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); - synchronized(out_buffer) - { - int consumed=0; - try - { - _outNIOBuffer.clear(); - out_buffer.position(0); - out_buffer.limit(out_buffer.capacity()); - - _result=null; - _result=_engine.wrap(_gather,out_buffer); - if (_debug) __log.debug(_session+" wrap wrap "+_result); - _outNIOBuffer.setGetIndex(0); - _outNIOBuffer.setPutIndex(_result.bytesProduced()); - consumed=_result.bytesConsumed(); - } - catch(SSLException e) - { - LOG.warn(getRemoteAddr()+":"+getRemotePort()+" "+e); - super.close(); - throw e; - } - finally - { - out_buffer.position(0); - - if (consumed>0) - { - int len=consumed0) - { - int len=consumed0?_result.bytesConsumed():-1; - - case OK: - return _result.bytesConsumed(); - case CLOSED: - _closing=true; - return _result.bytesConsumed()>0?_result.bytesConsumed():-1; - - default: - LOG.warn("wrap "+_result); - throw new IOException(_result.toString()); - } - } - /* ------------------------------------------------------------ */ private int wrap(final Buffer buffer) throws IOException { - _gather[0]=extractOutputBuffer(buffer); - synchronized(_gather[0]) + ByteBuffer bbuf=extractOutputBuffer(buffer); + synchronized(bbuf) { - ByteBuffer bb; - - _gather[0].position(buffer.getIndex()); - _gather[0].limit(buffer.putIndex()); - int consumed=0; needOutBuffer(); + _outNIOBuffer.compact(); ByteBuffer out_buffer=_outNIOBuffer.getByteBuffer(); synchronized(out_buffer) { try { - _outNIOBuffer.clear(); - out_buffer.position(0); + bbuf.position(buffer.getIndex()); + bbuf.limit(buffer.putIndex()); + out_buffer.position(_outNIOBuffer.putIndex()); out_buffer.limit(out_buffer.capacity()); - _result=null; - _result=_engine.wrap(_gather[0],out_buffer); - if (_debug) __log.debug(_session+" wrap wrap "+_result); - _outNIOBuffer.setGetIndex(0); - _outNIOBuffer.setPutIndex(_result.bytesProduced()); + _result=_engine.wrap(bbuf,out_buffer); + if (_debug) LOG.debug("{} wrap {}",_session,_result); + if (!_handshook && _result.getHandshakeStatus()==SSLEngineResult.HandshakeStatus.FINISHED) + _handshook=true; + _outNIOBuffer.setPutIndex(out_buffer.position()); consumed=_result.bytesConsumed(); } catch(SSLException e) { - LOG.warn(getRemoteAddr()+":"+getRemotePort()+" "+e); + LOG.warn(getRemoteAddr()+":"+getRemotePort()+" ",e); super.close(); throw e; } finally { out_buffer.position(0); + bbuf.position(0); + bbuf.limit(bbuf.capacity()); if (consumed>0) { int len=consumed0?_result.bytesConsumed():-1; + throw new IllegalStateException(); + + case BUFFER_OVERFLOW: + LOG.debug("{} wrap {}",_session,_result); + flush(); + return 0; case OK: return _result.bytesConsumed(); @@ -986,7 +677,7 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint return _result.bytesConsumed()>0?_result.bytesConsumed():-1; default: - LOG.warn("wrap "+_result); + LOG.warn("{} wrap default {}",_session,_result); throw new IOException(_result.toString()); } } @@ -996,15 +687,15 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint public boolean isBufferingInput() { final Buffer in = _inNIOBuffer; - return in==null?false:_inNIOBuffer.hasContent(); + return in!=null && in.hasContent(); } /* ------------------------------------------------------------ */ @Override public boolean isBufferingOutput() { - final NIOBuffer b=_outNIOBuffer; - return b==null?false:b.hasContent(); + final NIOBuffer out = _outNIOBuffer; + return out!=null && out.hasContent(); } /* ------------------------------------------------------------ */ @@ -1020,23 +711,14 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint return _engine; } - /* ------------------------------------------------------------ */ - @Override - public void scheduleWrite() - { - // only set !writable if we are not waiting for input - if (!HandshakeStatus.NEED_UNWRAP.equals(_engine.getHandshakeStatus()) || super.isBufferingOutput()) - super.scheduleWrite(); - } - /* ------------------------------------------------------------ */ @Override public String toString() { final NIOBuffer i=_inNIOBuffer; final NIOBuffer o=_outNIOBuffer; - return "SSL"+super.toString()+","+_engine.getHandshakeStatus()+", in/out="+ - (i==null?0:_inNIOBuffer.length())+"/"+(o==null?0:o.length())+ + return "SSL"+super.toString()+","+(_engine==null?"-":_engine.getHandshakeStatus())+", in/out="+ + (i==null?0:i.length())+"/"+(o==null?0:o.length())+ " bi/o="+isBufferingInput()+"/"+isBufferingOutput()+ " "+_result; } diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java index 72fd8e35d00..1ca3b75fdfc 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java @@ -14,9 +14,15 @@ package org.eclipse.jetty.io; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.Reader; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; import org.eclipse.jetty.util.IO; import org.junit.Test; @@ -42,4 +48,88 @@ public class IOTest out.toString(), "The quick brown fox jumped over the lazy dog"); } + + @Test + public void testHalfCloses() throws Exception + { + ServerSocket connector = new ServerSocket(0); + + Socket client = new Socket("localhost",connector.getLocalPort()); + System.err.println(client); + Socket server = connector.accept(); + System.err.println(server); + + // we can write both ways + client.getOutputStream().write(1); + assertEquals(1,server.getInputStream().read()); + server.getOutputStream().write(1); + assertEquals(1,client.getInputStream().read()); + + // shutdown output results in read -1 + client.shutdownOutput(); + assertEquals(-1,server.getInputStream().read()); + + // Even though EOF has been read, the server input is not seen as shutdown + assertFalse(server.isInputShutdown()); + + // and we can read -1 again + assertEquals(-1,server.getInputStream().read()); + + // but cannot write + try { client.getOutputStream().write(1); assertTrue(false); } catch (SocketException e) {} + + // but can still write in opposite direction. + server.getOutputStream().write(1); + assertEquals(1,client.getInputStream().read()); + + + // server can shutdown input to match the shutdown out of client + server.shutdownInput(); + + // now we EOF instead of reading -1 + try { server.getInputStream().read(); assertTrue(false); } catch (SocketException e) {} + + + // but can still write in opposite direction. + server.getOutputStream().write(1); + assertEquals(1,client.getInputStream().read()); + + // client can shutdown input + client.shutdownInput(); + + // now we EOF instead of reading -1 + try { client.getInputStream().read(); assertTrue(false); } catch (SocketException e) {} + + // But we can still write at the server (data which will never be read) + server.getOutputStream().write(1); + + // and the server output is not shutdown + assertFalse( server.isOutputShutdown() ); + + // until we explictly shut it down + server.shutdownOutput(); + + // and now we can't write + try { server.getOutputStream().write(1); assertTrue(false); } catch (SocketException e) {} + + // but the sockets are still open + assertFalse(client.isClosed()); + assertFalse(server.isClosed()); + + // but if we close one end + client.close(); + + // it is seen as closed. + assertTrue(client.isClosed()); + + // but not the other end + assertFalse(server.isClosed()); + + // which has to be closed explictly + server.close(); + assertTrue(server.isClosed()); + + + + } } diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java new file mode 100644 index 00000000000..14a2b8fe35d --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/BindingEnumeration.java @@ -0,0 +1,69 @@ +// ======================================================================== +// Copyright (c) 2011 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.jndi; + +import java.util.Iterator; + +import javax.naming.Binding; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +/** BindingEnumeration + *

    Implementation of NamingEnumeration + * + *

    Notes

    + *

    Used to return results of Context.listBindings(); + * + *

    Usage

    + * + */ +public class BindingEnumeration implements NamingEnumeration +{ + Iterator _delegate; + + public BindingEnumeration (Iterator e) + { + _delegate = e; + } + + public void close() + throws NamingException + { + } + + public boolean hasMore () + throws NamingException + { + return _delegate.hasNext(); + } + + public Binding next() + throws NamingException + { + Binding b = (Binding)_delegate.next(); + return new Binding (b.getName(), b.getClassName(), b.getObject(), true); + } + + public boolean hasMoreElements() + { + return _delegate.hasNext(); + } + + public Binding nextElement() + { + Binding b = (Binding)_delegate.next(); + return new Binding (b.getName(), b.getClassName(), b.getObject(),true); + } +} \ No newline at end of file diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NameEnumeration.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NameEnumeration.java new file mode 100644 index 00000000000..432fa65f3df --- /dev/null +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NameEnumeration.java @@ -0,0 +1,69 @@ +// ======================================================================== +// Copyright (c) 2011 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +package org.eclipse.jetty.jndi; + +import java.util.Iterator; + +import javax.naming.Binding; +import javax.naming.NameClassPair; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; + +/** NameEnumeration + *

    Implementation of NamingEnumeration interface. + * + *

    Notes

    + *

    Used for returning results of Context.list(); + * + *

    Usage

    + * + */ +public class NameEnumeration implements NamingEnumeration +{ + Iterator _delegate; + + public NameEnumeration (Iterator e) + { + _delegate = e; + } + + public void close() + throws NamingException + { + } + + public boolean hasMore () + throws NamingException + { + return _delegate.hasNext(); + } + + public NameClassPair next() + throws NamingException + { + Binding b = _delegate.next(); + return new NameClassPair(b.getName(),b.getClassName(),true); + } + + public boolean hasMoreElements() + { + return _delegate.hasNext(); + } + + public NameClassPair nextElement() + { + Binding b = _delegate.next(); + return new NameClassPair(b.getName(),b.getClassName(),true); + } +} \ No newline at end of file diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java index e6dec8bbae0..da2ee7d35b0 100644 --- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/NamingContext.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -31,7 +30,6 @@ import javax.naming.InitialContext; import javax.naming.LinkRef; import javax.naming.Name; import javax.naming.NameAlreadyBoundException; -import javax.naming.NameClassPair; import javax.naming.NameNotFoundException; import javax.naming.NameParser; import javax.naming.NamingEnumeration; @@ -53,17 +51,8 @@ import org.eclipse.jetty.util.log.Logger; *

    Notes

    *

    All Names are expected to be Compound, not Composite. * - *

    Usage

    - *
    + * 
      */
    -/*
    -* 
    -* -* @see -* -* -* @version 1.0 -*/ public class NamingContext implements Context, Cloneable, Dumpable { private final static Logger __log=NamingUtil.__log; @@ -101,123 +90,6 @@ public class NamingContext implements Context, Cloneable, Dumpable void unbind(NamingContext ctx, Binding binding); } - /*------------------------------------------------*/ - /** NameEnumeration - *

    Implementation of NamingEnumeration interface. - * - *

    Notes

    - *

    Used for returning results of Context.list(); - * - *

    Usage

    - *
    -     */
    -    /*
    -     * 
    - * - * @see - * - */ - public class NameEnumeration implements NamingEnumeration - { - Iterator _delegate; - - public NameEnumeration (Iterator e) - { - _delegate = e; - } - - public void close() - throws NamingException - { - } - - public boolean hasMore () - throws NamingException - { - return _delegate.hasNext(); - } - - public NameClassPair next() - throws NamingException - { - Binding b = _delegate.next(); - return new NameClassPair(b.getName(),b.getClassName(),true); - } - - public boolean hasMoreElements() - { - return _delegate.hasNext(); - } - - public NameClassPair nextElement() - { - Binding b = _delegate.next(); - return new NameClassPair(b.getName(),b.getClassName(),true); - } - } - - - - - - - /*------------------------------------------------*/ - /** BindingEnumeration - *

    Implementation of NamingEnumeration - * - *

    Notes

    - *

    Used to return results of Context.listBindings(); - * - *

    Usage

    - *
    -     */
    -    /*
    -     * 
    - * - * @see - * - */ - public class BindingEnumeration implements NamingEnumeration - { - Iterator _delegate; - - public BindingEnumeration (Iterator e) - { - _delegate = e; - } - - public void close() - throws NamingException - { - } - - public boolean hasMore () - throws NamingException - { - return _delegate.hasNext(); - } - - public Binding next() - throws NamingException - { - Binding b = (Binding)_delegate.next(); - return new Binding (b.getName(), b.getClassName(), b.getObject(), true); - } - - public boolean hasMoreElements() - { - return _delegate.hasNext(); - } - - public Binding nextElement() - { - Binding b = (Binding)_delegate.next(); - return new Binding (b.getName(), b.getClassName(), b.getObject(),true); - } - } - - - /*------------------------------------------------*/ /** * Constructor @@ -240,26 +112,6 @@ public class NamingContext implements Context, Cloneable, Dumpable } - /*------------------------------------------------*/ - /** - * Creates a new NamingContext instance. - * - * @param env a Hashtable value - */ - public NamingContext (Hashtable env) - { - if (env != null) - _env.putAll(env); - } - - /*------------------------------------------------*/ - /** - * Constructor - * - */ - public NamingContext () - { - } /*------------------------------------------------*/ @@ -312,8 +164,24 @@ public class NamingContext implements Context, Cloneable, Dumpable _parser = parser; } + + public void setEnv (Hashtable env) + { + _env.clear(); + _env.putAll(env); + } + + public Map getBindings () + { + return _bindings; + } + public void setBindings(Map bindings) + { + _bindings = bindings; + } + /*------------------------------------------------*/ /** * Bind a name to an object @@ -435,8 +303,6 @@ public class NamingContext implements Context, Cloneable, Dumpable ne.setRemainingName(name); throw ne; } - - Name cname = toCanonicalName (name); @@ -521,7 +387,7 @@ public class NamingContext implements Context, Cloneable, Dumpable /*------------------------------------------------*/ /** - * Not supported + * * * @param name name of subcontext to remove * @exception NamingException if an error occurs @@ -536,7 +402,7 @@ public class NamingContext implements Context, Cloneable, Dumpable /*------------------------------------------------*/ /** - * Not supported + * * * @param name name of subcontext to remove * @exception NamingException if an error occurs @@ -1128,7 +994,6 @@ public class NamingContext implements Context, Cloneable, Dumpable ctx = binding.getObject(); - if (ctx instanceof Reference) { //deference the object @@ -1154,8 +1019,7 @@ public class NamingContext implements Context, Cloneable, Dumpable } else throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); - } - + } } /*------------------------------------------------*/ @@ -1182,11 +1046,11 @@ public class NamingContext implements Context, Cloneable, Dumpable * @param newName a Name value * @exception NamingException if an error occurs */ public void rename(String oldName, - String newName) - throws NamingException - { - throw new OperationNotSupportedException(); - } + String newName) + throws NamingException + { + throw new OperationNotSupportedException(); + } @@ -1247,9 +1111,7 @@ public class NamingContext implements Context, Cloneable, Dumpable */ public void close () throws NamingException - { - - + { } @@ -1362,7 +1224,7 @@ public class NamingContext implements Context, Cloneable, Dumpable * @param name a Name value * @param obj an Object value */ - protected void addBinding (Name name, Object obj) throws NameAlreadyBoundException + public void addBinding (Name name, Object obj) throws NameAlreadyBoundException { String key = name.toString(); Binding binding=new Binding (key, obj); @@ -1394,7 +1256,7 @@ public class NamingContext implements Context, Cloneable, Dumpable * @param name a Name value * @return a Binding value */ - protected Binding getBinding (Name name) + public Binding getBinding (Name name) { return (Binding) _bindings.get(name.toString()); } @@ -1407,13 +1269,13 @@ public class NamingContext implements Context, Cloneable, Dumpable * @param name as a String * @return null or the Binding */ - protected Binding getBinding (String name) + public Binding getBinding (String name) { return (Binding) _bindings.get(name); } /*------------------------------------------------*/ - protected void removeBinding (Name name) + public void removeBinding (Name name) { String key = name.toString(); if (__log.isDebugEnabled()) @@ -1455,7 +1317,7 @@ public class NamingContext implements Context, Cloneable, Dumpable } /* ------------------------------------------------------------ */ - private boolean isLocked() + public boolean isLocked() { if ((_env.get(LOCK_PROPERTY) == null) && (_env.get(UNLOCK_PROPERTY) == null)) return false; diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java index 09dcfc8f49a..d26b8475b18 100644 --- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/javaRootURLContext.java @@ -67,8 +67,7 @@ public class javaRootURLContext implements Context try { __javaNameParser = new javaNameParser(); - __nameRoot = new NamingContext(); - __nameRoot.setNameParser(__javaNameParser); + __nameRoot = new NamingContext(null,null,null,__javaNameParser); StringRefAddr parserAddr = new StringRefAddr("parser", __javaNameParser.getClass().getName()); diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java index 7c187a7166e..a915ad1917d 100644 --- a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java +++ b/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/localContextRoot.java @@ -13,36 +13,63 @@ package org.eclipse.jetty.jndi.local; +import java.util.Collections; +import java.util.HashMap; import java.util.Hashtable; +import java.util.List; +import java.util.Map; import java.util.Properties; +import javax.naming.Binding; import javax.naming.CompoundName; import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.LinkRef; import javax.naming.Name; +import javax.naming.NameAlreadyBoundException; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; import javax.naming.NameParser; import javax.naming.NamingEnumeration; import javax.naming.NamingException; +import javax.naming.NotContextException; +import javax.naming.OperationNotSupportedException; +import javax.naming.Reference; +import javax.naming.Referenceable; +import javax.naming.spi.NamingManager; +import org.eclipse.jetty.jndi.BindingEnumeration; +import org.eclipse.jetty.jndi.NameEnumeration; import org.eclipse.jetty.jndi.NamingContext; +import org.eclipse.jetty.jndi.NamingUtil; +import org.eclipse.jetty.util.log.Logger; /** * * localContext * + * Implementation of the delegate for InitialContext for the local namespace. + * * * @version $Revision: 4780 $ $Date: 2009-03-17 16:36:08 +0100 (Tue, 17 Mar 2009) $ * */ public class localContextRoot implements Context { - private static final NamingContext __root = new NamingContext(); + private final static Logger __log=NamingUtil.__log; + protected final static NamingContext __root = new NamingRoot(); private final Hashtable _env; + - // make a root for the static namespace local: - static + static class NamingRoot extends NamingContext { - __root.setNameParser(new LocalNameParser()); + public NamingRoot() + { + super (null,null,null,new LocalNameParser()); + } } + + static class LocalNameParser implements NameParser { @@ -61,6 +88,19 @@ public class localContextRoot implements Context } } + + /* + * Root has to use the localContextRoot's env for all operations. + * So, if createSubcontext in the root, use the env of the localContextRoot. + * If lookup binding in the root, use the env of the localContextRoot. + * + */ + + + + + + public static NamingContext getRoot() { return __root; @@ -91,6 +131,21 @@ public class localContextRoot implements Context return ""; } + + /** + * + * + * @see javax.naming.Context#destroySubcontext(javax.naming.Name) + */ + public void destroySubcontext(Name name) throws NamingException + { + synchronized (__root) + { + __root.destroySubcontext(getSuffix(name)); + } + } + + /** * * @@ -100,23 +155,12 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.destroySubcontext(getSuffix(name)); - } - } - - /** - * - * - * @see javax.naming.Context#unbind(java.lang.String) - */ - public void unbind(String name) throws NamingException - { - synchronized (__root) - { - __root.unbind(getSuffix(name)); + + destroySubcontext(__root.getNameParser("").parse(getSuffix(name))); } } + /** * * @@ -127,18 +171,7 @@ public class localContextRoot implements Context return _env; } - /** - * - * - * @see javax.naming.Context#destroySubcontext(javax.naming.Name) - */ - public void destroySubcontext(Name name) throws NamingException - { - synchronized (__root) - { - __root.destroySubcontext(getSuffix(name)); - } - } + /** * @@ -149,23 +182,92 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.unbind(getSuffix(name)); + //__root.unbind(getSuffix(name)); + + if (name.size() == 0) + return; + + + if (__root.isLocked()) + throw new NamingException ("This context is immutable"); + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + throw new NamingException ("Name is null"); + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + + //if no subcontexts, just unbind it + if (cname.size() == 1) + { + __root.removeBinding (cname); + } + else + { + //walk down the subcontext hierarchy + if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0)); + + String firstComponent = cname.get(0); + Object ctx = null; + + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (name.get(0)); + if (binding == null) + throw new NameNotFoundException (name.get(0)+ " is not bound"); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (ctx instanceof Context) + { + ((Context)ctx).unbind (cname.getSuffix(1)); + } + else + throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); + } + + + } } /** * * - * @see javax.naming.Context#lookup(java.lang.String) + * @see javax.naming.Context#unbind(java.lang.String) */ - public Object lookup(String name) throws NamingException + public void unbind(String name) throws NamingException { - synchronized (__root) - { - return __root.lookup(getSuffix(name)); - } + unbind(__root.getNameParser("").parse(getSuffix(name))); } + + /** * * @@ -175,56 +277,7 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.lookupLink(getSuffix(name)); - } - } - - /** - * - * - * @see javax.naming.Context#removeFromEnvironment(java.lang.String) - */ - public Object removeFromEnvironment(String propName) throws NamingException - { - return _env.remove(propName); - } - - /** - * - * - * @see javax.naming.Context#bind(java.lang.String, java.lang.Object) - */ - public void bind(String name, Object obj) throws NamingException - { - synchronized (__root) - { - __root.bind(getSuffix(name), obj); - } - } - - /** - * - * - * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object) - */ - public void rebind(String name, Object obj) throws NamingException - { - synchronized (__root) - { - __root.rebind(getSuffix(name), obj); - } - } - - /** - * - * - * @see javax.naming.Context#lookup(javax.naming.Name) - */ - public Object lookup(Name name) throws NamingException - { - synchronized (__root) - { - return __root.lookup(getSuffix(name)); + return lookupLink(__root.getNameParser("").parse(getSuffix(name))); } } @@ -237,10 +290,262 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.lookupLink(getSuffix(name)); + //return __root.lookupLink(getSuffix(name)); + + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + { + //If no name create copy of this context with same bindings, but with copy of the environment so it can be modified + NamingContext ctx = new NamingContext (_env, null, null, __root.getNameParser("")); + ctx.setBindings(__root.getBindings()); + return ctx; + } + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + if (cname.size() == 1) + { + Binding binding = __root.getBinding (cname); + if (binding == null) + throw new NameNotFoundException(); + + Object o = binding.getObject(); + + //handle links by looking up the link + if (o instanceof Reference) + { + //deference the object + try + { + return NamingManager.getObjectInstance(o, cname.getPrefix(1), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + else + { + //object is either a LinkRef which we don't dereference + //or a plain object in which case spec says we return it + return o; + } + } + + + //it is a multipart name, recurse to the first subcontext + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).lookup (cname.getSuffix(1)); + + } } + + /** + * + * + * @see javax.naming.Context#removeFromEnvironment(java.lang.String) + */ + public Object removeFromEnvironment(String propName) throws NamingException + { + return _env.remove(propName); + } + + + /** + * + * + * @see javax.naming.Context#lookup(javax.naming.Name) + */ + public Object lookup(Name name) throws NamingException + { + synchronized (__root) + { + //return __root.lookup(getSuffix(name)); + + if(__log.isDebugEnabled())__log.debug("Looking up name=\""+name+"\""); + Name cname = __root.toCanonicalName(name); + + if ((cname == null) || (cname.size() == 0)) + { + __log.debug("Null or empty name, returning copy of this context"); + NamingContext ctx = new NamingContext (_env, null, null, __root.getNameParser("")); + ctx.setBindings(__root.getBindings()); + return ctx; + } + + + + if (cname.size() == 1) + { + Binding binding = __root.getBinding (cname); + if (binding == null) + { + NameNotFoundException nnfe = new NameNotFoundException(); + nnfe.setRemainingName(cname); + throw nnfe; + } + + + Object o = binding.getObject(); + + //handle links by looking up the link + if (o instanceof LinkRef) + { + //if link name starts with ./ it is relative to current context + String linkName = ((LinkRef)o).getLinkName(); + if (linkName.startsWith("./")) + return lookup (linkName.substring(2)); + else + { + //link name is absolute + InitialContext ictx = new InitialContext(); + return ictx.lookup (linkName); + } + } + else if (o instanceof Reference) + { + //deference the object + try + { + return NamingManager.getObjectInstance(o, cname, __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + else + return o; + } + + //it is a multipart name, get the first subcontext + + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + { + NameNotFoundException nnfe = new NameNotFoundException(); + nnfe.setRemainingName(cname); + throw nnfe; + } + + //as we have bound a reference to an object factory + //for the component specific contexts + //at "comp" we need to resolve the reference + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).lookup (cname.getSuffix(1)); + + } + } + + + /** + * + * + * @see javax.naming.Context#lookup(java.lang.String) + */ + public Object lookup(String name) throws NamingException + { + synchronized (__root) + { + return lookup(__root.getNameParser("").parse(getSuffix(name))); + } + } + + + /** + * + * + * @see javax.naming.Context#bind(java.lang.String, java.lang.Object) + */ + public void bind(String name, Object obj) throws NamingException + { + synchronized (__root) + { + bind(__root.getNameParser("").parse(getSuffix(name)), obj); + + } + } + + /** * * @@ -250,7 +555,83 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.bind(getSuffix(name), obj); + // __root.bind(getSuffix(name), obj); + + + if (__root.isLocked()) + throw new NamingException ("This context is immutable"); + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + throw new NamingException ("Name is null"); + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + + //if no subcontexts, just bind it + if (cname.size() == 1) + { + //get the object to be bound + Object objToBind = NamingManager.getStateToBind(obj, name,this, _env); + // Check for Referenceable + if (objToBind instanceof Referenceable) + { + objToBind = ((Referenceable)objToBind).getReference(); + } + + //anything else we should be able to bind directly + __root.addBinding (cname, objToBind); + } + else + { + if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0)); + + //walk down the subcontext hierarchy + //need to ignore trailing empty "" name components + + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (firstComponent+ " is not bound"); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), this, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + + if (ctx instanceof Context) + { + ((Context)ctx).bind (cname.getSuffix(1), obj); + } + else + throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); + } } } @@ -263,7 +644,105 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.rebind(getSuffix(name), obj); + //__root.rebind(getSuffix(name), obj); + + + if (__root.isLocked()) + throw new NamingException ("This context is immutable"); + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + throw new NamingException ("Name is null"); + + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + + //if no subcontexts, just bind it + if (cname.size() == 1) + { + //check if it is a Referenceable + Object objToBind = NamingManager.getStateToBind(obj, name, __root, _env); + + if (objToBind instanceof Referenceable) + { + objToBind = ((Referenceable)objToBind).getReference(); + } + __root.removeBinding(cname); + __root.addBinding (cname, objToBind); + } + else + { + //walk down the subcontext hierarchy + if(__log.isDebugEnabled())__log.debug("Checking for existing binding for name="+cname+" for first element of name="+cname.get(0)); + + String firstComponent = cname.get(0); + Object ctx = null; + + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (name.get(0)); + if (binding == null) + throw new NameNotFoundException (name.get(0)+ " is not bound"); + + ctx = binding.getObject(); + + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (ctx instanceof Context) + { + ((Context)ctx).rebind (cname.getSuffix(1), obj); + } + else + throw new NotContextException ("Object bound at "+firstComponent +" is not a Context"); + } + } + } + + /** + * + * + * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object) + */ + public void rebind(String name, Object obj) throws NamingException + { + synchronized (__root) + { + rebind(__root.getNameParser("").parse(getSuffix(name)), obj); + } + } + /** + * + * + * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name) + */ + public void rename(Name oldName, Name newName) throws NamingException + { + synchronized (__root) + { + throw new OperationNotSupportedException(); } } @@ -276,7 +755,7 @@ public class localContextRoot implements Context { synchronized (__root) { - __root.rename(getSuffix(oldName), getSuffix(newName)); + throw new OperationNotSupportedException(); } } @@ -289,7 +768,15 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.createSubcontext(getSuffix(name)); + //if the subcontext comes directly off the root, use the env of the InitialContext + //as the root itself has no environment. Otherwise, it inherits the env of the parent + //Context further down the tree. + //NamingContext ctx = (NamingContext)__root.createSubcontext(name); + //if (ctx.getParent() == __root) + // ctx.setEnv(_env); + //return ctx; + + return createSubcontext(__root.getNameParser("").parse(name)); } } @@ -301,24 +788,92 @@ public class localContextRoot implements Context public Context createSubcontext(Name name) throws NamingException { synchronized (__root) - { - return __root.createSubcontext(getSuffix(name)); - } - } - - /** - * - * - * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name) - */ - public void rename(Name oldName, Name newName) throws NamingException - { - synchronized (__root) - { - __root.rename(getSuffix(oldName), getSuffix(newName)); + { + //if the subcontext comes directly off the root, use the env of the InitialContext + //as the root itself has no environment. Otherwise, it inherits the env of the parent + //Context further down the tree. + //NamingContext ctx = (NamingContext)__root.createSubcontext(getSuffix(name)); + //if (ctx.getParent() == __root) + // ctx.setEnv(_env); + //return ctx; + + + + + if (__root.isLocked()) + { + NamingException ne = new NamingException ("This context is immutable"); + ne.setRemainingName(name); + throw ne; + } + + Name cname = __root.toCanonicalName (name); + + if (cname == null) + throw new NamingException ("Name is null"); + if (cname.size() == 0) + throw new NamingException ("Name is empty"); + + if (cname.size() == 1) + { + //not permitted to bind if something already bound at that name + Binding binding = __root.getBinding (cname); + if (binding != null) + throw new NameAlreadyBoundException (cname.toString()); + + //make a new naming context with the root as the parent + Context ctx = new NamingContext ((Hashtable)_env.clone(), cname.get(0), __root, __root.getNameParser("")); + __root.addBinding (cname, ctx); + return ctx; + } + + + //If the name has multiple subcontexts, walk the hierarchy by + //fetching the first one. All intermediate subcontexts in the + //name must already exist. + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (firstComponent + " is not bound"); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + if(__log.isDebugEnabled())__log.debug("Object bound at "+firstComponent +" is a Reference"); + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (ctx instanceof Context) + { + return ((Context)ctx).createSubcontext (cname.getSuffix(1)); + } + else + throw new NotContextException (firstComponent +" is not a Context"); } } + /** * * @@ -348,22 +903,10 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.list(getSuffix(name)); + return list(__root.getNameParser("").parse(getSuffix(name))); } } - /** - * - * - * @see javax.naming.Context#listBindings(java.lang.String) - */ - public NamingEnumeration listBindings(String name) throws NamingException - { - synchronized (__root) - { - return __root.listBindings(getSuffix(name)); - } - } /** * @@ -374,7 +917,64 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.list(getSuffix(name)); + //return __root.list(getSuffix(name)); + + + Name cname = __root.toCanonicalName(name); + + if (cname == null) + { + List empty = Collections.emptyList(); + return new NameEnumeration(empty.iterator()); + } + + + if (cname.size() == 0) + { + return new NameEnumeration (__root.getBindings().values().iterator()); + } + + + + //multipart name + String firstComponent = cname.get(0); + Object ctx = null; + + if (firstComponent.equals("")) + ctx = this; + else + { + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + if(__log.isDebugEnabled())__log.debug("Dereferencing Reference for "+name.get(0)); + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).list (cname.getSuffix(1)); + } } @@ -387,10 +987,82 @@ public class localContextRoot implements Context { synchronized (__root) { - return __root.listBindings(getSuffix(name)); + //return __root.listBindings(getSuffix(name)); + + Name cname = __root.toCanonicalName (name); + + if (cname == null) + { + List empty = Collections.emptyList(); + return new BindingEnumeration(empty.iterator()); + } + + if (cname.size() == 0) + { + return new BindingEnumeration (__root.getBindings().values().iterator()); + } + + + + //multipart name + String firstComponent = cname.get(0); + Object ctx = null; + + //if a name has a leading "/" it is parsed as "" so ignore it by staying + //at this level in the tree + if (firstComponent.equals("")) + ctx = this; + else + { + //it is a non-empty name component + Binding binding = __root.getBinding (firstComponent); + if (binding == null) + throw new NameNotFoundException (); + + ctx = binding.getObject(); + + if (ctx instanceof Reference) + { + //deference the object + try + { + ctx = NamingManager.getObjectInstance(ctx, getNameParser("").parse(firstComponent), __root, _env); + } + catch (NamingException e) + { + throw e; + } + catch (Exception e) + { + __log.warn("",e); + throw new NamingException (e.getMessage()); + } + } + } + + if (!(ctx instanceof Context)) + throw new NotContextException(); + + return ((Context)ctx).listBindings (cname.getSuffix(1)); + } } + + /** + * + * + * @see javax.naming.Context#listBindings(java.lang.String) + */ + public NamingEnumeration listBindings(String name) throws NamingException + { + synchronized (__root) + { + return listBindings(__root.getNameParser("").parse(getSuffix(name))); + } + } + + /** * * diff --git a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java index 0caa18fcefd..d41539de51d 100644 --- a/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java +++ b/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestLocalJNDI.java @@ -12,11 +12,19 @@ // ======================================================================== package org.eclipse.jetty.jndi.java; +import java.util.Hashtable; + import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NameNotFoundException; import javax.naming.NameParser; +import javax.naming.NamingException; +import javax.naming.RefAddr; +import javax.naming.Reference; +import javax.naming.Referenceable; +import javax.naming.StringRefAddr; +import javax.naming.spi.ObjectFactory; import org.eclipse.jetty.jndi.NamingUtil; import org.junit.After; @@ -31,13 +39,152 @@ import static org.junit.Assert.fail; */ public class TestLocalJNDI { + public static class FruitFactory implements ObjectFactory + { + public FruitFactory() + { + } + + public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable env) throws Exception + { + + if (!env.containsKey("flavour")) + throw new Exception ("No flavour!"); + + if (obj instanceof Reference) + { + Reference ref = (Reference)obj; + if (ref.getClassName().equals(Fruit.class.getName())) + { + RefAddr addr = ref.get("fruit"); + if (addr != null) + { + return new Fruit((String)addr.getContent()); + } + } + } + return null; + } + } + + + public static class Fruit implements Referenceable + { + String fruit; + + public Fruit(String f) + { + fruit = f; + } + + public Reference getReference() throws NamingException + { + return new Reference( + Fruit.class.getName(), + new StringRefAddr("fruit", fruit), + FruitFactory.class.getName(), + null); // Factory location + } + + public String toString() + { + return fruit; + } + } + + + + + + + + @After public void tearDown() throws Exception { InitialContext ic = new InitialContext(); ic.destroySubcontext("a"); } + + + @Test + public void testLocalReferenceable() throws Exception + { + Hashtable env1 = new Hashtable(); + env1.put("flavour", "orange"); + InitialContext ic1 = new InitialContext(env1); + + ic1.bind("valencia", new Fruit("orange")); + + Object o = ic1.lookup("valencia"); + Hashtable env2 = new Hashtable(); + InitialContext ic2 = new InitialContext(env2); + try + { + o = ic2.lookup("valencia"); + fail("Constructed object from reference without correct environment"); + } + catch (Exception e) + { + assertEquals("No flavour!", e.getMessage()); + } + } + + + @Test + public void testLocalEnvironment() throws Exception + { + Hashtable env1 = new Hashtable(); + env1.put("make", "holden"); + env1.put("model", "commodore"); + + Object car1 = new Object(); + + InitialContext ic = new InitialContext(env1); + ic.bind("car1", car1); + assertNotNull(ic.lookup("car1")); + assertEquals(car1, ic.lookup("car1")); + + Context carz = ic.createSubcontext("carz"); + assertNotNull(carz); + Hashtable ht = carz.getEnvironment(); + assertNotNull(ht); + assertEquals("holden", ht.get("make")); + assertEquals("commodore", ht.get("model")); + + Hashtable env2 = new Hashtable(); + env2.put("flavour", "strawberry"); + InitialContext ic2 = new InitialContext(env2); + assertEquals(car1, ic2.lookup("car1")); + Context c = (Context)ic2.lookup("carz"); + assertNotNull(c); + ht = c.getEnvironment(); + assertEquals("holden", ht.get("make")); + assertEquals("commodore", ht.get("model")); + + Context icecreamz = ic2.createSubcontext("icecreamz"); + ht = icecreamz.getEnvironment(); + assertNotNull(ht); + assertEquals("strawberry", ht.get("flavour")); + + Context hatchbackz = ic2.createSubcontext("carz/hatchbackz"); + assertNotNull(hatchbackz); + ht = hatchbackz.getEnvironment(); + assertNotNull(ht); + assertEquals("holden", ht.get("make")); + assertEquals("commodore", ht.get("model")); + assertEquals(null, ht.get("flavour")); + + c = (Context)ic.lookup("carz/hatchbackz"); + assertNotNull(c); + assertEquals(hatchbackz, c); + + } + + + + @Test public void testLocal () throws Exception { diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java index f55f2fa24b2..6516e84601e 100644 --- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java +++ b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedConnection.java @@ -84,7 +84,8 @@ public class NestedConnection extends HttpConnection { getServer().handle(this); completeResponse(); - _generator.flushBuffer(); + while (!_generator.isComplete() && _endp.isOpen()) + _generator.flushBuffer(); _endp.flush(); } finally diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java index 30d7fbb7907..efe0d4a801d 100644 --- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java +++ b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedEndPoint.java @@ -4,29 +4,25 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.nested; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.bio.StreamEndPoint; public class NestedEndPoint extends StreamEndPoint { private final HttpServletRequest _outerRequest; - + public NestedEndPoint(HttpServletRequest outerRequest, HttpServletResponse outerResponse) throws IOException { @@ -65,7 +61,6 @@ public class NestedEndPoint extends StreamEndPoint @Override public String getRemoteHost() { - // TODO Auto-generated method stub return _outerRequest.getRemoteHost(); } @Override diff --git a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java index 3ab098ccf1c..b9c970207e8 100644 --- a/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java +++ b/jetty-nested/src/main/java/org/eclipse/jetty/nested/NestedGenerator.java @@ -43,7 +43,7 @@ public class NestedGenerator extends AbstractGenerator public void addContent(Buffer content, boolean last) throws IOException { - // LOG.debug("addContent {} {}",content.length(),last); + LOG.debug("addContent {} {}",content.length(),last); if (_noContent) { content.clear(); @@ -70,7 +70,6 @@ public class NestedGenerator extends AbstractGenerator // Handle any unfinished business? if (_content != null && _content.length() > 0) { - flushBuffer(); if (_content != null && _content.length() > 0) throw new IllegalStateException("FULL"); @@ -86,20 +85,22 @@ public class NestedGenerator extends AbstractGenerator content.clear(); _content = null; } - else + else if (!last || _buffer!=null) { // Yes - so we better check we have a buffer - initContent(); + initBuffer(); // Copy _content to buffer; int len = 0; len = _buffer.put(_content); - // make sure there is space for a trailing null + // make sure there is space for a trailing null (???) if (len > 0 && _buffer.space() == 0) { len--; _buffer.setPutIndex(_buffer.putIndex() - 1); } + + LOG.debug("copied {} to buffer",len); _content.skip(len); @@ -139,7 +140,7 @@ public class NestedGenerator extends AbstractGenerator return false; // we better check we have a buffer - initContent(); + initBuffer(); // Copy _content to buffer; @@ -149,7 +150,7 @@ public class NestedGenerator extends AbstractGenerator } /* ------------------------------------------------------------ */ - private void initContent() throws IOException + private void initBuffer() throws IOException { if (_buffer == null) { @@ -176,7 +177,7 @@ public class NestedGenerator extends AbstractGenerator @Override public int prepareUncheckedAddContent() throws IOException { - initContent(); + initBuffer(); return _buffer.space(); } @@ -229,6 +230,26 @@ public class NestedGenerator extends AbstractGenerator _state = STATE_CONTENT; } + /* ------------------------------------------------------------ */ + /** + * Complete the message. + * + * @throws IOException + */ + @Override + public void complete() throws IOException + { + if (_state == STATE_END) + return; + + super.complete(); + + if (_state < STATE_FLUSHING) + _state = STATE_FLUSHING; + + flushBuffer(); + } + /* ------------------------------------------------------------ */ @Override public long flushBuffer() throws IOException @@ -236,23 +257,44 @@ public class NestedGenerator extends AbstractGenerator if (_state == STATE_HEADER) throw new IllegalStateException("State==HEADER"); - - if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) - { - initContent(); - _buffer.put(_content); - _content.clear(); - _content = null; - } + int len = 0; if (_buffer==null) - return 0; + { + + if (_content!=null && _content.length()>0) + { + // flush content directly + len = _endp.flush(_content); + if (len>0) + _content.skip(len); + } + } + else + { + if (_buffer.length()==0 && _content!=null && _content.length()>0) + { + // Copy content to buffer + _content.skip(_buffer.put(_content)); + } + + int size=_buffer.length(); + len =_endp.flush(_buffer); + LOG.debug("flushBuffer {} of {}",len,size); + if (len>0) + _buffer.skip(len); + } - int size=_buffer.length(); - int len = _buffer==null?0:_endp.flush(_buffer); - LOG.debug("flushBuffer {} of {}",len,size); - if (len>0) - _buffer.skip(len); + if (_content!=null && _content.length()==0) + _content=null; + if (_buffer!=null && _buffer.length()==0 && _content==null) + { + _buffers.returnBuffer(_buffer); + _buffer=null; + } + + if (_state==STATE_FLUSHING && _buffer==null && _content==null) + _state=STATE_END; return len; } diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java index cceb8551032..4790834199e 100644 --- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java @@ -428,7 +428,6 @@ public class MongoSessionManager extends NoSqlSessionManager return o; } - bout.reset(); out.reset(); out.writeUnshared(value); out.flush(); diff --git a/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/BridgeServletExtended.java b/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/BridgeServletExtended.java new file mode 100644 index 00000000000..4bf16ee2b4d --- /dev/null +++ b/jetty-osgi/jetty-osgi-servletbridge/src/main/java/org/eclipse/jetty/osgi/servletbridge/BridgeServletExtended.java @@ -0,0 +1,48 @@ +// ======================================================================== +// Copyright (c) 2010-2011 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +package org.eclipse.jetty.osgi.servletbridge; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.equinox.servletbridge.BridgeServlet; + +/** + * Override the BridgeServlet to report on whether equinox is actually started or not + * in case it is started asynchroneously. + * + * @author hmalphettes + */ +public class BridgeServletExtended extends BridgeServlet { + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + if (FrameworkLauncherExtended.ASYNCH_START_IN_PROGRESS != null + && req.getMethod().equals("GET")) { + if (FrameworkLauncherExtended.ASYNCH_START_IN_PROGRESS) { + resp.getWriter().append("Equinox is currently starting...\n"); + return; + } else if (FrameworkLauncherExtended.ASYNCH_START_FAILURE != null) { + resp.getWriter().append("Equinox failed to start:\n"); + FrameworkLauncherExtended.ASYNCH_START_FAILURE.printStackTrace(resp.getWriter()); + return; + } + } + super.service(req, resp); + } + +} diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java index 67a207042ff..1e36eb5f5fc 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java @@ -13,146 +13,114 @@ package org.eclipse.jetty.plus.jaas.spi; -import java.io.File; -import java.io.FileInputStream; +import java.security.Principal; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; +import java.util.List; import java.util.Map; -import java.util.Properties; -import java.util.StringTokenizer; +import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.security.PropertyUserStore; +import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** * PropertyFileLoginModule - * - * + * + * */ public class PropertyFileLoginModule extends AbstractLoginModule { + public static final String DEFAULT_FILENAME = "realm.properties"; + private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class); - public static final String DEFAULT_FILENAME = "realm.properties"; - public static final Map> fileMap = new HashMap>(); - - private String propertyFileName; - - + private static Map _propertyUserStores = new HashMap(); - /** + private int _refreshInterval = 0; + private String _filename = DEFAULT_FILENAME; + + /** * Read contents of the configured property file. * - * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map) + * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, + * java.util.Map) * @param subject * @param callbackHandler * @param sharedState * @param options */ - public void initialize(Subject subject, CallbackHandler callbackHandler, - Map sharedState, Map options) + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - super.initialize(subject, callbackHandler, sharedState, options); - loadProperties((String)options.get("file")); + super.initialize(subject,callbackHandler,sharedState,options); + setupPropertyUserStore(options); } - - - - public void loadProperties (String filename) - { - File propsFile; - - if (filename == null) - { - propsFile = new File(System.getProperty("user.dir"), DEFAULT_FILENAME); - //look for a file called realm.properties in the current directory - //if that fails, look for a file called realm.properties in $jetty.home/etc - if (!propsFile.exists()) - propsFile = new File(System.getProperty("jetty.home"), DEFAULT_FILENAME); - } - else - { - propsFile = new File(filename); - } - - //give up, can't find a property file to load - if (!propsFile.exists()) - { - LOG.warn("No property file found"); - throw new IllegalStateException ("No property file specified in login module configuration file"); - } - - - - try - { - this.propertyFileName = propsFile.getCanonicalPath(); - if (fileMap.get(propertyFileName) != null) - { - if (LOG.isDebugEnabled()) {LOG.debug("Properties file "+propertyFileName+" already in cache, skipping load");} - return; - } - - Map userInfoMap = new HashMap(); - Properties props = new Properties(); - props.load(new FileInputStream(propsFile)); - Iterator> iter = props.entrySet().iterator(); - while(iter.hasNext()) - { - - Map.Entry entry = iter.next(); - String username=entry.getKey().toString().trim(); - String credentials=entry.getValue().toString().trim(); - String roles=null; - int c=credentials.indexOf(','); - if (c>0) - { - roles=credentials.substring(c+1).trim(); - credentials=credentials.substring(0,c).trim(); - } - if (username!=null && username.length()>0 && - credentials!=null && credentials.length()>0) - { - ArrayList roleList = new ArrayList(); - if(roles!=null && roles.length()>0) - { - StringTokenizer tok = new StringTokenizer(roles,", "); - - while (tok.hasMoreTokens()) - roleList.add(tok.nextToken()); - } - - userInfoMap.put(username, (new UserInfo(username, Credential.getCredential(credentials.toString()), roleList))); - } - } - - fileMap.put(propertyFileName, userInfoMap); - } - catch (Exception e) + private void setupPropertyUserStore(Map options) + { + if (_propertyUserStores.get(_filename) == null) { - LOG.warn("Error loading properties from file", e); - throw new RuntimeException(e); + parseConfig(options); + + PropertyUserStore _propertyUserStore = new PropertyUserStore(); + _propertyUserStore.setConfig(_filename); + _propertyUserStore.setRefreshInterval(_refreshInterval); + LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval); + + try + { + _propertyUserStore.start(); + } + catch (Exception e) + { + LOG.warn("Exception while starting propertyUserStore: ",e); + } + + _propertyUserStores.put(_filename,_propertyUserStore); } } - /** - * Don't implement this as we want to pre-fetch all of the - * users. - * @param username + private void parseConfig(Map options) + { + _filename = (String)options.get("file") != null?(String)options.get("file"):DEFAULT_FILENAME; + String refreshIntervalString = (String)options.get("refreshInterval"); + _refreshInterval = refreshIntervalString == null?_refreshInterval:Integer.parseInt(refreshIntervalString); + } + + /** + * Don't implement this as we want to pre-fetch all of the users. + * + * @param userName * @throws Exception */ - public UserInfo getUserInfo (String username) throws Exception + public UserInfo getUserInfo(String userName) throws Exception { - Map userInfoMap = (Map)fileMap.get(propertyFileName); - if (userInfoMap == null) + PropertyUserStore propertyUserStore = _propertyUserStores.get(_filename); + if (propertyUserStore == null) + throw new IllegalStateException("PropertyUserStore should never be null here!"); + + UserIdentity userIdentity = propertyUserStore.getUserIdentity(userName); + if(userIdentity==null) return null; - return (UserInfo)userInfoMap.get(username); + + Set principals = userIdentity.getSubject().getPrincipals(); + + List roles = new ArrayList(); + + for ( Principal principal : principals ) + { + roles.add( principal.getName() ); + } + + Credential credential = (Credential)userIdentity.getSubject().getPrivateCredentials().iterator().next(); + LOG.debug("Found: " + userName + " in PropertyUserStore"); + return new UserInfo(userName, credential, roles); } -} +} \ No newline at end of file diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml index 0687e7f7a23..6a11749f97c 100644 --- a/jetty-rewrite/pom.xml +++ b/jetty-rewrite/pom.xml @@ -74,10 +74,11 @@ jetty-server ${project.version} - - ${servlet.spec.groupId} - ${servlet.spec.artifactId} - + + org.eclipse.jetty + jetty-client + ${project.version} + org.eclipse.jetty test-jetty-servlet diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ProxyRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ProxyRule.java new file mode 100644 index 00000000000..99c6877e52b --- /dev/null +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ProxyRule.java @@ -0,0 +1,487 @@ +package org.eclipse.jetty.rewrite.handler; + +//======================================================================== +//Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.util.Enumeration; +import java.util.HashSet; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpHeaderValues; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.io.Buffer; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +/** + * This rule allows the user to configure a particular rewrite rule that will proxy out + * to a configured location. This rule uses the jetty http client. + * + * Rule rule = new ProxyRule(); + * rule.setPattern("/foo/*"); + * rule.setProxyTo("http://url.com"); + * + * see api for other configuration options which influence the configuration of the jetty + * client instance + * + */ +public class ProxyRule extends PatternRule +{ + private static final Logger _log = Log.getLogger(ProxyRule.class); + + private HttpClient _client; + private String _hostHeader; + private String _proxyTo; + + private int _connectorType = 2; + private String _maxThreads; + private String _maxConnections; + private String _timeout; + private String _idleTimeout; + private String _requestHeaderSize; + private String _requestBufferSize; + private String _responseHeaderSize; + private String _responseBufferSize; + + private HashSet _DontProxyHeaders = new HashSet(); + { + _DontProxyHeaders.add("proxy-connection"); + _DontProxyHeaders.add("connection"); + _DontProxyHeaders.add("keep-alive"); + _DontProxyHeaders.add("transfer-encoding"); + _DontProxyHeaders.add("te"); + _DontProxyHeaders.add("trailer"); + _DontProxyHeaders.add("proxy-authorization"); + _DontProxyHeaders.add("proxy-authenticate"); + _DontProxyHeaders.add("upgrade"); + } + + /* ------------------------------------------------------------ */ + public ProxyRule() + { + _handling = true; + _terminating = true; + } + + /* ------------------------------------------------------------ */ + private void initializeClient() throws Exception + { + _client = new HttpClient(); + _client.setConnectorType(_connectorType); + + if ( _maxThreads != null ) + { + _client.setThreadPool(new QueuedThreadPool(Integer.parseInt(_maxThreads))); + } + else + { + _client.setThreadPool(new QueuedThreadPool()); + } + + if ( _maxConnections != null ) + { + _client.setMaxConnectionsPerAddress(Integer.parseInt(_maxConnections)); + } + + if ( _timeout != null ) + { + _client.setTimeout(Long.parseLong(_timeout)); + } + + if ( _idleTimeout != null ) + { + _client.setIdleTimeout(Long.parseLong(_idleTimeout)); + } + + if ( _requestBufferSize != null ) + { + _client.setRequestBufferSize(Integer.parseInt(_requestBufferSize)); + } + + if ( _requestHeaderSize != null ) + { + _client.setRequestHeaderSize(Integer.parseInt(_requestHeaderSize)); + } + + if ( _responseBufferSize != null ) + { + _client.setResponseBufferSize(Integer.parseInt(_responseBufferSize)); + } + + if ( _responseHeaderSize != null ) + { + _client.setResponseHeaderSize(Integer.parseInt(_responseHeaderSize)); + } + + _client.start(); + } + + /* ------------------------------------------------------------ */ + private HttpURI proxyHttpURI(String uri) throws MalformedURLException + { + return new HttpURI(_proxyTo + uri); + } + + /* ------------------------------------------------------------ */ + @Override + protected String apply(String target, HttpServletRequest request, final HttpServletResponse response) throws IOException + { + synchronized (this) + { + if (_client == null) + { + try + { + initializeClient(); + } + catch (Exception e) + { + throw new IOException("Unable to proxy: " + e.getMessage()); + } + } + } + + final int debug = _log.isDebugEnabled()?request.hashCode():0; + + final InputStream in = request.getInputStream(); + final OutputStream out = response.getOutputStream(); + + HttpURI url = createUrl(request,debug); + + if (url == null) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return target; + } + + HttpExchange exchange = new HttpExchange() + { + @Override + protected void onRequestCommitted() throws IOException + { + } + + @Override + protected void onRequestComplete() throws IOException + { + } + + @Override + protected void onResponseComplete() throws IOException + { + if (debug != 0) + _log.debug(debug + " complete"); + } + + @Override + protected void onResponseContent(Buffer content) throws IOException + { + if (debug != 0) + _log.debug(debug + " content" + content.length()); + content.writeTo(out); + } + + @Override + protected void onResponseHeaderComplete() throws IOException + { + } + + @SuppressWarnings("deprecation") + @Override + protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + if (debug != 0) + _log.debug(debug + " " + version + " " + status + " " + reason); + + if (reason != null && reason.length() > 0) + response.setStatus(status,reason.toString()); + else + response.setStatus(status); + } + + @Override + protected void onResponseHeader(Buffer name, Buffer value) throws IOException + { + String s = name.toString().toLowerCase(); + if (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value))) + { + if (debug != 0) + _log.debug(debug + " " + name + ": " + value); + + response.addHeader(name.toString(),value.toString()); + } + else if (debug != 0) + _log.debug(debug + " " + name + "! " + value); + } + + @Override + protected void onConnectionFailed(Throwable ex) + { + _log.warn(ex.toString()); + _log.debug(ex); + if (!response.isCommitted()) + { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + + @Override + protected void onException(Throwable ex) + { + if (ex instanceof EofException) + { + _log.ignore(ex); + return; + } + _log.warn(ex.toString()); + _log.debug(ex); + if (!response.isCommitted()) + { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + + @Override + protected void onExpire() + { + if (!response.isCommitted()) + { + response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); + } + } + + }; + + exchange.setMethod(request.getMethod()); + exchange.setURL(url.toString()); + exchange.setVersion(request.getProtocol()); + + if (debug != 0) + { + _log.debug(debug + " " + request.getMethod() + " " + url + " " + request.getProtocol()); + } + + boolean hasContent = createHeaders(request,debug,exchange); + + if (hasContent) + { + exchange.setRequestContentSource(in); + } + + /* + * we need to set the timeout on the exchange to take into account the timeout of the HttpClient and the HttpExchange + */ + long ctimeout = (_client.getTimeout() > exchange.getTimeout())?_client.getTimeout():exchange.getTimeout(); + exchange.setTimeout(ctimeout); + + _client.send(exchange); + + try + { + exchange.waitForDone(); + } + catch (InterruptedException e) + { + _log.info("Exception while waiting for response on proxied request", e); + } + return target; + } + + /* ------------------------------------------------------------ */ + private HttpURI createUrl(HttpServletRequest request, final int debug) throws MalformedURLException + { + String uri = request.getRequestURI(); + + if (request.getQueryString() != null) + { + uri += "?" + request.getQueryString(); + } + + uri = PathMap.pathInfo(_pattern,uri); + + if(uri==null) + { + uri = "/"; + } + + HttpURI url = proxyHttpURI(uri); + + if (debug != 0) + { + _log.debug(debug + " proxy " + uri + "-->" + url); + } + + return url; + } + + /* ------------------------------------------------------------ */ + private boolean createHeaders(final HttpServletRequest request, final int debug, HttpExchange exchange) + { + // check connection header + String connectionHdr = request.getHeader("Connection"); + if (connectionHdr != null) + { + connectionHdr = connectionHdr.toLowerCase(); + if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0) + { + connectionHdr = null; + } + } + + // force host + if (_hostHeader != null) + { + exchange.setRequestHeader("Host",_hostHeader); + } + + // copy headers + boolean xForwardedFor = false; + boolean hasContent = false; + long contentLength = -1; + Enumeration enm = request.getHeaderNames(); + while (enm.hasMoreElements()) + { + // TODO could be better than this! + String hdr = (String)enm.nextElement(); + String lhdr = hdr.toLowerCase(); + + if (_DontProxyHeaders.contains(lhdr)) + continue; + if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0) + continue; + if (_hostHeader != null && "host".equals(lhdr)) + continue; + + if ("content-type".equals(lhdr)) + hasContent = true; + else if ("content-length".equals(lhdr)) + { + contentLength = request.getContentLength(); + exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(contentLength)); + if (contentLength > 0) + hasContent = true; + } + else if ("x-forwarded-for".equals(lhdr)) + xForwardedFor = true; + + Enumeration vals = request.getHeaders(hdr); + while (vals.hasMoreElements()) + { + String val = (String)vals.nextElement(); + if (val != null) + { + if (debug != 0) + _log.debug(debug + " " + hdr + ": " + val); + + exchange.setRequestHeader(hdr,val); + } + } + } + + // Proxy headers + exchange.setRequestHeader("Via","1.1 (jetty)"); + if (!xForwardedFor) + { + exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr()); + exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme()); + exchange.addRequestHeader("X-Forwarded-Host",request.getServerName()); + exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName()); + } + return hasContent; + } + + /* ------------------------------------------------------------ */ + public void setProxyTo(String proxyTo) + { + this._proxyTo = proxyTo; + } + + /* ------------------------------------------------------------ */ + public void setMaxThreads(String maxThreads) + { + this._maxThreads = maxThreads; + } + + /* ------------------------------------------------------------ */ + public void setMaxConnections(String maxConnections) + { + _maxConnections = maxConnections; + } + + /* ------------------------------------------------------------ */ + public void setTimeout(String timeout) + { + _timeout = timeout; + } + + /* ------------------------------------------------------------ */ + public void setIdleTimeout(String idleTimeout) + { + _idleTimeout = idleTimeout; + } + + /* ------------------------------------------------------------ */ + public void setRequestHeaderSize(String requestHeaderSize) + { + _requestHeaderSize = requestHeaderSize; + } + + /* ------------------------------------------------------------ */ + public void setRequestBufferSize(String requestBufferSize) + { + _requestBufferSize = requestBufferSize; + } + + /* ------------------------------------------------------------ */ + public void setResponseHeaderSize(String responseHeaderSize) + { + _responseHeaderSize = responseHeaderSize; + } + + /* ------------------------------------------------------------ */ + public void setResponseBufferSize(String responseBufferSize) + { + _responseBufferSize = responseBufferSize; + } + + /* ------------------------------------------------------------ */ + public void addDontProxyHeaders(String dontProxyHeader) + { + _DontProxyHeaders.add(dontProxyHeader); + } + + /* ------------------------------------------------------------ */ + /** + * CONNECTOR_SOCKET = 0; + * CONNECTOR_SELECT_CHANNEL = 2; (default) + * + * @param connectorType + */ + public void setConnectorType( int connectorType ) + { + _connectorType = connectorType; + } + +} diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java index 98b9d0631a2..e563abd72f4 100644 --- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java @@ -5,11 +5,11 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.rewrite.handler; @@ -19,25 +19,24 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; /* ------------------------------------------------------------ */ /** *

    Rewrite handler is responsible for managing the rules. Its capabilities - * is not only limited for URL rewrites such as RewritePatternRule or RewriteRegexRule. - * There is also handling for cookies, headers, redirection, setting status or error codes - * whenever the rule finds a match. - * - *

    The rules can be matched by the either: pattern matching of PathMap - * (eg {@link PatternRule}), regular expressions (eg {@link RegexRule}) or certain conditions set + * is not only limited for URL rewrites such as RewritePatternRule or RewriteRegexRule. + * There is also handling for cookies, headers, redirection, setting status or error codes + * whenever the rule finds a match. + * + *

    The rules can be matched by the either: pattern matching of PathMap + * (eg {@link PatternRule}), regular expressions (eg {@link RegexRule}) or certain conditions set * (eg {@link MsieSslRule} - the requests must be in SSL mode). - * - *

    The rules can be grouped into rule containers (class {@link RuleContainer}), and will only + * + *

    The rules can be grouped into rule containers (class {@link RuleContainer}), and will only * be applied if the request matches the conditions for their container * (e.g., by virtual host name) - * + * *

    The list of predefined rules is: *

      *
    • {@link CookiePatternRule} - adds a new cookie in response.
    • @@ -46,28 +45,36 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; *
    • {@link ResponsePatternRule} - sets the status/error codes.
    • *
    • {@link RewritePatternRule} - rewrites the requested URI.
    • *
    • {@link RewriteRegexRule} - rewrites the requested URI using regular expression for pattern matching.
    • + *
    • {@link ProxyRule} - proxies the requested URI to the host defined in proxyTo.
    • *
    • {@link MsieSslRule} - disables the keep alive on SSL for IE5 and IE6.
    • *
    • {@link LegacyRule} - the old version of rewrite.
    • *
    • {@link ForwardedSchemeHeaderRule} - set the scheme according to the headers present.
    • *
    • {@link VirtualHostRuleContainer} - checks whether the request matches one of a set of virtual host names.
    • *
    * - * + * * Here is a typical jetty.xml configuration would be:
    - * 
    + *
      *   <Set name="handler">
      *     <New id="Handlers" class="org.eclipse.jetty.rewrite.handler.RewriteHandler">
      *       <Set name="rules">
      *         <Array type="org.eclipse.jetty.rewrite.handler.Rule">
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="rewrite" class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
      *               <Set name="pattern">/*</Set>
      *               <Set name="replacement">/test</Set>
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
    + *             <New id="rewrite" class="org.eclipse.jetty.rewrite.handler.ProxyRule">
    + *               <Set name="pattern">/*</Set>
    + *               <Set name="proxyTo">http://webtide.com:8080</Set>
    + *             </New>
    + *           </Item>
    + *
    + *           <Item>
      *             <New id="response" class="org.eclipse.jetty.rewrite.handler.ResponsePatternRule">
      *               <Set name="pattern">/session/</Set>
      *               <Set name="code">400</Set>
    @@ -75,7 +82,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="header" class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
      *               <Set name="pattern">*.jsp</Set>
      *               <Set name="name">server</Set>
    @@ -83,7 +90,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="header" class="org.eclipse.jetty.rewrite.handler.HeaderPatternRule">
      *               <Set name="pattern">*.jsp</Set>
      *               <Set name="name">title</Set>
    @@ -91,28 +98,28 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="redirect" class="org.eclipse.jetty.rewrite.handler.RedirectPatternRule">
      *               <Set name="pattern">/test/dispatch</Set>
      *               <Set name="location">http://jetty.eclipse.org</Set>
      *             </New>
      *           </Item>
      *
    - *           <Item> 
    + *           <Item>
      *             <New id="regexRewrite" class="org.eclipse.jetty.rewrite.handler.RewriteRegexRule">
      *               <Set name="regex">/test-jaas/$</Set>
      *               <Set name="replacement">/demo</Set>
      *             </New>
      *           </Item>
    - *           
    - *           <Item> 
    + *
    + *           <Item>
      *             <New id="forwardedHttps" class="org.eclipse.jetty.rewrite.handler.ForwardedSchemeHeaderRule">
      *               <Set name="header">X-Forwarded-Scheme</Set>
      *               <Set name="headerValue">https</Set>
      *               <Set name="scheme">https</Set>
      *             </New>
      *           </Item>
    - *           
    + *
      *           <Item>
      *             <New id="virtualHost" class="org.eclipse.jetty.rewrite.handler.VirtualHostRuleContainer">
      *
    @@ -134,10 +141,10 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *                   </New>
      *                 </Arg>
      *               </Call>
    - *    
    + *
      *             </New>
      *           </      Item>
    - * 
    + *
      *         </Array>
      *       </Set>
      *
    @@ -162,13 +169,13 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
      *     </New>
      *   </Set>
      * 
    - * + * */ public class RewriteHandler extends HandlerWrapper { - + private RuleContainer _rules; - + /* ------------------------------------------------------------ */ public RewriteHandler() { @@ -179,7 +186,7 @@ public class RewriteHandler extends HandlerWrapper /** * To enable configuration from jetty.xml on rewriteRequestURI, rewritePathInfo and * originalPathAttribute - * + * * @param legacyRule old style rewrite rule */ @Deprecated @@ -201,7 +208,7 @@ public class RewriteHandler extends HandlerWrapper /* ------------------------------------------------------------ */ /** * Assigns the rules to process. - * @param rules an array of {@link Rule}. + * @param rules an array of {@link Rule}. */ public void setRules(Rule[] rules) { @@ -297,13 +304,13 @@ public class RewriteHandler extends HandlerWrapper public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { if (isStarted()) - { + { String returned = _rules.matchAndApply(target, request, response); target = (returned == null) ? target : returned; - + if (!baseRequest.isHandled()) super.handle(target, baseRequest, request, response); } } - + } diff --git a/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ProxyRuleTest.java b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ProxyRuleTest.java new file mode 100644 index 00000000000..9cb9def596a --- /dev/null +++ b/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ProxyRuleTest.java @@ -0,0 +1,132 @@ +package org.eclipse.jetty.rewrite.handler; + +//======================================================================== +//Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.net.URLEncoder; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.ContentExchange; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ProxyRuleTest +{ + private static ProxyRule _rule; + private static RewriteHandler _handler; + private static Server _proxyServer = new Server(); + private static Connector _proxyServerConnector = new SelectChannelConnector(); + private static Server _targetServer = new Server(); + private static Connector _targetServerConnector = new SelectChannelConnector(); + private static HttpClient _httpClient = new HttpClient(); + + @BeforeClass + public static void setupOnce() throws Exception + { + _targetServer.addConnector(_targetServerConnector); + _targetServer.setHandler(new AbstractHandler() + { + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + String responseString = "uri: " + request.getRequestURI() + " some content"; + response.getOutputStream().write(responseString.getBytes()); + response.setStatus(201); + } + }); + _targetServer.start(); + + _rule = new ProxyRule(); + _rule.setPattern("/foo/*"); + _rule.setProxyTo("http://localhost:" + _targetServerConnector.getLocalPort()); + _handler = new RewriteHandler(); + _handler.setRewriteRequestURI(true); + _handler.setRules(new Rule[] { _rule }); + + _proxyServer.addConnector(_proxyServerConnector); + _proxyServer.setHandler(_handler); + _proxyServer.start(); + + _httpClient.start(); + } + + @AfterClass + public static void destroy() throws Exception + { + _httpClient.stop(); + _proxyServer.stop(); + _targetServer.stop(); + _rule = null; + } + + @Test + public void testProxy() throws Exception + { + + ContentExchange exchange = new ContentExchange(true); + exchange.setMethod(HttpMethods.GET); + String body = "BODY"; + String url = "http://localhost:" + _proxyServerConnector.getLocalPort() + "/foo?body=" + URLEncoder.encode(body,"UTF-8"); + exchange.setURL(url); + + _httpClient.send(exchange); + assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone()); + assertEquals("uri: / some content",exchange.getResponseContent()); + assertEquals(201,exchange.getResponseStatus()); + } + + @Test + public void testProxyWithDeeperPath() throws Exception + { + + ContentExchange exchange = new ContentExchange(true); + exchange.setMethod(HttpMethods.GET); + String body = "BODY"; + String url = "http://localhost:" + _proxyServerConnector.getLocalPort() + "/foo/bar/foobar?body=" + URLEncoder.encode(body,"UTF-8"); + exchange.setURL(url); + + _httpClient.send(exchange); + assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone()); + assertEquals("uri: /bar/foobar some content",exchange.getResponseContent()); + assertEquals(201,exchange.getResponseStatus()); + } + + @Test + public void testProxyNoMatch() throws Exception + { + ContentExchange exchange = new ContentExchange(true); + exchange.setMethod(HttpMethods.GET); + String body = "BODY"; + String url = "http://localhost:" + _proxyServerConnector.getLocalPort() + "/foobar?body=" + URLEncoder.encode(body,"UTF-8"); + exchange.setURL(url); + + _httpClient.send(exchange); + assertEquals(HttpExchange.STATUS_COMPLETED,exchange.waitForDone()); + assertEquals(404,exchange.getResponseStatus()); + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java index 2b2b7462761..472da9c6654 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultIdentityService.java @@ -17,6 +17,9 @@ import java.security.Principal; import javax.security.auth.Subject; +import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.security.MappedLoginService.KnownUser; +import org.eclipse.jetty.security.MappedLoginService.RolePrincipal; import org.eclipse.jetty.server.UserIdentity; diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java index 5e5a8445863..12b51f51502 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java @@ -13,21 +13,12 @@ package org.eclipse.jetty.security; -import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.security.PropertyUserStore.UserListener; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.Scanner; -import org.eclipse.jetty.util.Scanner.BulkListener; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; @@ -36,27 +27,24 @@ import org.eclipse.jetty.util.resource.Resource; /** * Properties User Realm. * - * An implementation of UserRealm that stores users and roles in-memory in - * HashMaps. + * An implementation of UserRealm that stores users and roles in-memory in HashMaps. *

    - * Typically these maps are populated by calling the load() method or passing a - * properties resource to the constructor. The format of the properties file is: + * Typically these maps are populated by calling the load() method or passing a properties resource to the constructor. The format of the properties file is: * *

      *  username: password [,rolename ...]
      * 
    * - * Passwords may be clear text, obfuscated or checksummed. The class - * com.eclipse.Util.Password should be used to generate obfuscated passwords or - * password checksums. + * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password + * checksums. * - * If DIGEST Authentication is used, the password must be in a recoverable - * format, either plain text or OBF:. + * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:. */ -public class HashLoginService extends MappedLoginService +public class HashLoginService extends MappedLoginService implements UserListener { private static final Logger LOG = Log.getLogger(HashLoginService.class); + private PropertyUserStore _propertyUserStore; private String _config; private Resource _configResource; private Scanner _scanner; @@ -72,14 +60,14 @@ public class HashLoginService extends MappedLoginService { setName(name); } - + /* ------------------------------------------------------------ */ public HashLoginService(String name, String config) { setName(name); setConfig(config); } - + /* ------------------------------------------------------------ */ public String getConfig() { @@ -89,7 +77,7 @@ public class HashLoginService extends MappedLoginService /* ------------------------------------------------------------ */ public void getConfig(String config) { - _config=config; + _config = config; } /* ------------------------------------------------------------ */ @@ -100,11 +88,10 @@ public class HashLoginService extends MappedLoginService /* ------------------------------------------------------------ */ /** - * Load realm users from properties file. The property file maps usernames - * to password specs followed by an optional comma separated list of role - * names. + * Load realm users from properties file. The property file maps usernames to password specs followed by an optional comma separated list of role names. * - * @param config Filename or url of user properties file. + * @param config + * Filename or url of user properties file. */ public void setConfig(String config) { @@ -129,52 +116,14 @@ public class HashLoginService extends MappedLoginService { return null; } - + /* ------------------------------------------------------------ */ @Override public void loadUsers() throws IOException { - if (_config==null) - return; - _configResource = Resource.newResource(_config); - - if (LOG.isDebugEnabled()) LOG.debug("Load " + this + " from " + _config); - Properties properties = new Properties(); - properties.load(_configResource.getInputStream()); - Set known = new HashSet(); - - for (Map.Entry entry : properties.entrySet()) - { - String username = ((String) entry.getKey()).trim(); - String credentials = ((String) entry.getValue()).trim(); - String roles = null; - int c = credentials.indexOf(','); - if (c > 0) - { - roles = credentials.substring(c + 1).trim(); - credentials = credentials.substring(0, c).trim(); - } - - if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0) - { - String[] roleArray = IdentityService.NO_ROLES; - if (roles != null && roles.length() > 0) - roleArray = roles.split(","); - known.add(username); - putUser(username,Credential.getCredential(credentials),roleArray); - } - } - - Iterator users = _users.keySet().iterator(); - while(users.hasNext()) - { - String user=users.next(); - if (!known.contains(user)) - users.remove(); - } + // TODO: Consider refactoring MappedLoginService to not have to override with unused methods } - /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() @@ -183,54 +132,17 @@ public class HashLoginService extends MappedLoginService { super.doStart(); - if (getRefreshInterval() > 0) + if (_propertyUserStore == null) { - _scanner = new Scanner(); - _scanner.setScanInterval(getRefreshInterval()); - List dirList = new ArrayList(1); - dirList.add(_configResource.getFile()); - _scanner.setScanDirs(dirList); - _scanner.setFilenameFilter(new FilenameFilter() + if(LOG.isDebugEnabled()) { - public boolean accept(File dir, String name) - { - File f = new File(dir, name); - try - { - if (f.compareTo(_configResource.getFile()) == 0) return true; - } - catch (IOException e) - { - return false; - } - - return false; - } - - }); - _scanner.addListener(new BulkListener() - { - public void filesChanged(List filenames) throws Exception - { - if (filenames == null) return; - if (filenames.isEmpty()) return; - if (filenames.size() == 1) - { - Resource r = Resource.newResource(filenames.get(0)); - if (r.getFile().equals(_configResource.getFile())) - loadUsers(); - } - } - - public String toString() - { - return "HashLoginService$Scanner"; - } - - }); - _scanner.setReportExistingFilesOnStartup(false); - _scanner.setRecursive(false); - _scanner.start(); + LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _config + " refreshInterval: " + _refreshInterval); + } + _propertyUserStore = new PropertyUserStore(); + _propertyUserStore.setRefreshInterval(_refreshInterval); + _propertyUserStore.setConfig(_config); + _propertyUserStore.registerUserListener(this); + _propertyUserStore.start(); } } @@ -241,9 +153,24 @@ public class HashLoginService extends MappedLoginService protected void doStop() throws Exception { super.doStop(); - if (_scanner != null) _scanner.stop(); + if (_scanner != null) + _scanner.stop(); _scanner = null; } + + /* ------------------------------------------------------------ */ + public void update(String userName, Credential credential, String[] roleArray) + { + if (LOG.isDebugEnabled()) + LOG.debug("update: " + userName + " Roles: " + roleArray.length); + putUser(userName,credential,roleArray); + } - + /* ------------------------------------------------------------ */ + public void remove(String userName) + { + if (LOG.isDebugEnabled()) + LOG.debug("remove: " + userName); + removeUser(userName); + } } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java index e05a000b2e0..216ae818d9b 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/IdentityService.java @@ -14,9 +14,9 @@ package org.eclipse.jetty.security; import java.security.Principal; - import javax.security.auth.Subject; +import org.eclipse.jetty.http.security.Credential; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.UserIdentity; diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java index 5e463165103..b7e64ac123b 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java @@ -3,7 +3,9 @@ package org.eclipse.jetty.security; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.security.Principal; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -11,7 +13,12 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import javax.security.auth.Subject; + import org.eclipse.jetty.http.security.Credential; +import org.eclipse.jetty.security.MappedLoginService.KnownUser; +import org.eclipse.jetty.security.MappedLoginService.RolePrincipal; +import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.Scanner.BulkListener; import org.eclipse.jetty.util.component.AbstractLifeCycle; @@ -42,8 +49,10 @@ public class PropertyUserStore extends AbstractLifeCycle private Scanner _scanner; private int _refreshInterval = 0;// default is not to reload + private IdentityService _identityService = new DefaultIdentityService(); private boolean _firstLoad = true; // true if first load, false from that point on private final List _knownUsers = new ArrayList(); + private final Map _knownUserIdentities = new HashMap(); private List _listeners; /* ------------------------------------------------------------ */ @@ -57,6 +66,12 @@ public class PropertyUserStore extends AbstractLifeCycle { _config = config; } + + /* ------------------------------------------------------------ */ + public UserIdentity getUserIdentity(String userName) + { + return _knownUserIdentities.get(userName); + } /* ------------------------------------------------------------ */ /** @@ -119,9 +134,29 @@ public class PropertyUserStore extends AbstractLifeCycle { String[] roleArray = IdentityService.NO_ROLES; if (roles != null && roles.length() > 0) + { roleArray = roles.split(","); + } known.add(username); - notifyUpdate(username,Credential.getCredential(credentials),roleArray); + Credential credential = Credential.getCredential(credentials); + + Principal userPrincipal = new KnownUser(username,credential); + Subject subject = new Subject(); + subject.getPrincipals().add(userPrincipal); + subject.getPrivateCredentials().add(credential); + + if (roles != null) + { + for (String role : roleArray) + { + subject.getPrincipals().add(new RolePrincipal(role)); + } + } + + subject.setReadOnly(); + + _knownUserIdentities.put(username,_identityService.newUserIdentity(subject,userPrincipal,roleArray)); + notifyUpdate(username,credential,roleArray); } } @@ -138,6 +173,7 @@ public class PropertyUserStore extends AbstractLifeCycle String user = users.next(); if (!known.contains(user)) { + _knownUserIdentities.remove(user); notifyRemove(user); } } @@ -201,15 +237,17 @@ public class PropertyUserStore extends AbstractLifeCycle _scanner.addListener(new BulkListener() { - public void filesChanged(List filenames) throws Exception + public void filesChanged(List filenames) throws Exception { if (filenames == null) return; if (filenames.isEmpty()) return; - if (filenames.size() == 1 && filenames.get(0).equals(getConfigResource().getFile().getAbsolutePath())) + if (filenames.size() == 1) { - loadUsers(); + Resource r = Resource.newResource(filenames.get(0)); + if (r.getFile().equals(_configResource.getFile())) + loadUsers(); } } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java index e680644609f..372f9b63ea0 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java @@ -65,20 +65,28 @@ public class BasicAuthenticator extends LoginAuthenticator return _deferred; if (credentials != null) - { - credentials = credentials.substring(credentials.indexOf(' ')+1); - credentials = B64Code.decode(credentials,StringUtil.__ISO_8859_1); - int i = credentials.indexOf(':'); - if (i>0) + { + int space=credentials.indexOf(' '); + if (space>0) { - String username = credentials.substring(0,i); - String password = credentials.substring(i+1); - - UserIdentity user = _loginService.login(username,password); - if (user!=null) + String method=credentials.substring(0,space); + if ("basic".equalsIgnoreCase(method)) { - renewSessionOnAuthentication(request,response); - return new UserAuthentication(getAuthMethod(),user); + credentials = credentials.substring(space+1); + credentials = B64Code.decode(credentials,StringUtil.__ISO_8859_1); + int i = credentials.indexOf(':'); + if (i>0) + { + String username = credentials.substring(0,i); + String password = credentials.substring(i+1); + + UserIdentity user = _loginService.login(username,password); + if (user!=null) + { + renewSessionOnAuthentication(request,response); + return new UserAuthentication(getAuthMethod(),user); + } + } } } } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java index 7fa563a8502..5a2ecf412e8 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java @@ -15,6 +15,12 @@ package org.eclipse.jetty.security.authentication; import java.io.IOException; import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -42,17 +48,29 @@ import org.eclipse.jetty.util.log.Logger; /** * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ * - * The nonce max age can be set with the {@link SecurityHandler#setInitParameter(String, String)} + * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)} * using the name "maxNonceAge" */ public class DigestAuthenticator extends LoginAuthenticator { private static final Logger LOG = Log.getLogger(DigestAuthenticator.class); + SecureRandom _random = new SecureRandom(); + private long _maxNonceAgeMs = 60*1000; + private ConcurrentMap _nonceCount = new ConcurrentHashMap(); + private Queue _nonceQueue = new ConcurrentLinkedQueue(); + private static class Nonce + { + final String _nonce; + final long _ts; + AtomicInteger _nc=new AtomicInteger(); + public Nonce(String nonce, long ts) + { + _nonce=nonce; + _ts=ts; + } + } - protected long _maxNonceAge = 0; - protected long _nonceSecret = this.hashCode() ^ System.currentTimeMillis(); - protected boolean _useStale = false; - + /* ------------------------------------------------------------ */ public DigestAuthenticator() { super(); @@ -69,19 +87,22 @@ public class DigestAuthenticator extends LoginAuthenticator String mna=configuration.getInitParameter("maxNonceAge"); if (mna!=null) - _maxNonceAge=Long.valueOf(mna); + _maxNonceAgeMs=Long.valueOf(mna); } + /* ------------------------------------------------------------ */ public String getAuthMethod() { return Constraint.__DIGEST_AUTH; } - + + /* ------------------------------------------------------------ */ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException { return true; } + /* ------------------------------------------------------------ */ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException { if (!mandatory) @@ -144,7 +165,7 @@ public class DigestAuthenticator extends LoginAuthenticator } } - int n = checkNonce(digest.nonce, (Request)request); + int n = checkNonce(digest,(Request)request); if (n > 0) { @@ -170,8 +191,8 @@ public class DigestAuthenticator extends LoginAuthenticator + domain + "\", nonce=\"" + newNonce((Request)request) - + "\", algorithm=MD5, qop=\"auth\"" - + (_useStale ? (" stale=" + stale) : "")); + + "\", algorithm=MD5, qop=\"auth\"," + + " stale=" + stale); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return Authentication.SEND_CONTINUE; @@ -186,87 +207,59 @@ public class DigestAuthenticator extends LoginAuthenticator } + /* ------------------------------------------------------------ */ public String newNonce(Request request) { - long ts=request.getTimeStamp(); - long sk = _nonceSecret; - - byte[] nounce = new byte[24]; - for (int i = 0; i < 8; i++) + Nonce nonce; + + do { - nounce[i] = (byte) (ts & 0xff); - ts = ts >> 8; - nounce[8 + i] = (byte) (sk & 0xff); - sk = sk >> 8; - } + byte[] nounce = new byte[24]; + _random.nextBytes(nounce); - byte[] hash = null; - try - { - MessageDigest md = MessageDigest.getInstance("MD5"); - md.reset(); - md.update(nounce, 0, 16); - hash = md.digest(); + nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp()); } - catch (Exception e) - { - LOG.warn(e); - } - - for (int i = 0; i < hash.length; i++) - { - nounce[8 + i] = hash[i]; - if (i == 23) break; - } - - return new String(B64Code.encode(nounce)); + while (_nonceCount.putIfAbsent(nonce._nonce,nonce)!=null); + _nonceQueue.add(nonce); + + return nonce._nonce; } /** - * @param nonce nonce to check + * @param nstring nonce to check * @param request * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce */ /* ------------------------------------------------------------ */ - private int checkNonce(String nonce, Request request) + private int checkNonce(Digest digest, Request request) { + // firstly let's expire old nonces + long expired = request.getTimeStamp()-_maxNonceAgeMs; + + Nonce nonce=_nonceQueue.peek(); + while (nonce!=null && nonce._ts> 8; - ts = (ts << 8) + (0xff & (long) n[7 - i]); - } - - long age = request.getTimeStamp() - ts; - if (LOG.isDebugEnabled()) LOG.debug("age=" + age); - - byte[] hash = null; - try - { - MessageDigest md = MessageDigest.getInstance("MD5"); - md.reset(); - md.update(n2, 0, 16); - hash = md.digest(); - } - catch (Exception e) - { - LOG.warn(e); - } - - for (int i = 0; i < 16; i++) - if (n[i + 8] != hash[i]) return -1; - - if (_maxNonceAge > 0 && (age < 0 || age > _maxNonceAge)) return 0; // stale - + nonce = _nonceCount.get(digest.nonce); + if (nonce==null) + return 0; + + long count = Long.parseLong(digest.nc,16); + if (count>Integer.MAX_VALUE) + return 0; + int old=nonce._nc.get(); + while (!nonce._nc.compareAndSet(old,(int)count)) + old=nonce._nc.get(); + if (count<=old) + return -1; + return 1; } catch (Exception e) @@ -276,18 +269,21 @@ public class DigestAuthenticator extends LoginAuthenticator return -1; } + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ private static class Digest extends Credential { private static final long serialVersionUID = -2484639019549527724L; - String method = null; - String username = null; - String realm = null; - String nonce = null; - String nc = null; - String cnonce = null; - String qop = null; - String uri = null; - String response = null; + final String method; + String username = ""; + String realm = ""; + String nonce = ""; + String nc = ""; + String cnonce = ""; + String qop = ""; + String uri = ""; + String response = ""; /* ------------------------------------------------------------ */ Digest(String m) diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java index 9a40357de9e..19eb3099011 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java @@ -201,13 +201,13 @@ public class ConstraintTest assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); @@ -218,20 +218,20 @@ public class ConstraintTest assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("admin:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 403 ")); assertTrue(response.indexOf("!role") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("admin:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); @@ -490,18 +490,18 @@ public class ConstraintTest assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 403")); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user2:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); @@ -512,20 +512,20 @@ public class ConstraintTest assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("admin:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); assertTrue(response.indexOf("WWW-Authenticate: basic realm=\"TestRealm\"") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 403 ")); assertTrue(response.indexOf("!role") > 0); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("admin:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); @@ -776,7 +776,7 @@ public class ConstraintTest assertTrue(response.startsWith("HTTP/1.1 200 OK")); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user2:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 500 ")); @@ -789,7 +789,7 @@ public class ConstraintTest _server.start(); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + - "Authorization: " + B64Code.encode("user2:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); } @@ -809,13 +809,13 @@ public class ConstraintTest assertTrue(response.indexOf("user=null") > 0); response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n"+ - "Authorization: " + B64Code.encode("admin:wrong") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:wrong") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertTrue(response.indexOf("user=null") > 0); response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n"+ - "Authorization: " + B64Code.encode("admin:password") + "\r\n" + + "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" + "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertTrue(response.indexOf("user=admin") > 0); diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java index dde1df50bc1..68220b8dbca 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/PropertyUserStoreTest.java @@ -78,6 +78,9 @@ public class PropertyUserStoreTest store.start(); + Assert.assertNotNull("Failed to retrieve UserIdentity directly from PropertyUserStore", store.getUserIdentity("tom")); + Assert.assertNotNull("Failed to retrieve UserIdentity directly from PropertyUserStore", store.getUserIdentity("dick")); + Assert.assertNotNull("Failed to retrieve UserIdentity directly from PropertyUserStore", store.getUserIdentity("harry")); Assert.assertEquals(3,userCount.get()); } @@ -117,7 +120,11 @@ public class PropertyUserStoreTest long start = System.currentTimeMillis(); while (userCount.get() < 4 && (System.currentTimeMillis() - start) < 10000) + { Thread.sleep(10); + } + + Assert.assertNotNull("Failed to retrieve UserIdentity from PropertyUserStore directly", store.getUserIdentity("skip")); Assert.assertEquals(4,userCount.get()); Assert.assertTrue(users.contains("skip")); diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml index 25f6b51f55b..c0fe7aee6b0 100644 --- a/jetty-server/pom.xml +++ b/jetty-server/pom.xml @@ -112,5 +112,11 @@ ${project.version} true
    + + org.mockito + mockito-core + 1.8.5 + test + diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java index 70175378cdf..e2a81e5de58 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java @@ -7,12 +7,18 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.AsyncEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.nio.ChannelEndPoint; +import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; public class AsyncHttpConnection extends HttpConnection { + private final static int NO_PROGRESS_INFO = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_INFO",100); + private final static int NO_PROGRESS_CLOSE = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_CLOSE",200); + private static final Logger LOG = Log.getLogger(AsyncHttpConnection.class); + private int _total_no_progress; public AsyncHttpConnection(Connector connector, EndPoint endpoint, Server server) { @@ -22,21 +28,21 @@ public class AsyncHttpConnection extends HttpConnection public Connection handle() throws IOException { Connection connection = this; + boolean some_progress=false; + boolean progress=true; // Loop while more in buffer try { setCurrentConnection(this); - boolean progress=true; boolean more_in_buffer =false; - while (_endp.isOpen() && (more_in_buffer || progress)) + while (_endp.isOpen() && (more_in_buffer || progress) && connection==this) { progress=false; try { - LOG.debug("async request",_request); // Handle resumed request if (_request._async.isAsync() && !_request._async.isComplete()) @@ -53,6 +59,11 @@ public class AsyncHttpConnection extends HttpConnection // Flush output from buffering endpoint if (_endp.isBufferingOutput()) _endp.flush(); + + // Special case close handling. + // If we were dispatched and have made no progress, but io is shutdown, then close + if (!progress && !some_progress && (_endp.isInputShutdown()||_endp.isOutputShutdown())) + _endp.close(); } catch (HttpException e) { @@ -88,7 +99,7 @@ public class AsyncHttpConnection extends HttpConnection { _parser.reset(); _generator.reset(true); - return switched; + connection=switched; } } @@ -98,7 +109,6 @@ public class AsyncHttpConnection extends HttpConnection reset(false); more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); } - // else Are we suspended? else if (_request.isAsyncStarted()) { @@ -108,6 +118,8 @@ public class AsyncHttpConnection extends HttpConnection } else more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput(); + + some_progress|=progress|((SelectChannelEndPoint)_endp).isProgressing(); } } } @@ -115,12 +127,31 @@ public class AsyncHttpConnection extends HttpConnection { setCurrentConnection(null); _parser.returnBuffers(); + _generator.returnBuffers(); - // Are we write blocked - if (_generator.isCommitted() && !_generator.isComplete()) - ((AsyncEndPoint)_endp).scheduleWrite(); - else - _generator.returnBuffers(); + // Check if we are write blocked + if (_generator.isCommitted() && !_generator.isComplete() && _endp.isOpen() && !_endp.isOutputShutdown()) + ((AsyncEndPoint)_endp).scheduleWrite(); // TODO. This should not be required + + if (!some_progress) + { + _total_no_progress++; + + if (NO_PROGRESS_INFO>0 && _total_no_progress%NO_PROGRESS_INFO==0 && (NO_PROGRESS_CLOSE<=0 || _total_no_progress< NO_PROGRESS_CLOSE)) + { + LOG.info("EndPoint making no progress: "+_total_no_progress+" "+_endp); + } + + if (NO_PROGRESS_CLOSE>0 && _total_no_progress>NO_PROGRESS_CLOSE) + { + LOG.warn("Closing EndPoint making no progress: "+_total_no_progress+" "+_endp); + _endp.close(); + if (_endp instanceof SelectChannelEndPoint) + { + System.err.println(((SelectChannelEndPoint)_endp).getSelectManager().dump()); + } + } + } } return connection; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java index a6e040f77b2..975f942389e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java @@ -76,9 +76,8 @@ public class BlockingHttpConnection extends HttpConnection LOG.debug(e); } _generator.sendError(e.getStatus(), e.getReason(), null, true); - _parser.reset(); - _endp.close(); + _endp.shutdownOutput(); } finally { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java index eb054afe72a..1383cb49ae2 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java @@ -205,6 +205,7 @@ public class Dispatcher implements RequestDispatcher if (!(response instanceof HttpServletResponse)) response = new ServletResponseHttpWrapper(response); + final boolean old_handled=baseRequest.isHandled(); final String old_uri=baseRequest.getRequestURI(); final String old_context_path=baseRequest.getContextPath(); final String old_servlet_path=baseRequest.getServletPath(); @@ -216,6 +217,7 @@ public class Dispatcher implements RequestDispatcher try { + baseRequest.setHandled(false); baseRequest.setDispatcherType(dispatch); if (_named!=null) @@ -262,6 +264,8 @@ public class Dispatcher implements RequestDispatcher baseRequest.setRequestURI(_uri); baseRequest.setContextPath(_contextHandler.getContextPath()); + baseRequest.setServletPath(null); + baseRequest.setPathInfo(_uri); baseRequest.setAttributes(attr); _contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); @@ -286,6 +290,7 @@ public class Dispatcher implements RequestDispatcher } finally { + baseRequest.setHandled(old_handled); baseRequest.setRequestURI(old_uri); baseRequest.setContextPath(old_context_path); baseRequest.setServletPath(old_servlet_path); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 4a80f80ece0..662d8869f2e 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -327,7 +327,7 @@ public abstract class HttpConnection extends AbstractConnection } if (_in == null) - _in = new HttpInput(((HttpParser)_parser),_connector.getMaxIdleTime()); + _in = new HttpInput(HttpConnection.this); return _in; } @@ -455,22 +455,22 @@ public abstract class HttpConnection extends AbstractConnection { async_exception=e; LOG.debug(e); - _request.setHandled(true); error=true; + _request.setHandled(true); } catch (UncheckedIOException e) { async_exception=e; LOG.debug(e); - _request.setHandled(true); error=true; + _request.setHandled(true); } catch (HttpException e) { LOG.debug(e); + error=true; _request.setHandled(true); _response.sendError(e.getStatus(), e.getReason()); - error=true; } catch (Throwable e) { @@ -478,9 +478,8 @@ public abstract class HttpConnection extends AbstractConnection throw (ThreadDeath)e; async_exception=e; - - error=true; LOG.warn(String.valueOf(_uri),e); + error=true; _request.setHandled(true); _generator.sendError(info==null?400:500, null, null, true); } @@ -515,7 +514,12 @@ public abstract class HttpConnection extends AbstractConnection if(_endp.isOpen()) { if (error) + { _endp.shutdownOutput(); + _generator.setPersistent(false); + if (!_generator.isComplete()) + _response.complete(); + } else { if (!_response.isCommitted() && !_request.isHandled()) @@ -677,6 +681,16 @@ public abstract class HttpConnection extends AbstractConnection return _expect102Processing; } + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + if (_connector.isLowResources() && _endp.getMaxIdleTime()==_connector.getMaxIdleTime()) + return _connector.getLowResourceMaxIdleTime(); + if (_endp.getMaxIdleTime()>0) + return _endp.getMaxIdleTime(); + return _connector.getMaxIdleTime(); + } + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ @@ -966,8 +980,7 @@ public abstract class HttpConnection extends AbstractConnection { Output() { - super((AbstractGenerator)HttpConnection.this._generator, - _connector.isLowResources()?_connector.getLowResourceMaxIdleTime():_connector.getMaxIdleTime()); + super(HttpConnection.this); } /* ------------------------------------------------------------ */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index f0bbc9aec8b..670fa95c81a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -22,14 +22,14 @@ import org.eclipse.jetty.io.Buffer; public class HttpInput extends ServletInputStream { + protected final HttpConnection _connection; protected final HttpParser _parser; - protected final long _maxIdleTime; /* ------------------------------------------------------------ */ - public HttpInput(HttpParser parser, long maxIdleTime) + public HttpInput(HttpConnection connection) { - _parser=parser; - _maxIdleTime=maxIdleTime; + _connection=connection; + _parser=(HttpParser)connection.getParser(); } /* ------------------------------------------------------------ */ @@ -40,7 +40,7 @@ public class HttpInput extends ServletInputStream public int read() throws IOException { int c=-1; - Buffer content=_parser.blockForContent(_maxIdleTime); + Buffer content=_parser.blockForContent(_connection.getMaxIdleTime()); if (content!=null) c= 0xff & content.get(); return c; @@ -54,7 +54,7 @@ public class HttpInput extends ServletInputStream public int read(byte[] b, int off, int len) throws IOException { int l=-1; - Buffer content=_parser.blockForContent(_maxIdleTime); + Buffer content=_parser.blockForContent(_connection.getMaxIdleTime()); if (content!=null) l= content.get(b, off, len); return l; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index ec9357e9912..6d595877851 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -36,9 +36,8 @@ import org.eclipse.jetty.util.ByteArrayOutputStream2; */ public class HttpOutput extends ServletOutputStream { + protected final HttpConnection _connection; protected final AbstractGenerator _generator; - protected final long _maxIdleTime; - protected final ByteArrayBuffer _buf = new ByteArrayBuffer(AbstractGenerator.NO_BYTES); private boolean _closed; // These are held here for reuse by Writer @@ -46,15 +45,20 @@ public class HttpOutput extends ServletOutputStream Writer _converter; char[] _chars; ByteArrayOutputStream2 _bytes; - /* ------------------------------------------------------------ */ - public HttpOutput(AbstractGenerator generator, long maxIdleTime) + public HttpOutput(HttpConnection connection) { - _generator=generator; - _maxIdleTime=maxIdleTime; + _connection=connection; + _generator=(AbstractGenerator)connection.getGenerator(); } + /* ------------------------------------------------------------ */ + public int getMaxIdleTime() + { + return _connection.getMaxIdleTime(); + } + /* ------------------------------------------------------------ */ public boolean isWritten() { @@ -87,7 +91,7 @@ public class HttpOutput extends ServletOutputStream @Override public void flush() throws IOException { - _generator.flush(_maxIdleTime); + _generator.flush(getMaxIdleTime()); } /* ------------------------------------------------------------ */ @@ -122,7 +126,7 @@ public class HttpOutput extends ServletOutputStream // Block until we can add _content. while (_generator.isBufferFull()) { - _generator.blockForOutput(_maxIdleTime); + _generator.blockForOutput(getMaxIdleTime()); if (_closed) throw new IOException("Closed"); if (!_generator.isOpen()) @@ -152,7 +156,7 @@ public class HttpOutput extends ServletOutputStream // Block until we can add _content. while (_generator.isBufferFull()) { - _generator.blockForOutput(_maxIdleTime); + _generator.blockForOutput(getMaxIdleTime()); if (_closed) throw new IOException("Closed"); if (!_generator.isOpen()) @@ -175,7 +179,7 @@ public class HttpOutput extends ServletOutputStream // Block until our buffer is free while (buffer.length() > 0 && _generator.isOpen()) { - _generator.blockForOutput(_maxIdleTime); + _generator.blockForOutput(getMaxIdleTime()); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java index 401f924ec78..d4849acb122 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java @@ -23,9 +23,12 @@ import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; public class LocalConnector extends AbstractConnector { + private static final Logger LOG = Log.getLogger(LocalConnector.class); private final BlockingQueue _requests = new LinkedBlockingQueue(); public LocalConnector() @@ -135,8 +138,14 @@ public class LocalConnector extends AbstractConnector } } } + catch (IOException x) + { + LOG.debug(x); + leaveOpen = false; + } catch (Exception x) { + LOG.warn(x); leaveOpen = false; } finally diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 3a0646cc85d..9ac05a00e5b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -202,79 +202,36 @@ public class Request implements HttpServletRequest /* ------------------------------------------------------------ */ /** - * Extract Paramters from query string and/or form _content. + * Extract Parameters from query string and/or form _content. */ public void extractParameters() { if (_baseParameters == null) _baseParameters = new MultiMap(16); - + if (_paramsExtracted) { if (_parameters==null) _parameters=_baseParameters; return; } - + _paramsExtracted = true; - // Handle query string - if (_uri!=null && _uri.hasQuery()) + try { - if (_queryEncoding==null) - _uri.decodeQueryTo(_baseParameters); - else + // Handle query string + if (_uri!=null && _uri.hasQuery()) { - try - { - _uri.decodeQueryTo(_baseParameters,_queryEncoding); - } - catch (UnsupportedEncodingException e) - { - if (LOG.isDebugEnabled()) - LOG.warn(e); - else - LOG.warn(e.toString()); - } - } - } - - // handle any _content. - String encoding = getCharacterEncoding(); - String content_type = getContentType(); - if (content_type != null && content_type.length() > 0) - { - content_type = HttpFields.valueParameters(content_type, null); - - if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) && _inputState==__NONE && - (HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod()))) - { - int content_length = getContentLength(); - if (content_length != 0) + if (_queryEncoding==null) + _uri.decodeQueryTo(_baseParameters); + else { try { - int maxFormContentSize=-1; - - if (_context!=null) - maxFormContentSize=_context.getContextHandler().getMaxFormContentSize(); - else - { - Integer size = (Integer)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); - if (size!=null) - maxFormContentSize =size.intValue(); - } - - if (content_length>maxFormContentSize && maxFormContentSize > 0) - { - throw new IllegalStateException("Form too large"+content_length+">"+maxFormContentSize); - } - InputStream in = getInputStream(); - - // Add form params to query params - UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1); + _uri.decodeQueryTo(_baseParameters,_queryEncoding); } - catch (IOException e) + catch (UnsupportedEncodingException e) { if (LOG.isDebugEnabled()) LOG.warn(e); @@ -283,23 +240,75 @@ public class Request implements HttpServletRequest } } } - } - - if (_parameters==null) - _parameters=_baseParameters; - else if (_parameters!=_baseParameters) - { - // Merge parameters (needed if parameters extracted after a forward). - Iterator iter = _baseParameters.entrySet().iterator(); - while (iter.hasNext()) + + // handle any _content. + String encoding = getCharacterEncoding(); + String content_type = getContentType(); + if (content_type != null && content_type.length() > 0) { - Map.Entry entry = (Map.Entry)iter.next(); - String name=(String)entry.getKey(); - Object values=entry.getValue(); - for (int i=0;imaxFormContentSize && maxFormContentSize > 0) + { + throw new IllegalStateException("Form too large"+content_length+">"+maxFormContentSize); + } + InputStream in = getInputStream(); + + // Add form params to query params + UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1); + } + catch (IOException e) + { + if (LOG.isDebugEnabled()) + LOG.warn(e); + else + LOG.warn(e.toString()); + } + } + } } - } + + if (_parameters==null) + _parameters=_baseParameters; + else if (_parameters!=_baseParameters) + { + // Merge parameters (needed if parameters extracted after a forward). + Iterator iter = _baseParameters.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + String name=(String)entry.getKey(); + Object values=entry.getValue(); + for (int i=0;i + * Server server = new Server(8080); + * HandlerList handlers = new HandlerList(); + * handlers.setHandlers(new Handler[] + * { someOtherHandler, new ShutdownHandler(server,"secret password") }); + * server.setHandler(handlers); + * server.start(); + * + */ +public class ShutdownHandler extends AbstractHandler +{ + private static final Logger LOG = Log.getLogger(ShutdownHandler.class); + + private final String _shutdownToken; + + private final Server _server; + + private boolean _exitJvm = false; + + /** + * Creates a listener that lets the server be shut down remotely (but only from localhost). + * + * @param server + * the Jetty instance that should be shut down + * @param shutdownToken + * a secret password to avoid unauthorized shutdown attempts + */ + public ShutdownHandler(Server server, String shutdownToken) + { + this._server = server; + this._shutdownToken = shutdownToken; + } + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!target.equals("/shutdown")) + { + return; + } + + if (!request.getMethod().equals("POST")) + { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + if (!hasCorrectSecurityToken(request)) + { + LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request)); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + if (!requestFromLocalhost(request)) + { + LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request)); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + LOG.info("Shutting down by request from " + getRemoteAddr(request)); + + try + { + shutdownServer(); + } + catch (Exception e) + { + throw new RuntimeException("Shutting down server",e); + } + } + + private boolean requestFromLocalhost(HttpServletRequest request) + { + return "127.0.0.1".equals(getRemoteAddr(request)); + } + + protected String getRemoteAddr(HttpServletRequest request) + { + return request.getRemoteAddr(); + } + + private boolean hasCorrectSecurityToken(HttpServletRequest request) + { + return _shutdownToken.equals(request.getParameter("token")); + } + + private void shutdownServer() throws Exception + { + _server.stop(); + + if (_exitJvm) + { + System.exit(0); + } + } + + public void setExitJvm(boolean exitJvm) + { + this._exitJvm = exitJvm; + } + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java index c8cff99341c..9a4f8f03bcf 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java @@ -133,6 +133,12 @@ public class SelectChannelConnector extends AbstractNIOConnector super.persist(endpoint); } + /* ------------------------------------------------------------ */ + public SelectorManager getSelectorManager() + { + return _manager; + } + /* ------------------------------------------------------------ */ public Object getConnection() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java index 5e0aea6b457..7624afcf87a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionIdManager.java @@ -14,7 +14,9 @@ package org.eclipse.jetty.server.session; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -46,6 +48,34 @@ public class HashSessionIdManager extends AbstractSessionIdManager super(random); } + /* ------------------------------------------------------------ */ + /** + * @return Collection of String session IDs + */ + public Collection getSessions() + { + return Collections.unmodifiableCollection(_sessions.keySet()); + } + + /* ------------------------------------------------------------ */ + /** + * @return Collection of Sessions for the passed session ID + */ + public Collection getSession(String id) + { + ArrayList sessions = new ArrayList(); + Set> refs =_sessions.get(id); + if (refs!=null) + { + for (WeakReference ref: refs) + { + HttpSession session = ref.get(); + if (session!=null) + sessions.add(session); + } + } + return sessions; + } /* ------------------------------------------------------------ */ /** Get the session ID with any worker ID. * diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java index 8e73f3af1d0..ad5b244d761 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java @@ -18,6 +18,7 @@ import java.io.InputStream; import java.sql.Blob; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.Driver; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -56,6 +57,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager protected final HashSet _sessionIds = new HashSet(); protected Server _server; + protected Driver _driver; protected String _driverClassName; protected String _connectionUrl; protected DataSource _datasource; @@ -184,6 +186,19 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager _connectionUrl=connectionUrl; } + /** + * Configure jdbc connection information via a jdbc Driver + * + * @param driverClass + * @param connectionUrl + */ + public void setDriverInfo (Driver driverClass, String connectionUrl) + { + _driver=driverClass; + _connectionUrl=connectionUrl; + } + + public String getDriverClassName() { return _driverClassName; @@ -461,7 +476,11 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager InitialContext ic = new InitialContext(); _datasource = (DataSource)ic.lookup(_jndiName); } - else if (_driverClassName!=null && _connectionUrl!=null) + else if ( _driver != null && _connectionUrl != null ) + { + DriverManager.registerDriver(_driver); + } + else if (_driverClassName != null && _connectionUrl != null) { Class.forName(_driverClassName); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java index d60f82dea6b..b1b3347321c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java @@ -16,6 +16,8 @@ package org.eclipse.jetty.server.ssl; import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.util.Arrays; + import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; @@ -36,7 +38,6 @@ import org.eclipse.jetty.io.nio.SslSelectChannelEndPoint; import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.nio.SelectChannelConnector; -import org.eclipse.jetty.util.log.Log; /* ------------------------------------------------------------ */ /** @@ -97,7 +98,7 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements SslSelectChannelEndPoint sslHttpChannelEndpoint=(SslSelectChannelEndPoint)endpoint; SSLEngine sslEngine=sslHttpChannelEndpoint.getSSLEngine(); SSLSession sslSession=sslEngine.getSession(); - + SslCertificates.customize(sslSession,endpoint,request); } @@ -565,33 +566,19 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements protected SSLEngine createSSLEngine(SocketChannel channel) throws IOException { SSLEngine engine; - if (channel != null && _sslContextFactory.isSessionCachingEnabled()) + if (channel != null) { String peerHost = channel.socket().getInetAddress().getHostAddress(); int peerPort = channel.socket().getPort(); - engine = _sslContextFactory.getSslContext().createSSLEngine(peerHost, peerPort); + engine = _sslContextFactory.newSslEngine(peerHost, peerPort); } else { - engine = _sslContextFactory.getSslContext().createSSLEngine(); + engine = _sslContextFactory.newSslEngine(); } - customizeEngine(engine); - return engine; - } - - /* ------------------------------------------------------------ */ - private void customizeEngine(SSLEngine engine) - { + engine.setUseClientMode(false); - - if (_sslContextFactory.getWantClientAuth()) - engine.setWantClientAuth(_sslContextFactory.getWantClientAuth()); - if (_sslContextFactory.getNeedClientAuth()) - engine.setNeedClientAuth(_sslContextFactory.getNeedClientAuth()); - - engine.setEnabledCipherSuites( - _sslContextFactory.selectCipherSuites(engine.getEnabledCipherSuites(), - engine.getSupportedCipherSuites())); + return engine; } /* ------------------------------------------------------------ */ @@ -601,22 +588,13 @@ public class SslSelectChannelConnector extends SelectChannelConnector implements @Override protected void doStart() throws Exception { - if (!_sslContextFactory.checkConfig()) - { - throw new IllegalStateException("SSL context is not configured correctly."); - } + _sslContextFactory.checkKeyStore(); _sslContextFactory.start(); - SSLEngine sslEngine = _sslContextFactory.getSslContext().createSSLEngine(); + SSLEngine sslEngine = _sslContextFactory.newSslEngine(); sslEngine.setUseClientMode(false); - sslEngine.setWantClientAuth(_sslContextFactory.getWantClientAuth()); - sslEngine.setNeedClientAuth(_sslContextFactory.getNeedClientAuth()); - - sslEngine.setEnabledCipherSuites(_sslContextFactory.selectCipherSuites( - sslEngine.getEnabledCipherSuites(), - sslEngine.getSupportedCipherSuites())); SSLSession sslSession = sslEngine.getSession(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java index 1783f9aba0e..3a6a3df299f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java @@ -4,32 +4,30 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.server.ssl; import java.io.IOException; -import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; - import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import org.eclipse.jetty.http.HttpSchemes; import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.io.bio.SocketEndPoint; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.bio.SocketConnector; @@ -39,17 +37,17 @@ import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ /** * SSL Socket Connector. - * + * * This specialization of SocketConnector is an abstract listener that can be used as the basis for a * specific JSSE listener. - * - * The original of this class was heavily based on the work from Court Demas, which in turn is + * + * The original of this class was heavily based on the work from Court Demas, which in turn is * based on the work from Forge Research. Since JSSE, this class has evolved significantly from * that early work. - * + * * @org.apache.xbean.XBean element="sslSocketConnector" description="Creates an ssl socket connector" * - * + * */ public class SslSocketConnector extends SocketConnector implements SslConnector { @@ -67,6 +65,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH)); } + /* ------------------------------------------------------------ */ public SslSocketConnector(SslContextFactory sslContextFactory) { _sslContextFactory = sslContextFactory; @@ -85,7 +84,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector /** * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered * a vulnerability in SSL/TLS with re-negotiation. If your JVM - * does not have CVE-2009-3555 fixed, then re-negotiation should + * does not have CVE-2009-3555 fixed, then re-negotiation should * not be allowed. * @param allowRenegotiate true if re-negotiation is allowed (default false) */ @@ -98,19 +97,19 @@ public class SslSocketConnector extends SocketConnector implements SslConnector @Override public void accept(int acceptorID) throws IOException, InterruptedException - { + { Socket socket = _serverSocket.accept(); configure(socket); - + ConnectorEndPoint connection=new SslConnectorEndPoint(socket); connection.dispatch(); } - + /* ------------------------------------------------------------ */ @Override protected void configure(Socket socket) throws IOException - { + { super.configure(socket); } @@ -129,8 +128,8 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * certificate in the chain is the one set by the client, the next is the one used to * authenticate the first, and so on. * - * - * @param endpoint The Socket the request arrived on. + * + * @param endpoint The Socket the request arrived on. * This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}. * @param request HttpRequest to be customised. */ @@ -140,7 +139,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { super.customize(endpoint, request); request.setScheme(HttpSchemes.HTTPS); - + SocketEndPoint socket_end_point = (SocketEndPoint)endpoint; SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport(); SSLSession sslSession = sslSocket.getSession(); @@ -148,7 +147,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector SslCertificates.customize(sslSession,endpoint,request); } - /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites() * @deprecated @@ -157,7 +156,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector public String[] getExcludeCipherSuites() { return _sslContextFactory.getExcludeCipherSuites(); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#getIncludeCipherSuites() @@ -186,7 +185,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getKeystoreType() + public String getKeystoreType() { return _sslContextFactory.getKeyStoreType(); } @@ -208,7 +207,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getProtocol() + public String getProtocol() { return _sslContextFactory.getProtocol(); } @@ -229,7 +228,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getSecureRandomAlgorithm() + public String getSecureRandomAlgorithm() { return _sslContextFactory.getSecureRandomAlgorithm(); } @@ -240,7 +239,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getSslKeyManagerFactoryAlgorithm() + public String getSslKeyManagerFactoryAlgorithm() { return _sslContextFactory.getSslKeyManagerFactoryAlgorithm(); } @@ -251,7 +250,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public String getSslTrustManagerFactoryAlgorithm() + public String getSslTrustManagerFactoryAlgorithm() { return _sslContextFactory.getTrustManagerFactoryAlgorithm(); } @@ -313,7 +312,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector final int confidentialPort = getConfidentialPort(); return confidentialPort == 0 || confidentialPort == request.getServerPort(); } - + /* ------------------------------------------------------------ */ /** * By default, we're integral, given we speak SSL. But, if we've been told about an integral @@ -329,6 +328,22 @@ public class SslSocketConnector extends SocketConnector implements SslConnector return integralPort == 0 || integralPort == request.getServerPort(); } + /* ------------------------------------------------------------ */ + @Override + public void open() throws IOException + { + _sslContextFactory.checkKeyStore(); + try + { + _sslContextFactory.start(); + } + catch(Exception e) + { + throw new RuntimeIOException(e); + } + super.open(); + } + /* ------------------------------------------------------------ */ /** * {@inheritDoc} @@ -336,16 +351,12 @@ public class SslSocketConnector extends SocketConnector implements SslConnector @Override protected void doStart() throws Exception { - if (!_sslContextFactory.checkConfig()) - { - throw new IllegalStateException("SSL context is not configured correctly."); - } - + _sslContextFactory.checkKeyStore(); _sslContextFactory.start(); - + super.doStart(); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.bio.SocketConnector#doStop() @@ -357,14 +368,14 @@ public class SslSocketConnector extends SocketConnector implements SslConnector super.doStop(); } - + /* ------------------------------------------------------------ */ /** * @param host The host name that this server should listen on - * @param port the port that this server should listen on + * @param port the port that this server should listen on * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)} * @return A new {@link ServerSocket socket object} bound to the supplied address with all other - * settings as per the current configuration of this connector. + * settings as per the current configuration of this connector. * @see #setWantClientAuth(boolean) * @see #setNeedClientAuth(boolean) * @exception IOException @@ -372,22 +383,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector @Override protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException { - SSLServerSocketFactory factory = _sslContextFactory.getSslContext().getServerSocketFactory(); - - SSLServerSocket socket = - (SSLServerSocket) (host==null ? - factory.createServerSocket(port,backlog): - factory.createServerSocket(port,backlog,InetAddress.getByName(host))); - - if (_sslContextFactory.getWantClientAuth()) - socket.setWantClientAuth(_sslContextFactory.getWantClientAuth()); - if (_sslContextFactory.getNeedClientAuth()) - socket.setNeedClientAuth(_sslContextFactory.getNeedClientAuth()); - - socket.setEnabledCipherSuites(_sslContextFactory.selectCipherSuites( - socket.getEnabledCipherSuites(), - socket.getSupportedCipherSuites())); - return socket; + return _sslContextFactory.newSslServerSocket(host,port,backlog); } /* ------------------------------------------------------------ */ @@ -440,7 +436,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public void setKeystoreType(String keystoreType) + public void setKeystoreType(String keystoreType) { _sslContextFactory.setKeyStoreType(keystoreType); } @@ -448,7 +444,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector /* ------------------------------------------------------------ */ /** * Set the value of the needClientAuth property - * + * * @param needClientAuth true iff we require client certificate authentication. * @deprecated */ @@ -457,7 +453,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _sslContextFactory.setNeedClientAuth(needClientAuth); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String) @@ -468,7 +464,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _sslContextFactory.setKeyStorePassword(password); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String) @@ -486,7 +482,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public void setProtocol(String protocol) + public void setProtocol(String protocol) { _sslContextFactory.setProtocol(protocol); } @@ -507,7 +503,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public void setSecureRandomAlgorithm(String algorithm) + public void setSecureRandomAlgorithm(String algorithm) { _sslContextFactory.setSecureRandomAlgorithm(algorithm); } @@ -518,18 +514,18 @@ public class SslSocketConnector extends SocketConnector implements SslConnector * @deprecated */ @Deprecated - public void setSslKeyManagerFactoryAlgorithm(String algorithm) + public void setSslKeyManagerFactoryAlgorithm(String algorithm) { _sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String) * @deprecated */ @Deprecated - public void setSslTrustManagerFactoryAlgorithm(String algorithm) + public void setSslTrustManagerFactoryAlgorithm(String algorithm) { _sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm); } @@ -544,7 +540,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _sslContextFactory.setTrustStore(truststore); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String) @@ -555,7 +551,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _sslContextFactory.setTrustStoreType(truststoreType); } - + /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext) @@ -580,9 +576,9 @@ public class SslSocketConnector extends SocketConnector implements SslConnector /* ------------------------------------------------------------ */ /** - * Set the value of the _wantClientAuth property. This property is used + * Set the value of the _wantClientAuth property. This property is used * internally when opening server sockets. - * + * * @param wantClientAuth true if we want client certificate authentication. * @see SSLServerSocket#setWantClientAuth * @deprecated @@ -603,7 +599,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { _handshakeTimeout = msec; } - + /* ------------------------------------------------------------ */ public int getHandshakeTimeout () @@ -618,19 +614,19 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { super(socket); } - + @Override public void shutdownOutput() throws IOException { close(); } - + @Override public void shutdownInput() throws IOException { close(); } - + @Override public void run() { @@ -638,7 +634,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector { int handshakeTimeout = getHandshakeTimeout(); int oldTimeout = _socket.getSoTimeout(); - if (handshakeTimeout > 0) + if (handshakeTimeout > 0) _socket.setSoTimeout(handshakeTimeout); final SSLSocket ssl=(SSLSocket)_socket; @@ -668,7 +664,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector } catch (SSLException e) { - LOG.debug(e); + LOG.debug(e); try{close();} catch(IOException e2){LOG.ignore(e2);} } @@ -677,14 +673,14 @@ public class SslSocketConnector extends SocketConnector implements SslConnector LOG.debug(e); try{close();} catch(IOException e2){LOG.ignore(e2);} - } + } } } /* ------------------------------------------------------------ */ /** * Unsupported. - * + * * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past) * @deprecated */ @@ -697,7 +693,7 @@ public class SslSocketConnector extends SocketConnector implements SslConnector /* ------------------------------------------------------------ */ /** * Unsupported. - * + * * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past) * @deprecated */ diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java index 5dde4771887..1f6c6852a8f 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/BusySelectChannelServerTest.java @@ -52,7 +52,10 @@ public class BusySelectChannelServerTest extends HttpServerTestBase { int x=write++&0xff; if (x<8) + { + clearWritable(); return 0; + } if (x<32) return flush(header); return super.flush(header,buffer,trailer); @@ -67,7 +70,10 @@ public class BusySelectChannelServerTest extends HttpServerTestBase { int x=write++&0xff; if (x<8) + { + clearWritable(); return 0; + } if (x<32) { View v = new View(buffer); @@ -75,6 +81,7 @@ public class BusySelectChannelServerTest extends HttpServerTestBase int l=super.flush(v); if (l>0) buffer.skip(l); + clearWritable(); return l; } return super.flush(buffer); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java index 8b1c982db7f..683bf5f5126 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java @@ -37,8 +37,11 @@ import javax.servlet.http.HttpServletResponse; import junit.framework.Assert; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.StdErrLog; import org.junit.Test; /** @@ -106,7 +109,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture * Feed the server the entire request at once. */ @Test - public void testRequest1_jetty() throws Exception + public void testRequest1() throws Exception { configureServer(new HelloWorldHandler()); @@ -164,7 +167,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture * Feed the server fragmentary headers and see how it copes with it. */ @Test - public void testRequest1Fragments_jetty() throws Exception, InterruptedException + public void testRequest1Fragments() throws Exception, InterruptedException { configureServer(new HelloWorldHandler()); @@ -197,7 +200,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } @Test - public void testRequest2_jetty() throws Exception + public void testRequest2() throws Exception { configureServer(new EchoHandler()); @@ -226,7 +229,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } @Test - public void testRequest2Fragments_jetty() throws Exception + public void testRequest2Fragments() throws Exception { configureServer(new EchoHandler()); @@ -270,7 +273,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } @Test - public void testRequest2Iterate_jetty() throws Exception + public void testRequest2Iterate() throws Exception { configureServer(new EchoHandler()); @@ -309,7 +312,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture * After several iterations, I generated some known bad fragment points. */ @Test - public void testRequest2KnownBad_jetty() throws Exception + public void testRequest2KnownBad() throws Exception { configureServer(new EchoHandler()); @@ -425,7 +428,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture while(len>=0) { - Thread.sleep(500); + Thread.sleep(100); len=is.read(buf); if (len>0) total+=len; @@ -447,6 +450,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture long start=System.currentTimeMillis(); Socket client=newSocket(HOST,_connector.getLocalPort()); + int total=0; try { OutputStream os=client.getOutputStream(); @@ -461,7 +465,6 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture ).getBytes()); os.flush(); - int total=0; int len=0; byte[] buf=new byte[1024*32]; @@ -480,6 +483,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } finally { + System.err.println("Got "+total+" of "+(512*1024)); client.close(); } } @@ -490,17 +494,17 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture configureServer(new BigBlockHandler()); Socket client=newSocket(HOST,_connector.getLocalPort()); - client.setSoTimeout(10000); + client.setSoTimeout(20000); try { OutputStream os=client.getOutputStream(); BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); os.write(( - "GET / HTTP/1.1\r\n"+ + "GET /r1 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "\r\n"+ - "GET / HTTP/1.1\r\n"+ + "GET /r2 HTTP/1.1\r\n"+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+ "connection: close\r\n"+ "\r\n" @@ -583,6 +587,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture } } + // Handler that sends big blocks of data in each of 10 writes, and then sends the time it took for each big block. protected static class BigBlockHandler extends AbstractHandler { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException @@ -598,10 +603,12 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture long[] times=new long[10]; for (int i=0;i=0); + assertTrue(in.indexOf("Transfer-Encoding: chunked")>0); + assertTrue(in.indexOf("Now is the time for all good men to come to the aid of the party")>0); + assertTrue(in.indexOf("\r\n0\r\n")==-1); // chunking is interrupted by error close + + client.close(); + Thread.sleep(100); + assertTrue(!handler._endp.isOpen()); + } + finally + { + ((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(false); + + if (!client.isClosed()) + client.close(); + } + } + + protected static class CommittedErrorHandler extends AbstractHandler + { + public EndPoint _endp; + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + _endp=baseRequest.getConnection().getEndPoint(); + response.setHeader("test","value"); + response.setStatus(200); + response.setContentType("text/plain"); + response.getWriter().println("Now is the time for all good men to come to the aid of the party"); + response.getWriter().flush(); + response.flushBuffer(); + + throw new ServletException(new Exception("exception after commit")); + } + } + protected static class AvailableHandler extends AbstractHandler { public Exchanger _ex = new Exchanger(); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java index fa86c3dcba0..73828e81bf1 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java @@ -12,6 +12,7 @@ import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayEndPoint; +import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.SimpleBuffers; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; @@ -75,7 +76,17 @@ public class HttpWriterTest }; - HttpOutput httpOut = new HttpOutput(generator,60000); + HttpConnection connection = new HttpConnection(null,endp,new Server(),null,generator,null) + { + @Override + public Connection handle() throws IOException + { + return null; + } + }; + endp.setMaxIdleTime(60000); + + HttpOutput httpOut = new HttpOutput(connection); _writer = new HttpWriter(httpOut); } @@ -158,7 +169,17 @@ public class HttpWriterTest hb.setResponse(200,"OK"); - HttpOutput output = new HttpOutput(hb,10000); + HttpConnection connection = new HttpConnection(null,endp,new Server(),null,hb,null) + { + @Override + public Connection handle() throws IOException + { + return null; + } + }; + endp.setMaxIdleTime(10000); + hb.setSendServerVersion(false); + HttpOutput output = new HttpOutput(connection); HttpWriter writer = new HttpWriter(output); writer.setCharacterEncoding(StringUtil.__UTF8); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java index 6915447d06b..ef94da7efcb 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java @@ -24,12 +24,16 @@ import java.io.InputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Enumeration; +import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import junit.framework.Assert; + import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; @@ -68,7 +72,50 @@ public class RequestTest _server.stop(); _server.join(); } + + @Test + public void testParamExtraction() throws Exception + { + _handler._checker = new RequestTester() + { + public boolean check(HttpServletRequest request,HttpServletResponse response) + { + Map map = null; + try + { + //do the parse + request.getParameterMap(); + Assert.fail("Expected parsing failure"); + return false; + } + catch (Exception e) + { + //catch the error and check the param map is not null + map = request.getParameterMap(); + assertFalse(map == null); + assertTrue(map.isEmpty()); + + Enumeration names = request.getParameterNames(); + assertFalse(names.hasMoreElements()); + + } + + return true; + } + }; + + //Send a request with query string with illegal hex code to cause + //an exception parsing the params + String request="GET /?param=%ZZaaa HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: text/html;charset=utf8\n"+ + "\n"; + + String response = _connector.getResponses(request); + } + + @Test public void testContentTypeEncoding() throws Exception { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index 57d4f28f355..807edf3ba37 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -383,31 +383,45 @@ public class ResponseTest public void testSendRedirect() throws Exception { - ByteArrayEndPoint out=new ByteArrayEndPoint(new byte[]{},4096); - HttpConnection connection=new TestHttpConnection(connector,out, connector.getServer()); - Response response = new Response(connection); - Request request = connection.getRequest(); - request.setServerName("myhost"); - request.setServerPort(8888); - request.setUri(new HttpURI("/path/info;param;jsessionid=12345?query=0&more=1#target")); - request.setContextPath("/path"); - request.setRequestedSessionId("12345"); - request.setRequestedSessionIdFromCookie(false); - AbstractSessionManager manager=new HashSessionManager(); - manager.setSessionIdManager(new HashSessionIdManager()); - request.setSessionManager(manager); - request.setSession(new TestSession(manager,"12345")); - manager.setCheckingRemoteSessionIdEncoding(false); + String[][] tests={ + {"/other/location?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"}, + {"/other/location","http://myhost:8888/other/location"}, + {"/other/l%20cation","http://myhost:8888/other/l%20cation"}, + {"location","http://myhost:8888/path/location"}, + {"./location","http://myhost:8888/path/location"}, + {"../location","http://myhost:8888/location"}, + {"/other/l%20cation","http://myhost:8888/other/l%20cation"}, + {"l%20cation","http://myhost:8888/path/l%20cation"}, + {"./l%20cation","http://myhost:8888/path/l%20cation"}, + {"../l%20cation","http://myhost:8888/l%20cation"}, + }; + + for (int i=1;i0); + AbstractSessionManager manager=new HashSessionManager(); + manager.setSessionIdManager(new HashSessionIdManager()); + request.setSessionManager(manager); + request.setSession(new TestSession(manager,"12345")); + manager.setCheckingRemoteSessionIdEncoding(false); - response.sendRedirect("/other/location"); - - String location = out.getOut().toString(); - int l=location.indexOf("Location: "); - int e=location.indexOf('\n',l); - location=location.substring(l+10,e).trim(); - - assertEquals("http://myhost:8888/other/location;jsessionid=12345",location); - + response.sendRedirect(tests[i][0]); + + String location = out.getOut().toString(); + int l=location.indexOf("Location: "); + int e=location.indexOf('\n',l); + location=location.substring(l+10,e).trim(); + assertEquals(tests[i][0],tests[i][1],location); + } } @Test diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java index ba0569f231b..d32593a83e2 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java @@ -25,10 +25,12 @@ public class SelectChannelServerTest extends HttpServerTestBase { startServer(new SelectChannelConnector()); } - + @Override - public void testBigBlocks() throws Exception + public void testCommittedError() throws Exception { - super.testBigBlocks(); + super.testCommittedError(); } + + } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java index ccf1dcf1ac5..0c46f2a3615 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java @@ -91,7 +91,7 @@ public class StressTest _server.setThreadPool(_threads); _connector = new SelectChannelConnector(); - _connector.setAcceptors(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + _connector.setAcceptors(1); _connector.setAcceptQueueSize(5000); _connector.setMaxIdleTime(30000); _server.addConnector(_connector); @@ -123,7 +123,7 @@ public class StressTest // TODO needs to be further investigated assumeTrue(!OS.IS_OSX || Stress.isEnabled()); - doThreads(10,100,false); + doThreads(10,10,false); if (Stress.isEnabled()) { Thread.sleep(1000); @@ -139,7 +139,7 @@ public class StressTest // TODO needs to be further investigated assumeTrue(!OS.IS_OSX || Stress.isEnabled()); - doThreads(20,100,true); + doThreads(20,10,true); if (Stress.isEnabled()) { Thread.sleep(1000); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java new file mode 100644 index 00000000000..91681b321f7 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java @@ -0,0 +1,76 @@ +package org.eclipse.jetty.server.handler; +//======================================================================== +//Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + +import java.net.URI; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.bio.SocketConnector; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.SimpleRequest; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import junit.framework.Assert; +import junit.framework.TestCase; + +/** + * Resource Handler test + * + * TODO: increase the testing going on here + */ +public class ResourceHandlerTest extends TestCase +{ + private static Server _server; + private static Connector _connector; + private static ContextHandler _contextHandler; + private static ResourceHandler _resourceHandler; + + + @BeforeClass + public void setUp() throws Exception + { + _server = new Server(); + _connector = new SocketConnector(); + _server.setConnectors(new Connector[] { _connector }); + + _resourceHandler = new ResourceHandler(); + + _contextHandler = new ContextHandler("/resource"); + _contextHandler.setHandler(_resourceHandler); + _server.setHandler(_contextHandler); + _server.start(); + } + + /* ------------------------------------------------------------ */ + @AfterClass + public void tearDown() throws Exception + { + _server.stop(); + } + + @Test + public void testSimpleResourceHandler() throws Exception + { + _resourceHandler.setResourceBase(MavenTestingUtils.getTestResourceDir("simple").getAbsolutePath()); + + SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort())); + + Assert.assertEquals("simple text", sr.getString("/resource/simple.txt")); + + Assert.assertNotNull("missing jetty.css" , sr.getString("/resource/jetty-dir.css")); + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java new file mode 100644 index 00000000000..02dae765e79 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java @@ -0,0 +1,80 @@ +package org.eclipse.jetty.server.handler; + +//======================================================================== +//Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//All rights reserved. This program and the accompanying materials +//are made available under the terms of the Eclipse Public License v1.0 +//and Apache License v2.0 which accompanies this distribution. +//The Eclipse Public License is available at +//http://www.eclipse.org/legal/epl-v10.html +//The Apache License v2.0 is available at +//http://www.opensource.org/licenses/apache2.0.php +//You may elect to redistribute this code under either of these licenses. +//======================================================================== + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Server; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ShutdownHandlerTest +{ + @Mock private HttpServletRequest request; + @Mock private HttpServletResponse response; + + private Server server = new Server(0); + private String shutdownToken = "asdlnsldgnklns"; + + // class under test + private ShutdownHandler shutdownHandler; + + @Before + public void startServer() throws Exception + { + MockitoAnnotations.initMocks(this); + server.start(); + shutdownHandler = new ShutdownHandler(server,shutdownToken); + } + + @Test + public void shutdownServerWithCorrectTokenAndIPTest() throws Exception + { + setDefaultExpectations(); + shutdownHandler.handle("/shutdown",null,request,response); + assertEquals("Server should be stopped","STOPPED",server.getState()); + } + + @Test + public void wrongTokenTest() throws Exception + { + setDefaultExpectations(); + when(request.getParameter("token")).thenReturn("anothertoken"); + shutdownHandler.handle("/shutdown",null,request,response); + assertEquals("Server should be running","STARTED",server.getState()); + } + + @Test + public void shutdownRequestNotFromLocalhostTest() throws Exception + { + setDefaultExpectations(); + when(request.getRemoteAddr()).thenReturn("192.168.3.3"); + shutdownHandler.handle("/shutdown",null,request,response); + assertEquals("Server should be running","STARTED",server.getState()); + } + + private void setDefaultExpectations() + { + when(request.getMethod()).thenReturn("POST"); + when(request.getParameter("token")).thenReturn(shutdownToken); + when(request.getRemoteAddr()).thenReturn("127.0.0.1"); + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java new file mode 100644 index 00000000000..b0d980d4f06 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java @@ -0,0 +1,181 @@ +//======================================================================== +//Copyright 2004-2008 Mort Bay Consulting Pty. Ltd. +//------------------------------------------------------------------------ +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +//http://www.apache.org/licenses/LICENSE-2.0 +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. +//======================================================================== + +// JettyTest.java -- +// +// Junit test that shows the Jetty SSL bug. +// + +package org.eclipse.jetty.server.ssl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; + +import org.eclipse.jetty.io.nio.SslSelectChannelEndPoint; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; + +/** + * HttpServer Tester. + */ +public class SSLCloseTest extends TestCase +{ + private static SslSelectChannelEndPoint __endp; + private static class CredulousTM implements TrustManager, X509TrustManager + { + public X509Certificate[] getAcceptedIssuers() + { + return null; + } + + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException + { + return; + } + + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException + { + return; + } + } + + private static final TrustManager[] s_dummyTrustManagers=new TrustManager[] { new CredulousTM() }; + + // ~ Methods + // ---------------------------------------------------------------- + + /** + * Feed the server the entire request at once. + * + * @throws Exception + */ + public void testClose() throws Exception + { + Server server=new Server(); + SslSelectChannelConnector connector=new SslSelectChannelConnector(); + + String keystore = System.getProperty("user.dir")+File.separator+"src"+File.separator+"test"+File.separator+"resources"+File.separator+"keystore"; + + connector.setPort(0); + connector.setKeystore(keystore); + connector.setPassword("storepwd"); + connector.setKeyPassword("keypwd"); + + server.setConnectors(new Connector[] + { connector }); + server.setHandler(new WriteHandler()); + + server.start(); + + + SSLContext ctx=SSLContext.getInstance("SSLv3"); + ctx.init(null,s_dummyTrustManagers,new java.security.SecureRandom()); + + int port=connector.getLocalPort(); + + // System.err.println("write:"+i); + Socket socket=ctx.getSocketFactory().createSocket("localhost",port); + OutputStream os=socket.getOutputStream(); + + os.write("GET /test HTTP/1.1\r\nHost:test\r\nConnection:close\r\n\r\n".getBytes()); + os.flush(); + + BufferedReader in =new BufferedReader(new InputStreamReader(socket.getInputStream())); + + String line; + while ((line=in.readLine())!=null) + { + System.err.println(line); + if (line.trim().length()==0) + break; + } + + Thread.sleep(2000); + System.err.println(__endp); + + while ((line=in.readLine())!=null) + System.err.println(line); + + } + + + private static class WriteHandler extends AbstractHandler + { + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + baseRequest.setHandled(true); + response.setStatus(200); + response.setHeader("test","value"); + __endp=(SslSelectChannelEndPoint)baseRequest.getConnection().getEndPoint(); + + OutputStream out=response.getOutputStream(); + + String data = "Now is the time for all good men to come to the aid of the party.\n"; + data+="How now brown cow.\n"; + data+="The quick brown fox jumped over the lazy dog.\n"; + // data=data+data+data+data+data+data+data+data+data+data+data+data+data; + // data=data+data+data+data+data+data+data+data+data+data+data+data+data; + data=data+data+data+data; + byte[] bytes=data.getBytes("UTF-8"); + + for (int i=0;i<2;i++) + { + System.err.println("Write "+i+" "+bytes.length); + out.write(bytes); + } + } + catch(RuntimeException e) + { + e.printStackTrace(); + throw e; + } + catch(IOException e) + { + e.printStackTrace(); + throw e; + } + catch(Error e) + { + e.printStackTrace(); + throw e; + } + catch(Throwable e) + { + e.printStackTrace(); + throw new ServletException(e); + } + } + + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java index 0373a3f3e4f..432d51ea689 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java @@ -133,7 +133,7 @@ public class SSLEngineTest @Test public void testBigResponse() throws Exception { - SSLContext ctx=SSLContext.getInstance("SSLv3"); + SSLContext ctx=SSLContext.getInstance("TLS"); ctx.init(null,s_dummyTrustManagers,new java.security.SecureRandom()); int port=connector.getLocalPort(); @@ -367,4 +367,5 @@ public class SSLEngineTest response.flushBuffer(); } } + } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelServerTest.java index 1acfa7d9e29..101951c15a4 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelServerTest.java @@ -23,6 +23,7 @@ import javax.net.ssl.TrustManagerFactory; import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.HttpServerTestBase; import org.junit.BeforeClass; +import org.junit.Test; /** * HttpServer Tester. @@ -60,14 +61,14 @@ public class SslSelectChannelServerTest extends HttpServerTestBase keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keystore); - __sslContext = SSLContext.getInstance("SSL"); + __sslContext = SSLContext.getInstance("TLS"); __sslContext.init(null, trustManagerFactory.getTrustManagers(), null); try { HttpsURLConnection.setDefaultHostnameVerifier(__hostnameverifier); - SSLContext sc = SSLContext.getInstance("SSL"); + SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, __trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); } @@ -79,5 +80,20 @@ public class SslSelectChannelServerTest extends HttpServerTestBase } + @Test + @Override + public void testBlockingWhileWritingResponseContent() throws Exception + { + super.testBlockingWhileWritingResponseContent(); + } + + + @Test + @Override + public void testBigBlocks() throws Exception + { + super.testBigBlocks(); + } + } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelTimeoutTest.java index 2da1cace9b4..159987866e6 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelTimeoutTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSelectChannelTimeoutTest.java @@ -23,6 +23,7 @@ import javax.net.ssl.TrustManagerFactory; import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.server.ConnectorTimeoutTest; import org.junit.BeforeClass; +import org.junit.Test; public class SslSelectChannelTimeoutTest extends ConnectorTimeoutTest { @@ -57,4 +58,12 @@ public class SslSelectChannelTimeoutTest extends ConnectorTimeoutTest } + @Test + public void testNoProgress() throws Exception + { + testMaxIdleNoRequest(); + super.testMaxIdleWithSlowRequest(); + } + + } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketServerTest.java index c9da61e856d..f77757beb2d 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketServerTest.java @@ -15,8 +15,10 @@ package org.eclipse.jetty.server.ssl; import java.io.FileInputStream; import java.net.Socket; import java.security.KeyStore; +import java.util.Arrays; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManagerFactory; import org.eclipse.jetty.http.ssl.SslContextFactory; @@ -37,7 +39,9 @@ public class SslSocketServerTest extends HttpServerTestBase @Override protected Socket newSocket(String host, int port) throws Exception { - return __sslContext.getSocketFactory().createSocket(host,port); + SSLSocket socket = (SSLSocket)__sslContext.getSocketFactory().createSocket(host,port); + socket.setEnabledProtocols(new String[] {"TLSv1"}); + return socket; } @@ -59,7 +63,7 @@ public class SslSocketServerTest extends HttpServerTestBase keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keystore); - __sslContext = SSLContext.getInstance("SSL"); + __sslContext = SSLContext.getInstance("TLSv1"); __sslContext.init(null, trustManagerFactory.getTrustManagers(), null); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketTimeoutTest.java index edcaac7b785..d995f0e5c3a 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketTimeoutTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketTimeoutTest.java @@ -18,6 +18,7 @@ import java.net.Socket; import java.security.KeyStore; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManagerFactory; import org.eclipse.jetty.http.ssl.SslContextFactory; @@ -26,12 +27,14 @@ import org.junit.BeforeClass; public class SslSocketTimeoutTest extends ConnectorTimeoutTest { - static SSLContext _sslContext; + static SSLContext __sslContext; @Override protected Socket newSocket(String host, int port) throws Exception { - return _sslContext.getSocketFactory().createSocket(host,port); + SSLSocket socket = (SSLSocket)__sslContext.getSocketFactory().createSocket(host,port); + socket.setEnabledProtocols(new String[] {"TLSv1"}); + return socket; } @BeforeClass @@ -53,8 +56,8 @@ public class SslSocketTimeoutTest extends ConnectorTimeoutTest keystore.load(new FileInputStream(connector.getKeystore()), "storepwd".toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keystore); - _sslContext = SSLContext.getInstance("SSL"); - _sslContext.init(null, trustManagerFactory.getTrustManagers(), null); + __sslContext = SSLContext.getInstance("TLSv1"); + __sslContext.init(null, trustManagerFactory.getTrustManagers(), null); } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslTruncationAttackTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslTruncationAttackTest.java index 0dc113cd210..6fc8e17f278 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslTruncationAttackTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslTruncationAttackTest.java @@ -159,17 +159,6 @@ public class SslTruncationAttackTest Assert.assertTrue("endpoint not closed", endPointClosed.get()); } - /** - * This test is currently failing because we are looping on SslSCEP.unwrap() - * to fill the buffer, so there is a case where we loop once, read some data - * loop again and read -1, but we can't close the connection yet as we have - * to notify the application (not sure that this is necessary... must assume - * the data is truncated, so it's not that safe to pass it to the application). - * This case needs to be revisited, and it also requires a review of the - * Connection:close case, especially on the client side. - * @throws Exception if the test fails - */ - @Ignore @Test public void testTruncationAttackBeforeReading() throws Exception { @@ -234,8 +223,8 @@ public class SslTruncationAttackTest // Sleep for a while to detect eventual spin looping TimeUnit.SECONDS.sleep(1); + Assert.assertTrue("endpoint closed", endPointClosed.get()); Assert.assertEquals("handle() invocations", 1, handleCount.get()); - Assert.assertTrue("endpoint not closed", endPointClosed.get()); } diff --git a/jetty-server/src/test/resources/simple/simple.txt b/jetty-server/src/test/resources/simple/simple.txt new file mode 100644 index 00000000000..f2403aead5a --- /dev/null +++ b/jetty-server/src/test/resources/simple/simple.txt @@ -0,0 +1 @@ +simple text \ No newline at end of file diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index 96691116f2b..5b04de6a810 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -482,7 +482,6 @@ public class ServletHandler extends ScopedHandler res = ((ServletResponseHttpWrapper)res).getResponse(); // Do the filter/handling thang - baseRequest.setHandled(true); if (chain!=null) chain.doFilter(req, res); else @@ -591,6 +590,10 @@ public class ServletHandler extends ScopedHandler else LOG.debug("Response already committed for handling ",e); } + finally + { + baseRequest.setHandled(true); + } } /* ------------------------------------------------------------ */ diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java index e2d8694939f..f294a8102f6 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java @@ -14,27 +14,47 @@ package org.eclipse.jetty.servlet; import java.io.IOException; +import java.io.PrintWriter; +import java.net.HttpRetryException; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; import javax.servlet.GenericServlet; import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; +import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestWrapper; import javax.servlet.ServletResponse; import javax.servlet.ServletResponseWrapper; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import junit.framework.Assert; import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -44,17 +64,27 @@ public class DispatcherTest { private Server _server; private LocalConnector _connector; - private ServletContextHandler _context; - + private ContextHandlerCollection _contextCollection; + private ServletContextHandler _contextHandler; + private ResourceHandler _resourceHandler; + @Before public void init() throws Exception { _server = new Server(); _server.setSendServerVersion(false); _connector = new LocalConnector(); - _context = new ServletContextHandler(); - _context.setContextPath("/context"); - _server.setHandler(_context); + + _contextCollection = new ContextHandlerCollection(); + _contextHandler = new ServletContextHandler(); + _contextHandler.setContextPath("/context"); + _contextCollection.addHandler(_contextHandler); + _resourceHandler = new ResourceHandler(); + _resourceHandler.setResourceBase(MavenTestingUtils.getTestResourceDir("dispatchResourceTest").getAbsolutePath()); + ContextHandler resourceContextHandler = new ContextHandler("/resource"); + resourceContextHandler.setHandler(_resourceHandler); + _contextCollection.addHandler(resourceContextHandler); + _server.setHandler(_contextCollection); _server.addConnector( _connector ); _server.start(); @@ -70,8 +100,8 @@ public class DispatcherTest @Test public void testForward() throws Exception { - _context.addServlet(ForwardServlet.class, "/ForwardServlet/*"); - _context.addServlet(AssertForwardServlet.class, "/AssertForwardServlet/*"); + _contextHandler.addServlet(ForwardServlet.class, "/ForwardServlet/*"); + _contextHandler.addServlet(AssertForwardServlet.class, "/AssertForwardServlet/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -87,8 +117,8 @@ public class DispatcherTest @Test public void testInclude() throws Exception { - _context.addServlet(IncludeServlet.class, "/IncludeServlet/*"); - _context.addServlet(AssertIncludeServlet.class, "/AssertIncludeServlet/*"); + _contextHandler.addServlet(IncludeServlet.class, "/IncludeServlet/*"); + _contextHandler.addServlet(AssertIncludeServlet.class, "/AssertIncludeServlet/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -103,9 +133,9 @@ public class DispatcherTest @Test public void testForwardThenInclude() throws Exception { - _context.addServlet(ForwardServlet.class, "/ForwardServlet/*"); - _context.addServlet(IncludeServlet.class, "/IncludeServlet/*"); - _context.addServlet(AssertForwardIncludeServlet.class, "/AssertForwardIncludeServlet/*"); + _contextHandler.addServlet(ForwardServlet.class, "/ForwardServlet/*"); + _contextHandler.addServlet(IncludeServlet.class, "/IncludeServlet/*"); + _contextHandler.addServlet(AssertForwardIncludeServlet.class, "/AssertForwardIncludeServlet/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -120,9 +150,9 @@ public class DispatcherTest @Test public void testIncludeThenForward() throws Exception { - _context.addServlet(IncludeServlet.class, "/IncludeServlet/*"); - _context.addServlet(ForwardServlet.class, "/ForwardServlet/*"); - _context.addServlet(AssertIncludeForwardServlet.class, "/AssertIncludeForwardServlet/*"); + _contextHandler.addServlet(IncludeServlet.class, "/IncludeServlet/*"); + _contextHandler.addServlet(ForwardServlet.class, "/ForwardServlet/*"); + _contextHandler.addServlet(AssertIncludeForwardServlet.class, "/AssertIncludeForwardServlet/*"); String expected= @@ -140,8 +170,8 @@ public class DispatcherTest @Test public void testServletForward() throws Exception { - _context.addServlet(DispatchServletServlet.class, "/dispatch/*"); - _context.addServlet(RogerThatServlet.class, "/roger/*"); + _contextHandler.addServlet(DispatchServletServlet.class, "/dispatch/*"); + _contextHandler.addServlet(RogerThatServlet.class, "/roger/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -157,8 +187,8 @@ public class DispatcherTest @Test public void testServletInclude() throws Exception { - _context.addServlet(DispatchServletServlet.class, "/dispatch/*"); - _context.addServlet(RogerThatServlet.class, "/roger/*"); + _contextHandler.addServlet(DispatchServletServlet.class, "/dispatch/*"); + _contextHandler.addServlet(RogerThatServlet.class, "/roger/*"); String expected= "HTTP/1.1 200 OK\r\n"+ @@ -171,6 +201,80 @@ public class DispatcherTest assertEquals(expected, responses); } + @Test + public void testWorkingResourceHandler() throws Exception + { + String responses = _connector.getResponses("GET /resource/content.txt HTTP/1.0\n" + "Host: localhost\n\n"); + + assertTrue(responses.contains("content goes here")); // from inside the context.txt file + } + + @Test + public void testIncludeToResourceHandler() throws Exception + { + _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*"); + + String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=include HTTP/1.0\n" + "Host: localhost\n\n"); + + // from inside the context.txt file + Assert.assertNotNull(responses); + + assertTrue(responses.contains("content goes here")); + } + + @Test + public void testForwardToResourceHandler() throws Exception + { + _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*"); + + String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=forward HTTP/1.0\n" + "Host: localhost\n\n"); + + // from inside the context.txt file + assertTrue(responses.contains("content goes here")); + } + + @Test + public void testWrappedIncludeToResourceHandler() throws Exception + { + _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*"); + + String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=include&wrapped=true HTTP/1.0\n" + "Host: localhost\n\n"); + + // from inside the context.txt file + assertTrue(responses.contains("content goes here")); + } + + @Test + public void testWrappedForwardToResourceHandler() throws Exception + { + _contextHandler.addServlet(DispatchToResourceServlet.class, "/resourceServlet/*"); + + String responses = _connector.getResponses("GET /context/resourceServlet/content.txt?do=forward&wrapped=true HTTP/1.0\n" + "Host: localhost\n\n"); + + // from inside the context.txt file + assertTrue(responses.contains("content goes here")); + } + + @Test + public void testForwardFilterToRogerServlet() throws Exception + { + _contextHandler.addServlet(RogerThatServlet.class, "/*"); + _contextHandler.addServlet(ReserveEchoServlet.class,"/recho/*"); + _contextHandler.addServlet(EchoServlet.class, "/echo/*"); + _contextHandler.addFilter(ForwardFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + + String rogerResponse = _connector.getResponses("GET /context/ HTTP/1.0\n" + "Host: localhost\n\n"); + + String echoResponse = _connector.getResponses("GET /context/foo?echo=echoText HTTP/1.0\n" + "Host: localhost\n\n"); + + String rechoResponse = _connector.getResponses("GET /context/?echo=echoText HTTP/1.0\n" + "Host: localhost\n\n"); + + assertTrue(rogerResponse.contains("Roger That!")); + assertTrue(echoResponse.contains("echoText")); + assertTrue(rechoResponse.contains("txeTohce")); + } + + public static class ForwardServlet extends HttpServlet implements Servlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException @@ -187,6 +291,59 @@ public class DispatcherTest } } + /* + * Forward filter works with roger, echo and reverse echo servlets to test various + * forwarding bits using filters. + * + * when there is an echo parameter and the path info is / it forwards to the reverse echo + * anything else in the pathInfo and it sends straight to the echo servlet...otherwise its + * all roger servlet + */ + public static class ForwardFilter implements Filter + { + ServletContext servletContext; + + public void init(FilterConfig filterConfig) throws ServletException + { + servletContext = filterConfig.getServletContext().getContext("/context"); + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + + if ( servletContext == null || !(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) + { + chain.doFilter(request,response); + return; + } + + HttpServletRequest req = (HttpServletRequest)request; + HttpServletResponse resp = (HttpServletResponse)response; + + if ( req.getParameter("echo") != null && "/".equals(req.getPathInfo())) + { + RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/recho"); + dispatcher.forward(request,response); + } + else if ( req.getParameter("echo") != null ) + { + RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/echo"); + dispatcher.forward(request,response); + } + else + { + chain.doFilter(request,response); + return; + } + } + + public void destroy() + { + + } + } + + public static class DispatchServletServlet extends HttpServlet implements Servlet { @Override @@ -230,6 +387,84 @@ public class DispatcherTest } } + public static class EchoServlet extends GenericServlet + { + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + String echoText = req.getParameter("echo"); + + if ( echoText == null ) + { + throw new ServletException("echo is a required parameter"); + } + else + { + res.getWriter().print(echoText); + } + } + } + + public static class ReserveEchoServlet extends GenericServlet + { + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + String echoText = req.getParameter("echo"); + + if ( echoText == null ) + { + throw new ServletException("echo is a required parameter"); + } + else + { + res.getWriter().print(new StringBuffer(echoText).reverse().toString()); + } + } + } + + public static class DispatchToResourceServlet extends HttpServlet implements Servlet + { + @Override + public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException + { + ServletContext targetContext = getServletConfig().getServletContext().getContext("/resource"); + + RequestDispatcher dispatcher = targetContext.getRequestDispatcher(req.getPathInfo()); + + if ( "true".equals(req.getParameter("wrapped"))) + { + if (req.getParameter("do").equals("forward")) + { + dispatcher.forward(new HttpServletRequestWrapper(req),new HttpServletResponseWrapper(res)); + } + else if (req.getParameter("do").equals("include")) + { + dispatcher.include(new HttpServletRequestWrapper(req),new HttpServletResponseWrapper(res)); + } + else + { + throw new ServletException("type of forward or include is required"); + } + } + else + { + if (req.getParameter("do").equals("forward")) + { + dispatcher.forward(req,res); + } + else if (req.getParameter("do").equals("include")) + { + dispatcher.include(req,res); + } + else + { + throw new ServletException("type of forward or include is required"); + } + } + } + } + public static class AssertForwardServlet extends HttpServlet implements Servlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException diff --git a/jetty-servlet/src/test/resources/dispatchResourceTest/content.txt b/jetty-servlet/src/test/resources/dispatchResourceTest/content.txt new file mode 100644 index 00000000000..815d4403267 --- /dev/null +++ b/jetty-servlet/src/test/resources/dispatchResourceTest/content.txt @@ -0,0 +1 @@ +content goes here \ No newline at end of file diff --git a/jetty-servlet/src/test/resources/dispatchTest/dispatch.txt b/jetty-servlet/src/test/resources/dispatchTest/dispatch.txt new file mode 100644 index 00000000000..f3a34851d44 --- /dev/null +++ b/jetty-servlet/src/test/resources/dispatchTest/dispatch.txt @@ -0,0 +1 @@ +text \ No newline at end of file diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java index 0f49e0b28b8..3668254783e 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Classpath.java @@ -23,36 +23,39 @@ import java.net.URLClassLoader; import java.util.StringTokenizer; import java.util.Vector; - /** * Class to handle CLASSPATH construction */ -public class Classpath { +public class Classpath +{ - private final Vector _elements = new Vector(); + private final Vector _elements = new Vector(); public Classpath() - {} + { + } public Classpath(String initial) { addClasspath(initial); } - + public File[] getElements() { return _elements.toArray(new File[_elements.size()]); } - + public int count() { return _elements.size(); } - + public boolean addComponent(String component) { - if ((component != null)&&(component.length()>0)) { - try { + if ((component != null) && (component.length() > 0)) + { + try + { File f = new File(component); if (f.exists()) { @@ -63,36 +66,46 @@ public class Classpath { return true; } } - } catch (IOException e) {} + } + catch (IOException e) + { + } } return false; } - + public boolean addComponent(File component) { - if (component != null) { - try { - if (component.exists()) { + if (component != null) + { + try + { + if (component.exists()) + { File key = component.getCanonicalFile(); - if (!_elements.contains(key)) { + if (!_elements.contains(key)) + { _elements.add(key); return true; } } - } catch (IOException e) {} + } + catch (IOException e) + { + } } return false; } public boolean addClasspath(String s) { - boolean added=false; + boolean added = false; if (s != null) { StringTokenizer t = new StringTokenizer(s, File.pathSeparator); while (t.hasMoreTokens()) { - added|=addComponent(t.nextToken()); + added |= addComponent(t.nextToken()); } } return added; @@ -103,40 +116,49 @@ public class Classpath { int i = 0; for (File element : _elements) { - out.printf("%2d: %s\n",i++,element.getAbsolutePath()); + out.printf("%2d: %s\n", i++, element.getAbsolutePath()); } } - + @Override public String toString() { StringBuffer cp = new StringBuffer(1024); int cnt = _elements.size(); - if (cnt >= 1) { - cp.append( ((_elements.elementAt(0))).getPath() ); + if (cnt >= 1) + { + cp.append(((_elements.elementAt(0))).getPath()); } - for (int i=1; i < cnt; i++) { + for (int i = 1; i < cnt; i++) + { cp.append(File.pathSeparatorChar); - cp.append( ((_elements.elementAt(i))).getPath() ); + cp.append(((_elements.elementAt(i))).getPath()); } return cp.toString(); } - - public ClassLoader getClassLoader() { + + public ClassLoader getClassLoader() + { int cnt = _elements.size(); URL[] urls = new URL[cnt]; - for (int i=0; i < cnt; i++) { - try { - String u=_elements.elementAt(i).toURI().toURL().toString(); - urls[i] = new URL(encodeFileURL(u)); - } catch (MalformedURLException e) {} + for (int i = 0; i < cnt; i++) + { + try + { + urls[i] = _elements.elementAt(i).toURI().toURL(); + } + catch (MalformedURLException e) + { + } } - + ClassLoader parent = Thread.currentThread().getContextClassLoader(); - if (parent == null) { + if (parent == null) + { parent = Classpath.class.getClassLoader(); } - if (parent == null) { + if (parent == null) + { parent = ClassLoader.getSystemClassLoader(); } return new Loader(urls, parent); @@ -152,71 +174,17 @@ public class Classpath { @Override public String toString() { - return "startJarLoader@"+Long.toHexString(hashCode()); + return "startJarLoader@" + Long.toHexString(hashCode()); } } - - public static String encodeFileURL(String path) - { - byte[] bytes; - try - { - bytes=path.getBytes("utf-8"); - } - catch (UnsupportedEncodingException e) - { - bytes=path.getBytes(); - } - - StringBuffer buf = new StringBuffer(bytes.length*2); - buf.append("file:"); - - synchronized(buf) - { - for (int i=5;i='a' && b<='z' || b>='A' && b<='Z' || b>='0' && b<='9') - { - buf.append((char)b); - continue; - } - } - buf.append('%'); - buf.append(Integer.toHexString((0xf0&b)>>4)); - buf.append(Integer.toHexString((0x0f&b))); - continue; - } - } - } - return buf.toString(); - } + /** - * Overlay another classpath, copying its elements into place on this Classpath, while eliminating duplicate entries - * on the classpath. + * Overlay another classpath, copying its elements into place on this + * Classpath, while eliminating duplicate entries on the classpath. * - * @param cpOther - * the other classpath to overlay + * @param cpOther the other classpath to overlay */ public void overlay(Classpath cpOther) { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java index 6e2d73f20dd..c646979de54 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java @@ -1,157 +1,181 @@ +// ======================================================================== +// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== package org.eclipse.jetty.util; import java.io.IOException; -import java.util.IllegalFormatCodePointException; +/* ------------------------------------------------------------ */ +/** + * Utf8 Appendable abstract base class + * + * This abstract class wraps a standard {@link java.lang.Appendable} and provides methods to append UTF-8 encoded bytes, that are converted into characters. + * + * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before state a character is appended to the string buffer. + * + * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. The UTF-8 code was inspired by + * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + * + * License information for Bjoern Hoehrmann's code: + * + * Copyright (c) 2008-2009 Bjoern Hoehrmann + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + **/ public abstract class Utf8Appendable { + private final char REPLACEMENT = '\ufffd'; + private static final int UTF8_ACCEPT = 0; + private static final int UTF8_REJECT = 12; + protected final Appendable _appendable; - protected int _more; - protected int _bits; + protected int _state = UTF8_ACCEPT; + + private static final byte[] BYTE_TABLE = + { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8 + }; + + private static final byte[] TRANS_TABLE = + { + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12 + }; + + private int _codep; public Utf8Appendable(Appendable appendable) { - _appendable=appendable; + _appendable = appendable; } public abstract int length(); - + + protected void reset() + { + _state = UTF8_ACCEPT; + } + public void append(byte b) { try { appendByte(b); } - catch(IOException e) - { - throw new RuntimeException(e); - } - } - - public void append(byte[] b,int offset, int length) - { - try - { - int end=offset+length; - for (int i=offset; imaxChars) + if (length() > maxChars) return false; appendByte(b[i]); } return true; } - catch(IOException e) + catch (IOException e) { throw new RuntimeException(e); } } - + protected void appendByte(byte b) throws IOException { - if (b>=0) + + if (b > 0 && isUtf8SequenceComplete()) { - if (_more>0) - { - _appendable.append('?'); - _more=0; - _bits=0; - throw new NotUtf8Exception(); - } - else - _appendable.append((char)(0x7f&b)); - } - else if (_more==0) - { - if ((b&0xc0)!=0xc0) - { - // 10xxxxxx - _appendable.append('?'); - _more=0; - _bits=0; - throw new NotUtf8Exception(); - } - else - { - if ((b & 0xe0) == 0xc0) - { - //110xxxxx - _more=1; - _bits=b&0x1f; - } - else if ((b & 0xf0) == 0xe0) - { - //1110xxxx - _more=2; - _bits=b&0x0f; - } - else if ((b & 0xf8) == 0xf0) - { - //11110xxx - _more=3; - _bits=b&0x07; - } - else if ((b & 0xfc) == 0xf8) - { - //111110xx - _more=4; - _bits=b&0x03; - } - else if ((b & 0xfe) == 0xfc) - { - //1111110x - _more=5; - _bits=b&0x01; - } - else - { - throw new NotUtf8Exception(); - } - } + _appendable.append((char)(b & 0xFF)); } else { - if ((b&0xc0)==0xc0) - { // 11?????? - _appendable.append('?'); - _more=0; - _bits=0; - throw new NotUtf8Exception(); - } - else + int i = b & 0xFF; + int type = BYTE_TABLE[i]; + _codep = isUtf8SequenceComplete() ? (0xFF >> type) & i : (i & 0x3F) | (_codep << 6); + _state = TRANS_TABLE[_state + type]; + + if (isUtf8SequenceComplete()) { - // 10xxxxxx - _bits=(_bits<<6)|(b&0x3f); - if (--_more==0) + if (_codep < Character.MIN_HIGH_SURROGATE) { - if (_bits>=0xD800 && _bits<=0xDFFF) - throw new NotUtf8Exception(); - _appendable.append(new String(Character.toChars(_bits))); + _appendable.append((char)_codep); } + else + { + for (char c : Character.toChars(_codep)) + _appendable.append(c); + } + } + else if (_state == UTF8_REJECT) + { + _state = UTF8_ACCEPT; + _appendable.append(REPLACEMENT); + throw new NotUtf8Exception(); } } } + protected boolean isUtf8SequenceComplete() + { + return _state == UTF8_ACCEPT; + } - public static class NotUtf8Exception extends IllegalStateException + public static class NotUtf8Exception extends IllegalArgumentException { public NotUtf8Exception() { - super("!UTF-8"); + super("Not valid UTF8!"); } } -} \ No newline at end of file +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java index 40de5306dce..b86058e584f 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuffer.java @@ -4,71 +4,73 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.util; -import java.io.IOException; - /* ------------------------------------------------------------ */ -/** UTF-8 StringBuffer. +/** + * UTF-8 StringBuffer. * - * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append + * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append * UTF-8 encoded bytes, that are converted into characters. - * - * This class is stateful and up to 6 calls to {@link #append(byte)} may be needed before + * + * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before * state a character is appended to the string buffer. - * + * * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. - * The UTF-8 code was inspired by http://javolution.org - * - * This class is not synchronised and should probably be called Utf8StringBuilder + * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ -public class Utf8StringBuffer extends Utf8Appendable +public class Utf8StringBuffer extends Utf8Appendable { final StringBuffer _buffer; - + public Utf8StringBuffer() { super(new StringBuffer()); - _buffer=(StringBuffer)_appendable; + _buffer = (StringBuffer)_appendable; } - + public Utf8StringBuffer(int capacity) { super(new StringBuffer(capacity)); - _buffer=(StringBuffer)_appendable; + _buffer = (StringBuffer)_appendable; } + @Override public int length() { return _buffer.length(); } - + + @Override public void reset() { + super.reset(); _buffer.setLength(0); - _more=0; - _bits=0; } - + public StringBuffer getStringBuffer() { - if (_more!=0) - throw new NotUtf8Exception(); + checkState(); return _buffer; } - + @Override public String toString() { - if (_more!=0) - throw new NotUtf8Exception(); + checkState(); return _buffer.toString(); } + + private void checkState() + { + if (!isUtf8SequenceComplete()) + throw new IllegalArgumentException("Tried to read incomplete UTF8 decoded String"); + } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java index 7e4477163c3..09866884eae 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Utf8StringBuilder.java @@ -4,70 +4,74 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.util; -import java.io.IOException; /* ------------------------------------------------------------ */ /** UTF-8 StringBuilder. * - * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append + * This class wraps a standard {@link java.lang.StringBuilder} and provides methods to append * UTF-8 encoded bytes, that are converted into characters. - * - * This class is stateful and up to 6 calls to {@link #append(byte)} may be needed before + * + * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before * state a character is appended to the string buffer. - * + * * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. - * The UTF-8 code was inspired by http://javolution.org - * + * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + * */ -public class Utf8StringBuilder extends Utf8Appendable +public class Utf8StringBuilder extends Utf8Appendable { final StringBuilder _buffer; - + public Utf8StringBuilder() { super(new StringBuilder()); _buffer=(StringBuilder)_appendable; } - + public Utf8StringBuilder(int capacity) { super(new StringBuilder(capacity)); _buffer=(StringBuilder)_appendable; } - + + @Override public int length() { return _buffer.length(); } - + + @Override public void reset() { + super.reset(); _buffer.setLength(0); - _more=0; - _bits=0; } - + public StringBuilder getStringBuilder() { - if (_more!=0) - throw new NotUtf8Exception(); + checkState(); return _buffer; } - + @Override public String toString() { - if (_more!=0) - throw new NotUtf8Exception(); + checkState(); return _buffer.toString(); } + + private void checkState() + { + if (!isUtf8SequenceComplete()) + throw new IllegalArgumentException("Tried to read incomplete UTF8 decoded String"); + } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JettyAwareLogger.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JettyAwareLogger.java index 8fffd91802a..52bb112079c 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/JettyAwareLogger.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/JettyAwareLogger.java @@ -13,9 +13,15 @@ package org.eclipse.jetty.util.log; import org.slf4j.Marker; +import org.slf4j.helpers.FormattingTuple; +import org.slf4j.helpers.MessageFormatter; - -/* ------------------------------------------------------------ */ +/** + * JettyAwareLogger is used to fix a FQCN bug that arises from how Jetty + * Log uses an indirect slf4j implementation. + * + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670 + */ class JettyAwareLogger implements org.slf4j.Logger { private static final int DEBUG = org.slf4j.spi.LocationAwareLogger.DEBUG_INT; @@ -24,6 +30,7 @@ class JettyAwareLogger implements org.slf4j.Logger private static final int TRACE = org.slf4j.spi.LocationAwareLogger.TRACE_INT; private static final int WARN = org.slf4j.spi.LocationAwareLogger.WARN_INT; + private static final String FQCN = Slf4jLog.class.getName(); private final org.slf4j.spi.LocationAwareLogger _logger; public JettyAwareLogger(org.slf4j.spi.LocationAwareLogger logger) @@ -586,8 +593,19 @@ class JettyAwareLogger implements org.slf4j.Logger return _logger.toString(); } - private void log(Marker marker, int level, String msg, Object[] objArray, Throwable t) + private void log(Marker marker, int level, String msg, Object[] argArray, Throwable t) { - _logger.log(marker,"org.eclipse.jetty.util.log.Log",level, msg, objArray,t); + if (argArray == null) + { + // Simple SLF4J Message (no args) + _logger.log(marker,FQCN,level,msg,null,t); + } + else + { + // Don't assume downstream handles argArray properly. + // Do it the SLF4J way here to eliminate that as a bug. + FormattingTuple ft = MessageFormatter.arrayFormat(msg,argArray); + _logger.log(marker,FQCN,level,ft.getMessage(),null,t); + } } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java index 400805b55f3..a1130ab6043 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java @@ -129,10 +129,6 @@ public class Log return __log; } - /** - * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)} - */ - @Deprecated static boolean isIgnored() { return __ignored; diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java index 82c0b7e9dce..bdd688f5fb8 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Slf4jLog.java @@ -42,6 +42,8 @@ public class Slf4jLog implements Logger } org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( name ); + // Fix LocationAwareLogger use to indicate FQCN of this class - + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670 if (logger instanceof org.slf4j.spi.LocationAwareLogger) { _logger = new JettyAwareLogger((org.slf4j.spi.LocationAwareLogger)logger); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java index 8a85c295627..a7992b533ec 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ScannerTest.java @@ -7,6 +7,8 @@ import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.util.Scanner.Notification; import org.junit.AfterClass; @@ -25,10 +27,8 @@ public class ScannerTest @BeforeClass public static void setUpBeforeClass() throws Exception { - _directory = File.createTempFile("scan",""); - _directory.delete(); - _directory.mkdir(); - _directory.deleteOnExit(); + _directory = MavenTestingUtils.getTargetTestingDir(ScannerTest.class.getSimpleName()); + FS.ensureEmpty(_directory); _scanner = new Scanner(); _scanner.addScanDir(_directory); @@ -88,7 +88,7 @@ public class ScannerTest public void testAddedChangeRemove() throws Exception { // TODO needs to be further investigated - Assume.assumeTrue(!OS.IS_WINDOWS && !OS.IS_OSX); + Assume.assumeTrue(!OS.IS_WINDOWS); touch("a0"); @@ -96,7 +96,7 @@ public class ScannerTest _scanner.scan(); _scanner.scan(); Event event = _queue.poll(); - Assert.assertTrue(event!=null); + Assert.assertNotNull("Event should not be null", event); Assert.assertEquals(_directory+"/a0",event._filename); Assert.assertEquals(Notification.ADDED,event._notification); @@ -197,7 +197,7 @@ public class ScannerTest public void testSizeChange() throws Exception { // TODO needs to be further investigated - Assume.assumeTrue(!OS.IS_WINDOWS && !OS.IS_OSX); + Assume.assumeTrue(!OS.IS_WINDOWS); touch("tsc0"); _scanner.scan(); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java index 5b0c3afae54..eacd85b33b9 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBufferTest.java @@ -4,92 +4,98 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import java.io.UnsupportedEncodingException; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class Utf8StringBufferTest { - public void testUtfStringBuffer() - throws Exception + @Test + public void testUtfStringBuffer() throws Exception { - String source="abcd012345\n\r\u0000\u00a4\u10fb\ufffdjetty"; + String source = "abcd012345\n\r\u0000\u00a4\u10fb\ufffdjetty"; byte[] bytes = source.getBytes(StringUtil.__UTF8); Utf8StringBuffer buffer = new Utf8StringBuffer(); - for (int i=0;i=0); - } - } - - @Test - public void testLong() - throws Exception - { - String source="abcXX"; - byte[] bytes = source.getBytes(StringUtil.__UTF8); - bytes[3]=(byte)0xc0; - bytes[4]=(byte)0x00; + String source = "\uD842\uDF9F"; + byte[] bytes = source.getBytes("UTF-8"); - Utf8StringBuffer buffer = new Utf8StringBuffer(); - try - { - for (int i=0;i=0); - } - assertEquals("abc?",buffer.toString()); - } - - @Test - public void testUTF32codes() - throws Exception - { - String source="\uD842\uDF9F"; - byte[] bytes=source.getBytes("UTF-8"); - String jvmcheck = new String(bytes,0,bytes.length,"UTF-8"); assertEquals(source,jvmcheck); - + Utf8StringBuffer buffer = new Utf8StringBuffer(); buffer.append(bytes,0,bytes.length); - String result=buffer.toString(); + String result = buffer.toString(); assertEquals(source,result); } + @Test + public void testGermanUmlauts() throws Exception + { + byte[] bytes = new byte[6]; + bytes[0] = (byte)0xC3; + bytes[1] = (byte)0xBC; + bytes[2] = (byte)0xC3; + bytes[3] = (byte)0xB6; + bytes[4] = (byte)0xC3; + bytes[5] = (byte)0xA4; + + Utf8StringBuffer buffer = new Utf8StringBuffer(); + for (int i = 0; i < bytes.length; i++) + buffer.append(bytes[i]); + + assertEquals("\u00FC\u00F6\u00E4",buffer.toString()); + } + + @Test(expected = Utf8Appendable.NotUtf8Exception.class) + public void testInvalidUTF8() throws UnsupportedEncodingException + { + Utf8StringBuffer buffer = new Utf8StringBuffer(); + buffer.append((byte)0xC2); + buffer.append((byte)0xC2); + } } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java index 9cfca551290..b83aa1099db 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/Utf8StringBuilderTest.java @@ -4,116 +4,102 @@ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. -// The Eclipse Public License is available at +// The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php -// You may elect to redistribute this code under either of these licenses. +// You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class Utf8StringBuilderTest { @Test - public void testInvalid() - throws Exception + public void testInvalid() throws Exception { + String[] invalids = + { "c0af", "EDA080", "f08080af", "f8808080af", "e080af", "F4908080", "fbbfbfbfbf", "10FFFF" }; + + for (String i : invalids) + { + byte[] bytes = TypeUtil.fromHexString(i); + try + { + Utf8StringBuilder buffer = new Utf8StringBuilder(); + buffer.append(bytes,0,bytes.length); + + assertEquals(i,"not expected",buffer.toString()); + } + catch (Utf8Appendable.NotUtf8Exception e) + { + assertTrue(i,true); + } + } + } + + @Test + public void testUtfStringBuilder() throws Exception + { + String source = "abcd012345\n\r\u0000\u00a4\u10fb\ufffdjetty"; + byte[] bytes = source.getBytes(StringUtil.__UTF8); + Utf8StringBuilder buffer = new Utf8StringBuilder(); + for (byte aByte : bytes) + buffer.append(aByte); + assertEquals(source,buffer.toString()); + assertTrue(buffer.toString().endsWith("jetty")); + } + + @Test(expected = IllegalArgumentException.class) + public void testShort() throws Exception + { + String source = "abc\u10fb"; + byte[] bytes = source.getBytes(StringUtil.__UTF8); + Utf8StringBuilder buffer = new Utf8StringBuilder(); + for (int i = 0; i < bytes.length - 1; i++) + buffer.append(bytes[i]); + buffer.toString(); + } + + @Test + public void testLong() throws Exception + { + String source = "abcXX"; + byte[] bytes = source.getBytes(StringUtil.__UTF8); + bytes[3] = (byte)0xc0; + bytes[4] = (byte)0x00; + Utf8StringBuilder buffer = new Utf8StringBuilder(); - buffer.append((byte)0xED); - buffer.append((byte)0xA0); try { - buffer.append((byte)0x80); + for (byte aByte : bytes) + buffer.append(aByte); assertTrue(false); } - catch(Utf8Appendable.NotUtf8Exception e) + catch (IllegalArgumentException e) { assertTrue(true); } - - } - - @Test - public void testUtfStringBuilder() - throws Exception - { - String source="abcd012345\n\r\u0000\u00a4\u10fb\ufffdjetty"; - byte[] bytes = source.getBytes(StringUtil.__UTF8); - Utf8StringBuilder buffer = new Utf8StringBuilder(); - for (int i=0;i=0); - } - } - - @Test - public void testLong() - throws Exception - { - String source="abcXX"; - byte[] bytes = source.getBytes(StringUtil.__UTF8); - bytes[3]=(byte)0xc0; - bytes[4]=(byte)0x00; - - Utf8StringBuilder buffer = new Utf8StringBuilder(); - try - { - for (int i = 0; i < bytes.length; i++) - buffer.append(bytes[i]); - assertTrue(false); - } - catch(IllegalStateException e) - { - assertTrue(e.toString().indexOf("!UTF-8")>=0); - } - assertEquals("abc?", buffer.toString()); + assertEquals("abc\ufffd",buffer.toString()); } - - @Test - public void testUTF32codes() - throws Exception + @Test + public void testUTF32codes() throws Exception { - String source="\uD842\uDF9F"; - byte[] bytes=source.getBytes("UTF-8"); - + String source = "\uD842\uDF9F"; + byte[] bytes = source.getBytes("UTF-8"); + String jvmcheck = new String(bytes,0,bytes.length,"UTF-8"); assertEquals(source,jvmcheck); - + Utf8StringBuilder buffer = new Utf8StringBuilder(); buffer.append(bytes,0,bytes.length); - String result=buffer.toString(); + String result = buffer.toString(); assertEquals(source,result); } - - } diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java index 1baeb04f7e1..8635ce82e7b 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/MetaData.java @@ -165,6 +165,8 @@ public class MetaData _webXmlRoot.parse(); _metaDataComplete=_webXmlRoot.getMetaDataComplete() == MetaDataComplete.True; + + if (_webXmlRoot.isOrdered()) { if (_ordering == null) diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 00d81b18754..6122b45ffb7 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -277,11 +277,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor } } - // TODO is this too soon? /* Set the webapp's classpath for Jasper */ context.setAttribute("org.apache.catalina.jsp_classpath", context.getClassPath()); + /* Set the system classpath for Jasper */ - holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context)); + holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context)); } //Set the servlet-class @@ -327,6 +327,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor { holder.setForcedPath(jsp_file); holder.setClassName(jspServletClass); + //set the system classpath explicitly for the holder that will represent the JspServlet instance + holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context)); } // handle load-on-startup diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java index f5f14b8e4d7..863c85c9113 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java @@ -14,13 +14,16 @@ package org.eclipse.jetty.webapp; import java.io.IOException; +import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.EventListener; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import javax.servlet.Servlet; @@ -58,6 +61,7 @@ public class TagLibConfiguration extends AbstractConfiguration public static final String TLD_RESOURCES = "org.eclipse.jetty.tlds"; + /** * TagLibListener * @@ -96,7 +100,37 @@ public class TagLibConfiguration extends AbstractConfiguration public void contextInitialized(ServletContextEvent sce) { - try { + try + { + //For jasper 2.1: + //Get the system classpath tlds and tell jasper about them, if jasper is on the classpath + try + { + Class clazz = getClass().getClassLoader().loadClass("org.apache.jasper.compiler.TldLocationsCache"); + Collection tld_resources = (Collection)_context.getAttribute(TLD_RESOURCES); + + Map> tldMap = new HashMap>(); + + if (tld_resources != null) + { + //get the jar file names of the files + for (Resource r:tld_resources) + { + Resource jarResource = extractJarResource(r); + //jasper is happy with an empty list of tlds + if (!tldMap.containsKey(jarResource.getURI())) + tldMap.put(jarResource.getURI(), null); + + } + //set the magic context attribute that tells jasper about the system tlds + sce.getServletContext().setAttribute("com.sun.appserv.tld.map", tldMap); + } + } + catch (ClassNotFoundException e) + { + LOG.ignore(e); + } + //find the tld files and parse them to get out their //listeners Set tlds = findTldResources(); @@ -117,12 +151,37 @@ public class TagLibConfiguration extends AbstractConfiguration } } - } catch (Exception e) { + } + catch (Exception e) { LOG.warn(e); } } + + + private Resource extractJarResource (Resource r) + { + if (r == null) + return null; + + try + { + String url = r.getURI().toURL().toString(); + int idx = url.lastIndexOf("!/"); + if (idx >= 0) + url = url.substring(0, idx); + if (url.startsWith("jar:")) + url = url.substring(4); + return Resource.newResource(url); + } + catch (IOException e) + { + LOG.warn(e); + return null; + } + } + /** * Find all the locations that can harbour tld files that may contain * a listener which the web container is supposed to instantiate and diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java index a2a362827a3..935ff224c71 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java @@ -53,6 +53,8 @@ public class WebXmlConfiguration extends AbstractConfiguration if (webxml != null) { context.getMetaData().setWebXml(webxml); + context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebXml().getMajorVersion()); + context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebXml().getMinorVersion()); } //parse but don't process override-web.xml diff --git a/jetty-websocket/pom.xml b/jetty-websocket/pom.xml index 09d60c8f492..edc5bb08f89 100644 --- a/jetty-websocket/pom.xml +++ b/jetty-websocket/pom.xml @@ -1,10 +1,12 @@ - + + jetty-project org.eclipse.jetty 8.0.2-SNAPSHOT + 4.0.0 jetty-websocket Jetty :: Websocket @@ -14,7 +16,7 @@ - + ${servlet.spec.groupId} ${servlet.spec.artifactId} provided diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java index b3d6f44b4d2..54e4f6dc141 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java @@ -87,7 +87,7 @@ public class DeflateFrameExtension extends AbstractExtension catch(DataFormatException e) { LOG.warn(e); - getConnection().close(WebSocketConnectionD13.CLOSE_PROTOCOL,e.toString()); + getConnection().close(WebSocketConnectionD13.CLOSE_BAD_PAYLOAD,e.toString()); } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD12.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java similarity index 93% rename from jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD12.java rename to jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java index 1f86bdd851d..41cb3c97c18 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD12.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java @@ -38,9 +38,9 @@ import org.eclipse.jetty.websocket.WebSocket.OnControl; import org.eclipse.jetty.websocket.WebSocket.OnFrame; import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; -public class WebSocketConnectionD12 extends AbstractConnection implements WebSocketConnection +public class WebSocketConnectionD08 extends AbstractConnection implements WebSocketConnection { - private static final Logger LOG = Log.getLogger(WebSocketConnectionD12.class); + private static final Logger LOG = Log.getLogger(WebSocketConnectionD08.class); final static byte OP_CONTINUATION = 0x00; final static byte OP_TEXT = 0x01; @@ -78,9 +78,9 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc private final static byte[] MAGIC; private final IdleCheck _idle; private final List _extensions; - private final WebSocketParserD12 _parser; + private final WebSocketParserD08 _parser; private final WebSocketParser.FrameHandler _inbound; - private final WebSocketGeneratorD12 _generator; + private final WebSocketGeneratorD08 _generator; private final WebSocketGenerator _outbound; private final WebSocket _webSocket; private final OnFrame _onFrame; @@ -115,14 +115,14 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc /* ------------------------------------------------------------ */ - public WebSocketConnectionD12(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft) + public WebSocketConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft) throws IOException { this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null); } /* ------------------------------------------------------------ */ - public WebSocketConnectionD12(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft, MaskGen maskgen) + public WebSocketConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List extensions,int draft, MaskGen maskgen) throws IOException { super(endpoint,timestamp); @@ -140,7 +140,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null; _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null; _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null; - _generator = new WebSocketGeneratorD12(buffers, _endp,maskgen); + _generator = new WebSocketGeneratorD08(buffers, _endp,maskgen); _extensions=extensions; if (_extensions!=null) @@ -159,7 +159,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1); _inbound=(_extensions==null||_extensions.size()==0)?_frameHandler:extensions.get(0); - _parser = new WebSocketParserD12(buffers, endpoint,_inbound,maskgen==null); + _parser = new WebSocketParserD08(buffers, endpoint,_inbound,maskgen==null); _protocol=protocol; @@ -268,7 +268,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc public void idleExpired() { long idle = System.currentTimeMillis()-((SelectChannelEndPoint)_endp).getIdleTimestamp(); - closeOut(WebSocketConnectionD12.CLOSE_NORMAL,"Idle for "+idle+"ms > "+_endp.getMaxIdleTime()+"ms"); + closeOut(WebSocketConnectionD08.CLOSE_NORMAL,"Idle for "+idle+"ms > "+_endp.getMaxIdleTime()+"ms"); } /* ------------------------------------------------------------ */ @@ -285,10 +285,10 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc { closed=_closeCode==0; if (closed) - _closeCode=WebSocketConnectionD12.CLOSE_NOCLOSE; + _closeCode=WebSocketConnectionD08.CLOSE_NOCLOSE; } if (closed) - _webSocket.onClose(WebSocketConnectionD12.CLOSE_NOCLOSE,"closed"); + _webSocket.onClose(WebSocketConnectionD08.CLOSE_NOCLOSE,"closed"); } /* ------------------------------------------------------------ */ @@ -364,11 +364,11 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc else { if (code<=0) - code=WebSocketConnectionD12.CLOSE_NORMAL; + code=WebSocketConnectionD08.CLOSE_NORMAL; byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1); bytes[0]=(byte)(code/0x100); bytes[1]=(byte)(code%0x100); - _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD12.OP_CLOSE,bytes,0,bytes.length); + _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_CLOSE,bytes,0,bytes.length); } _outbound.flush(); @@ -408,7 +408,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc if (_closedOut) throw new IOException("closedOut "+_closeCode+":"+_closeMessage); byte[] data = content.getBytes(StringUtil.__UTF8); - _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD12.OP_TEXT,data,0,data.length); + _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_TEXT,data,0,data.length); checkWriteable(); _idle.access(_endp); } @@ -418,7 +418,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc { if (_closedOut) throw new IOException("closedOut "+_closeCode+":"+_closeMessage); - _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD12.OP_BINARY,content,offset,length); + _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_BINARY,content,offset,length); checkWriteable(); _idle.access(_endp); } @@ -461,7 +461,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc if (_disconnecting) return; _disconnecting=true; - WebSocketConnectionD12.this.closeOut(code,message); + WebSocketConnectionD08.this.closeOut(code,message); } /* ------------------------------------------------------------ */ @@ -601,7 +601,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc @Override public String toString() { - return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort(); + return this.getClass().getSimpleName()+"D08@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort(); } } @@ -618,7 +618,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc { boolean lastFrame = isLastFrame(flags); - synchronized(WebSocketConnectionD12.this) + synchronized(WebSocketConnectionD08.this) { // Ignore incoming after a close if (_closedIn) @@ -643,10 +643,10 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc switch(opcode) { - case WebSocketConnectionD12.OP_CONTINUATION: + case WebSocketConnectionD08.OP_CONTINUATION: { // If text, append to the message buffer - if (_onTextMessage!=null && _opcode==WebSocketConnectionD12.OP_TEXT) + if (_onTextMessage!=null && _opcode==WebSocketConnectionD08.OP_TEXT) { if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize())) { @@ -686,23 +686,23 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc } break; } - case WebSocketConnectionD12.OP_PING: + case WebSocketConnectionD08.OP_PING: { LOG.debug("PING {}",this); if (!_closedOut) - _connection.sendControl(WebSocketConnectionD12.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length()); + _connection.sendControl(WebSocketConnectionD08.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length()); break; } - case WebSocketConnectionD12.OP_PONG: + case WebSocketConnectionD08.OP_PONG: { LOG.debug("PONG {}",this); break; } - case WebSocketConnectionD12.OP_CLOSE: + case WebSocketConnectionD08.OP_CLOSE: { - int code=WebSocketConnectionD12.CLOSE_NOCODE; + int code=WebSocketConnectionD08.CLOSE_NOCODE; String message=null; if (buffer.length()>=2) { @@ -714,7 +714,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc break; } - case WebSocketConnectionD12.OP_TEXT: + case WebSocketConnectionD08.OP_TEXT: { if(_onTextMessage!=null) { @@ -726,7 +726,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc else { LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp); - _connection.close(WebSocketConnectionD12.CLOSE_BADDATA,"Text frame aggregation disabled"); + _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Text frame aggregation disabled"); } } // append bytes to message buffer (if they fit) @@ -740,7 +740,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc } else { - _opcode=WebSocketConnectionD12.OP_TEXT; + _opcode=WebSocketConnectionD08.OP_TEXT; } } else @@ -767,7 +767,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc else { LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp); - _connection.close(WebSocketConnectionD12.CLOSE_BADDATA,"Binary frame aggregation disabled"); + _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Binary frame aggregation disabled"); } } } @@ -789,7 +789,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc if (max>0 && (bufferLen+length)>max) { LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp); - _connection.close(WebSocketConnectionD12.CLOSE_BADDATA,"Message size > "+_connection.getMaxBinaryMessageSize()); + _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Message size > "+_connection.getMaxBinaryMessageSize()); _opcode=-1; if (_aggregate!=null) _aggregate.clear(); @@ -801,7 +801,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc private void textMessageTooLarge() { LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp); - _connection.close(WebSocketConnectionD12.CLOSE_BADDATA,"Text message size > "+_connection.getMaxTextMessageSize()+" chars"); + _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Text message size > "+_connection.getMaxTextMessageSize()+" chars"); _opcode=-1; _utf8.reset(); @@ -817,7 +817,7 @@ public class WebSocketConnectionD12 extends AbstractConnection implements WebSoc @Override public String toString() { - return WebSocketConnectionD12.this.toString()+"FH"; + return WebSocketConnectionD08.this.toString()+"FH"; } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD13.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD13.java index 03f4ca9d61c..3ca71841a9e 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD13.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD13.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.nio.SelectChannelEndPoint; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.Utf8Appendable; import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.log.Log; @@ -42,7 +43,8 @@ import org.eclipse.jetty.websocket.WebSocket.OnTextMessage; public class WebSocketConnectionD13 extends AbstractConnection implements WebSocketConnection { private static final Logger LOG = Log.getLogger(WebSocketConnectionD13.class); - private static final boolean STRICT=true; + private static final boolean STRICT=Boolean.getBoolean("org.eclipse.jetty.websocket.STRICT"); + private static final boolean BRUTAL=Boolean.getBoolean("org.eclipse.jetty.websocket.BRUTAL"); final static byte OP_CONTINUATION = 0x00; final static byte OP_TEXT = 0x01; @@ -62,7 +64,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc final static int CLOSE_UNDEFINED=1004; final static int CLOSE_NO_CODE=1005; final static int CLOSE_NO_CLOSE=1006; - final static int CLOSE_NOT_UTF8=1007; + final static int CLOSE_BAD_PAYLOAD=1007; final static int CLOSE_POLICY_VIOLATION=1008; final static int CLOSE_MESSAGE_TOO_LARGE=1009; final static int CLOSE_REQUIRED_EXTENSION=1010; @@ -610,7 +612,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc @Override public String toString() { - return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort(); + return this.getClass().getSimpleName()+"D13@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort(); } } @@ -626,7 +628,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc public void onFrame(final byte flags, final byte opcode, final Buffer buffer) { boolean lastFrame = isLastFrame(flags); - + synchronized(WebSocketConnectionD13.this) { // Ignore incoming after a close @@ -641,19 +643,13 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc { if (isControlFrame(opcode) && buffer.length()>125) { - _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Control frame too large"); + errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Control frame too large"); return; } if ((flags&0x7)!=0) { - _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"RSV bits set 0x"+Integer.toHexString(flags)); - return; - } - - if (_opcode!=-1 && opcode!=WebSocketConnectionD13.OP_CONTINUATION) - { - _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad continuation"+Integer.toHexString(opcode)); + errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"RSV bits set 0x"+Integer.toHexString(flags)); return; } @@ -681,7 +677,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc { if (_opcode==-1) { - _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad Continuation"); + errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad Continuation"); return; } @@ -756,6 +752,12 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc case WebSocketConnectionD13.OP_TEXT: { + if (STRICT && _opcode!=-1) + { + errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode)); + return; + } + if(_onTextMessage!=null) { if (_connection.getMaxTextMessageSize()<=0) @@ -766,7 +768,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc else { LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp); - _connection.close(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Text frame aggregation disabled"); + errorClose(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Text frame aggregation disabled"); } } // append bytes to message buffer (if they fit) @@ -788,9 +790,15 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc } break; } - + case WebSocketConnectionD13.OP_BINARY: { + if (STRICT && _opcode!=-1) + { + errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode)); + return; + } + if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length())) { if (lastFrame) @@ -808,36 +816,53 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc else { LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp); - _connection.close(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Binary frame aggregation disabled"); + errorClose(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Binary frame aggregation disabled"); } } break; } - + default: if (STRICT) - _connection.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode)); + errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode)); return; - } } - catch(Utf8Appendable.NotUtf8Exception notUtf8) - { - LOG.warn(notUtf8); - LOG.warn("{} for {}",notUtf8,_endp); - LOG.debug(notUtf8); - _connection.close(WebSocketConnectionD13.CLOSE_NOT_UTF8,"Invalid UTF-8"); - } catch(ThreadDeath th) { throw th; } - catch(Throwable th) + catch(Utf8Appendable.NotUtf8Exception notUtf8) { - LOG.warn(th); + LOG.warn("{} for {}",notUtf8,_endp); + LOG.debug(notUtf8); + errorClose(WebSocketConnectionD13.CLOSE_BAD_PAYLOAD,"Invalid UTF-8"); + } + catch(Throwable probablyNotUtf8) + { + LOG.warn("{} for {}",probablyNotUtf8,_endp); + LOG.debug(probablyNotUtf8); + errorClose(WebSocketConnectionD13.CLOSE_BAD_PAYLOAD,"Invalid Payload: "+probablyNotUtf8); } } + private void errorClose(int code, String message) + { + _connection.close(code,message); + if (BRUTAL) + { + try + { + _endp.close(); + } + catch (IOException e) + { + LOG.warn(e.toString()); + LOG.debug(e); + } + } + } + private boolean checkBinaryMessageSize(int bufferLen, int length) { int max = _connection.getMaxBinaryMessageSize(); diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java index 4752ac67d28..4fc1bb53627 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java @@ -213,18 +213,19 @@ public class WebSocketFactory extensions=Collections.emptyList(); connection = new WebSocketConnectionD00(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol); break; - case 6: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: extensions=Collections.emptyList(); connection = new WebSocketConnectionD06(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol); break; case 7: case 8: - case 9: - case 10: - case 11: - case 12: - extensions= initExtensions(extensions_requested,8-WebSocketConnectionD12.OP_EXT_DATA, 16-WebSocketConnectionD13.OP_EXT_CTRL,3); - connection = new WebSocketConnectionD12(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft); + extensions= initExtensions(extensions_requested,8-WebSocketConnectionD08.OP_EXT_DATA, 16-WebSocketConnectionD08.OP_EXT_CTRL,3); + connection = new WebSocketConnectionD08(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft); break; case 13: extensions= initExtensions(extensions_requested,8-WebSocketConnectionD13.OP_EXT_DATA, 16-WebSocketConnectionD13.OP_EXT_CTRL,3); @@ -232,6 +233,7 @@ public class WebSocketFactory break; default: LOG.warn("Unsupported Websocket version: "+draft); + response.setHeader("Sec-WebSocket-Version","0,6,8,13"); throw new HttpException(400, "Unsupported draft specification: " + draft); } @@ -251,6 +253,8 @@ public class WebSocketFactory request.setAttribute("org.eclipse.jetty.io.Connection", connection); } + /** + */ protected String[] parseProtocols(String protocol) { if (protocol == null) @@ -264,6 +268,8 @@ public class WebSocketFactory return protocols; } + /** + */ public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -280,24 +286,34 @@ public class WebSocketFactory // Try each requested protocol WebSocket websocket = null; - String protocol = request.getHeader("Sec-WebSocket-Protocol"); - if (protocol == null) // TODO remove once draft period is over - protocol = request.getHeader("WebSocket-Protocol"); - for (String p : parseProtocols(protocol)) + + Enumeration protocols = request.getHeaders("Sec-WebSocket-Protocol"); + String protocol=null; + while (protocol==null && protocols!=null && protocols.hasMoreElements()) { - websocket = _acceptor.doWebSocketConnect(request, p); - if (websocket != null) + String candidate = protocols.nextElement(); + for (String p : parseProtocols(candidate)) { - protocol = p; - break; + websocket = _acceptor.doWebSocketConnect(request, p); + if (websocket != null) + { + protocol = p; + break; + } } } // Did we get a websocket? if (websocket == null) { - response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - return false; + // Try with no protocol + websocket = _acceptor.doWebSocketConnect(request, null); + + if (websocket==null) + { + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return false; + } } // Send the upgrade @@ -308,6 +324,8 @@ public class WebSocketFactory return false; } + /** + */ public List initExtensions(List requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits) { List extensions = new ArrayList(); @@ -339,6 +357,8 @@ public class WebSocketFactory return extensions; } + /** + */ private Extension newExtension(String name) { try @@ -354,6 +374,4 @@ public class WebSocketFactory return null; } - - } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java similarity index 95% rename from jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12.java rename to jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java index 12731393652..22c7fd38a3f 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java @@ -27,7 +27,7 @@ import org.eclipse.jetty.io.EofException; * threads will call the addMessage methods while other * threads are flushing the generator. */ -public class WebSocketGeneratorD12 implements WebSocketGenerator +public class WebSocketGeneratorD08 implements WebSocketGenerator { final private WebSocketBuffers _buffers; final private EndPoint _endp; @@ -37,14 +37,14 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator private boolean _opsent; private final MaskGen _maskGen; - public WebSocketGeneratorD12(WebSocketBuffers buffers, EndPoint endp) + public WebSocketGeneratorD08(WebSocketBuffers buffers, EndPoint endp) { _buffers=buffers; _endp=endp; _maskGen=null; } - public WebSocketGeneratorD12(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen) + public WebSocketGeneratorD08(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen) { _buffers=buffers; _endp=endp; @@ -65,13 +65,13 @@ public class WebSocketGeneratorD12 implements WebSocketGenerator if (_buffer==null) _buffer=mask?_buffers.getBuffer():_buffers.getDirectBuffer(); - boolean last=WebSocketConnectionD12.isLastFrame(flags); + boolean last=WebSocketConnectionD08.isLastFrame(flags); int space=mask?14:10; do { - opcode = _opsent?WebSocketConnectionD12.OP_CONTINUATION:opcode; + opcode = _opsent?WebSocketConnectionD08.OP_CONTINUATION:opcode; opcode=(byte)(((0xf&flags)<<4)+(0xf&opcode)); _opsent=true; diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD12.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD08.java similarity index 94% rename from jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD12.java rename to jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD08.java index e84c4957bf8..073d9ae1dd0 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD12.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD08.java @@ -28,9 +28,9 @@ import org.eclipse.jetty.util.log.Logger; * Parser the WebSocket protocol. * */ -public class WebSocketParserD12 implements WebSocketParser +public class WebSocketParserD08 implements WebSocketParser { - private static final Logger LOG = Log.getLogger(WebSocketParserD12.class); + private static final Logger LOG = Log.getLogger(WebSocketParserD08.class); public enum State { @@ -74,7 +74,7 @@ public class WebSocketParserD12 implements WebSocketParser * @param handler the handler to notify when a parse event occurs * @param shouldBeMasked whether masking should be handled */ - public WebSocketParserD12(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked) + public WebSocketParserD08(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked) { _buffers=buffers; _endp=endp; @@ -160,9 +160,9 @@ public class WebSocketParserD12 implements WebSocketParser // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length()); events++; _bytesNeeded-=data.length(); - _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD12.FLAG_FIN)), _opcode, data); + _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD08.FLAG_FIN)), _opcode, data); - _opcode=WebSocketConnectionD12.OP_CONTINUATION; + _opcode=WebSocketConnectionD08.OP_CONTINUATION; } if (_buffer.space() == 0) @@ -205,11 +205,11 @@ public class WebSocketParserD12 implements WebSocketParser _opcode=(byte)(b&0xf); _flags=(byte)(0xf&(b>>4)); - if (WebSocketConnectionD12.isControlFrame(_opcode)&&!WebSocketConnectionD12.isLastFrame(_flags)) + if (WebSocketConnectionD08.isControlFrame(_opcode)&&!WebSocketConnectionD08.isLastFrame(_flags)) { events++; LOG.warn("Fragmented Control from "+_endp); - _handler.close(WebSocketConnectionD12.CLOSE_PROTOCOL,"Fragmented control"); + _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"Fragmented control"); _skip=true; } @@ -250,7 +250,7 @@ public class WebSocketParserD12 implements WebSocketParser if (_length>_buffer.capacity() && !_fakeFragments) { events++; - _handler.close(WebSocketConnectionD12.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity()); + _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity()); _skip=true; } @@ -269,7 +269,7 @@ public class WebSocketParserD12 implements WebSocketParser if (_length>=_buffer.capacity() && !_fakeFragments) { events++; - _handler.close(WebSocketConnectionD12.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity()); + _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity()); _skip=true; } @@ -312,7 +312,7 @@ public class WebSocketParserD12 implements WebSocketParser _buffer.skip(_bytesNeeded); _state=State.START; events++; - _handler.close(WebSocketConnectionD12.CLOSE_PROTOCOL,"bad mask"); + _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"bad mask"); } else { @@ -367,7 +367,7 @@ public class WebSocketParserD12 implements WebSocketParser public String toString() { Buffer buffer=_buffer; - return WebSocketParserD12.class.getSimpleName()+"@"+ Integer.toHexString(hashCode())+"|"+_state+"|"+(buffer==null?"<>":buffer.toDetailString()); + return WebSocketParserD08.class.getSimpleName()+"@"+ Integer.toHexString(hashCode())+"|"+_state+"|"+(buffer==null?"<>":buffer.toDetailString()); } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD13.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD13.java index 43db357578c..5b21494df50 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD13.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParserD13.java @@ -312,7 +312,7 @@ public class WebSocketParserD13 implements WebSocketParser _buffer.skip(_bytesNeeded); _state=State.START; events++; - _handler.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"bad mask"); + _handler.close(WebSocketConnectionD13.CLOSE_PROTOCOL,"Not masked"); } else { diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java index 80716955347..8a29da81323 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestClient.java @@ -52,6 +52,8 @@ public class TestClient implements WebSocket.OnFrame public void onOpen(Connection connection) { + if (_verbose) + System.err.printf("%s#onHandshake %s %s\n",this.getClass().getSimpleName(),connection,connection.getClass().getSimpleName()); } public void onClose(int closeCode, String message) @@ -141,7 +143,7 @@ public class TestClient implements WebSocket.OnFrame byte op=(byte)(off==0?opcode:WebSocketConnectionD13.OP_CONTINUATION); if (_verbose) - System.err.printf("%s#addFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(op),TypeUtil.toHexString(data,off,len)); + System.err.printf("%s#sendFrame %s|%s %s\n",this.getClass().getSimpleName(),TypeUtil.toHexString(flags),TypeUtil.toHexString(op),TypeUtil.toHexString(data,off,len)); _connection.sendFrame(flags,op,data,off,len); @@ -168,6 +170,7 @@ public class TestClient implements WebSocket.OnFrame System.err.println(" -p|--port PORT (default 8080)"); System.err.println(" -b|--binary"); System.err.println(" -v|--verbose"); + System.err.println(" -q|--quiet"); System.err.println(" -c|--count n (default 10)"); System.err.println(" -s|--size n (default 64)"); System.err.println(" -f|--fragment n (default 4000) "); @@ -226,6 +229,8 @@ public class TestClient implements WebSocket.OnFrame try { __start=System.currentTimeMillis(); + protocol=protocol==null?"echo":protocol; + for (int i=0;i1?"s":"")+" ---"); - System.out.println(__framesSent+" frames transmitted, "+__framesReceived+" received, "+ - __messagesSent+" messages transmitted, "+__messagesReceived+" received, "+ - "time "+duration+"ms "+ (1000L*__messagesReceived.get()/duration)+" req/s"); + System.out.printf("%d/%d frames sent/recv, %d/%d mesg sent/recv, time %dms %dm/s %.2fbps%n", + __framesSent,__framesReceived.get(), + __messagesSent,__messagesReceived.get(), + duration,(1000L*__messagesReceived.get()/duration), + 1000.0D*__messagesReceived.get()*8*size/duration/1024/1024); System.out.printf("rtt min/ave/max = %.3f/%.3f/%.3f ms\n",__minDuration.get()/1000000.0,__messagesReceived.get()==0?0.0:(__totalTime.get()/__messagesReceived.get()/1000000.0),__maxDuration.get()/1000000.0); __clientFactory.stop(); diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java index ab9743392e2..5ffc4bb87c2 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TestServer.java @@ -2,6 +2,8 @@ package org.eclipse.jetty.websocket; import java.io.IOException; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; @@ -12,6 +14,7 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.WebSocket.FrameConnection; public class TestServer extends Server { @@ -42,7 +45,10 @@ public class TestServer extends Server else if ("org.ietf.websocket.test-echo-broadcast".equals(protocol) || "echo-broadcast".equals(protocol)) { _websocket = new TestEchoBroadcastWebSocket(); - + } + else if ("echo-broadcast-ping".equals(protocol)) + { + _websocket = new TestEchoBroadcastPingWebSocket(); } else if ("org.ietf.websocket.test-echo-assemble".equals(protocol) || "echo-assemble".equals(protocol)) { @@ -107,7 +113,7 @@ public class TestServer extends Server public void onOpen(Connection connection) { if (_verbose) - System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),connection); + System.err.printf("%s#onOpen %s %s\n",this.getClass().getSimpleName(),connection,connection.getProtocol()); } public void onHandshake(FrameConnection connection) @@ -169,7 +175,8 @@ public class TestServer extends Server try { if (!getConnection().isControl(opcode)) - getConnection().sendFrame(flags,opcode,data,offset,length); } + getConnection().sendFrame(flags,opcode,data,offset,length); + } catch (IOException e) { e.printStackTrace(); @@ -179,6 +186,61 @@ public class TestServer extends Server } } + /* ------------------------------------------------------------ */ + /* ------------------------------------------------------------ */ + class TestEchoBroadcastPingWebSocket extends TestEchoBroadcastWebSocket + { + Thread _keepAlive; // A dedicated thread is not a good way to do this + CountDownLatch _latch = new CountDownLatch(1); + + @Override + public void onHandshake(final FrameConnection connection) + { + super.onHandshake(connection); + _keepAlive=new Thread() + { + @Override + public void run() + { + try + { + while(!_latch.await(10,TimeUnit.SECONDS)) + { + System.err.println("Ping "+connection); + byte[] data = { (byte)1, (byte) 2, (byte) 3 }; + connection.sendControl(WebSocketConnectionD13.OP_PING,data,0,data.length); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }; + _keepAlive.start(); + } + + + @Override + public boolean onControl(byte controlCode, byte[] data, int offset, int length) + { + if (controlCode==WebSocketConnectionD13.OP_PONG) + System.err.println("Pong "+getConnection()); + return super.onControl(controlCode,data,offset,length); + } + + + @Override + public void onClose(int code, String message) + { + _latch.countDown(); + super.onClose(code,message); + } + + + } + + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ class TestEchoBroadcastWebSocket extends TestWebSocket diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08Test.java similarity index 93% rename from jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12Test.java rename to jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08Test.java index 6d3fb5fb45f..05dfca5a151 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD12Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08Test.java @@ -11,7 +11,7 @@ import org.junit.Test; /** * @version $Revision$ $Date$ */ -public class WebSocketGeneratorD12Test +public class WebSocketGeneratorD08Test { private ByteArrayBuffer _out; private WebSocketGenerator _generator; @@ -42,7 +42,7 @@ public class WebSocketGeneratorD12Test @Test public void testOneString() throws Exception { - _generator = new WebSocketGeneratorD12(_buffers, _endPoint,null); + _generator = new WebSocketGeneratorD08(_buffers, _endPoint,null); byte[] data = "Hell\uFF4F W\uFF4Frld".getBytes(StringUtil.__UTF8); _generator.addFrame((byte)0x8,(byte)0x04,data,0,data.length); @@ -69,7 +69,7 @@ public class WebSocketGeneratorD12Test @Test public void testOneBuffer() throws Exception { - _generator = new WebSocketGeneratorD12(_buffers, _endPoint,null); + _generator = new WebSocketGeneratorD08(_buffers, _endPoint,null); String string = "Hell\uFF4F W\uFF4Frld"; byte[] bytes=string.getBytes(StringUtil.__UTF8); @@ -97,7 +97,7 @@ public class WebSocketGeneratorD12Test @Test public void testOneLongBuffer() throws Exception { - _generator = new WebSocketGeneratorD12(_buffers, _endPoint,null); + _generator = new WebSocketGeneratorD08(_buffers, _endPoint,null); byte[] b=new byte[150]; for (int i=0;i "+message); diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java index 99781856907..53b31f1bf30 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java @@ -84,6 +84,7 @@ public class WebSocketMessageD00Test InputStream input = socket.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(input, "ISO-8859-1")); String responseLine = reader.readLine(); + System.err.println(responseLine); assertTrue(responseLine.startsWith("HTTP/1.1 101 WebSocket Protocol Handshake")); // Read until we find an empty line, which signals the end of the http response String line; diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD12Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD08Test.java similarity index 94% rename from jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD12Test.java rename to jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD08Test.java index 8003709a3ea..ee321cd032f 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD12Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD08Test.java @@ -35,7 +35,7 @@ import org.junit.Test; /** * @version $Revision$ $Date$ */ -public class WebSocketMessageD12Test +public class WebSocketMessageD08Test { private static Server __server; private static Connector __connector; @@ -82,7 +82,7 @@ public class WebSocketMessageD12Test @Test public void testHash() { - assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionD12.hashKey("dGhlIHNhbXBsZSBub25jZQ==")); + assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",WebSocketConnectionD08.hashKey("dGhlIHNhbXBsZSBub25jZQ==")); } @Test @@ -98,7 +98,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: chat, superchat\r\n"+ - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -123,7 +123,7 @@ public class WebSocketMessageD12Test String data=message.toString(); __serverWebSocket.connection.sendMessage(data); - assertEquals(WebSocketConnectionD12.OP_TEXT,input.read()); + assertEquals(WebSocketConnectionD08.OP_TEXT,input.read()); assertEquals(0x7e,input.read()); assertEquals(0x1f,input.read()); assertEquals(0xf6,input.read()); @@ -146,7 +146,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -181,7 +181,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "Sec-WebSocket-Extensions: identity;param=0\r\n"+ "Sec-WebSocket-Extensions: identity;param=1, identity ; param = '2' ; other = ' some = value ' \r\n"+ "\r\n").getBytes("ISO-8859-1")); @@ -224,7 +224,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "Sec-WebSocket-Extensions: fragment;maxLength=4;minFragments=7\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -278,7 +278,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: echo\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "Sec-WebSocket-Extensions: x-deflate-frame;minLength=64\r\n"+ "Sec-WebSocket-Extensions: fragment;minFragments=2\r\n"+ "\r\n").getBytes("ISO-8859-1")); @@ -329,7 +329,7 @@ public class WebSocketMessageD12Test output.write(buf,0,l+3); output.flush(); - assertEquals(0x40+WebSocketConnectionD12.OP_TEXT,input.read()); + assertEquals(0x40+WebSocketConnectionD08.OP_TEXT,input.read()); assertEquals(0x20+3,input.read()); assertEquals(0x7e,input.read()); assertEquals(0x02,input.read()); @@ -376,7 +376,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: echo\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); output.write(0x84); @@ -415,7 +415,7 @@ public class WebSocketMessageD12Test byte[] bytes="This is a long message of text that we will send again and again".getBytes(StringUtil.__ISO_8859_1); byte[] mesg=new byte[bytes.length+6]; - mesg[0]=(byte)(0x80+WebSocketConnectionD12.OP_TEXT); + mesg[0]=(byte)(0x80+WebSocketConnectionD08.OP_TEXT); mesg[1]=(byte)(0x80+bytes.length); mesg[2]=(byte)0xff; mesg[3]=(byte)0xff; @@ -434,7 +434,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: latch\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -521,7 +521,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: latch\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -608,7 +608,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: echo\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); output.write(0x89); @@ -647,7 +647,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -679,10 +679,10 @@ public class WebSocketMessageD12Test output.flush(); - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(33,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Text message size > 10240 chars",input); } @@ -699,7 +699,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -737,10 +737,10 @@ public class WebSocketMessageD12Test output.write(bytes[i]^0xff); output.flush(); - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(30,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Text message size > 15 chars",input); } @@ -758,7 +758,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -788,10 +788,10 @@ public class WebSocketMessageD12Test - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(30,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Text message size > 15 chars",input); } @@ -808,7 +808,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: aggregate\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -824,7 +824,7 @@ public class WebSocketMessageD12Test assertNotNull(__serverWebSocket.connection); __serverWebSocket.getConnection().setMaxBinaryMessageSize(1024); - output.write(WebSocketConnectionD12.OP_BINARY); + output.write(WebSocketConnectionD08.OP_BINARY); output.write(0x8a); output.write(0xff); output.write(0xff); @@ -845,7 +845,7 @@ public class WebSocketMessageD12Test output.write(bytes[i]^0xff); output.flush(); - assertEquals(0x80+WebSocketConnectionD12.OP_BINARY,input.read()); + assertEquals(0x80+WebSocketConnectionD08.OP_BINARY,input.read()); assertEquals(20,input.read()); lookFor("01234567890123456789",input); } @@ -863,7 +863,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -902,10 +902,10 @@ public class WebSocketMessageD12Test output.flush(); - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(19,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Message size > 15",input); } @@ -923,7 +923,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: other\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -951,10 +951,10 @@ public class WebSocketMessageD12Test output.write(bytes[i]^0xff); output.flush(); - assertEquals(0x80|WebSocketConnectionD12.OP_CLOSE,input.read()); + assertEquals(0x80|WebSocketConnectionD08.OP_CLOSE,input.read()); assertEquals(19,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,code); lookFor("Message size > 15",input); } @@ -971,7 +971,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -1033,7 +1033,7 @@ public class WebSocketMessageD12Test "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+ "Sec-WebSocket-Origin: http://example.com\r\n"+ "Sec-WebSocket-Protocol: onConnect\r\n" + - "Sec-WebSocket-Version: 7\r\n"+ + "Sec-WebSocket-Version: 8\r\n"+ "\r\n").getBytes("ISO-8859-1")); output.flush(); @@ -1077,14 +1077,14 @@ public class WebSocketMessageD12Test final AtomicReference received = new AtomicReference(); ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); - WebSocketGeneratorD12 gen = new WebSocketGeneratorD12(new WebSocketBuffers(8096),endp,null); + WebSocketGeneratorD08 gen = new WebSocketGeneratorD08(new WebSocketBuffers(8096),endp,null); byte[] data = message.getBytes(StringUtil.__UTF8); gen.addFrame((byte)0x8,(byte)0x4,data,0,data.length); endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096); - WebSocketParserD12 parser = new WebSocketParserD12(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() + WebSocketParserD08 parser = new WebSocketParserD08(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() { public void onFrame(byte flags, byte opcode, Buffer buffer) { @@ -1111,13 +1111,13 @@ public class WebSocketMessageD12Test MaskGen maskGen = new RandomMaskGen(); - WebSocketGeneratorD12 gen = new WebSocketGeneratorD12(new WebSocketBuffers(8096),endp,maskGen); + WebSocketGeneratorD08 gen = new WebSocketGeneratorD08(new WebSocketBuffers(8096),endp,maskGen); byte[] data = message.getBytes(StringUtil.__UTF8); gen.addFrame((byte)0x8,(byte)0x1,data,0,data.length); endp = new ByteArrayEndPoint(endp.getOut().asArray(),4096); - WebSocketParserD12 parser = new WebSocketParserD12(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() + WebSocketParserD08 parser = new WebSocketParserD08(new WebSocketBuffers(8096),endp,new WebSocketParser.FrameHandler() { public void onFrame(byte flags, byte opcode, Buffer buffer) { @@ -1241,9 +1241,9 @@ public class WebSocketMessageD12Test { switch(opcode) { - case WebSocketConnectionD12.OP_CLOSE: - case WebSocketConnectionD12.OP_PING: - case WebSocketConnectionD12.OP_PONG: + case WebSocketConnectionD08.OP_CLOSE: + case WebSocketConnectionD08.OP_PING: + case WebSocketConnectionD08.OP_PONG: break; default: diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD13Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD13Test.java index 6c4e8ae253a..e48885c2f01 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD13Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD13Test.java @@ -998,7 +998,7 @@ public class WebSocketMessageD13Test assertEquals(0x80|WebSocketConnectionD13.OP_CLOSE,input.read()); assertEquals(15,input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read()); - assertEquals(WebSocketConnectionD13.CLOSE_NOT_UTF8,code); + assertEquals(WebSocketConnectionD13.CLOSE_BAD_PAYLOAD,code); lookFor("Invalid UTF-8",input); } diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD12Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD08Test.java similarity index 96% rename from jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD12Test.java rename to jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD08Test.java index 3685eefb0da..b34c1ff04ba 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD12Test.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketParserD08Test.java @@ -20,11 +20,11 @@ import org.junit.Test; /** * @version $Revision$ $Date$ */ -public class WebSocketParserD12Test +public class WebSocketParserD08Test { private MaskedByteArrayBuffer _in; private Handler _handler; - private WebSocketParserD12 _parser; + private WebSocketParserD08 _parser; private byte[] _mask = new byte[] {(byte)0x00,(byte)0xF0,(byte)0x0F,(byte)0xFF}; private int _m; @@ -87,7 +87,7 @@ public class WebSocketParserD12Test ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); endPoint.setNonBlocking(true); _handler = new Handler(); - _parser=new WebSocketParserD12(buffers, endPoint,_handler,true); + _parser=new WebSocketParserD08(buffers, endPoint,_handler,true); _parser.setFakeFragments(false); _in = new MaskedByteArrayBuffer(); @@ -192,7 +192,7 @@ public class WebSocketParserD12Test { WebSocketBuffers buffers = new WebSocketBuffers(0x20000); ByteArrayEndPoint endPoint = new ByteArrayEndPoint(); - WebSocketParserD12 parser=new WebSocketParserD12(buffers, endPoint,_handler,false); + WebSocketParserD08 parser=new WebSocketParserD08(buffers, endPoint,_handler,false); ByteArrayBuffer in = new ByteArrayBuffer(0x20000); endPoint.setIn(in); @@ -269,7 +269,7 @@ public class WebSocketParserD12Test assertTrue(progress>0); - assertEquals(WebSocketConnectionD12.CLOSE_BADDATA,_handler._code); + assertEquals(WebSocketConnectionD08.CLOSE_BADDATA,_handler._code); for (int i=0;i<2048;i++) _in.put((byte)'a'); progress =_parser.parseNext(); @@ -313,7 +313,7 @@ public class WebSocketParserD12Test assertTrue(progress>0); assertEquals(2,_handler._frames); - assertEquals(WebSocketConnectionD12.OP_CONTINUATION,_handler._opcode); + assertEquals(WebSocketConnectionD08.OP_CONTINUATION,_handler._opcode); assertEquals(1,_handler._data.size()); String mesg=_handler._data.remove(0); diff --git a/jetty-websocket/src/test/webapp/index.html b/jetty-websocket/src/test/webapp/index.html index 427e7c1522c..3f7f023ab90 100644 --- a/jetty-websocket/src/test/webapp/index.html +++ b/jetty-websocket/src/test/webapp/index.html @@ -17,7 +17,7 @@ join: function(name) { this._username=name; var location = document.location.toString().replace('http://','ws://').replace('https://','wss://'); - this._ws=new WebSocket(location,"echo-broadcast"); + this._ws=new WebSocket(location,"echo-broadcast-ping"); this._ws.onopen=this._onopen; this._ws.onmessage=this._onmessage; this._ws.onclose=this._onclose; diff --git a/pom.xml b/pom.xml index c2c8d4e171c..431a1903404 100644 --- a/pom.xml +++ b/pom.xml @@ -3,9 +3,8 @@ org.eclipse.jetty jetty-parent - 18 + 19 - org.eclipse.jetty jetty-project 8.0.2-SNAPSHOT Jetty :: Project @@ -188,7 +187,7 @@ org.eclipse.jetty.toolchain jetty-version-maven-plugin - 1.0.3 + 1.0.6 org.apache.maven.plugins @@ -247,7 +246,6 @@ org.codehaus.mojo findbugs-maven-plugin - 2.3.2 true true @@ -262,28 +260,29 @@ org.apache.maven.plugins maven-jxr-plugin - 2.2 org.apache.maven.plugins maven-javadoc-plugin - 2.8 + + true + true + true + org.apache.maven.plugins maven-pmd-plugin - 2.5 - - + org.apache.maven.plugins maven-jxr-plugin - 2.2 + 2.1 org.apache.maven.plugins @@ -414,6 +413,11 @@ junit ${junit-version} + + org.mockito + mockito-core + 1.8.5 +