From c19642baf4ffdd7ee559141af68410d2ade21949 Mon Sep 17 00:00:00 2001 From: Jesse McConnell Date: Tue, 23 Apr 2013 10:28:03 -0500 Subject: [PATCH 01/28] [Bug 404010] fix cast exception in mongodb session manager --- .../org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 80b29b578db..22285ca3517 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 @@ -143,7 +143,7 @@ public class MongoSessionManager extends NoSqlSessionManager } else { - version = new Long(((Long)version).intValue() + 1); + version = new Long(((Number)version).longValue() + 1); update.put("$inc",__version_1); } From 8740616b3738dd0502859d82c6bb8267e0dfbd3f Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 25 Apr 2013 08:58:15 +1000 Subject: [PATCH 02/28] uncommented basic tests --- .../jetty/security/ConstraintTest.java | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) 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 abbfe732e72..efc4b13855f 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 @@ -39,6 +39,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.security.authentication.DigestAuthenticator; import org.eclipse.jetty.security.authentication.FormAuthenticator; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; @@ -205,7 +206,6 @@ public class ConstraintTest @Test public void testBasic() throws Exception { - List list = new ArrayList(_security.getConstraintMappings()); Constraint constraint6 = new Constraint(); @@ -250,14 +250,11 @@ public class ConstraintTest _server.start(); String response; - /* response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n"); assertThat(response,startsWith("HTTP/1.1 200 OK")); -*/ response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n"); assertThat(response,startsWith("HTTP/1.1 403 Forbidden")); - /* response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); assertThat(response,startsWith("HTTP/1.1 401 Unauthorized")); assertThat(response,containsString("WWW-Authenticate: basic realm=\"TestRealm\"")); @@ -272,8 +269,7 @@ public class ConstraintTest "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "\r\n"); assertThat(response,startsWith("HTTP/1.1 200 OK")); -*/ -/* + // test admin response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n"); assertThat(response,startsWith("HTTP/1.1 401 Unauthorized")); @@ -304,28 +300,27 @@ public class ConstraintTest response = _connector.getResponses("GET /ctx/omit/x HTTP/1.0\r\n" + "Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" + "\r\n"); - assertTrue(response.startsWith("HTTP/1.1 200 OK")); + assertThat(response,startsWith("HTTP/1.1 200 OK")); //check POST is in role user response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" + "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" + "\r\n"); - assertTrue(response.startsWith("HTTP/1.1 200 OK")); + assertThat(response,startsWith("HTTP/1.1 200 OK")); //check POST can be in role foo too response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" + "Authorization: Basic " + B64Code.encode("user3:password") + "\r\n" + "\r\n"); - assertTrue(response.startsWith("HTTP/1.1 200 OK")); + assertThat(response,startsWith("HTTP/1.1 200 OK")); //check HEAD cannot be in role user response = _connector.getResponses("HEAD /ctx/omit/x HTTP/1.0\r\n" + "Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" + "\r\n"); - assertTrue(response.startsWith("HTTP/1.1 200 OK"));*/ + assertThat(response,startsWith("HTTP/1.1 403 ")); } - - + @Test public void testFormDispatch() throws Exception @@ -863,32 +858,32 @@ public class ConstraintTest String response; response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n"); - assertTrue(response.startsWith("HTTP/1.1 403")); + assertThat(response,startsWith("HTTP/1.1 403")); _config.setSecurePort(8443); _config.setSecureScheme("https"); response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n"); - assertTrue(response.startsWith("HTTP/1.1 302 Found")); + assertThat(response,startsWith("HTTP/1.1 302 Found")); assertTrue(response.indexOf("Location") > 0); assertTrue(response.indexOf(":8443/ctx/data/info") > 0); _config.setSecurePort(443); response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n"); - assertTrue(response.startsWith("HTTP/1.1 302 Found")); + assertThat(response,startsWith("HTTP/1.1 302 Found")); assertTrue(response.indexOf("Location") > 0); assertTrue(response.indexOf(":443/ctx/data/info") < 0); _config.setSecurePort(8443); response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n"); - assertTrue(response.startsWith("HTTP/1.1 302 Found")); + assertThat(response,startsWith("HTTP/1.1 302 Found")); assertTrue(response.indexOf("Location") > 0); assertTrue(response.indexOf("https://wobble.com:8443/ctx/data/info") > 0); _config.setSecurePort(443); response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n"); System.err.println(response); - assertTrue(response.startsWith("HTTP/1.1 302 Found")); + assertThat(response,startsWith("HTTP/1.1 302 Found")); assertTrue(response.indexOf("Location") > 0); assertTrue(response.indexOf(":443") < 0); assertTrue(response.indexOf("https://wobble.com/ctx/data/info") > 0); From 7da94048fae6b3922f0625c05bcdf63d5aa7bfed Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 25 Apr 2013 10:26:29 +1000 Subject: [PATCH 03/28] 406437 Digest Auth supports out of order nc --- .../authentication/DigestAuthenticator.java | 76 +++++++---- .../jetty/security/ConstraintTest.java | 129 +++++++++++++++++- 2 files changed, 178 insertions(+), 27 deletions(-) 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 8b588269447..761080bccea 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 @@ -21,6 +21,7 @@ package org.eclipse.jetty.security.authentication; import java.io.IOException; import java.security.MessageDigest; import java.security.SecureRandom; +import java.util.BitSet; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -60,17 +61,32 @@ 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 int _maxNC=1024; + private ConcurrentMap _nonceMap = 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) + final BitSet _seen; + + public Nonce(String nonce, long ts, int size) { _nonce=nonce; _ts=ts; + _seen = new BitSet(size); + } + + public boolean seen(int count) + { + synchronized (this) + { + if (count>=_seen.size()) + return true; + boolean s=_seen.get(count); + _seen.set(count); + return s; + } } } @@ -92,19 +108,35 @@ public class DigestAuthenticator extends LoginAuthenticator String mna=configuration.getInitParameter("maxNonceAge"); if (mna!=null) { - synchronized (this) - { - _maxNonceAgeMs=Long.valueOf(mna); - } + _maxNonceAgeMs=Long.valueOf(mna); } } + + + /* ------------------------------------------------------------ */ + public int getMaxNonceCount() + { + return _maxNC; + } + + /* ------------------------------------------------------------ */ + public void setMaxNonceCount(int maxNC) + { + _maxNC = maxNC; + } /* ------------------------------------------------------------ */ - public synchronized void setMaxNonceAge(long maxNonceAgeInMillis) + public void setMaxNonceAge(long maxNonceAgeInMillis) { _maxNonceAgeMs = maxNonceAgeInMillis; } + /* ------------------------------------------------------------ */ + public long getMaxNonceAge() + { + return _maxNonceAgeMs; + } + /* ------------------------------------------------------------ */ public String getAuthMethod() { @@ -233,9 +265,9 @@ public class DigestAuthenticator extends LoginAuthenticator byte[] nounce = new byte[24]; _random.nextBytes(nounce); - nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp()); + nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC); } - while (_nonceCount.putIfAbsent(nonce._nonce,nonce)!=null); + while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null); _nonceQueue.add(nonce); return nonce._nonce; @@ -250,36 +282,27 @@ public class DigestAuthenticator extends LoginAuthenticator private int checkNonce(Digest digest, Request request) { // firstly let's expire old nonces - long expired; - synchronized (this) - { - expired = request.getTimeStamp()-_maxNonceAgeMs; - } - + long expired = request.getTimeStamp()-_maxNonceAgeMs; Nonce nonce=_nonceQueue.peek(); while (nonce!=null && nonce._tsInteger.MAX_VALUE) + if (count>=_maxNC) return 0; - int old=nonce._nc.get(); - while (!nonce._nc.compareAndSet(old,(int)count)) - old=nonce._nc.get(); - if (count<=old) + if (nonce.seen((int)count)) return -1; - return 1; } catch (Exception e) @@ -378,6 +401,7 @@ public class DigestAuthenticator extends LoginAuthenticator return false; } + @Override public String toString() { return username + "," + response; 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 6ed2800f6e9..474b5eafd67 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 @@ -22,17 +22,21 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.security.MessageDigest; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.security.authentication.DigestAuthenticator; import org.eclipse.jetty.security.authentication.FormAuthenticator; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.LocalConnector; @@ -44,7 +48,10 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.security.Password; import org.junit.After; import org.junit.Before; @@ -222,7 +229,6 @@ public class ConstraintTest "\r\n"); assertTrue(response.startsWith("HTTP/1.1 200 OK")); - // test admin response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n"); assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); @@ -250,6 +256,127 @@ public class ConstraintTest assertTrue(response.startsWith("HTTP/1.1 200 OK")); } + + private static String CNONCE="1234567890"; + private String digest(String nonce, String username,String password,String uri,String nc) throws Exception + { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] ha1; + // calc A1 digest + md.update(username.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update("TestRealm".getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(password.getBytes(StringUtil.__ISO_8859_1)); + ha1 = md.digest(); + // calc A2 digest + md.reset(); + md.update("GET".getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(uri.getBytes(StringUtil.__ISO_8859_1)); + byte[] ha2 = md.digest(); + + // calc digest + // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" + // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) ) + // <"> + // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) + // ) > <"> + + md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(nonce.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(nc.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(CNONCE.getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update("auth".getBytes(StringUtil.__ISO_8859_1)); + md.update((byte) ':'); + md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1)); + byte[] digest = md.digest(); + + // check digest + return TypeUtil.toString(digest, 16); + } + + @Test + public void testDigest() throws Exception + { + _security.setAuthenticator(new DigestAuthenticator()); + _security.setStrict(false); + _server.start(); + + String response; + response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 403 Forbidden")); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + assertTrue(response.contains("WWW-Authenticate: Digest realm=\"TestRealm\"")); + + Pattern nonceP = Pattern.compile("nonce=\"([^\"]*)\","); + Matcher matcher = nonceP.matcher(response); + assertTrue(matcher.find()); + String nonce=matcher.group(1); + + + //wrong password + String digest= digest(nonce,"user","WRONG","/ctx/auth/info","1"); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+ + "nc=1, "+ + "nonce=\""+nonce+"\", "+ + "response=\""+digest+"\"\r\n"+ + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + + // right password + digest= digest(nonce,"user","password","/ctx/auth/info","2"); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+ + "nc=2, "+ + "nonce=\""+nonce+"\", "+ + "response=\""+digest+"\"\r\n"+ + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + + // once only + digest= digest(nonce,"user","password","/ctx/auth/info","2"); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+ + "nc=2, "+ + "nonce=\""+nonce+"\", "+ + "response=\""+digest+"\"\r\n"+ + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 401 Unauthorized")); + + // increasing + digest= digest(nonce,"user","password","/ctx/auth/info","4"); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+ + "nc=4, "+ + "nonce=\""+nonce+"\", "+ + "response=\""+digest+"\"\r\n"+ + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + // out of order + digest= digest(nonce,"user","password","/ctx/auth/info","3"); + response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" + + "Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+ + "nc=3, "+ + "nonce=\""+nonce+"\", "+ + "response=\""+digest+"\"\r\n"+ + "\r\n"); + assertTrue(response.startsWith("HTTP/1.1 200 OK")); + + } + + @Test public void testFormDispatch() throws Exception { From dbedbfa67e2ccfb41cb41de6f6bb65961a5ea315 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 25 Apr 2013 10:44:33 +1000 Subject: [PATCH 04/28] 406437 Digest Auth supports out of order nc --- .../security/authentication/DigestAuthenticator.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 5abd9113b5f..0fe374774ba 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 @@ -26,7 +26,6 @@ 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; @@ -54,7 +53,8 @@ import org.eclipse.jetty.util.security.Credential; * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ * * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)} - * using the name "maxNonceAge" + * using the name "maxNonceAge". The nonce max count can be set with {@link SecurityHandler#setInitParameter(String, String)} + * using the name "maxNonceCount". When the age or count is exceeded, the nonce is considered stale. */ public class DigestAuthenticator extends LoginAuthenticator { @@ -110,6 +110,11 @@ public class DigestAuthenticator extends LoginAuthenticator { _maxNonceAgeMs=Long.valueOf(mna); } + String mnc=configuration.getInitParameter("maxNonceCount"); + if (mnc!=null) + { + _maxNC=Integer.valueOf(mnc); + } } /* ------------------------------------------------------------ */ From 9558d7e815ac9815bdfbb8820177384a8d5e16eb Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 26 Apr 2013 13:53:28 +1000 Subject: [PATCH 05/28] modular start.ini files and --download start option --- jetty-distribution/pom.xml | 6 +- .../resources/start.available/000-jvm.ini | 21 ++ .../resources/start.available/110-logging.ini | 7 + .../resources/start.available/110-setuid.ini | 13 ++ .../resources/start.available/200-npn.ini | 12 ++ .../resources/start.available/200-ssl.ini | 12 ++ .../resources/start.available/210-https.ini | 6 + .../resources/start.available/210-spdy.ini | 9 + .../resources/start.available/300-plus.ini | 8 + .../start.available/300-requestlog.ini | 9 + .../resources/start.available/400-debug.ini | 1 + .../start.available/400-ipaccess.ini | 1 + .../start.available/400-lowresources.ini | 11 + .../resources/start.available/400-stats.ini | 4 + .../src/main/resources/start.d/100-jetty.ini | 16 ++ .../src/main/resources/start.d/110-jmx.ini | 10 + .../src/main/resources/start.d/110-jsp.ini | 5 + .../src/main/resources/start.d/210-http.ini | 6 + .../src/main/resources/start.d/500-deploy.ini | 4 + .../src/main/resources/start.ini | 200 +----------------- .../main/resources/webapps.demo/README.TXT | 7 + .../ROOT/images/jetty-header.jpg | Bin .../ROOT/images/webtide_logo.jpg | Bin .../{webapps => webapps.demo}/ROOT/index.html | 0 .../{webapps => webapps.demo}/ROOT/jetty.css | 0 .../example-moved.xml | 0 .../{webapps => webapps.demo}/javadoc.xml | 0 .../src/main/resources/webapps/.donotdelete | 0 .../src/main/config/etc/jetty-http.xml | 2 +- .../main/config/etc/jetty-lowresources.xml | 12 +- .../src/main/config/etc/jetty-requestlog.xml | 6 +- jetty-server/src/main/config/etc/jetty.xml | 6 +- .../java/org/eclipse/jetty/start/Main.java | 123 +++++++++++ .../org/eclipse/jetty/start/usage.txt | 4 + .../{webapps => webapps.demo}/test-jaas.xml | 0 .../src/main/config/etc/jetty-demo.xml | 21 ++ .../start.d/{test-webapp.ini => 900-demo.ini} | 1 + .../test.d/override-web.xml | 0 .../config/{webapps => webapps.demo}/test.xml | 6 +- 39 files changed, 335 insertions(+), 214 deletions(-) create mode 100644 jetty-distribution/src/main/resources/start.available/000-jvm.ini create mode 100644 jetty-distribution/src/main/resources/start.available/110-logging.ini create mode 100644 jetty-distribution/src/main/resources/start.available/110-setuid.ini create mode 100644 jetty-distribution/src/main/resources/start.available/200-npn.ini create mode 100644 jetty-distribution/src/main/resources/start.available/200-ssl.ini create mode 100644 jetty-distribution/src/main/resources/start.available/210-https.ini create mode 100644 jetty-distribution/src/main/resources/start.available/210-spdy.ini create mode 100644 jetty-distribution/src/main/resources/start.available/300-plus.ini create mode 100644 jetty-distribution/src/main/resources/start.available/300-requestlog.ini create mode 100644 jetty-distribution/src/main/resources/start.available/400-debug.ini create mode 100644 jetty-distribution/src/main/resources/start.available/400-ipaccess.ini create mode 100644 jetty-distribution/src/main/resources/start.available/400-lowresources.ini create mode 100644 jetty-distribution/src/main/resources/start.available/400-stats.ini create mode 100644 jetty-distribution/src/main/resources/start.d/100-jetty.ini create mode 100644 jetty-distribution/src/main/resources/start.d/110-jmx.ini create mode 100644 jetty-distribution/src/main/resources/start.d/110-jsp.ini create mode 100644 jetty-distribution/src/main/resources/start.d/210-http.ini create mode 100644 jetty-distribution/src/main/resources/start.d/500-deploy.ini create mode 100644 jetty-distribution/src/main/resources/webapps.demo/README.TXT rename jetty-distribution/src/main/resources/{webapps => webapps.demo}/ROOT/images/jetty-header.jpg (100%) rename jetty-distribution/src/main/resources/{webapps => webapps.demo}/ROOT/images/webtide_logo.jpg (100%) rename jetty-distribution/src/main/resources/{webapps => webapps.demo}/ROOT/index.html (100%) rename jetty-distribution/src/main/resources/{webapps => webapps.demo}/ROOT/jetty.css (100%) rename jetty-distribution/src/main/resources/{webapps => webapps.demo}/example-moved.xml (100%) rename jetty-distribution/src/main/resources/{webapps => webapps.demo}/javadoc.xml (100%) delete mode 100644 jetty-distribution/src/main/resources/webapps/.donotdelete rename tests/test-webapps/test-jaas-webapp/src/main/config/{webapps => webapps.demo}/test-jaas.xml (100%) create mode 100644 tests/test-webapps/test-jetty-webapp/src/main/config/etc/jetty-demo.xml rename tests/test-webapps/test-jetty-webapp/src/main/config/start.d/{test-webapp.ini => 900-demo.ini} (83%) rename tests/test-webapps/test-jetty-webapp/src/main/config/{webapps => webapps.demo}/test.d/override-web.xml (100%) rename tests/test-webapps/test-jetty-webapp/src/main/config/{webapps => webapps.demo}/test.xml (92%) diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index 2189d3bb75c..2ed6b82ff93 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -100,7 +100,7 @@ war true ** - ${assembly-directory}/webapps + ${assembly-directory}/webapps.demo test.war @@ -110,7 +110,7 @@ war true ** - ${assembly-directory}/webapps + ${assembly-directory}/webapps.demo xref-proxy.war @@ -120,7 +120,7 @@ war true ** - ${assembly-directory}/webapps + ${assembly-directory}/webapps.demo async-rest.war diff --git a/jetty-distribution/src/main/resources/start.available/000-jvm.ini b/jetty-distribution/src/main/resources/start.available/000-jvm.ini new file mode 100644 index 00000000000..1f5e32946c2 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/000-jvm.ini @@ -0,0 +1,21 @@ +#=========================================================== +# Configure JVM arguments. +#----------------------------------------------------------- +--exec + +-Xmx2000m +-Xmn512m +-XX:+UseConcMarkSweepGC +-XX:ParallelCMSThreads=2 +-XX:+CMSClassUnloadingEnabled +-XX:+UseCMSCompactAtFullCollection +-XX:CMSInitiatingOccupancyFraction=80 +# -verbose:gc +# -XX:+PrintGCDateStamps +# -XX:+PrintGCTimeStamps +# -XX:+PrintGCDetails +# -XX:+PrintTenuringDistribution +# -XX:+PrintCommandLineFlags +# -XX:+DisableExplicitGC + +# -Dorg.apache.jasper.compiler.disablejsr199=true diff --git a/jetty-distribution/src/main/resources/start.available/110-logging.ini b/jetty-distribution/src/main/resources/start.available/110-logging.ini new file mode 100644 index 00000000000..c8dfa64da1a --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/110-logging.ini @@ -0,0 +1,7 @@ +#=========================================================== +# Server logging. +# The following configuration will redirect stderr and stdout +# to file which is rolled over daily. +#----------------------------------------------------------- +jetty.log.retain=90 +etc/jetty-logging.xml diff --git a/jetty-distribution/src/main/resources/start.available/110-setuid.ini b/jetty-distribution/src/main/resources/start.available/110-setuid.ini new file mode 100644 index 00000000000..97b1682ced3 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/110-setuid.ini @@ -0,0 +1,13 @@ +#=========================================================== +# Enable SetUID +# The default user and group is 'jetty' and if you are +# starting as root you must change the run privledged to true +#----------------------------------------------------------- +OPTIONS=setuid +jetty.startServerAsPrivileged=false +jetty.username=jetty +jetty.groupname=jetty +jetty.umask=002 + +etc/jetty-setuid.xml + diff --git a/jetty-distribution/src/main/resources/start.available/200-npn.ini b/jetty-distribution/src/main/resources/start.available/200-npn.ini new file mode 100644 index 00000000000..0ca98db0fd6 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/200-npn.ini @@ -0,0 +1,12 @@ +#=========================================================== +# NPN Next Protocol Negotiation +# +# The SPDY and HTTP/2.0 connectors require NPN. The jar for +# NPN cannot be downloaded from eclipse. So the --download +# option is used to install the NPN jar if it does not already +# exist +# +#----------------------------------------------------------- +--exec +--download=http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar:lib/npn/npn-boot-1.1.5.v20130313.jar +-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar diff --git a/jetty-distribution/src/main/resources/start.available/200-ssl.ini b/jetty-distribution/src/main/resources/start.available/200-ssl.ini new file mode 100644 index 00000000000..6a56dd54e65 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/200-ssl.ini @@ -0,0 +1,12 @@ +#=========================================================== +# SSL Context +# For use by HTTPS and SPDY +#----------------------------------------------------------- +jetty.keystore=etc/keystore +jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 +jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g +jetty.truststore=etc/keystore +jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 +jetty.secure.port=8443 + +etc/jetty-ssl.xml diff --git a/jetty-distribution/src/main/resources/start.available/210-https.ini b/jetty-distribution/src/main/resources/start.available/210-https.ini new file mode 100644 index 00000000000..42897c42da4 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/210-https.ini @@ -0,0 +1,6 @@ +#=========================================================== +# HTTPS Connector +# Must be used with 200-ssl.ini +#----------------------------------------------------------- +jetty.https.port=8443 +etc/jetty-https.xml diff --git a/jetty-distribution/src/main/resources/start.available/210-spdy.ini b/jetty-distribution/src/main/resources/start.available/210-spdy.ini new file mode 100644 index 00000000000..0d0eac46911 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/210-spdy.ini @@ -0,0 +1,9 @@ +#=========================================================== +# SPDY Connector +# Must be used with 200-ssl.ini and 200-npn.ini +#----------------------------------------------------------- +OPTIONS=spdy +jetty.spdy.port=8443 + +etc/jetty-spdy.xml + diff --git a/jetty-distribution/src/main/resources/start.available/300-plus.ini b/jetty-distribution/src/main/resources/start.available/300-plus.ini new file mode 100644 index 00000000000..021c48a7847 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/300-plus.ini @@ -0,0 +1,8 @@ +#=========================================================== +# Annotations JNDI JAAS processing +#----------------------------------------------------------- +OPTIONS=plus +etc/jetty-plus.xml +OPTIONS=annotations +etc/jetty-annotations.xml + diff --git a/jetty-distribution/src/main/resources/start.available/300-requestlog.ini b/jetty-distribution/src/main/resources/start.available/300-requestlog.ini new file mode 100644 index 00000000000..195d2b94687 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/300-requestlog.ini @@ -0,0 +1,9 @@ +#=========================================================== +# Request logger +# Will add a handler to log all HTTP requests to a standard +# request log format file. +#----------------------------------------------------------- +requestlog.retain=90 +requestlog.append=true +requestlog.extended=true +etc/jetty-requestlog.xml diff --git a/jetty-distribution/src/main/resources/start.available/400-debug.ini b/jetty-distribution/src/main/resources/start.available/400-debug.ini new file mode 100644 index 00000000000..d2f69bb8101 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/400-debug.ini @@ -0,0 +1 @@ +etc/jetty-debug.xml diff --git a/jetty-distribution/src/main/resources/start.available/400-ipaccess.ini b/jetty-distribution/src/main/resources/start.available/400-ipaccess.ini new file mode 100644 index 00000000000..d272540be4f --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/400-ipaccess.ini @@ -0,0 +1 @@ +etc/jetty-ipaccess.xml diff --git a/jetty-distribution/src/main/resources/start.available/400-lowresources.ini b/jetty-distribution/src/main/resources/start.available/400-lowresources.ini new file mode 100644 index 00000000000..c6847804e8a --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/400-lowresources.ini @@ -0,0 +1,11 @@ +#=========================================================== +#----------------------------------------------------------- + +lowresources.period=1050 +lowresources.lowResourcesIdleTimeout=200 +lowresources.monitorThreads=true +lowresources.maxConnections=0 +lowresources.maxMemory=0 +lowresources.maxLowResourcesTime=5000 +etc/jetty-lowresources.xml + diff --git a/jetty-distribution/src/main/resources/start.available/400-stats.ini b/jetty-distribution/src/main/resources/start.available/400-stats.ini new file mode 100644 index 00000000000..1f09e7d70a3 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.available/400-stats.ini @@ -0,0 +1,4 @@ +#=========================================================== +#----------------------------------------------------------- +etc/jetty-stats.xml + diff --git a/jetty-distribution/src/main/resources/start.d/100-jetty.ini b/jetty-distribution/src/main/resources/start.d/100-jetty.ini new file mode 100644 index 00000000000..cbd5f75a20f --- /dev/null +++ b/jetty-distribution/src/main/resources/start.d/100-jetty.ini @@ -0,0 +1,16 @@ +#=========================================================== +# Default Server Options +# Use the core server jars with websocket on the classpath +# Add the contents of the resources directory to the classpath +# Add jars discovered in lib/ext to the classpath +# Include the core jetty configuration file +#----------------------------------------------------------- +OPTIONS=Server,websocket,resources,ext +threads.min=10 +threads.max=200 +threads.timeout=60000 +#jetty.host=myhost.com +jetty.dump.start=false +jetty.dump.stop=false + +etc/jetty.xml diff --git a/jetty-distribution/src/main/resources/start.d/110-jmx.ini b/jetty-distribution/src/main/resources/start.d/110-jmx.ini new file mode 100644 index 00000000000..ce176332b2d --- /dev/null +++ b/jetty-distribution/src/main/resources/start.d/110-jmx.ini @@ -0,0 +1,10 @@ +#=========================================================== +# JMX Management +# To enable remote JMX access uncomment jmxremote and +# enable --exec +#----------------------------------------------------------- +OPTIONS=jmx +# jetty.jmxrmihost=localhost +# jetty.jmxrmiport=1099 +# -Dcom.sun.management.jmxremote +etc/jetty-jmx.xml diff --git a/jetty-distribution/src/main/resources/start.d/110-jsp.ini b/jetty-distribution/src/main/resources/start.d/110-jsp.ini new file mode 100644 index 00000000000..525fb6214b8 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.d/110-jsp.ini @@ -0,0 +1,5 @@ +#=========================================================== +# Java Server Pages +#----------------------------------------------------------- +OPTIONS=jsp + diff --git a/jetty-distribution/src/main/resources/start.d/210-http.ini b/jetty-distribution/src/main/resources/start.d/210-http.ini new file mode 100644 index 00000000000..ee8133306c3 --- /dev/null +++ b/jetty-distribution/src/main/resources/start.d/210-http.ini @@ -0,0 +1,6 @@ +#=========================================================== +# HTTP Connector +#----------------------------------------------------------- +jetty.port=8080 +http.timeout=30000 +etc/jetty-http.xml diff --git a/jetty-distribution/src/main/resources/start.d/500-deploy.ini b/jetty-distribution/src/main/resources/start.d/500-deploy.ini new file mode 100644 index 00000000000..11e7f03a14b --- /dev/null +++ b/jetty-distribution/src/main/resources/start.d/500-deploy.ini @@ -0,0 +1,4 @@ +#=========================================================== +# Webapplication Deployer +#----------------------------------------------------------- +etc/jetty-deploy.xml diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini index 382010d19b3..bc18db8dcca 100644 --- a/jetty-distribution/src/main/resources/start.ini +++ b/jetty-distribution/src/main/resources/start.ini @@ -1,5 +1,6 @@ #=========================================================== # Jetty start.jar arguments +# # The contents of this file, together with the start.ini # fragments found in start.d directory are used to build # the classpath and command line on a call to @@ -23,199 +24,8 @@ # #=========================================================== -#=========================================================== -# The --exec option should be used if any of the JVM options -# in this file are uncommented (eg -D* or -X*). Because a -# JVM cannot change it's own options, the --exec flag causes -# start.jar to fork a new JVM with the requested arguments. -# -# Alternately, a command line may be generated by running -# -# java -jar start.jar --exec-print -# -# and the results executed to start the jetty server. -# For example --exec can be avoided if jetty is started on unix with -# -# eval $(java -jar start.jar --exec-print) -# -#----------------------------------------------------------- -# --exec -#=========================================================== - -#=========================================================== -# Configure Properties. -# The properties defined here may be used by the -# element in the XML files -# passed to start.jar. -# Alternately a file ending with ".properties" can be -# added that will include multiple properties. -# Properties, unlike SystemProperties, do not need --exec -# to be specified. -#----------------------------------------------------------- -# jetty.home=. -# jetty.logs=./logs -# jetty.host=0.0.0.0 -#=========================================================== - - -#=========================================================== -# Configure JVM arguments. -# Must be used with --exec or --exec-print -#----------------------------------------------------------- -# -Dorg.apache.jasper.compiler.disablejsr199=true -# -Xmx2000m -# -Xmn512m -# -verbose:gc -# -XX:+PrintGCDateStamps -# -XX:+PrintGCTimeStamps -# -XX:+PrintGCDetails -# -XX:+PrintTenuringDistribution -# -XX:+PrintCommandLineFlags -# -XX:+DisableExplicitGC -# -XX:+UseConcMarkSweepGC -# -XX:ParallelCMSThreads=2 -# -XX:+CMSClassUnloadingEnabled -# -XX:+UseCMSCompactAtFullCollection -# -XX:CMSInitiatingOccupancyFraction=80 -#=========================================================== - -#=========================================================== -# Default Server Options -# Use the core server jars with websocket on the classpath -# Add the contents of the resources directory to the classpath -# Add jars discovered in lib/ext to the classpath -# Include the core jetty configuration file -#----------------------------------------------------------- -OPTIONS=Server,websocket,resources,ext -etc/jetty.xml -#=========================================================== - -#=========================================================== -# Enable SetUID -# The default user and group is 'jetty' and if you are -# starting as root you must change the run privledged to true -#----------------------------------------------------------- -# OPTIONS=setuid -# etc/jetty-setuid.xml -# jetty.startServerAsPrivileged=false -# jetty.username=jetty -# jetty.groupname=jetty -# jetty.umask=002 -#=========================================================== - -#=========================================================== -# Server logging. -# The following configuration will redirect stderr and stdout -# to file which is rolled over daily. -#----------------------------------------------------------- -# etc/jetty-logging.xml -#=========================================================== - -#=========================================================== -# JMX Management -# To enable remote JMX access uncomment jmxremote and -# enable --exec or use --exec-print (see above) -#----------------------------------------------------------- -OPTIONS=jmx -# jetty.jmxrmihost=localhost -# jetty.jmxrmiport=1099 -# -Dcom.sun.management.jmxremote -etc/jetty-jmx.xml -#=========================================================== - -#=========================================================== -# Java Server Pages -#----------------------------------------------------------- -OPTIONS=jsp -#=========================================================== - - -#=========================================================== -# Annotations JNDI JAAS processing -#----------------------------------------------------------- -# OPTIONS=plus -# etc/jetty-plus.xml -# OPTIONS=annotations -# etc/jetty-annotations.xml -#=========================================================== - -#=========================================================== -# HTTP Connector -#----------------------------------------------------------- -# jetty.port=8080 -etc/jetty-http.xml -#=========================================================== - -#=========================================================== -# SSL Context -# For use by HTTPS and SPDY -#----------------------------------------------------------- -# jetty.keystore=etc/keystore -# jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 -# jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g -# jetty.truststore=etc/keystore -# jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 -# jetty.secure.port=8443 -# etc/jetty-ssl.xml -#=========================================================== - -#=========================================================== -# HTTPS Connector -#----------------------------------------------------------- -# jetty.https.port=8443 -# etc/jetty-https.xml -#=========================================================== - -#=========================================================== -# SPDY Connector -# -# SPDY requires the NPN jar which must be separately downloaded: -# -# http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar -# -# Which should be saved in lib/npn-boot-1.1.5.v20130313.jar -# -# To include the NPN jar on the boot path, you must either: -# -# a) enable --exec above and uncomment the -Xbootclass line -# below -# -# b) Add -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar -# to the command line when running jetty. -# -#----------------------------------------------------------- -# OPTIONS=spdy -# -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar -# jetty.spdy.port=8443 -# etc/jetty-spdy.xml -#=========================================================== - -#=========================================================== -# Webapplication Deployer -#----------------------------------------------------------- -etc/jetty-deploy.xml -#=========================================================== - -#=========================================================== -# Request logger -# Will add a handler to log all HTTP requests to a standard -# request log format file. -#----------------------------------------------------------- -etc/jetty-requestlog.xml -#=========================================================== - -#=========================================================== -# Additional configurations -# See headers of individual files for explanations -#----------------------------------------------------------- -# etc/jetty-stats.xml -# etc/jetty-debug.xml -# etc/jetty-ipaccess.xml -# etc/jetty-lowresources.xml -#=========================================================== - -#=========================================================== -# Lookup additional ini files in start.d -#----------------------------------------------------------- +# The start.d directory contains the active start.ini fragments start.d/ -#=========================================================== + +# More start.ini fragments are in start.available and may be +# enabled by copying or linking to start.d diff --git a/jetty-distribution/src/main/resources/webapps.demo/README.TXT b/jetty-distribution/src/main/resources/webapps.demo/README.TXT new file mode 100644 index 00000000000..ec2bea255cb --- /dev/null +++ b/jetty-distribution/src/main/resources/webapps.demo/README.TXT @@ -0,0 +1,7 @@ + +This directory is scanned by the demo WebAppDeployer provider +created in the etc/jetty-demo.xml file and enabled by the +start.d/900-demo.ini file. + +For normal deployment, use the webapps directory. + diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/images/jetty-header.jpg b/jetty-distribution/src/main/resources/webapps.demo/ROOT/images/jetty-header.jpg similarity index 100% rename from jetty-distribution/src/main/resources/webapps/ROOT/images/jetty-header.jpg rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/images/jetty-header.jpg diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/images/webtide_logo.jpg b/jetty-distribution/src/main/resources/webapps.demo/ROOT/images/webtide_logo.jpg similarity index 100% rename from jetty-distribution/src/main/resources/webapps/ROOT/images/webtide_logo.jpg rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/images/webtide_logo.jpg diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/index.html b/jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html similarity index 100% rename from jetty-distribution/src/main/resources/webapps/ROOT/index.html rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/jetty.css b/jetty-distribution/src/main/resources/webapps.demo/ROOT/jetty.css similarity index 100% rename from jetty-distribution/src/main/resources/webapps/ROOT/jetty.css rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/jetty.css diff --git a/jetty-distribution/src/main/resources/webapps/example-moved.xml b/jetty-distribution/src/main/resources/webapps.demo/example-moved.xml similarity index 100% rename from jetty-distribution/src/main/resources/webapps/example-moved.xml rename to jetty-distribution/src/main/resources/webapps.demo/example-moved.xml diff --git a/jetty-distribution/src/main/resources/webapps/javadoc.xml b/jetty-distribution/src/main/resources/webapps.demo/javadoc.xml similarity index 100% rename from jetty-distribution/src/main/resources/webapps/javadoc.xml rename to jetty-distribution/src/main/resources/webapps.demo/javadoc.xml diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-distribution/src/main/resources/webapps/.donotdelete deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/jetty-server/src/main/config/etc/jetty-http.xml b/jetty-server/src/main/config/etc/jetty-http.xml index 731304e7a28..7ae3064a098 100644 --- a/jetty-server/src/main/config/etc/jetty-http.xml +++ b/jetty-server/src/main/config/etc/jetty-http.xml @@ -33,7 +33,7 @@ - 30000 + diff --git a/jetty-server/src/main/config/etc/jetty-lowresources.xml b/jetty-server/src/main/config/etc/jetty-lowresources.xml index 5b264f1749f..060919a8d3c 100644 --- a/jetty-server/src/main/config/etc/jetty-lowresources.xml +++ b/jetty-server/src/main/config/etc/jetty-lowresources.xml @@ -10,12 +10,12 @@ - 1000 - 200 - true - 0 - 0 - 5000 + + + + + + diff --git a/jetty-server/src/main/config/etc/jetty-requestlog.xml b/jetty-server/src/main/config/etc/jetty-requestlog.xml index 6e6eb054f0c..213177731a2 100644 --- a/jetty-server/src/main/config/etc/jetty-requestlog.xml +++ b/jetty-server/src/main/config/etc/jetty-requestlog.xml @@ -17,9 +17,9 @@ /yyyy_mm_dd.request.log yyyy_MM_dd - 90 - true - true + + + false GMT diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml index 42b80a31140..a551326cc6a 100644 --- a/jetty-server/src/main/config/etc/jetty.xml +++ b/jetty-server/src/main/config/etc/jetty.xml @@ -44,9 +44,9 @@ - 10 - 200 - 60000 + + + false diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java index 0be66d81cf9..f3931206d34 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -50,6 +50,8 @@ import java.util.Locale; import java.util.Properties; import java.util.Set; +import javax.naming.OperationNotSupportedException; + /*-------------------------------------------*/ /** *

@@ -175,6 +177,12 @@ public class Main stop(port,key,timeout); return null; } + + if (arg.startsWith("--download=")) + { + download(arg); + continue; + } if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg)) { @@ -302,6 +310,121 @@ public class Main return xmls; } + private void download(String arg) + { + try + { + String[] split = arg.split(":",3); + if (split.length!=3 || "http".equalsIgnoreCase(split[0]) || !split[1].startsWith("//")) + throw new IllegalArgumentException("Not --download=:"); + + String location=split[2]; + if (File.separatorChar!='/') + location.replaceAll("/",File.separator); + File file = new File(location); + + if (Config.isDebug()) + System.err.println("Download to "+file.getAbsolutePath()+(file.exists()?" Exists!":"")); + if (file.exists()) + return; + + String[] parse = split[1].split("/",4); + String host=parse[2]; + String uri="/"+split[1].substring(3+host.length()); + + try (Socket socket = new Socket(host,80)) + { + String request="GET "+uri+" HTTP/1.0\r\n"+ + "Host: "+host+"\r\n"+ + "User-Agent: jetty-start.jar\r\n"+ + "\r\n"; + + socket.getOutputStream().write(request.getBytes("ISO-8859-1")); + socket.getOutputStream().flush(); + + InputStream in = socket.getInputStream(); + + int state=0; + loop: while (state>=0) + { + char c = (char)in.read(); + + switch (state) + { + case 0: + c=Character.toLowerCase(c); + if (c==' ') + state=1; + else if (c!='h' && c!='t' && c!='p' && c!='/' && c!='.' && !Character.isDigit(c)) + break loop; + break; + case 1: + if (c==' ') + state=2; + else if (c!='2' && c!='0') + break loop; + break; + case 2: + if (c=='\r') + state=3; + else if (c=='\n') + state=4; + break; + case 3: + if (c=='\n') + state=4; + else if (c=='\r') + state=-1; + break; + + case 4: + if (c=='\r') + state=5; + else if (c=='\n') + state=-1; + else + state=2; + break; + + case 5: + if (c=='\n') + state=-1; + else + state=2; + break; + } + } + + if (state>=0) + throw new IOException("Bad HTTP response: "+state); + + System.err.println("DOWNLOAD: "+uri+" from "+host+" to "+location); + if (!file.getParentFile().exists()) + file.getParentFile().mkdirs(); + + byte[] buf=new byte[8192]; + try (OutputStream out = new FileOutputStream(file)) + { + while(!socket.isInputShutdown()) + { + int len = in.read(buf); + + if (len>0) + out.write(buf,0,len); + if (len<0) + break; + } + } + } + } + catch(Exception e) + { + System.err.println("ERROR: processing "+arg+"\n"+e); + e.printStackTrace(); + usageExit(EXIT_USAGE); + } + } + private void usage() { String usageResource = "org/eclipse/jetty/start/usage.txt"; diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt index 7272c5f674c..e25a73d277c 100644 --- a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt +++ b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt @@ -45,6 +45,10 @@ Command Line Options: and additional .ini files in jetty.home/start.d/ will NOT be read. A --ini option with no file indicates that start.ini should not be read. + + --download=:location + If the file does not exist at the given location, then + download it from the given http URI System Properties: These are set with a command line like "java -Dname=value ..." and are diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml similarity index 100% rename from tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml rename to tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/etc/jetty-demo.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/etc/jetty-demo.xml new file mode 100644 index 00000000000..938e7cf213d --- /dev/null +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/etc/jetty-demo.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + /webapps.demo + /etc/webdefault.xml + 1 + true + + + + + + + diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/test-webapp.ini b/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/900-demo.ini similarity index 83% rename from tests/test-webapps/test-jetty-webapp/src/main/config/start.d/test-webapp.ini rename to tests/test-webapps/test-jetty-webapp/src/main/config/start.d/900-demo.ini index 2c4006b6bc7..95e58b53ae5 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/test-webapp.ini +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/900-demo.ini @@ -1,3 +1,4 @@ # Enabled services and configuration needed by the test webapp OPTIONS=client etc/test-realm.xml +etc/jetty-demo.xml diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.d/override-web.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.d/override-web.xml similarity index 100% rename from tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.d/override-web.xml rename to tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.d/override-web.xml diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml similarity index 92% rename from tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml rename to tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml index ff7eb6b8056..44cbce55c37 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml +++ b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml @@ -19,15 +19,15 @@ detected. /test - /webapps/test.war + /test.war true false - /etc/webdefault.xml - /webapps/test.d/override-web.xml + /etc/webdefault.xml + /test.d/override-web.xml From 04bcde9b14b12b02b4f07012d5ec21fddea1b66c Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 26 Apr 2013 16:10:33 +1000 Subject: [PATCH 06/28] 406617 fixed close handling during content --- .../main/java/org/eclipse/jetty/http/HttpParser.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 239b44f0dfb..17d9c01bc44 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 @@ -1340,8 +1340,16 @@ public class HttpParser case CLOSED: case END: break; + + case EOF_CONTENT: + _handler.messageComplete(); + break; + default: - LOG.warn("Closing {}",this); + if (_state.ordinal()>State.END.ordinal()) + _handler.earlyEOF(); + else + LOG.warn("Closing {}",this); } setState(State.CLOSED); _endOfContent=EndOfContent.UNKNOWN_CONTENT; From 28d4f41572fec132e960f40aa5efacbbfc0886cd Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Fri, 26 Apr 2013 16:30:31 +1000 Subject: [PATCH 07/28] 405631 Plugin gives error when its started twice --- .../jetty/maven/plugin/AbstractJettyMojo.java | 9 +- .../jetty/maven/plugin/JettyServer.java | 12 +- .../maven/plugin/MavenServerConnector.java | 244 +++++++++++++++++- .../eclipse/jetty/maven/plugin/Starter.java | 5 +- 4 files changed, 250 insertions(+), 20 deletions(-) diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java index 9429512905a..558899c1819 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java @@ -284,7 +284,7 @@ public abstract class AbstractJettyMojo extends AbstractMojo /** * A wrapper for the Server object */ - protected JettyServer server = JettyServer.getInstance(); + protected JettyServer server = new JettyServer(); /** @@ -494,6 +494,8 @@ public abstract class AbstractJettyMojo extends AbstractMojo String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR); httpConnector.setPort(Integer.parseInt(tmp.trim())); } + if (httpConnector.getServer() == null) + httpConnector.setServer(this.server); this.server.addConnector(httpConnector); } @@ -504,12 +506,13 @@ public abstract class AbstractJettyMojo extends AbstractMojo //if not configured in the pom, create one if (httpConnector == null) { - httpConnector = new MavenServerConnector(); + httpConnector = new MavenServerConnector(); //use any jetty.port settings provided String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR); httpConnector.setPort(Integer.parseInt(tmp.trim())); } - + if (httpConnector.getServer() == null) + httpConnector.setServer(this.server); this.server.setConnectors(new Connector[] {httpConnector}); } diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java index 5be4aafff5e..8018052680a 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java @@ -38,16 +38,6 @@ public class JettyServer extends org.eclipse.jetty.server.Server { public static final JettyServer __instance = new JettyServer(); - /** - * Singleton instance - * @return - */ - public static JettyServer getInstance() - { - return __instance; - } - - private RequestLog requestLog; private ContextHandlerCollection contexts; @@ -56,7 +46,7 @@ public class JettyServer extends org.eclipse.jetty.server.Server /** * */ - private JettyServer() + public JettyServer() { super(); setStopAtShutdown(true); diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java index f7c20c5878c..54aedb1303e 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenServerConnector.java @@ -19,21 +19,259 @@ package org.eclipse.jetty.maven.plugin; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; + +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.thread.Scheduler; + + + /** * MavenServerConnector * - * + * As the ServerConnector class does not have a no-arg constructor, and moreover requires + * the server instance passed in to all its constructors, it cannot + * be referenced in the pom.xml. This class wraps a ServerConnector, delaying setting the + * server instance. Only a few of the setters from the ServerConnector class are supported. */ -public class MavenServerConnector extends ServerConnector +public class MavenServerConnector extends AbstractLifeCycle implements Connector { public static int DEFAULT_PORT = 8080; public static String DEFAULT_PORT_STR = String.valueOf(DEFAULT_PORT); public static int DEFAULT_MAX_IDLE_TIME = 30000; + private Server server; + private ServerConnector delegate; + private String host; + private String name; + private int port; + private long idleTimeout; + private int lingerTime; + + public MavenServerConnector() { - super(JettyServer.getInstance()); + } + + public void setServer(Server server) + { + this.server = server; + } + + public void setHost(String host) + { + this.host = host; + } + + public String getHost() + { + return this.host; + } + + public void setPort(int port) + { + this.port = port; + } + + public int getPort () + { + return this.port; + } + + public void setName (String name) + { + this.name = name; + } + + public void setIdleTimeout(long idleTimeout) + { + this.idleTimeout = idleTimeout; + } + + public void setSoLingerTime(int lingerTime) + { + this.lingerTime = lingerTime; + } + + @Override + protected void doStart() throws Exception + { + + if (this.server == null) + throw new IllegalStateException("Server not set for MavenServerConnector"); + + this.delegate = new ServerConnector(this.server); + this.delegate.setName(this.name); + this.delegate.setPort(this.port); + this.delegate.setHost(this.host); + this.delegate.setIdleTimeout(idleTimeout); + this.delegate.setSoLingerTime(lingerTime); + this.delegate.start(); + + super.doStart(); + } + + @Override + protected void doStop() throws Exception + { + this.delegate.stop(); + super.doStop(); + this.delegate = null; + } + + /** + * @see org.eclipse.jetty.util.component.Graceful#shutdown() + */ + @Override + public Future shutdown() + { + checkDelegate(); + return this.delegate.shutdown(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getServer() + */ + @Override + public Server getServer() + { + return this.server; + } + + /** + * @see org.eclipse.jetty.server.Connector#getExecutor() + */ + @Override + public Executor getExecutor() + { + checkDelegate(); + return this.delegate.getExecutor(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getScheduler() + */ + @Override + public Scheduler getScheduler() + { + checkDelegate(); + return this.delegate.getScheduler(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getByteBufferPool() + */ + @Override + public ByteBufferPool getByteBufferPool() + { + checkDelegate(); + return this.delegate.getByteBufferPool(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.String) + */ + @Override + public ConnectionFactory getConnectionFactory(String nextProtocol) + { + checkDelegate(); + return this.delegate.getConnectionFactory(nextProtocol); + } + + /** + * @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.Class) + */ + @Override + public T getConnectionFactory(Class factoryType) + { + checkDelegate(); + return this.delegate.getConnectionFactory(factoryType); + } + + /** + * @see org.eclipse.jetty.server.Connector#getDefaultConnectionFactory() + */ + @Override + public ConnectionFactory getDefaultConnectionFactory() + { + checkDelegate(); + return getDefaultConnectionFactory(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getConnectionFactories() + */ + @Override + public Collection getConnectionFactories() + { + checkDelegate(); + return this.delegate.getConnectionFactories(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getProtocols() + */ + @Override + public List getProtocols() + { + checkDelegate(); + return this.delegate.getProtocols(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getIdleTimeout() + */ + @Override + @ManagedAttribute("maximum time a connection can be idle before being closed (in ms)") + public long getIdleTimeout() + { + checkDelegate(); + return this.delegate.getIdleTimeout(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getTransport() + */ + @Override + public Object getTransport() + { + checkDelegate(); + return this.delegate.getTransport(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getConnectedEndPoints() + */ + @Override + public Collection getConnectedEndPoints() + { + checkDelegate(); + return this.delegate.getConnectedEndPoints(); + } + + /** + * @see org.eclipse.jetty.server.Connector#getName() + */ + @Override + public String getName() + { + return this.name; + } + + private void checkDelegate() throws IllegalStateException + { + if (this.delegate == null) + throw new IllegalStateException ("MavenServerConnector delegate not ready"); } } diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java index 649b8477d5a..4d56d0ffd05 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java @@ -55,7 +55,7 @@ public class Starter private List jettyXmls; // list of jetty.xml config files to apply - Mandatory private File contextXml; //name of context xml file to configure the webapp - Mandatory - private JettyServer server; + private JettyServer server = new JettyServer(); private JettyWebAppContext webApp; @@ -120,8 +120,6 @@ public class Starter { LOG.debug("Starting Jetty Server ..."); - this.server = JettyServer.getInstance(); - //apply any configs from jetty.xml files first applyJettyXml (); @@ -132,6 +130,7 @@ public class Starter { //if a SystemProperty -Djetty.port= has been supplied, use that as the default port MavenServerConnector httpConnector = new MavenServerConnector(); + httpConnector.setServer(this.server); String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR); httpConnector.setPort(Integer.parseInt(tmp.trim())); connectors = new Connector[] {httpConnector}; From f62cec561bcbc4297faad16859fd252c4325a21c Mon Sep 17 00:00:00 2001 From: Thomas Becker Date: Fri, 26 Apr 2013 16:32:47 +0200 Subject: [PATCH 08/28] 406214 fix constructor for PushSynInfo ignores timeout, remove timeout for creating push streams in HttpTransportOverSPDY --- .../src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java | 2 +- .../eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java index 7f5030441e6..de411b476cb 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/PushSynInfo.java @@ -32,7 +32,7 @@ public class PushSynInfo extends SynInfo private int associatedStreamId; public PushSynInfo(int associatedStreamId, PushInfo pushInfo){ - super(pushInfo.getHeaders(), pushInfo.isClose()); + super(pushInfo.getTimeout(), pushInfo.getUnit(), pushInfo.getHeaders(), pushInfo.isClose(), (byte)0); this.associatedStreamId = associatedStreamId; } diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java index c98633d580e..11eecba5871 100644 --- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java +++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpTransportOverSPDY.java @@ -284,8 +284,8 @@ public class HttpTransportOverSPDY implements HttpTransport Fields pushHeaders = createPushHeaders(scheme, host, pushResource); final Fields pushRequestHeaders = createRequestHeaders(scheme, host, uri, pushResource); - // TODO: handle the timeout better - stream.push(new PushInfo(0, TimeUnit.MILLISECONDS, pushHeaders, false), new Promise.Adapter() + stream.push(new PushInfo(pushHeaders, false), + new Promise.Adapter() { @Override public void succeeded(Stream pushStream) From ef77b854faf3c530d855e3cbad651ca71439698a Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 26 Apr 2013 14:11:24 -0700 Subject: [PATCH 09/28] 404911 - WebSocketCloseTest fails spuriously + Renabling test + Using EventQueue from jetty-test-helper instead + Standarizing HttpResponseHeaderParser into websocket-common so that remaining bytebuffer logic is maintained in one place. + Making BlockingClient also use HttpResponseHeaderParser --- .../client/ClientUpgradeResponse.java | 4 +- .../client/io/UpgradeConnection.java | 7 +- .../http/HttpResponseHeaderParseListener.java | 32 +++++ .../io/http}/HttpResponseHeaderParser.java | 29 +++-- .../http}/HttpResponseHeaderParserTest.java | 88 +++++++++++--- .../io/http/HttpResponseParseCapture.java | 76 ++++++++++++ .../server/AnnotatedMaxMessageSizeTest.java | 2 +- .../jetty/websocket/server/ChromeTest.java | 7 +- .../server/FragmentExtensionTest.java | 7 +- .../server/FrameCompressionExtensionTest.java | 9 +- .../server/IdentityExtensionTest.java | 7 +- .../websocket/server/WebSocketCloseTest.java | 8 +- .../server/WebSocketInvalidVersionTest.java | 8 +- .../server/WebSocketServerSessionTest.java | 10 +- .../server/WebSocketServletRFCTest.java | 16 +-- .../jetty/websocket/server/ab/Fuzzer.java | 14 ++- .../server/blockhead/BlockheadClient.java | 114 ++++++------------ .../server/blockhead/HttpResponse.java | 95 +++++++++++++++ .../server/helper/IncomingFramesCapture.java | 17 +-- pom.xml | 2 +- 20 files changed, 387 insertions(+), 165 deletions(-) create mode 100644 jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java rename jetty-websocket/{websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io => websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http}/HttpResponseHeaderParser.java (79%) rename jetty-websocket/{websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io => websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http}/HttpResponseHeaderParserTest.java (53%) create mode 100644 jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java create mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java index 7c9e971cb93..2751fc84bf4 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeResponse.java @@ -22,8 +22,9 @@ import java.io.IOException; import java.nio.ByteBuffer; import org.eclipse.jetty.websocket.api.UpgradeResponse; +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener; -public class ClientUpgradeResponse extends UpgradeResponse +public class ClientUpgradeResponse extends UpgradeResponse implements HttpResponseHeaderParseListener { private ByteBuffer remainingBuffer; @@ -43,6 +44,7 @@ public class ClientUpgradeResponse extends UpgradeResponse throw new UnsupportedOperationException("Not supported on client implementation"); } + @Override public void setRemainingBuffer(ByteBuffer remainingBuffer) { this.remainingBuffer = remainingBuffer; diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java index b37a02a4a83..c24a17b6f9c 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java @@ -39,11 +39,12 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.ClientUpgradeResponse; -import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser.ParseException; import org.eclipse.jetty.websocket.common.AcceptHash; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser; +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser.ParseException; /** * This is the initial connection handling that exists immediately after physical connection is established to destination server. @@ -92,7 +93,7 @@ public class UpgradeConnection extends AbstractConnection this.request = connectPromise.getRequest(); // Setup the parser - this.parser = new HttpResponseHeaderParser(); + this.parser = new HttpResponseHeaderParser(new ClientUpgradeResponse()); } public void disconnect(boolean onlyOutput) @@ -173,7 +174,7 @@ public class UpgradeConnection extends AbstractConnection { LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer)); } - ClientUpgradeResponse resp = parser.parse(buffer); + ClientUpgradeResponse resp = (ClientUpgradeResponse)parser.parse(buffer); if (resp != null) { // Got a response! diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java new file mode 100644 index 00000000000..6cb2ae99dcb --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParseListener.java @@ -0,0 +1,32 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.websocket.common.io.http; + +import java.nio.ByteBuffer; + +public interface HttpResponseHeaderParseListener +{ + void addHeader(String name, String value); + + void setRemainingBuffer(ByteBuffer copy); + + void setStatusCode(int statusCode); + + void setStatusReason(String statusReason); +} \ No newline at end of file diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java similarity index 79% rename from jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java index 8b67dfdef44..b1b01c9879f 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.client.io; +package org.eclipse.jetty.websocket.common.io.http; import java.nio.ByteBuffer; import java.util.regex.Matcher; @@ -25,7 +25,6 @@ import java.util.regex.Pattern; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Utf8LineParser; -import org.eclipse.jetty.websocket.client.ClientUpgradeResponse; /** * Responsible for reading UTF8 Response Header lines and parsing them into a provided UpgradeResponse object. @@ -56,12 +55,13 @@ public class HttpResponseHeaderParser private static final Pattern PAT_HEADER = Pattern.compile("([^:]+):\\s*(.*)"); private static final Pattern PAT_STATUS_LINE = Pattern.compile("^HTTP/1.[01]\\s+(\\d+)\\s+(.*)",Pattern.CASE_INSENSITIVE); - private ClientUpgradeResponse response; - private Utf8LineParser lineParser; + private final HttpResponseHeaderParseListener listener; + private final Utf8LineParser lineParser; private State state; - public HttpResponseHeaderParser() + public HttpResponseHeaderParser(HttpResponseHeaderParseListener listener) { + this.listener = listener; this.lineParser = new Utf8LineParser(); this.state = State.STATUS_LINE; } @@ -71,7 +71,7 @@ public class HttpResponseHeaderParser return (state == State.END); } - public ClientUpgradeResponse parse(ByteBuffer buf) throws ParseException + public HttpResponseHeaderParseListener parse(ByteBuffer buf) throws ParseException { while (!isDone() && (buf.remaining() > 0)) { @@ -84,8 +84,8 @@ public class HttpResponseHeaderParser ByteBuffer copy = ByteBuffer.allocate(buf.remaining()); BufferUtil.put(buf,copy); BufferUtil.flipToFlush(copy,0); - this.response.setRemainingBuffer(copy); - return this.response; + this.listener.setRemainingBuffer(copy); + return listener; } } } @@ -98,22 +98,21 @@ public class HttpResponseHeaderParser { case STATUS_LINE: { - this.response = new ClientUpgradeResponse(); Matcher mat = PAT_STATUS_LINE.matcher(line); if (!mat.matches()) { - throw new ParseException("Unexpected HTTP upgrade response status line [" + line + "]"); + throw new ParseException("Unexpected HTTP response status line [" + line + "]"); } try { - response.setStatusCode(Integer.parseInt(mat.group(1))); + listener.setStatusCode(Integer.parseInt(mat.group(1))); } catch (NumberFormatException e) { - throw new ParseException("Unexpected HTTP upgrade response status code",e); + throw new ParseException("Unexpected HTTP response status code",e); } - response.setStatusReason(mat.group(2)); + listener.setStatusReason(mat.group(2)); state = State.HEADER; break; } @@ -130,8 +129,8 @@ public class HttpResponseHeaderParser { String headerName = header.group(1); String headerValue = header.group(2); - // TODO: need to split header/value if comma delimited - response.addHeader(headerName,headerValue); + // do need to split header/value if comma delimited? + listener.addHeader(headerName,headerValue); } break; } diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java similarity index 53% rename from jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java rename to jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java index d2f882216f3..837cf10bcbe 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java @@ -16,9 +16,10 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.client.internal.io; +package org.eclipse.jetty.websocket.common.io.http; import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -27,8 +28,6 @@ import java.util.List; import org.eclipse.jetty.toolchain.test.TestTracker; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.websocket.api.UpgradeResponse; -import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -43,6 +42,32 @@ public class HttpResponseHeaderParserTest buf.put(ByteBuffer.wrap(StringUtil.getBytes(line,StringUtil.__UTF8))); } + @Test + public void testParseNotFound() + { + StringBuilder resp = new StringBuilder(); + resp.append("HTTP/1.1 404 Not Found\r\n"); + resp.append("Date: Fri, 26 Apr 2013 21:43:08 GMT\r\n"); + resp.append("Content-Type: text/html; charset=ISO-8859-1\r\n"); + resp.append("Cache-Control: must-revalidate,no-cache,no-store\r\n"); + resp.append("Content-Length: 38\r\n"); + resp.append("Server: Jetty(9.0.0.v20130308)\r\n"); + resp.append("\r\n"); + // and some body content + resp.append("What you are looking for is not here\r\n"); + + ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StringUtil.__UTF8_CHARSET); + + HttpResponseParseCapture capture = new HttpResponseParseCapture(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture); + assertThat("Parser.parse",parser.parse(buf),notNullValue()); + assertThat("Response.statusCode",capture.getStatusCode(),is(404)); + assertThat("Response.statusReason",capture.getStatusReason(),is("Not Found")); + assertThat("Response.headers[Content-Length]",capture.getHeader("Content-Length"),is("38")); + + assertThat("Response.remainingBuffer",capture.getRemainingBuffer().remaining(),is(38)); + } + @Test public void testParseRealWorldResponse() { @@ -73,14 +98,14 @@ public class HttpResponseHeaderParserTest BufferUtil.flipToFlush(buf,0); // Parse Buffer - HttpResponseHeaderParser parser = new HttpResponseHeaderParser(); - UpgradeResponse response = parser.parse(buf); - Assert.assertThat("Response",response,notNullValue()); + HttpResponseParseCapture capture = new HttpResponseParseCapture(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture); + assertThat("Parser.parse",parser.parse(buf),notNullValue()); - Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200)); - Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK")); + Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200)); + Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK")); - Assert.assertThat("Response.header[age]",response.getHeader("age"),is("518097")); + Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097")); } @Test @@ -122,24 +147,47 @@ public class HttpResponseHeaderParserTest small3.position(70); // Parse Buffer - HttpResponseHeaderParser parser = new HttpResponseHeaderParser(); - UpgradeResponse response; + HttpResponseParseCapture capture = new HttpResponseParseCapture(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture); + assertThat("Parser.parse",parser.parse(buf),notNullValue()); // Parse small 1 - response = parser.parse(small1); - Assert.assertThat("Small 1",response,nullValue()); + Assert.assertThat("Small 1",parser.parse(small1),nullValue()); // Parse small 2 - response = parser.parse(small2); - Assert.assertThat("Small 2",response,nullValue()); + Assert.assertThat("Small 2",parser.parse(small2),nullValue()); // Parse small 3 - response = parser.parse(small3); - Assert.assertThat("Small 3",response,notNullValue()); + Assert.assertThat("Small 3",parser.parse(small3),notNullValue()); - Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200)); - Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK")); + Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200)); + Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK")); - Assert.assertThat("Response.header[age]",response.getHeader("age"),is("518097")); + Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097")); + } + + @Test + public void testParseUpgrade() + { + // Example from RFC6455 - Section 1.2 (Protocol Overview) + StringBuilder resp = new StringBuilder(); + resp.append("HTTP/1.1 101 Switching Protocols\r\n"); + resp.append("Upgrade: websocket\r\n"); + resp.append("Connection: Upgrade\r\n"); + resp.append("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"); + resp.append("Sec-WebSocket-Protocol: chat\r\n"); + resp.append("\r\n"); + + ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StringUtil.__UTF8_CHARSET); + + HttpResponseParseCapture capture = new HttpResponseParseCapture(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture); + assertThat("Parser.parse",parser.parse(buf),notNullValue()); + assertThat("Response.statusCode",capture.getStatusCode(),is(101)); + assertThat("Response.statusReason",capture.getStatusReason(),is("Switching Protocols")); + assertThat("Response.headers[Upgrade]",capture.getHeader("Upgrade"),is("websocket")); + assertThat("Response.headers[Connection]",capture.getHeader("Connection"),is("Upgrade")); + + assertThat("Buffer.remaining",buf.remaining(),is(0)); } } diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java new file mode 100644 index 00000000000..a7973e6e5cd --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java @@ -0,0 +1,76 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.websocket.common.io.http; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class HttpResponseParseCapture implements HttpResponseHeaderParseListener +{ + private int statusCode; + private String statusReason; + private Map headers = new HashMap<>(); + private ByteBuffer remainingBuffer; + + @Override + public void addHeader(String name, String value) + { + headers.put(name.toLowerCase(Locale.ENGLISH),value); + } + + public String getHeader(String name) + { + return headers.get(name.toLowerCase(Locale.ENGLISH)); + } + + public ByteBuffer getRemainingBuffer() + { + return remainingBuffer; + } + + public int getStatusCode() + { + return statusCode; + } + + public String getStatusReason() + { + return statusReason; + } + + @Override + public void setRemainingBuffer(ByteBuffer copy) + { + this.remainingBuffer = copy; + } + + @Override + public void setStatusCode(int code) + { + this.statusCode = code; + } + + @Override + public void setStatusReason(String reason) + { + this.statusReason = reason; + } +} \ No newline at end of file diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java index 09d06a5a558..2db4a48e2dc 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java @@ -103,7 +103,7 @@ public class AnnotatedMaxMessageSizeTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java index fd16ef78686..68bc1b1a3a2 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.examples.MyEchoServlet; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.AfterClass; @@ -60,8 +61,8 @@ public class ChromeTest client.setProtocols("chat"); client.connect(); client.sendStandardRequest(); - String response = client.expectUpgradeResponse(); - Assert.assertThat("Response",response,containsString("x-webkit-deflate-frame")); + HttpResponse response = client.expectUpgradeResponse(); + Assert.assertThat("Response",response.getExtensionsHeader(),containsString("x-webkit-deflate-frame")); // Generate text frame String msg = "this is an echo ... cho ... ho ... o"; @@ -69,7 +70,7 @@ public class ChromeTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java index cab75e16c89..b84ee24b9e3 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.AfterClass; @@ -80,9 +81,9 @@ public class FragmentExtensionTest client.setTimeout(TimeUnit.SECONDS,1); client.connect(); client.sendStandardRequest(); - String resp = client.expectUpgradeResponse(); + HttpResponse resp = client.expectUpgradeResponse(); - Assert.assertThat("Response",resp,containsString("fragment")); + Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("fragment")); String msg = "Sent as a long message that should be split"; client.write(WebSocketFrame.text(msg)); @@ -91,7 +92,7 @@ public class FragmentExtensionTest IncomingFramesCapture capture = client.readFrames(parts.length,TimeUnit.MILLISECONDS,1000); for (int i = 0; i < parts.length; i++) { - WebSocketFrame frame = capture.getFrames().get(i); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("text[" + i + "].payload",frame.getPayloadAsUTF8(),is(parts[i])); } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java index 9e0dfebc295..78a6be25a28 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.AfterClass; @@ -64,9 +65,9 @@ public class FrameCompressionExtensionTest client.setTimeout(TimeUnit.SECONDS,1); client.connect(); client.sendStandardRequest(); - String resp = client.expectUpgradeResponse(); + HttpResponse resp = client.expectUpgradeResponse(); - Assert.assertThat("Response",resp,containsString("x-webkit-deflate-frame")); + Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("x-webkit-deflate-frame")); String msg = "Hello"; @@ -74,7 +75,7 @@ public class FrameCompressionExtensionTest client.write(WebSocketFrame.text(msg)); IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); // Client sends second message @@ -83,7 +84,7 @@ public class FrameCompressionExtensionTest client.write(WebSocketFrame.text(msg)); capture = client.readFrames(1,TimeUnit.SECONDS,1); - frame = capture.getFrames().get(0); + frame = capture.getFrames().poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java index e56dc954627..47fa7caa52f 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.AfterClass; @@ -65,14 +66,14 @@ public class IdentityExtensionTest client.setTimeout(TimeUnit.SECONDS,1); client.connect(); client.sendStandardRequest(); - String resp = client.expectUpgradeResponse(); + HttpResponse resp = client.expectUpgradeResponse(); - Assert.assertThat("Response",resp,containsString("identity")); + Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("identity")); client.write(WebSocketFrame.text("Hello")); IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is("Hello")); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java index b7e80d3f258..00c001bcd2e 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.websocket.server; +import static org.hamcrest.Matchers.*; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -43,15 +45,11 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; -import static org.hamcrest.Matchers.is; - /** * Tests various close scenarios */ -@Ignore public class WebSocketCloseTest { @SuppressWarnings("serial") @@ -145,7 +143,7 @@ public class WebSocketCloseTest client.expectUpgradeResponse(); IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL)); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java index 5d47449e841..410fa82af0e 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.server; import static org.hamcrest.Matchers.*; import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient; +import org.eclipse.jetty.websocket.server.blockhead.HttpResponse; import org.eclipse.jetty.websocket.server.examples.MyEchoServlet; import org.junit.AfterClass; import org.junit.Assert; @@ -56,9 +57,10 @@ public class WebSocketInvalidVersionTest { client.connect(); client.sendStandardRequest(); - String respHeader = client.readResponseHeader(); - Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 400 Unsupported websocket version specification")); - Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13\r\n")); + HttpResponse response = client.readResponseHeader(); + Assert.assertThat("Response Status Code",response.getStatusCode(),is(400)); + Assert.assertThat("Response Status Reason",response.getStatusReason(),containsString("Unsupported websocket version specification")); + Assert.assertThat("Response Versions",response.getHeader("Sec-WebSocket-Version"),is("13")); } finally { diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java index f2a6dc5d5c3..61940904dbb 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.server; import static org.hamcrest.Matchers.*; import java.net.URI; +import java.util.Queue; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.toolchain.test.AdvancedRunner; @@ -96,13 +97,14 @@ public class WebSocketServerSessionTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(4,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().pop(); + Queue frames = capture.getFrames(); + WebSocketFrame tf = frames.poll(); Assert.assertThat("Parameter Map[snack]",tf.getPayloadAsUTF8(),is("[cashews]")); - tf = capture.getFrames().pop(); + tf = frames.poll(); Assert.assertThat("Parameter Map[amount]",tf.getPayloadAsUTF8(),is("[handful]")); - tf = capture.getFrames().pop(); + tf = frames.poll(); Assert.assertThat("Parameter Map[brand]",tf.getPayloadAsUTF8(),is("[off]")); - tf = capture.getFrames().pop(); + tf = frames.poll(); Assert.assertThat("Parameter Map[cost]",tf.getPayloadAsUTF8(),is("")); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java index 294ee654953..8ca6db30d5e 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java @@ -115,7 +115,7 @@ public class WebSocketServletRFCTest // Read frame echo'd back (hopefully a single binary frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - Frame binmsg = capture.getFrames().get(0); + Frame binmsg = capture.getFrames().poll(); int expectedSize = buf1.length + buf2.length + buf3.length; Assert.assertThat("BinaryFrame.payloadLength",binmsg.getPayloadLength(),is(expectedSize)); @@ -181,7 +181,7 @@ public class WebSocketServletRFCTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally @@ -209,7 +209,7 @@ public class WebSocketServletRFCTest // Read frame (hopefully close frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - Frame cf = capture.getFrames().get(0); + Frame cf = capture.getFrames().poll(); CloseInfo close = new CloseInfo(cf); Assert.assertThat("Close Frame.status code",close.getStatusCode(),is(StatusCode.SERVER_ERROR)); } @@ -252,7 +252,7 @@ public class WebSocketServletRFCTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally @@ -292,7 +292,7 @@ public class WebSocketServletRFCTest } IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE)); @@ -334,7 +334,7 @@ public class WebSocketServletRFCTest } IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE)); @@ -367,7 +367,7 @@ public class WebSocketServletRFCTest client.writeRaw(bb); IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().get(0); + WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.BAD_PAYLOAD)); @@ -413,7 +413,7 @@ public class WebSocketServletRFCTest // Read frame (hopefully text frame) IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().get(0); + WebSocketFrame tf = capture.getFrames().poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java index 09e9f3ec173..5c9d07c6f74 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java @@ -151,7 +151,7 @@ public class Fuzzer for (int i = 0; i < expectedCount; i++) { WebSocketFrame expected = expect.get(i); - WebSocketFrame actual = capture.getFrames().pop(); + WebSocketFrame actual = capture.getFrames().poll(); prefix = "Frame[" + i + "]"; @@ -188,14 +188,16 @@ public class Fuzzer // we expect that the close handshake to have occurred and the server should have closed the connection try { - @SuppressWarnings("unused") - int val = client.read(); + ByteBuffer buf = ByteBuffer.wrap(new byte[] + { 0x00 }); + BufferUtil.flipToFill(buf); + int len = client.read(buf); - Assert.fail("Server has not closed socket"); + Assert.assertThat("Server has not closed socket",len,lessThanOrEqualTo(0)); } - catch (SocketException e) + catch (IOException e) { - + // valid path } IOState ios = client.getIOState(); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java index 561f9a89c50..ce46ba23fe6 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java @@ -19,13 +19,10 @@ package org.eclipse.jetty.websocket.server.blockhead; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import java.io.BufferedReader; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetAddress; @@ -41,8 +38,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.net.ssl.HttpsURLConnection; @@ -51,7 +46,6 @@ import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.WebSocketException; @@ -71,6 +65,7 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory; import org.eclipse.jetty.websocket.common.io.IOState; +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.Assert; @@ -117,6 +112,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames private ExtensionStack extensionStack; private IOState ioState; private CountDownLatch disconnectedLatch = new CountDownLatch(1); + private ByteBuffer remainingBuffer; public BlockheadClient(URI destWebsocketURI) throws URISyntaxException { @@ -234,32 +230,31 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames } } - public String expectUpgradeResponse() throws IOException + public HttpResponse expectUpgradeResponse() throws IOException { - String respHeader = readResponseHeader(); + HttpResponse response = readResponseHeader(); if (LOG.isDebugEnabled()) { - LOG.debug("Response Header: {}{}",'\n',respHeader); + LOG.debug("Response Header: {}{}",'\n',response); } - Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 101 Switching Protocols")); - Assert.assertThat("Response Header Upgrade",respHeader,containsString("Upgrade: WebSocket\r\n")); - Assert.assertThat("Response Header Connection",respHeader,containsString("Connection: Upgrade\r\n")); + Assert.assertThat("Response Status Code",response.getStatusCode(),is(101)); + Assert.assertThat("Response Status Reason",response.getStatusReason(),is("Switching Protocols")); + Assert.assertThat("Response Header[Upgrade]",response.getHeader("Upgrade"),is("WebSocket")); + Assert.assertThat("Response Header[Connection]",response.getHeader("Connection"),is("Upgrade")); // Validate the Sec-WebSocket-Accept - Pattern patAcceptHeader = Pattern.compile("Sec-WebSocket-Accept: (.*=)",Pattern.CASE_INSENSITIVE); - Matcher matAcceptHeader = patAcceptHeader.matcher(respHeader); - Assert.assertThat("Response Header Sec-WebSocket-Accept Exists?",matAcceptHeader.find(),is(true)); + String acceptKey = response.getHeader("Sec-WebSocket-Accept"); + Assert.assertThat("Response Header[Sec-WebSocket-Accept Exists]",acceptKey,notNullValue()); String reqKey = REQUEST_HASH_KEY; String expectedHash = AcceptHash.hashKey(reqKey); - String acceptKey = matAcceptHeader.group(1); Assert.assertThat("Valid Sec-WebSocket-Accept Hash?",acceptKey,is(expectedHash)); // collect extensions configured in response header - List configs = getExtensionConfigs(respHeader); + List configs = getExtensionConfigs(response); extensionStack = new ExtensionStack(this.extensionFactory); extensionStack.negotiate(configs); @@ -288,7 +283,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames LOG.debug("outgoing = {}",outgoing); LOG.debug("incoming = {}",extensionStack); - return respHeader; + return response; } public void flush() throws IOException @@ -296,22 +291,16 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames out.flush(); } - private List getExtensionConfigs(String respHeader) + private List getExtensionConfigs(HttpResponse response) { List configs = new ArrayList<>(); - Pattern expat = Pattern.compile("Sec-WebSocket-Extensions: (.*)\r",Pattern.CASE_INSENSITIVE); - Matcher mat = expat.matcher(respHeader); - int offset = 0; - while (mat.find(offset)) + String econf = response.getHeader("Sec-WebSocket-Extensions"); + if (econf != null) { - String econf = mat.group(1); LOG.debug("Found Extension Response: {}",econf); - ExtensionConfig config = ExtensionConfig.parse(econf); configs.add(config); - - offset = mat.end(1); } return configs; } @@ -423,35 +412,6 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames return (socket != null) && (socket.isConnected()); } - public void lookFor(String string) throws IOException - { - String orig = string; - Utf8StringBuilder scanned = new Utf8StringBuilder(); - try - { - while (true) - { - int b = in.read(); - if (b < 0) - { - throw new EOFException(); - } - scanned.append((byte)b); - assertEquals("looking for\"" + orig + "\" in '" + scanned + "'",string.charAt(0),b); - if (string.length() == 1) - { - break; - } - string = string.substring(1); - } - } - catch (IOException e) - { - System.err.println("IOE while looking for \"" + orig + "\" in '" + scanned + "'"); - throw e; - } - } - @Override public void outgoingFrame(Frame frame, WriteCallback callback) { @@ -487,17 +447,18 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames } } - public int read() throws IOException - { - return in.read(); - } - public int read(ByteBuffer buf) throws IOException { if (eof) { throw new EOFException("Hit EOF"); } + + if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0)) + { + return BufferUtil.put(remainingBuffer,buf); + } + int len = 0; int b; while ((in.available() > 0) && (buf.remaining() > 0)) @@ -572,25 +533,24 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames return incomingFrames; } - public String readResponseHeader() throws IOException + public HttpResponse readResponseHeader() throws IOException { - InputStreamReader isr = new InputStreamReader(in); - BufferedReader reader = new BufferedReader(isr); - StringBuilder header = new StringBuilder(); - // Read the response header - String line = reader.readLine(); - Assert.assertNotNull(line); - Assert.assertThat(line,startsWith("HTTP/1.1 ")); - header.append(line).append("\r\n"); - while ((line = reader.readLine()) != null) + HttpResponse response = new HttpResponse(); + HttpResponseHeaderParser parser = new HttpResponseHeaderParser(response); + + ByteBuffer buf = BufferUtil.allocate(512); + + do { - if (line.trim().length() == 0) - { - break; - } - header.append(line).append("\r\n"); + BufferUtil.flipToFill(buf); + read(buf); + BufferUtil.flipToFlush(buf,0); } - return header.toString(); + while (parser.parse(buf) == null); + + remainingBuffer = response.getRemainingBuffer(); + + return response; } public void sendStandardRequest() throws IOException diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java new file mode 100644 index 00000000000..59d44ebea86 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java @@ -0,0 +1,95 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.websocket.server.blockhead; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener; + +public class HttpResponse implements HttpResponseHeaderParseListener +{ + private int statusCode; + private String statusReason; + private Map headers = new HashMap<>(); + private ByteBuffer remainingBuffer; + + @Override + public void addHeader(String name, String value) + { + headers.put(name.toLowerCase(Locale.ENGLISH),value); + } + + public String getExtensionsHeader() + { + return getHeader("Sec-WebSocket-Extensions"); + } + + public String getHeader(String name) + { + return headers.get(name.toLowerCase(Locale.ENGLISH)); + } + + public ByteBuffer getRemainingBuffer() + { + return remainingBuffer; + } + + public int getStatusCode() + { + return statusCode; + } + + public String getStatusReason() + { + return statusReason; + } + + @Override + public void setRemainingBuffer(ByteBuffer copy) + { + this.remainingBuffer = copy; + } + + @Override + public void setStatusCode(int code) + { + this.statusCode = code; + } + + @Override + public void setStatusReason(String reason) + { + this.statusReason = reason; + } + + @Override + public String toString() + { + StringBuilder str = new StringBuilder(); + str.append("HTTP/1.1 ").append(statusCode).append(' ').append(statusReason); + for (Map.Entry entry : headers.entrySet()) + { + str.append('\n').append(entry.getKey()).append(": ").append(entry.getValue()); + } + return str.toString(); + } +} diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java index a0e2889f6ea..82a47232738 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java @@ -20,8 +20,9 @@ package org.eclipse.jetty.websocket.server.helper; import static org.hamcrest.Matchers.*; -import java.util.LinkedList; +import java.util.Queue; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -35,8 +36,8 @@ import org.junit.Assert; public class IncomingFramesCapture implements IncomingFrames { private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class); - private LinkedList frames = new LinkedList<>(); - private LinkedList errors = new LinkedList<>(); + private EventQueue frames = new EventQueue<>(); + private EventQueue errors = new EventQueue<>(); public void assertErrorCount(int expectedCount) { @@ -81,10 +82,10 @@ public class IncomingFramesCapture implements IncomingFrames public void dump() { System.err.printf("Captured %d incoming frames%n",frames.size()); - for (int i = 0; i < frames.size(); i++) + int i = 0; + for (Frame frame : frames) { - Frame frame = frames.get(i); - System.err.printf("[%3d] %s%n",i,frame); + System.err.printf("[%3d] %s%n",i++,frame); System.err.printf(" %s%n",BufferUtil.toDetailString(frame.getPayload())); } } @@ -102,7 +103,7 @@ public class IncomingFramesCapture implements IncomingFrames return count; } - public LinkedList getErrors() + public Queue getErrors() { return errors; } @@ -120,7 +121,7 @@ public class IncomingFramesCapture implements IncomingFrames return count; } - public LinkedList getFrames() + public Queue getFrames() { return frames; } diff --git a/pom.xml b/pom.xml index 7b480d92ceb..417706f9e06 100644 --- a/pom.xml +++ b/pom.xml @@ -542,7 +542,7 @@ org.eclipse.jetty.toolchain jetty-test-helper - 2.0 + 2.2 org.slf4j From fbf89e75aaeb5a287aab3d21b79f1db189ced248 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 29 Apr 2013 09:16:47 +1000 Subject: [PATCH 10/28] 406753 jetty-runner contains invalid signature files --- jetty-runner/pom.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml index 0f6dff63647..3ddb4caed02 100644 --- a/jetty-runner/pom.xml +++ b/jetty-runner/pom.xml @@ -27,9 +27,7 @@ ** - **/MANIFEST.MF - **/ECLIPSEF.RSA - **/ECLIPSEF.SF + **/MANIFEST.MF,META-INF/*.RSA,META-INF/*.DSA,META-INF/*.SF ${project.build.directory}/classes false true From a5c701c237bc909592e837e9bd1cabaaa5888330 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 29 Apr 2013 09:55:34 +1000 Subject: [PATCH 11/28] 406390 Close if at END and content remaining --- .../org/eclipse/jetty/http/HttpParser.java | 8 +++ .../eclipse/jetty/server/HttpConnection.java | 57 ++++++++++--------- 2 files changed, 38 insertions(+), 27 deletions(-) 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 17d9c01bc44..8a0f941740c 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 @@ -652,6 +652,9 @@ public class HttpParser case CACHE_CONTROL: case USER_AGENT: add_to_connection_trie=_connectionFields!=null && _field==null; + break; + + default: break; } if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null) @@ -1089,6 +1092,8 @@ public class HttpParser BufferUtil.clear(buffer); } return false; + default: break; + } // Request/response line @@ -1262,6 +1267,9 @@ public class HttpParser BufferUtil.clear(buffer); return false; } + + default: + break; } } 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 69433f5e3ab..fa44d560690 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 @@ -207,8 +207,29 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http // Can the parser progress (even with an empty buffer) boolean call_channel=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer); - // If there is a request buffer, we are re-entering here - if (!call_channel && BufferUtil.isEmpty(_requestBuffer)) + // Parse the buffer + if (call_channel) + { + // Parse as much content as there is available before calling the channel + // this is both efficient (may queue many chunks), will correctly set available for 100 continues + // and will drive the parser to completion if all content is available. + while (_parser.inContentState()) + { + if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer)) + break; + } + + // The parser returned true, which indicates the channel is ready to handle a request. + // Call the channel and this will either handle the request/response to completion OR, + // if the request suspends, the request/response will be incomplete so the outer loop will exit. + + _channel.run(); + + // Return if suspended or upgraded + if (_channel.getState().isSuspended() || getEndPoint().getConnection()!=this) + return; + } + else if (BufferUtil.isEmpty(_requestBuffer)) { if (_requestBuffer == null) _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT); @@ -242,33 +263,15 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http releaseRequestBuffer(); return; } - - // Parse what we have read - call_channel=_parser.parseNext(_requestBuffer); } - - // Parse the buffer - if (call_channel) + else { - // Parse as much content as there is available before calling the channel - // this is both efficient (may queue many chunks), will correctly set available for 100 continues - // and will drive the parser to completion if all content is available. - while (_parser.inContentState()) - { - if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer)) - break; - } - - // The parser returned true, which indicates the channel is ready to handle a request. - // Call the channel and this will either handle the request/response to completion OR, - // if the request suspends, the request/response will be incomplete so the outer loop will exit. - - _channel.run(); - - // Return if suspended or upgraded - if (_channel.getState().isSuspended() || getEndPoint().getConnection()!=this) - return; - } + // TODO work out how we can get here and a better way to handle it + LOG.warn("Unexpected state: "+this+ " "+_channel+" "+_channel.getRequest()); + getEndPoint().close(); + return; + } + System.err.println(_channel.getRequest()); } } catch (EofException e) From cbb76283c95ad7674e5b750f76bf2120aa252d95 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 29 Apr 2013 10:40:05 +1000 Subject: [PATCH 12/28] 406390 Close if at END and content remaining --- .../src/main/java/org/eclipse/jetty/http/HttpParser.java | 3 +++ .../main/java/org/eclipse/jetty/server/HttpConnection.java | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) 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 8a0f941740c..85475b61f75 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 @@ -1355,7 +1355,10 @@ public class HttpParser default: if (_state.ordinal()>State.END.ordinal()) + { _handler.earlyEOF(); + _handler.messageComplete(); + } else LOG.warn("Closing {}",this); } 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 fa44d560690..f8d53ef6545 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 @@ -268,10 +268,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http { // TODO work out how we can get here and a better way to handle it LOG.warn("Unexpected state: "+this+ " "+_channel+" "+_channel.getRequest()); - getEndPoint().close(); + if (!_channel.getState().isSuspended()) + getEndPoint().close(); return; } - System.err.println(_channel.getRequest()); } } catch (EofException e) From 7955548d10c4e085e32196654785e624b6256db3 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 29 Apr 2013 14:01:11 +1000 Subject: [PATCH 13/28] 406390 Close if at END and content remaining --- .../org/eclipse/jetty/http/HttpParser.java | 1 + .../eclipse/jetty/io/AbstractConnection.java | 137 +++++++++++++++++- .../eclipse/jetty/server/HttpConnection.java | 4 +- .../jetty/servlet/AsyncServletTest.java | 88 +++++++++-- 4 files changed, 209 insertions(+), 21 deletions(-) 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 85475b61f75..39cf670d5db 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 @@ -1388,6 +1388,7 @@ public class HttpParser /* ------------------------------------------------------------------------------- */ private void setState(State state) { + // LOG.debug("{} --> {}",_state,state); _state=state; } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java index a0b090ccb59..3fc9bef3ea6 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java @@ -24,6 +24,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.util.BlockingCallback; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -112,13 +113,130 @@ public abstract class AbstractConnection implements Connection if (_state.compareAndSet(State.FILLING,State.FILLING_INTERESTED)) break loop; break; + + case FILLING_BLOCKED: + if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING_BLOCKED_INTERESTED)) + break loop; + break; + + case BLOCKED: + if (_state.compareAndSet(State.BLOCKED,State.BLOCKED_INTERESTED)) + break loop; + break; + case FILLING_BLOCKED_INTERESTED: + case FILLING_INTERESTED: + case BLOCKED_INTERESTED: + case INTERESTED: + break loop; + } + } + } + + + private void unblock() + { + LOG.debug("unblock {}",this); + + loop:while(true) + { + switch(_state.get()) + { + case FILLING_BLOCKED: + if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING)) + break loop; + break; + + case FILLING_BLOCKED_INTERESTED: + if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.FILLING_INTERESTED)) + break loop; + break; + + case BLOCKED_INTERESTED: + if (_state.compareAndSet(State.BLOCKED_INTERESTED,State.INTERESTED)) + { + getEndPoint().fillInterested(_readCallback); + break loop; + } + break; + + case BLOCKED: + if (_state.compareAndSet(State.BLOCKED,State.IDLE)) + break loop; + break; + + case FILLING: + case IDLE: case FILLING_INTERESTED: case INTERESTED: break loop; } } } + + + /** + */ + protected void block(final BlockingCallback callback) + { + LOG.debug("block {}",this); + + final Callback blocked=new Callback() + { + @Override + public void succeeded() + { + unblock(); + callback.succeeded(); + } + + @Override + public void failed(Throwable x) + { + unblock(); + callback.failed(x); + } + }; + + loop:while(true) + { + switch(_state.get()) + { + case IDLE: + if (_state.compareAndSet(State.IDLE,State.BLOCKED)) + { + getEndPoint().fillInterested(blocked); + break loop; + } + break; + + case FILLING: + if (_state.compareAndSet(State.FILLING,State.FILLING_BLOCKED)) + { + getEndPoint().fillInterested(blocked); + break loop; + } + break; + + case FILLING_INTERESTED: + if (_state.compareAndSet(State.FILLING_INTERESTED,State.FILLING_BLOCKED_INTERESTED)) + { + getEndPoint().fillInterested(blocked); + break loop; + } + break; + + case BLOCKED: + case BLOCKED_INTERESTED: + case FILLING_BLOCKED: + case FILLING_BLOCKED_INTERESTED: + throw new IllegalStateException("Already Blocked"); + + case INTERESTED: + throw new IllegalStateException(); + } + } + } /** *

Callback method invoked when the endpoint is ready to be read.

@@ -225,7 +343,7 @@ public abstract class AbstractConnection implements Connection private enum State { - IDLE, INTERESTED, FILLING, FILLING_INTERESTED + IDLE, INTERESTED, FILLING, FILLING_INTERESTED, FILLING_BLOCKED, BLOCKED, FILLING_BLOCKED_INTERESTED, BLOCKED_INTERESTED } private class ReadCallback implements Callback, Runnable @@ -247,12 +365,25 @@ public abstract class AbstractConnection implements Connection { case IDLE: case INTERESTED: - throw new IllegalStateException(); + case BLOCKED: + case BLOCKED_INTERESTED: + LOG.warn(new IllegalStateException()); + return; case FILLING: if (_state.compareAndSet(State.FILLING,State.IDLE)) break loop; break; + + case FILLING_BLOCKED: + if (_state.compareAndSet(State.FILLING_BLOCKED,State.BLOCKED)) + break loop; + break; + + case FILLING_BLOCKED_INTERESTED: + if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.BLOCKED_INTERESTED)) + break loop; + break; case FILLING_INTERESTED: if (_state.compareAndSet(State.FILLING_INTERESTED,State.INTERESTED)) @@ -266,7 +397,7 @@ public abstract class AbstractConnection implements Connection } } else - LOG.warn(new Throwable()); + LOG.warn(new IllegalStateException()); } @Override 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 f8d53ef6545..17f09a7b913 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 @@ -550,7 +550,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http // need to call blockForContent again while (event && BufferUtil.hasContent(_requestBuffer) && _parser.inContentState()) _parser.parseNext(_requestBuffer); - + // If we have an event, return if (event) return; @@ -566,7 +566,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } // Wait until we can read - getEndPoint().fillInterested(_readBlocker); + block(_readBlocker); LOG.debug("{} block readable on {}",this,_readBlocker); _readBlocker.block(); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java index d5ad4ee4522..c2703fe7799 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.servlet; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.InputStream; @@ -345,6 +344,45 @@ public class AsyncServletTest Assert.assertThat(response,Matchers.not(Matchers.containsString(content))); } + + @Test + public void testAsyncRead() throws Exception + { + String header="GET /ctx/path/info?suspend=2000&resume=1500 HTTP/1.1\r\n"+ + "Host: localhost\r\n"+ + "Content-Length: 10\r\n"+ + "\r\n"; + String body="12345678\r\n"; + String close="GET /ctx/path/info HTTP/1.1\r\n"+ + "Host: localhost\r\n"+ + "Connection: close\r\n"+ + "\r\n"; + + try (Socket socket = new Socket("localhost",_port);) + { + socket.setSoTimeout(10000); + socket.getOutputStream().write(header.getBytes("ISO-8859-1")); + Thread.sleep(500); + socket.getOutputStream().write(body.getBytes("ISO-8859-1"),0,2); + Thread.sleep(500); + socket.getOutputStream().write(body.getBytes("ISO-8859-1"),2,8); + socket.getOutputStream().write(close.getBytes("ISO-8859-1")); + + String response = IO.toString(socket.getInputStream()); + assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + assertContains( + "history: REQUEST\r\n"+ + "history: initial\r\n"+ + "history: suspend\r\n"+ + "history: async-read=10\r\n"+ + "history: resume\r\n"+ + "history: ASYNC\r\n"+ + "history: !initial\r\n"+ + "history: onComplete\r\n",response); + } + } + + public synchronized String process(String query,String content) throws Exception { String request = "GET /ctx/path/info"; @@ -364,9 +402,8 @@ public class AsyncServletTest int port=_port; String response=null; - try + try (Socket socket = new Socket("localhost",port);) { - Socket socket = new Socket("localhost",port); socket.setSoTimeout(1000000); socket.getOutputStream().write(request.getBytes("UTF-8")); @@ -379,11 +416,10 @@ public class AsyncServletTest throw e; } - // System.err.println(response); - return response; } - + + private static class AsyncServlet extends HttpServlet @@ -429,7 +465,7 @@ public class AsyncServletTest if (request.getDispatcherType()==DispatcherType.REQUEST) { - ((HttpServletResponse)response).addHeader("history","initial"); + response.addHeader("history","initial"); if (read_before>0) { byte[] buf=new byte[read_before]; @@ -442,6 +478,30 @@ public class AsyncServletTest while(b!=-1) b=in.read(); } + else if (request.getContentLength()>0) + { + new Thread() + { + @Override + public void run() + { + int c=0; + try + { + InputStream in=request.getInputStream(); + int b=0; + while(b!=-1) + if((b=in.read())>=0) + c++; + response.addHeader("history","async-read="+c); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + }.start(); + } if (suspend_for>=0) { @@ -449,7 +509,7 @@ public class AsyncServletTest if (suspend_for>0) async.setTimeout(suspend_for); async.addListener(__listener); - ((HttpServletResponse)response).addHeader("history","suspend"); + response.addHeader("history","suspend"); if (complete_after>0) { @@ -527,7 +587,7 @@ public class AsyncServletTest } else { - ((HttpServletResponse)response).addHeader("history","!initial"); + response.addHeader("history","!initial"); if (suspend2_for>=0 && request.getAttribute("2nd")==null) { @@ -540,7 +600,7 @@ public class AsyncServletTest async.setTimeout(suspend2_for); } // continuation.addContinuationListener(__listener); - ((HttpServletResponse)response).addHeader("history","suspend"); + response.addHeader("history","suspend"); if (complete2_after>0) { @@ -581,7 +641,7 @@ public class AsyncServletTest @Override public void run() { - ((HttpServletResponse)response).addHeader("history","resume"); + response.addHeader("history","resume"); async.dispatch(); } }; @@ -592,7 +652,7 @@ public class AsyncServletTest } else if (resume2_after==0) { - ((HttpServletResponse)response).addHeader("history","dispatch"); + response.addHeader("history","dispatch"); async.dispatch(); } } @@ -633,15 +693,11 @@ public class AsyncServletTest @Override public void onStartAsync(AsyncEvent event) throws IOException { - // TODO Auto-generated method stub - } @Override public void onError(AsyncEvent event) throws IOException { - // TODO Auto-generated method stub - } @Override From e5967344a9b8588a79f6cfa85a66ac3ae6b8007e Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 29 Apr 2013 15:11:45 +1000 Subject: [PATCH 14/28] 406202 re-enabled connector statistics --- .../src/main/config/etc/jetty-stats.xml | 3 + .../jetty/server/ConnectorStatistics.java | 84 +++++++++++++------ 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/jetty-server/src/main/config/etc/jetty-stats.xml b/jetty-server/src/main/config/etc/jetty-stats.xml index dbef0e2c568..2e7a57c7fa9 100644 --- a/jetty-server/src/main/config/etc/jetty-stats.xml +++ b/jetty-server/src/main/config/etc/jetty-stats.xml @@ -12,4 +12,7 @@ + + + diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java index 5fb942e56aa..56bb66d9936 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java @@ -23,14 +23,22 @@ import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedOperation; import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.Container; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.statistic.CounterStatistic; import org.eclipse.jetty.util.statistic.SampleStatistic; + +/* ------------------------------------------------------------ */ +/** A Connector.Listener that gathers Connector and Connections Statistics. + * Adding an instance of this class as with {@link AbstractConnector#addBean(Object)} + * will register the listener with all connections accepted by that connector. + */ @ManagedObject("Connector Statistics") public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, Connection.Listener { @@ -52,78 +60,93 @@ public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, connectionClosed(System.currentTimeMillis()-connection.getCreatedTimeStamp(),connection.getMessagesIn(),connection.getMessagesOut()); } + @ManagedAttribute("Total number of bytes received by this connector") public int getBytesIn() { // TODO return -1; } + @ManagedAttribute("Total number of bytes sent by this connector") public int getBytesOut() { // TODO return -1; } + @ManagedAttribute("Total number of connections seen by this connector") public int getConnections() { return (int)_connectionStats.getTotal(); } + @ManagedAttribute("Connection duraton maximum in ms") public long getConnectionsDurationMax() { return _connectionDurationStats.getMax(); } + @ManagedAttribute("Connection duraton mean in ms") public double getConnectionsDurationMean() { return _connectionDurationStats.getMean(); } + @ManagedAttribute("Connection duraton standard deviation") public double getConnectionsDurationStdDev() { return _connectionDurationStats.getStdDev(); } + @ManagedAttribute("Connection duraton total of all connections in ms") public long getConnectionsDurationTotal() { return _connectionDurationStats.getTotal(); } - public int getConnectionsMessagesInMax() - { - return (int)_messagesIn.getMax(); - } - - public double getConnectionsMessagesInMean() - { - return _messagesIn.getMean(); - } - - public double getConnectionsMessagesInStdDev() - { - return _messagesIn.getStdDev(); - } - - public int getConnectionsOpen() - { - return (int)_connectionStats.getCurrent(); - } - - public int getConnectionsOpenMax() - { - return (int)_connectionStats.getMax(); - } - + @ManagedAttribute("Messages In for all connections") public int getMessagesIn() { return (int)_messagesIn.getTotal(); } + @ManagedAttribute("Messages In per connection maximum") + public int getConnectionsMessagesInMax() + { + return (int)_messagesIn.getMax(); + } + + @ManagedAttribute("Messages In per connection mean") + public double getConnectionsMessagesInMean() + { + return _messagesIn.getMean(); + } + + @ManagedAttribute("Messages In per connection standard deviation") + public double getConnectionsMessagesInStdDev() + { + return _messagesIn.getStdDev(); + } + + @ManagedAttribute("Connections open") + public int getConnectionsOpen() + { + return (int)_connectionStats.getCurrent(); + } + + @ManagedAttribute("Connections open maximum") + public int getConnectionsOpenMax() + { + return (int)_connectionStats.getMax(); + } + + @ManagedAttribute("Messages Out for all connections") public int getMessagesOut() { return (int)_messagesIn.getTotal(); } + @ManagedAttribute("Connection statistics started ms since epoch") public long getStartedMillis() { long start = _startMillis.get(); @@ -141,6 +164,7 @@ public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, { } + @ManagedOperation("Reset the statistics") public void reset() { _startMillis.set(System.currentTimeMillis()); @@ -178,7 +202,6 @@ public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, } } - @Override @ManagedOperation("dump thread state") public String dump() @@ -192,4 +215,13 @@ public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, ContainerLifeCycle.dumpObject(out,this); ContainerLifeCycle.dump(out,indent,Arrays.asList(new String[]{"connections="+_connectionStats,"duration="+_connectionDurationStats,"in="+_messagesIn,"out="+_messagesOut})); } + + public static void addToAllConnectors(Server server) + { + for (Connector connector : server.getConnectors()) + { + if (connector instanceof Container) + ((Container)connector).addBean(new ConnectorStatistics()); + } + } } From 140419c097755c80cecdbd76341c0fd9b2792d90 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 29 Apr 2013 15:09:50 +1000 Subject: [PATCH 15/28] 406618 Jetty startup in OSGi Equinox fails when using option jetty.home.bundle=org.eclipse.jetty.osgi.boot --- .../internal/serverfactory/DefaultJettyAtJettyHomeHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java index a89ff5b7300..f20838b7625 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java @@ -236,7 +236,7 @@ public class DefaultJettyAtJettyHomeHelper // can define their own configuration. if ((enUrls == null || !enUrls.hasMoreElements())) { - String tmp = DEFAULT_JETTYHOME+etcFile; + String tmp = DEFAULT_JETTYHOME+(DEFAULT_JETTYHOME.endsWith("/")?"":"/")+etcFile; enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, tmp); LOG.info("Configuring jetty from bundle: " + configurationBundle.getSymbolicName() From f4b8e043e5ccd3392671f4333da21e2cad14e788 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 29 Apr 2013 16:54:32 +0200 Subject: [PATCH 16/28] Added SPDY profile for JDK 7u21. --- jetty-spdy/pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/jetty-spdy/pom.xml b/jetty-spdy/pom.xml index 8b3fa2d9f6d..dd13d3c20ef 100644 --- a/jetty-spdy/pom.xml +++ b/jetty-spdy/pom.xml @@ -89,6 +89,18 @@ 1.1.5.v20130313 + + 7u21 + + + java.version + 1.7.0_21 + + + + 1.1.5.v20130313 + + From fdb993e76382f313cc938613eb18731e300f3b01 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 29 Apr 2013 18:31:46 +0200 Subject: [PATCH 17/28] Modified Travis CI build to prefer IPv4. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 80b6f4b7806..90bce4d70d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,6 @@ language: java jdk: - openjdk7 - oraclejdk7 +env: + global: + - MAVEN_OPTS="-Djava.net.preferIPv4Stack=true" From 9529d0334bde8e6118cd04754ee9889a113306b6 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 29 Apr 2013 21:07:33 +0200 Subject: [PATCH 18/28] Further modifications to Travis CI build to prefer IPv4. --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 90bce4d70d8..16c4946a2e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,4 @@ language: java jdk: - openjdk7 - oraclejdk7 -env: - global: - - MAVEN_OPTS="-Djava.net.preferIPv4Stack=true" +env: MAVEN_OPTS="-Djava.net.preferIPv4Stack=true" From 9d32209576fdc306bab27a50710cb7e8f7affc9a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 29 Apr 2013 22:06:53 +0200 Subject: [PATCH 19/28] Modifications to Travis CI build to prefer IPv4 did not work, removing them. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 16c4946a2e2..80b6f4b7806 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,3 @@ language: java jdk: - openjdk7 - oraclejdk7 -env: MAVEN_OPTS="-Djava.net.preferIPv4Stack=true" From 68b9115e07f62f749ef4831894c9be8bbe9de2f8 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 30 Apr 2013 14:23:26 +1000 Subject: [PATCH 20/28] 406768 Close inputstream and channels for 0 length resources --- .../main/java/org/eclipse/jetty/server/HttpOutput.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 fbe65976213..014322c39ba 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 @@ -268,7 +268,7 @@ public class HttpOutput extends ServletOutputStream { Resource resource = (Resource)content; _channel.getResponse().getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, resource.lastModified()); - content = resource.getInputStream(); + content=resource.getInputStream(); // Closed below } // Process content. @@ -279,9 +279,8 @@ public class HttpOutput extends ServletOutputStream } else if (content instanceof ReadableByteChannel) { - ReadableByteChannel channel = (ReadableByteChannel)content; ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), CHANNEL_BUFFER_DIRECT); - try + try (ReadableByteChannel channel = (ReadableByteChannel)content;) { while(channel.isOpen()) { @@ -301,12 +300,11 @@ public class HttpOutput extends ServletOutputStream } else if (content instanceof InputStream) { - InputStream in = (InputStream)content; ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), STREAM_BUFFER_DIRECT); byte[] array = buffer.array(); int offset=buffer.arrayOffset(); int size=array.length-offset; - try + try (InputStream in = (InputStream)content;) { while(true) { From e781393258e17f2e251b2fd263ab369d1b826ab9 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 30 Apr 2013 15:52:11 +1000 Subject: [PATCH 21/28] 406768 Improved handling of static content resources --- .../org/eclipse/jetty/server/HttpChannel.java | 10 +++ .../org/eclipse/jetty/server/HttpOutput.java | 19 ++--- .../jetty/server/InclusiveByteRange.java | 2 +- .../org/eclipse/jetty/server/Request.java | 5 +- .../eclipse/jetty/server/ResponseTest.java | 69 +++++++++++-------- .../eclipse/jetty/servlet/DefaultServlet.java | 52 ++++---------- .../eclipse/jetty/servlet/ServletHandler.java | 22 +++--- 7 files changed, 89 insertions(+), 90 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 9fbbde7ab5e..73f7f890831 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.ChannelEndPoint; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.server.handler.ErrorHandler; @@ -664,4 +665,13 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable { return _connector.getScheduler(); } + + /* ------------------------------------------------------------ */ + /** + * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol) + */ + public boolean useDirectBuffers() + { + return getEndPoint() instanceof ChannelEndPoint; + } } 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 014322c39ba..56bc29da4e6 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 @@ -47,9 +47,6 @@ import org.eclipse.jetty.util.resource.Resource; */ public class HttpOutput extends ServletOutputStream { - private static final boolean OUTPUT_BUFFER_DIRECT=false; - private static final boolean CHANNEL_BUFFER_DIRECT=true; - private static final boolean STREAM_BUFFER_DIRECT=false; private static Logger LOG = Log.getLogger(HttpOutput.class); private final HttpChannel _channel; private boolean _closed; @@ -165,8 +162,9 @@ public class HttpOutput extends ServletOutputStream return; } - // Allocate an aggregate buffer - _aggregate = _channel.getByteBufferPool().acquire(size, OUTPUT_BUFFER_DIRECT); + // Allocate an aggregate buffer. + // Never direct as it is slow to do little writes to a direct buffer. + _aggregate = _channel.getByteBufferPool().acquire(size, false); } // Do we have space to aggregate ? @@ -206,8 +204,10 @@ public class HttpOutput extends ServletOutputStream if (isClosed()) throw new EOFException("Closed"); + // Allocate an aggregate buffer. + // Never direct as it is slow to do little writes to a direct buffer. if (_aggregate == null) - _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), OUTPUT_BUFFER_DIRECT); + _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false); BufferUtil.append(_aggregate, (byte)b); _written++; @@ -256,7 +256,7 @@ public class HttpOutput extends ServletOutputStream if (etag!=null) response.getHttpFields().put(HttpHeader.ETAG,etag); - content = httpContent.getDirectBuffer(); + content = _channel.useDirectBuffers()?httpContent.getDirectBuffer():null; if (content == null) content = httpContent.getIndirectBuffer(); if (content == null) @@ -279,7 +279,7 @@ public class HttpOutput extends ServletOutputStream } else if (content instanceof ReadableByteChannel) { - ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), CHANNEL_BUFFER_DIRECT); + ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers()); try (ReadableByteChannel channel = (ReadableByteChannel)content;) { while(channel.isOpen()) @@ -300,7 +300,8 @@ public class HttpOutput extends ServletOutputStream } else if (content instanceof InputStream) { - ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), STREAM_BUFFER_DIRECT); + // allocate non direct buffer so array may be directly accessed. + ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false); byte[] array = buffer.array(); int offset=buffer.arrayOffset(); int size=array.length-offset; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java index 7910c6e80dc..ebf002584bc 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java @@ -81,7 +81,7 @@ public class InclusiveByteRange * @param size Size of the resource. * @return LazyList of satisfiable ranges */ - public static List satisfiableRanges(Enumeration headers, long size) + public static List satisfiableRanges(Enumeration headers, long size) { Object satRanges=null; 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 94c98f6c646..1b92872f36d 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 @@ -1071,7 +1071,10 @@ public class Request implements HttpServletRequest url.append(scheme); url.append("://"); - url.append(getServerName()); + if (getServerName().contains(":")) + url.append('[').append(getServerName()).append(']'); + else + url.append(getServerName()); if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80) || (scheme.equalsIgnoreCase("https") && port != 443))) { 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 afa0107dec6..b4d00a19244 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 @@ -432,43 +432,54 @@ public class ResponseTest String[][] tests = { // No cookie {"http://myhost:8888/other/location;jsessionid=12345?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"}, - {"/other/location;jsessionid=12345?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"}, - {"./location;jsessionid=12345?name=value","http://myhost:8888/path/location;jsessionid=12345?name=value"}, + {"/other/location;jsessionid=12345?name=value","http://@HOST@@PORT@/other/location;jsessionid=12345?name=value"}, + {"./location;jsessionid=12345?name=value","http://@HOST@@PORT@/path/location;jsessionid=12345?name=value"}, // From cookie - {"/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"}, - {"../locati%C3%abn", "http://myhost:8888/locati%C3%ABn"}, + {"/other/location","http://@HOST@@PORT@/other/location"}, + {"/other/l%20cation", "http://@HOST@@PORT@/other/l%20cation"}, + {"location", "http://@HOST@@PORT@/path/location"}, + {"./location", "http://@HOST@@PORT@/path/location"}, + {"../location", "http://@HOST@@PORT@/location"}, + {"/other/l%20cation", "http://@HOST@@PORT@/other/l%20cation"}, + {"l%20cation", "http://@HOST@@PORT@/path/l%20cation"}, + {"./l%20cation", "http://@HOST@@PORT@/path/l%20cation"}, + {"../l%20cation","http://@HOST@@PORT@/l%20cation"}, + {"../locati%C3%abn", "http://@HOST@@PORT@/locati%C3%ABn"}, + {"http://somehost.com/other/location","http://somehost.com/other/location"}, }; - for (int i=0;i2); - HashSessionManager manager = new HashSessionManager(); - manager.setSessionIdManager(new HashSessionIdManager()); - request.setSessionManager(manager); - request.setSession(new TestSession(manager, "12345")); - manager.setCheckingRemoteSessionIdEncoding(false); + request.setServerName(host); + request.setServerPort(port); + request.setUri(new HttpURI("/path/info;param;jsessionid=12345?query=0&more=1#target")); + request.setContextPath("/path"); + request.setRequestedSessionId("12345"); + request.setRequestedSessionIdFromCookie(i>2); + HashSessionManager manager = new HashSessionManager(); + manager.setSessionIdManager(new HashSessionIdManager()); + request.setSessionManager(manager); + request.setSession(new TestSession(manager, "12345")); + manager.setCheckingRemoteSessionIdEncoding(false); - response.sendRedirect(tests[i][0]); + response.sendRedirect(tests[i][0]); - String location = response.getHeader("Location"); - assertEquals("test-"+i,tests[i][1],location); + String location = response.getHeader("Location"); + + String expected=tests[i][1].replace("@HOST@",host.contains(":")?("["+host+"]"):host).replace("@PORT@",port==80?"":(":"+port)); + assertEquals("test-"+i+" "+host+":"+port,expected,location); + } + } } } diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index 736647f7dd4..ac1dca9ca56 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -27,7 +27,6 @@ import java.net.URL; import java.nio.ByteBuffer; import java.util.Enumeration; import java.util.List; -import java.util.Map; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; @@ -42,9 +41,8 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.PathMap.MappedEntry; import org.eclipse.jetty.io.WriterOutputStream; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.InclusiveByteRange; import org.eclipse.jetty.server.ResourceCache; @@ -267,7 +265,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory throw new UnavailableException(e.toString()); } - _servletHandler= (ServletHandler) _contextHandler.getChildHandlerByClass(ServletHandler.class); + _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class); for (ServletHolder h :_servletHandler.getServlets()) if (h.getServletInstance()==this) _defaultHolder=h; @@ -342,6 +340,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory * @param pathInContext The path to find a resource for. * @return The resource to serve. */ + @Override public Resource getResource(String pathInContext) { Resource r=null; @@ -631,7 +630,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null) { - Map.Entry entry=_servletHandler.getHolderEntry(welcome_in_context); + MappedEntry entry=_servletHandler.getHolderEntry(welcome_in_context); if (entry!=null && entry.getValue()!=_defaultHolder && (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context)))) welcome_servlet=welcome_in_context; @@ -827,24 +826,10 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory boolean include, Resource resource, HttpContent content, - Enumeration reqRanges) + Enumeration reqRanges) throws IOException { - boolean direct; - long content_length; - if (content==null) - { - direct=false; - content_length=resource.length(); - } - else - { - // TODO sometimes we should be direct! - Connector connector = HttpChannel.getCurrentHttpChannel().getConnector(); - direct=false; - content_length=content.getContentLength(); - } - + final long content_length = (content==null)?resource.length():content.getContentLength(); // Get the output stream (or writer) OutputStream out =null; @@ -883,17 +868,8 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory } else { - ByteBuffer buffer = direct?content.getDirectBuffer():content.getIndirectBuffer(); - if (buffer!=null) - { - writeHeaders(response,content,content_length); - ((HttpOutput)out).sendContent(buffer); - } - else - { - writeHeaders(response,content,content_length); - resource.writeTo(out,0,content_length); - } + writeHeaders(response,content,content_length); + ((HttpOutput)out).sendContent(content.getResource()); } } else @@ -913,7 +889,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory else { // Parse the satisfiable ranges - List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length); + List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length); // if there are no satisfiable ranges, send 416 response if (ranges==null || ranges.size()==0) @@ -930,8 +906,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory // since were here now), send that range with a 216 response if ( ranges.size()== 1) { - InclusiveByteRange singleSatisfiableRange = - (InclusiveByteRange)ranges.get(0); + InclusiveByteRange singleSatisfiableRange = ranges.get(0); long singleLength = singleSatisfiableRange.getSize(content_length); writeHeaders(response,content,singleLength ); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); @@ -946,7 +921,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory // content-length header // writeHeaders(response,content,-1); - String mimetype=(content.getContentType()==null?null:content.getContentType().toString()); + String mimetype=(content==null||content.getContentType()==null?null:content.getContentType().toString()); if (mimetype==null) LOG.warn("Unknown mimetype for "+request.getRequestURI()); MultiPartOutputStream multi = new MultiPartOutputStream(out); @@ -970,7 +945,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory String[] header = new String[ranges.size()]; for (int i=0;i0)?2:0)+ @@ -985,7 +960,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory for (int i=0;i _filterNameMappings; private final Map _servletNameMap=new HashMap<>(); - private PathMap _servletPathMap; + private PathMap _servletPathMap; - protected final ConcurrentMap _chainCache[] = new ConcurrentMap[FilterMapping.ALL]; - protected final Queue[] _chainLRU = new Queue[FilterMapping.ALL]; + protected final ConcurrentMap _chainCache[] = new ConcurrentMap[FilterMapping.ALL]; + protected final Queue[] _chainLRU = new Queue[FilterMapping.ALL]; /* ------------------------------------------------------------ */ @@ -253,7 +253,7 @@ public class ServletHandler extends ScopedHandler * @param pathInContext Path within _context. * @return PathMap Entries pathspec to ServletHolder */ - public PathMap.MappedEntry getHolderEntry(String pathInContext) + public PathMap.MappedEntry getHolderEntry(String pathInContext) { if (_servletPathMap==null) return null; @@ -334,10 +334,10 @@ public class ServletHandler extends ScopedHandler if (target.startsWith("/")) { // Look for the servlet by path - PathMap.MappedEntry entry=getHolderEntry(target); + PathMap.MappedEntry entry=getHolderEntry(target); if (entry!=null) { - servlet_holder=(ServletHolder)entry.getValue(); + servlet_holder=entry.getValue(); String servlet_path_spec= entry.getKey(); String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target); @@ -345,8 +345,8 @@ public class ServletHandler extends ScopedHandler if (DispatcherType.INCLUDE.equals(type)) { - baseRequest.setAttribute(Dispatcher.INCLUDE_SERVLET_PATH,servlet_path); - baseRequest.setAttribute(Dispatcher.INCLUDE_PATH_INFO, path_info); + baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,servlet_path); + baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, path_info); } else { @@ -618,8 +618,8 @@ public class ServletHandler extends ScopedHandler if (filters.size() > 0) chain= new CachedChain(filters, servletHolder); - final Map cache=_chainCache[dispatch]; - final Queue lru=_chainLRU[dispatch]; + final Map cache=(Map)_chainCache[dispatch]; + final Queue lru=(Queue)_chainLRU[dispatch]; // Do we have too many cached chains? while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize) @@ -721,7 +721,7 @@ public class ServletHandler extends ScopedHandler { if (servlet.getClassName() == null && servlet.getForcedPath() != null) { - ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlet.getForcedPath()); + ServletHolder forced_holder = _servletPathMap.match(servlet.getForcedPath()); if (forced_holder == null || forced_holder.getClassName() == null) { mx.add(new IllegalStateException("No forced path servlet for " + servlet.getForcedPath())); From 4e62b953e13acc1dd24f01f46ddf7bc750e67ed1 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 30 Apr 2013 16:00:17 +1000 Subject: [PATCH 22/28] 406861 Fixed IPv6 redirect --- .../java/org/eclipse/jetty/server/Request.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 1b92872f36d..09a9db50978 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 @@ -62,6 +62,7 @@ import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; @@ -1067,19 +1068,17 @@ public class Request implements HttpServletRequest { StringBuilder url = new StringBuilder(48); String scheme = getScheme(); + String server=getServerName(); int port = getServerPort(); - - url.append(scheme); - url.append("://"); - if (getServerName().contains(":")) - url.append('[').append(getServerName()).append(']'); + + if (server.indexOf(':')>=0) + url.append(scheme).append("://").append('[').append(server).append(']'); else - url.append(getServerName()); + url.append(scheme).append("://").append(getServerName()); - if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80) || (scheme.equalsIgnoreCase("https") && port != 443))) + if (port > 0 && ((HttpScheme.HTTP.is(scheme) && port != 80) || (HttpScheme.HTTPS.is(scheme) && port != 443))) { - url.append(':'); - url.append(port); + url.append(':').append(port); } return url; } From e26d8e67e5e39f4a8fdd720317a42296e1e48b05 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 30 Apr 2013 18:49:29 +1000 Subject: [PATCH 23/28] 406861 Fixed IPv6 redirect --- .../org/eclipse/jetty/http/HttpParser.java | 17 ++- .../java/org/eclipse/jetty/http/HttpURI.java | 2 + .../eclipse/jetty/http/HttpParserTest.java | 130 ++++++++++++++++- .../org/eclipse/jetty/http/HttpURITest.java | 54 ++++--- .../org/eclipse/jetty/server/Request.java | 68 ++++----- .../server/CheckReverseProxyHeadersTest.java | 43 +++++- .../org/eclipse/jetty/server/HttpURITest.java | 12 +- .../org/eclipse/jetty/server/RequestTest.java | 133 ++++++++++++------ .../java/org/eclipse/jetty/util/URIUtil.java | 24 ++++ 9 files changed, 374 insertions(+), 109 deletions(-) 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 39cf670d5db..82ea39ac0c8 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 @@ -609,7 +609,8 @@ public class HttpParser return true; } - loop: for (int i = host.length(); i-- > 0;) + int len=host.length(); + loop: for (int i = len; i-- > 0;) { char c2 = (char)(0xff & host.charAt(i)); switch (c2) @@ -620,6 +621,7 @@ public class HttpParser case ':': try { + len=i; port = StringUtil.toInt(host.substring(i+1)); } catch (NumberFormatException e) @@ -628,10 +630,21 @@ public class HttpParser badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Host header"); return true; } - host = host.substring(0,i); break loop; } } + if (host.charAt(0)=='[') + { + if (host.charAt(len-1)!=']') + { + badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header"); + return true; + } + host = host.substring(1,len-1); + } + else if (len!=host.length()) + host = host.substring(0,len); + if (_requestHandler!=null) _requestHandler.parsedHostHeader(host,port); diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java index f7297f2a61e..dcd00cf2174 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java @@ -546,6 +546,8 @@ public class HttpURI { if (_host==_port) return null; + if (_raw[_host]=='[') + return new String(_raw,_host+1,_port-_host-2,_charset); return new String(_raw,_host,_port-_host,_charset); } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index 16651a97669..33f6a9b8297 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -809,7 +809,132 @@ public class HttpParserTest assertFalse(buffer.hasRemaining()); assertEquals(HttpParser.State.CLOSED,parser.getState()); } + + @Test + public void testHost() throws Exception + { + ByteBuffer buffer= BufferUtil.toBuffer( + "GET / HTTP/1.1\015\012" + + "Host: host\015\012" + + "Connection: close\015\012" + + "\015\012"); + Handler handler = new Handler(); + HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + parser.parseNext(buffer); + assertEquals("host",_host); + assertEquals(0,_port); + } + + @Test + public void testIPHost() throws Exception + { + ByteBuffer buffer= BufferUtil.toBuffer( + "GET / HTTP/1.1\015\012" + + "Host: 192.168.0.1\015\012" + + "Connection: close\015\012" + + "\015\012"); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + parser.parseNext(buffer); + assertEquals("192.168.0.1",_host); + assertEquals(0,_port); + } + + @Test + public void testIPv6Host() throws Exception + { + ByteBuffer buffer= BufferUtil.toBuffer( + "GET / HTTP/1.1\015\012" + + "Host: [::1]\015\012" + + "Connection: close\015\012" + + "\015\012"); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + parser.parseNext(buffer); + assertEquals("::1",_host); + assertEquals(0,_port); + } + + @Test + public void testBadIPv6Host() throws Exception + { + ByteBuffer buffer= BufferUtil.toBuffer( + "GET / HTTP/1.1\015\012" + + "Host: [::1\015\012" + + "Connection: close\015\012" + + "\015\012"); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + parser.parseNext(buffer); + assertEquals("Bad IPv6 Host header",_bad); + } + + @Test + public void testHostPort() throws Exception + { + ByteBuffer buffer= BufferUtil.toBuffer( + "GET / HTTP/1.1\015\012" + + "Host: myhost:8888\015\012" + + "Connection: close\015\012" + + "\015\012"); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + parser.parseNext(buffer); + assertEquals("myhost",_host); + assertEquals(8888,_port); + } + + @Test + public void testHostBadPort() throws Exception + { + ByteBuffer buffer= BufferUtil.toBuffer( + "GET / HTTP/1.1\015\012" + + "Host: myhost:xxx\015\012" + + "Connection: close\015\012" + + "\015\012"); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + parser.parseNext(buffer); + assertEquals("Bad Host header",_bad); + } + + @Test + public void testIPHostPort() throws Exception + { + ByteBuffer buffer= BufferUtil.toBuffer( + "GET / HTTP/1.1\015\012" + + "Host: 192.168.0.1:8888\015\012" + + "Connection: close\015\012" + + "\015\012"); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + parser.parseNext(buffer); + assertEquals("192.168.0.1",_host); + assertEquals(8888,_port); + } + + @Test + public void testIPv6HostPort() throws Exception + { + ByteBuffer buffer= BufferUtil.toBuffer( + "GET / HTTP/1.1\015\012" + + "Host: [::1]:8888\015\012" + + "Connection: close\015\012" + + "\015\012"); + + Handler handler = new Handler(); + HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + parser.parseNext(buffer); + assertEquals("::1",_host); + assertEquals(8888,_port); + } @Before public void init() @@ -826,6 +951,8 @@ public class HttpParserTest _messageCompleted=false; } + private String _host; + private int _port; private String _bad; private String _content; private String _methodOrVersion; @@ -884,7 +1011,8 @@ public class HttpParserTest @Override public boolean parsedHostHeader(String host,int port) { - // TODO test this + _host=host; + _port=port; return false; } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java index 6262efd3680..52eb707736e 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java @@ -29,39 +29,51 @@ import org.junit.Test; /* ------------------------------------------------------------ */ public class HttpURITest { - public static final String __input = "http://example.com:8080/path/to/context?parameter=%22value%22#fragment"; - public static final String __scheme = "http"; - public static final String __host = "example.com"; - public static final int __port = 8080; - public static final String __path = "/path/to/context"; - public static final String __query = "parameter=%22value%22"; - public static final String __fragment = "fragment"; + String[][] tests= + { + {"/path/to/context",null,null,"-1","/path/to/context",null,null,null}, + {"http://example.com/path/to/context;param?query=%22value%22#fragment","http","example.com","-1","/path/to/context","param","query=%22value%22","fragment"}, + {"http://[::1]/path/to/context;param?query=%22value%22#fragment","http","::1","-1","/path/to/context","param","query=%22value%22","fragment"}, + {"http://example.com:8080/path/to/context;param?query=%22value%22#fragment","http","example.com","8080","/path/to/context","param","query=%22value%22","fragment"}, + {"http://[::1]:8080/path/to/context;param?query=%22value%22#fragment","http","::1","8080","/path/to/context","param","query=%22value%22","fragment"}, + }; + + public static int + INPUT=0,SCHEME=1,HOST=2,PORT=3,PATH=4,PARAM=5,QUERY=6,FRAGMENT=7; /* ------------------------------------------------------------ */ @Test public void testFromString() throws Exception { - HttpURI uri = new HttpURI(__input); + for (String[] test:tests) + { + HttpURI uri = new HttpURI(test[INPUT]); - assertEquals(__scheme, uri.getScheme()); - assertEquals(__host,uri.getHost()); - assertEquals(__port,uri.getPort()); - assertEquals(__path,uri.getPath()); - assertEquals(__query,uri.getQuery()); - assertEquals(__fragment,uri.getFragment()); + assertEquals(test[SCHEME], uri.getScheme()); + assertEquals(test[HOST], uri.getHost()); + assertEquals(Integer.parseInt(test[PORT]), uri.getPort()); + assertEquals(test[PATH], uri.getPath()); + assertEquals(test[PARAM], uri.getParam()); + assertEquals(test[QUERY], uri.getQuery()); + assertEquals(test[FRAGMENT], uri.getFragment()); + } } /* ------------------------------------------------------------ */ @Test public void testFromURI() throws Exception { - HttpURI uri = new HttpURI(new URI(__input)); + for (String[] test:tests) + { + HttpURI uri = new HttpURI(new URI(test[INPUT])); - assertEquals(__scheme, uri.getScheme()); - assertEquals(__host,uri.getHost()); - assertEquals(__port,uri.getPort()); - assertEquals(__path,uri.getPath()); - assertEquals(__query,uri.getQuery()); - assertEquals(__fragment,uri.getFragment()); + assertEquals(test[SCHEME], uri.getScheme()); + assertEquals(test[HOST], uri.getHost()); + assertEquals(Integer.parseInt(test[PORT]), uri.getPort()); + assertEquals(test[PATH], uri.getPath()); + assertEquals(test[PARAM], uri.getParam()); + assertEquals(test[QUERY], uri.getQuery()); + assertEquals(test[FRAGMENT], uri.getFragment()); + } } } 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 09a9db50978..1b981812552 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 @@ -1030,19 +1030,8 @@ public class Request implements HttpServletRequest @Override public StringBuffer getRequestURL() { - final StringBuffer url = new StringBuffer(48); - String scheme = getScheme(); - int port = getServerPort(); - - url.append(scheme); - url.append("://"); - url.append(getServerName()); - if (_port > 0 && ((scheme.equalsIgnoreCase(URIUtil.HTTP) && port != 80) || (scheme.equalsIgnoreCase(URIUtil.HTTPS) && port != 443))) - { - url.append(':'); - url.append(_port); - } - + final StringBuffer url = new StringBuffer(128); + URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort()); url.append(getRequestURI()); return url; } @@ -1066,20 +1055,8 @@ public class Request implements HttpServletRequest */ public StringBuilder getRootURL() { - StringBuilder url = new StringBuilder(48); - String scheme = getScheme(); - String server=getServerName(); - int port = getServerPort(); - - if (server.indexOf(':')>=0) - url.append(scheme).append("://").append('[').append(server).append(']'); - else - url.append(scheme).append("://").append(getServerName()); - - if (port > 0 && ((HttpScheme.HTTP.is(scheme) && port != 80) || (HttpScheme.HTTPS.is(scheme) && port != 443))) - { - url.append(':').append(port); - } + StringBuilder url = new StringBuilder(128); + URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort()); return url; } @@ -1109,41 +1086,58 @@ public class Request implements HttpServletRequest // Return host from absolute URI _serverName = _uri.getHost(); - _port = _uri.getPort(); if (_serverName != null) + { + _port = _uri.getPort(); return _serverName; + } // Return host from header field String hostPort = _fields.getStringField(HttpHeader.HOST); + + _port=0; if (hostPort != null) { - loop: for (int i = hostPort.length(); i-- > 0;) + int len=hostPort.length(); + loop: for (int i = len; i-- > 0;) { - char ch = (char)(0xff & hostPort.charAt(i)); - switch (ch) + char c2 = (char)(0xff & hostPort.charAt(i)); + switch (c2) { case ']': break loop; case ':': - _serverName = hostPort.substring(0,i); try { + len=i; _port = StringUtil.toInt(hostPort.substring(i+1)); } catch (NumberFormatException e) { LOG.warn(e); + _serverName=hostPort; + _port=0; + return _serverName; } - return _serverName; + break loop; } } - - if (_serverName == null || _port < 0) + if (hostPort.charAt(0)=='[') { - _serverName = hostPort; - _port = 0; + if (hostPort.charAt(len-1)!=']') + { + LOG.warn("Bad IPv6 "+hostPort); + _serverName=hostPort; + _port=0; + return _serverName; + } + _serverName = hostPort.substring(1,len-1); } + else if (len==hostPort.length()) + _serverName=hostPort; + else + _serverName = hostPort.substring(0,len); return _serverName; } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java index 58a22b77b05..f4eec3e86c9 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.util.concurrent.TimeUnit; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -44,6 +45,7 @@ public class CheckReverseProxyHeadersTest "X-Forwarded-For: 10.20.30.40\n" + "X-Forwarded-Host: example.com", new RequestValidator() { + @Override public void validate(HttpServletRequest request) { assertEquals("example.com", request.getServerName()); @@ -55,6 +57,42 @@ public class CheckReverseProxyHeadersTest assertFalse(request.isSecure()); } }); + + // IPv6 ProxyPass from example.com:80 to localhost:8080 + testRequest("Host: localhost:8080\n" + + "X-Forwarded-For: 10.20.30.40\n" + + "X-Forwarded-Host: [::1]", new RequestValidator() + { + @Override + public void validate(HttpServletRequest request) + { + assertEquals("::1", request.getServerName()); + assertEquals(80, request.getServerPort()); + assertEquals("10.20.30.40", request.getRemoteAddr()); + assertEquals("10.20.30.40", request.getRemoteHost()); + assertEquals("[::1]", request.getHeader("Host")); + assertEquals("http",request.getScheme()); + assertFalse(request.isSecure()); + } + }); + + // IPv6 ProxyPass from example.com:80 to localhost:8080 + testRequest("Host: localhost:8080\n" + + "X-Forwarded-For: 10.20.30.40\n" + + "X-Forwarded-Host: [::1]:8888", new RequestValidator() + { + @Override + public void validate(HttpServletRequest request) + { + assertEquals("::1", request.getServerName()); + assertEquals(8888, request.getServerPort()); + assertEquals("10.20.30.40", request.getRemoteAddr()); + assertEquals("10.20.30.40", request.getRemoteHost()); + assertEquals("[::1]:8888", request.getHeader("Host")); + assertEquals("http",request.getScheme()); + assertFalse(request.isSecure()); + } + }); // ProxyPass from example.com:81 to localhost:8080 testRequest("Host: localhost:8080\n" + @@ -63,6 +101,7 @@ public class CheckReverseProxyHeadersTest "X-Forwarded-Server: example.com\n"+ "X-Forwarded-Proto: https", new RequestValidator() { + @Override public void validate(HttpServletRequest request) { assertEquals("example.com", request.getServerName()); @@ -82,6 +121,7 @@ public class CheckReverseProxyHeadersTest "X-Forwarded-Server: example.com, rp.example.com\n"+ "X-Forwarded-Proto: https, http", new RequestValidator() { + @Override public void validate(HttpServletRequest request) { assertEquals("example.com", request.getServerName()); @@ -111,7 +151,8 @@ public class CheckReverseProxyHeadersTest try { server.start(); - connector.getResponses("GET / HTTP/1.1\r\n" +"Connection: close\r\n" + headers + "\r\n\r\n"); + connector.getResponses("GET / HTTP/1.1\r\n" +"Connection: close\r\n" + headers + "\r\n\r\n", + 1000,TimeUnit.SECONDS); Error error = validationHandler.getError(); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java index 6cb0a2b6003..a770f95c786 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java @@ -135,11 +135,11 @@ public class HttpURITest /*33*/ {"/?abc=test",null, null, null,null,"/", null,"abc=test",null}, /*34*/ {"/#fragment",null, null, null,null,"/", null,null,"fragment"}, /*35*/ {"http://192.0.0.1:8080/","http","//192.0.0.1:8080","192.0.0.1","8080","/",null,null,null}, - /*36*/ {"http://[2001:db8::1]:8080/","http","//[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null}, - /*37*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null}, - /*38*/ {"http://[2001:db8::1]/","http","//[2001:db8::1]","[2001:db8::1]",null,"/",null,null,null}, + /*36*/ {"http://[2001:db8::1]:8080/","http","//[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null}, + /*37*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null}, + /*38*/ {"http://[2001:db8::1]/","http","//[2001:db8::1]","2001:db8::1",null,"/",null,null,null}, /*39*/ {"//[2001:db8::1]:8080/",null,null,null,null,"//[2001:db8::1]:8080/",null,null,null}, - /*40*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null}, + /*40*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null}, /*41*/ {"*",null,null,null,null,"*",null, null,null} }; @@ -366,7 +366,7 @@ public class HttpURITest { /* 0*/ {" localhost:8080 ","localhost","8080"}, /* 1*/ {" 127.0.0.1:8080 ","127.0.0.1","8080"}, - /* 2*/ {" [127::0::0::1]:8080 ","[127::0::0::1]","8080"}, + /* 2*/ {" [127::0::0::1]:8080 ","127::0::0::1","8080"}, /* 3*/ {" error ",null,null}, /* 4*/ {" http://localhost:8080/ ",null,null}, }; @@ -382,7 +382,7 @@ public class HttpURITest byte[] buf = connect_tests[i][0].getBytes(StringUtil.__UTF8); uri.parseConnect(buf,2,buf.length-4); - assertEquals("path"+i,connect_tests[i][1]+":"+connect_tests[i][2],uri.getPath()); + assertEquals("path"+i,connect_tests[i][0].trim(),uri.getPath()); assertEquals("host"+i,connect_tests[i][1],uri.getHost()); assertEquals("port"+i,Integer.parseInt(connect_tests[i][2]),uri.getPort()); } 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 6bb09c19430..218ba1f137b 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 @@ -354,6 +354,7 @@ public class RequestTest @Override public boolean check(HttpServletRequest request,HttpServletResponse response) { + results.add(request.getRequestURL().toString()); results.add(request.getRemoteAddr()); results.add(request.getServerName()); results.add(String.valueOf(request.getServerPort())); @@ -361,71 +362,121 @@ public class RequestTest } }; - String responses=_connector.getResponses( + results.clear(); + _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ - "\n"+ - + "Connection: close\n"+ + "\n"); + int i=0; + assertEquals("http://myhost/",results.get(i++)); + assertEquals("0.0.0.0",results.get(i++)); + assertEquals("myhost",results.get(i++)); + assertEquals("80",results.get(i++)); + + + results.clear(); + _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: myhost:8888\n"+ - "\n"+ - + "Connection: close\n"+ + "\n"); + i=0; + assertEquals("http://myhost:8888/",results.get(i++)); + assertEquals("0.0.0.0",results.get(i++)); + assertEquals("myhost",results.get(i++)); + assertEquals("8888",results.get(i++)); + + + results.clear(); + _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: 1.2.3.4\n"+ - "\n"+ - + "Connection: close\n"+ + "\n"); + i=0; + + assertEquals("http://1.2.3.4/",results.get(i++)); + assertEquals("0.0.0.0",results.get(i++)); + assertEquals("1.2.3.4",results.get(i++)); + assertEquals("80",results.get(i++)); + + + results.clear(); + _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: 1.2.3.4:8888\n"+ - "\n"+ - + "Connection: close\n"+ + "\n"); + i=0; + assertEquals("http://1.2.3.4:8888/",results.get(i++)); + assertEquals("0.0.0.0",results.get(i++)); + assertEquals("1.2.3.4",results.get(i++)); + assertEquals("8888",results.get(i++)); + + + results.clear(); + _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: [::1]\n"+ - "\n"+ - + "Connection: close\n"+ + "\n"); + i=0; + assertEquals("http://[::1]/",results.get(i++)); + assertEquals("0.0.0.0",results.get(i++)); + assertEquals("::1",results.get(i++)); + assertEquals("80",results.get(i++)); + + + results.clear(); + _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: [::1]:8888\n"+ - "\n"+ - + "Connection: close\n"+ + "\n"); + i=0; + assertEquals("http://[::1]:8888/",results.get(i++)); + assertEquals("0.0.0.0",results.get(i++)); + assertEquals("::1",results.get(i++)); + assertEquals("8888",results.get(i++)); + + + results.clear(); + _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: [::1]\n"+ "x-forwarded-for: remote\n"+ "x-forwarded-proto: https\n"+ - "\n"+ - + "Connection: close\n"+ + "\n"); + i=0; + assertEquals("https://[::1]/",results.get(i++)); + assertEquals("remote",results.get(i++)); + assertEquals("::1",results.get(i++)); + assertEquals("443",results.get(i++)); + + + results.clear(); + _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: [::1]:8888\n"+ "Connection: close\n"+ "x-forwarded-for: remote\n"+ "x-forwarded-proto: https\n"+ - "\n",10,TimeUnit.SECONDS); - - - int i=0; - assertEquals("0.0.0.0",results.get(i++)); - assertEquals("myhost",results.get(i++)); - assertEquals("80",results.get(i++)); - assertEquals("0.0.0.0",results.get(i++)); - assertEquals("myhost",results.get(i++)); - assertEquals("8888",results.get(i++)); - assertEquals("0.0.0.0",results.get(i++)); - assertEquals("1.2.3.4",results.get(i++)); - assertEquals("80",results.get(i++)); - assertEquals("0.0.0.0",results.get(i++)); - assertEquals("1.2.3.4",results.get(i++)); - assertEquals("8888",results.get(i++)); - assertEquals("0.0.0.0",results.get(i++)); - assertEquals("[::1]",results.get(i++)); - assertEquals("80",results.get(i++)); - assertEquals("0.0.0.0",results.get(i++)); - assertEquals("[::1]",results.get(i++)); - assertEquals("8888",results.get(i++)); + "\n"); + i=0; + + assertEquals("https://[::1]:8888/",results.get(i++)); assertEquals("remote",results.get(i++)); - assertEquals("[::1]",results.get(i++)); - assertEquals("443",results.get(i++)); - assertEquals("remote",results.get(i++)); - assertEquals("[::1]",results.get(i++)); + assertEquals("::1",results.get(i++)); assertEquals("8888",results.get(i++)); + + + + + + } @Test diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index a3e549e6003..ddae8d052ee 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -657,6 +657,30 @@ public class URIUtil return false; } + public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port) + { + if (server.indexOf(':')>=0&&server.charAt(0)!='[') + url.append(scheme).append("://").append('[').append(server).append(']'); + else + url.append(scheme).append("://").append(server); + + if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443))) + url.append(':').append(port); + } + + public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port) + { + synchronized (url) + { + if (server.indexOf(':')>=0&&server.charAt(0)!='[') + url.append(scheme).append("://").append('[').append(server).append(']'); + else + url.append(scheme).append("://").append(server); + + if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443))) + url.append(':').append(port); + } + } } From b67f8204a5b131085c8ed2a7e2c1cc9ff7392905 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 30 Apr 2013 12:24:40 +0200 Subject: [PATCH 24/28] Reverted commit 39d690e. --- .../org/eclipse/jetty/io/SelectorManager.java | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java index 317c55ef6d3..f8b13277259 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java @@ -35,7 +35,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -167,20 +166,6 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa selector.submit(selector.new Accept(channel)); } - /** - *

Registers a channel to perform non-blocking read/write operations.

- *

This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()}, - * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.

- * - * @param channel the channel to register - * @param attachment An attachment to be passed via the selection key to the {@link SelectorManager#newConnection(SocketChannel, EndPoint, Object)} method. - */ - public void accept(final SocketChannel channel, Object attachment) - { - final ManagedSelector selector = chooseSelector(); - selector.submit(selector.new Accept(channel, attachment)); - } - @Override protected void doStart() throws Exception { @@ -333,7 +318,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable { private final Queue _changes = new ConcurrentArrayQueue<>(); - + private final int _id; private Selector _selector; private volatile Thread _thread; @@ -699,18 +684,10 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa private class Accept implements Runnable { private final SocketChannel _channel; - private final Object _attachment; public Accept(SocketChannel channel) { this._channel = channel; - this._attachment = null; - } - - public Accept(SocketChannel channel, Object attachment) - { - this._channel = channel; - this._attachment = attachment; } @Override @@ -718,7 +695,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { try { - SelectionKey key = _channel.register(_selector, 0, _attachment); + SelectionKey key = _channel.register(_selector, 0, null); EndPoint endpoint = createEndPoint(_channel, key); key.attach(endpoint); } From 9ad5ab1ed5e5a74e3874155d7ed580d9f02913a2 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 30 Apr 2013 15:53:42 -0700 Subject: [PATCH 25/28] 406449 - Session's disconnect not detected + Vastly cleaned up IOState and ConnectionState behavior --- .../jetty/websocket/client/TimeoutTest.java | 4 +- .../websocket/client/TrackingSocket.java | 31 +- .../client/blockhead/BlockheadServer.java | 7 + .../websocket/common/ConnectionState.java | 30 ++ .../websocket/common/WebSocketSession.java | 52 ++- .../websocket/common/events/EventDriver.java | 11 +- .../common/extensions/mux/MuxChannel.java | 16 +- .../io/AbstractWebSocketConnection.java | 86 ++--- .../jetty/websocket/common/io/IOState.java | 363 ++++++++++++++---- .../common/io/WriteBytesProvider.java | 4 + .../common/events/EventDriverTest.java | 5 +- .../websocket/common/io/IOStateTest.java | 245 ++++++++++++ .../common/io/LocalWebSocketConnection.java | 43 ++- .../server/WebSocketServerConnection.java | 7 - .../server/blockhead/BlockheadClient.java | 62 +-- .../server/browser/BrowserDebugTool.java | 3 + .../examples/echo/ExampleEchoServer.java | 26 +- .../test/resources/jetty-logging.properties | 1 - 18 files changed, 780 insertions(+), 216 deletions(-) create mode 100644 jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java index f404e6c0cba..ecfd50ceb29 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java @@ -104,8 +104,8 @@ public class TimeoutTest // Make sure idle timeout takes less than 5 total seconds Assert.assertThat("Idle Timeout",dur,lessThanOrEqualTo(5000L)); - // Client should see a close event, with status NO_CLOSE - wsocket.assertCloseCode(StatusCode.NORMAL); + // Client should see a close event, with status SHUTDOWN + wsocket.assertCloseCode(StatusCode.SHUTDOWN); } finally { diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java index c55c10385b5..eb1c4ae5657 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java @@ -20,13 +20,12 @@ package org.eclipse.jetty.websocket.client; import static org.hamcrest.Matchers.*; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.Session; @@ -46,8 +45,8 @@ public class TrackingSocket extends WebSocketAdapter public CountDownLatch openLatch = new CountDownLatch(1); public CountDownLatch closeLatch = new CountDownLatch(1); public CountDownLatch dataLatch = new CountDownLatch(1); - public BlockingQueue messageQueue = new BlockingArrayQueue(); - public BlockingQueue errorQueue = new BlockingArrayQueue<>(); + public EventQueue messageQueue = new EventQueue<>(); + public EventQueue errorQueue = new EventQueue<>(); public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException { @@ -93,29 +92,9 @@ public class TrackingSocket extends WebSocketAdapter Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true)); } - public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException + public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException, InterruptedException { - long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit); - long now = System.currentTimeMillis(); - long expireOn = now + msDur; - LOG.debug("Await Message.. Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur); - - while (messageQueue.size() < expectedMessageCount) - { - try - { - TimeUnit.MILLISECONDS.sleep(20); - } - catch (InterruptedException gnore) - { - /* ignore */ - } - if (!LOG.isDebugEnabled() && (System.currentTimeMillis() > expireOn)) - { - throw new TimeoutException(String.format("Timeout reading all %d expected messages. (managed to only read %d messages)",expectedMessageCount, - messageQueue.size())); - } - } + messageQueue.awaitEventCount(expectedMessageCount,timeoutDuration,timeoutUnit); } public void clear() diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java index b4d5ea4ba11..cb1936824c0 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java @@ -54,6 +54,7 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.api.extensions.Frame; +import org.eclipse.jetty.websocket.api.extensions.Frame.Type; import org.eclipse.jetty.websocket.api.extensions.IncomingFrames; import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames; import org.eclipse.jetty.websocket.common.AcceptHash; @@ -219,6 +220,12 @@ public class BlockheadServer } WebSocketFrame copy = new WebSocketFrame(frame); incomingFrames.incomingFrame(copy); + + if (frame.getType() == Type.CLOSE) + { + CloseInfo close = new CloseInfo(frame); + LOG.debug("Close frame: {}",close); + } } @Override diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java index d40073f6b38..aefc7193a50 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/ConnectionState.java @@ -18,13 +18,43 @@ package org.eclipse.jetty.websocket.common; +import org.eclipse.jetty.websocket.common.io.IOState; + /** * Connection states as outlined in RFC6455. */ public enum ConnectionState { + /** [RFC] Initial state of a connection, the upgrade request / response is in progress */ CONNECTING, + /** + * [Impl] Intermediate state between CONNECTING and OPEN, used to indicate that a upgrade request/response is successful, but the end-user provided socket's + * onOpen code has yet to run. + *

+ * This state is to allow the local socket to initiate messages and frames, but to NOT start reading yet. + */ + CONNECTED, + /** + * [RFC] The websocket connection is established and open. + *

+ * This indicates that the Upgrade has succeed, and the end-user provided socket's onOpen code has completed. + *

+ * It is now time to start reading from the remote endpoint. + */ OPEN, + /** + * [RFC] The websocket closing handshake is started. + *

+ * This can be considered a half-closed state. + *

+ * When receiving this as an event on {@link IOState.ConnectionStateListener#onConnectionStateChange(ConnectionState)} a close frame should be sent using + * the {@link CloseInfo} available from {@link IOState#getCloseInfo()} + */ CLOSING, + /** + * [RFC] The websocket connection is closed. + *

+ * Connection should be disconnected and no further reads or writes should occur. + */ CLOSED; } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index 8abdfff1d69..7ca103d5b28 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -48,9 +48,11 @@ import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.IncomingFrames; import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames; import org.eclipse.jetty.websocket.common.events.EventDriver; +import org.eclipse.jetty.websocket.common.io.IOState; +import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; -@ManagedObject -public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames +@ManagedObject("A Jetty WebSocket Session") +public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames, ConnectionStateListener { private static final Logger LOG = Log.getLogger(WebSocketSession.class); private final URI requestURI; @@ -80,6 +82,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc this.outgoingHandler = connection; this.incomingHandler = websocket; + this.connection.getIOState().addListener(this); + // Get the parameter map (use the jetty MultiMap to do this right) MultiMap params = new MultiMap<>(); String query = requestURI.getQuery(); @@ -254,6 +258,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc return remote.getInetSocketAddress(); } + public URI getRequestURI() + { + return requestURI; + } + @Override public UpgradeRequest getUpgradeRequest() { @@ -281,12 +290,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc @Override public void incomingError(WebSocketException e) { - if (connection.getIOState().isInputClosed()) + if (connection.getIOState().isInputAvailable()) { - return; // input is closed + // Forward Errors to User WebSocket Object + websocket.incomingError(e); } - // Forward Errors to User WebSocket Object - websocket.incomingError(e); } /** @@ -295,13 +303,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc @Override public void incomingFrame(Frame frame) { - if (connection.getIOState().isInputClosed()) + if (connection.getIOState().isInputAvailable()) { - return; // input is closed + // Forward Frames Through Extension List + incomingHandler.incomingFrame(frame); } - - // Forward Frames Through Extension List - incomingHandler.incomingFrame(frame); } @Override @@ -332,6 +338,24 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc websocket.onClose(new CloseInfo(statusCode,reason)); } + @Override + public void onConnectionStateChange(ConnectionState state) + { + if (state == ConnectionState.CLOSED) + { + IOState ioState = this.connection.getIOState(); + // The session only cares about abnormal close, as we need to notify + // the endpoint of this close scenario. + if (ioState.wasAbnormalClose()) + { + CloseInfo close = ioState.getCloseInfo(); + LOG.debug("Detected abnormal close: {}",close); + // notify local endpoint + notifyClose(close.getStatusCode(),close.getReason()); + } + } + } + /** * Open/Activate the session * @@ -345,12 +369,18 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc return; } + // Upgrade success + connection.getIOState().onConnected(); + // Connect remote remote = new WebSocketRemoteEndpoint(connection,outgoingHandler); // Open WebSocket websocket.openSession(this); + // Open connection + connection.getIOState().onOpened(); + if (LOG.isDebugEnabled()) { LOG.debug("open -> {}",dump()); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java index bf74188947e..d217937d478 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java @@ -103,16 +103,7 @@ public abstract class EventDriver implements IncomingFrames onClose(close); // process handshake - if (session.getConnection().getIOState().onCloseHandshake(true)) - { - // handshake resolved, disconnect. - session.getConnection().disconnect(); - } - else - { - // respond - session.close(close.getStatusCode(),close.getReason()); - } + session.getConnection().getIOState().onCloseRemote(close); return; } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java index b8bd82508fb..c3c42ce4491 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java @@ -38,11 +38,12 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.io.FutureWriteCallback; import org.eclipse.jetty.websocket.common.io.IOState; +import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; /** * MuxChannel, acts as WebSocketConnection for specific sub-channel. */ -public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken +public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken, ConnectionStateListener { private static final Logger LOG = Log.getLogger(MuxChannel.class); @@ -65,7 +66,7 @@ public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendTok this.suspendToken = new AtomicBoolean(false); this.ioState = new IOState(); - ioState.setState(ConnectionState.CONNECTING); + this.ioState.addListener(this); this.inputClosed = new AtomicBoolean(false); this.outputClosed = new AtomicBoolean(false); @@ -88,7 +89,6 @@ public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendTok @Override public void disconnect() { - this.ioState.setState(ConnectionState.CLOSED); // TODO: disconnect the virtual end-point? } @@ -173,12 +173,18 @@ public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendTok public void onClose() { - this.ioState.setState(ConnectionState.CLOSED); + } + + @Override + public void onConnectionStateChange(ConnectionState state) + { + // TODO Auto-generated method stub + } public void onOpen() { - this.ioState.setState(ConnectionState.OPEN); + this.ioState.onOpened(); } /** diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index f3dffcbb2c4..cf37519d0a3 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -56,11 +56,12 @@ import org.eclipse.jetty.websocket.common.Generator; import org.eclipse.jetty.websocket.common.LogicalConnection; import org.eclipse.jetty.websocket.common.Parser; import org.eclipse.jetty.websocket.common.WebSocketSession; +import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; /** * Provides the implementation of {@link WebSocketConnection} within the framework of the new {@link Connection} framework of jetty-io */ -public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection +public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, ConnectionStateListener { private class FlushCallback implements Callback { @@ -141,21 +142,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } } - private class OnCloseCallback implements WriteCallback - { - @Override - public void writeFailed(Throwable x) - { - disconnect(); - } - - @Override - public void writeSuccess() - { - onWriteWebSocketClose(); - } - } - public static class Stats { private AtomicLong countFillInterestedEvents = new AtomicLong(0); @@ -211,7 +197,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp this.extensions = new ArrayList<>(); this.suspendToken = new AtomicBoolean(false); this.ioState = new IOState(); - this.ioState.setState(ConnectionState.CONNECTING); + this.ioState.addListener(this); this.writeBytes = new WriteBytesProvider(generator,new FlushCallback()); this.setInputBufferSize(policy.getInputBufferSize()); } @@ -247,12 +233,18 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp @Override public void disconnect() { + synchronized (writeBytes) + { + if (!writeBytes.isClosed()) + { + writeBytes.close(); + } + } disconnect(false); } public void disconnect(boolean onlyOutput) { - ioState.setState(ConnectionState.CLOSED); EndPoint endPoint = getEndPoint(); // We need to gently close first, to allow // SSL close alerts to be sent by Jetty @@ -276,18 +268,8 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp */ private void enqueClose(int statusCode, String reason) { - synchronized (writeBytes) - { - // It is possible to get close events from many different sources. - // Make sure we only sent 1 over the network. - if (writeBytes.isClosed()) - { - // already sent the close - return; - } - } CloseInfo close = new CloseInfo(statusCode,reason); - outgoingFrame(close.asFrame(),new OnCloseCallback()); + ioState.onCloseLocal(close); } protected void execute(Runnable task) @@ -438,10 +420,31 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp public void onClose() { super.onClose(); - this.getIOState().setState(ConnectionState.CLOSED); writeBytes.close(); } + @Override + public void onConnectionStateChange(ConnectionState state) + { + LOG.debug("Connection State Change: {}",state); + switch (state) + { + case OPEN: + LOG.debug("fillInterested"); + fillInterested(); + break; + case CLOSED: + this.disconnect(); + break; + case CLOSING: + CloseInfo close = ioState.getCloseInfo(); + // append close frame + outgoingFrame(close.asFrame(),null); + default: + break; + } + } + @Override public void onFillable() { @@ -482,18 +485,16 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp public void onOpen() { super.onOpen(); - this.ioState.setState(ConnectionState.OPEN); - LOG.debug("fillInterested"); - fillInterested(); + this.ioState.onOpened(); } @Override protected boolean onReadTimeout() { - LOG.info("Read Timeout"); + LOG.debug("Read Timeout"); IOState state = getIOState(); - if ((state.getState() == ConnectionState.CLOSING) || (state.getState() == ConnectionState.CLOSED)) + if ((state.getConnectionState() == ConnectionState.CLOSING) || (state.getConnectionState() == ConnectionState.CLOSED)) { // close already initiated, extra timeouts not relevant // allow underlying connection and endpoint to disconnect on its own @@ -501,24 +502,12 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } // Initiate close - politely send close frame. - // Note: it is not possible in 100% of cases during read timeout to send this close frame. session.incomingError(new WebSocketTimeoutException("Timeout on Read")); - session.close(StatusCode.NORMAL,"Idle Timeout"); - - // Force closure of writeBytes - writeBytes.close(); + close(StatusCode.SHUTDOWN,"Idle Timeout"); return false; } - public void onWriteWebSocketClose() - { - if (ioState.onCloseHandshake(false)) - { - disconnect(); - } - } - /** * Frame from API, User, or Internal implementation destined for network. */ @@ -550,6 +539,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp else if (filled < 0) { LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress()); + ioState.onReadEOF(); return -1; } else diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java index e90ab7ecc5a..3836ae6633d 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java @@ -19,39 +19,79 @@ package org.eclipse.jetty.websocket.common.io; import java.io.IOException; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.ConnectionState; /** - * Simple state tracker for Input / Output and {@link ConnectionState} + * Simple state tracker for Input / Output and {@link ConnectionState}. + *

+ * Use the various known .on*() methods to trigger a state change. + *

    + *
  • {@link #onOpened()} - connection has been opened
  • + *
*/ public class IOState { + /** + * The source of a close handshake. (ie: who initiated it). + */ + private static enum CloseHandshakeSource + { + /** No close handshake initiated (yet) */ + NONE, + /** Local side initiated the close handshake */ + LOCAL, + /** Remote side initiated the close handshake */ + REMOTE, + /** An abnormal close situation (disconnect, timeout, etc...) */ + ABNORMAL; + } + + public static interface ConnectionStateListener + { + public void onConnectionStateChange(ConnectionState state); + } + private static final Logger LOG = Log.getLogger(IOState.class); private ConnectionState state; - private final AtomicBoolean inputClosed; - private final AtomicBoolean outputClosed; + private final List listeners = new CopyOnWriteArrayList<>(); + + private final AtomicBoolean inputAvailable; + private final AtomicBoolean outputAvailable; + private final AtomicReference closeHandshakeSource; + private final AtomicReference closeInfo; private final AtomicBoolean cleanClose; - private final AtomicBoolean remoteCloseInitiated; - private final AtomicBoolean localCloseInitiated; + /** + * Create a new IOState, initialized to {@link ConnectionState#CONNECTING} + */ public IOState() { this.state = ConnectionState.CONNECTING; - this.inputClosed = new AtomicBoolean(false); - this.outputClosed = new AtomicBoolean(false); - this.remoteCloseInitiated = new AtomicBoolean(false); - this.localCloseInitiated = new AtomicBoolean(false); + this.inputAvailable = new AtomicBoolean(false); + this.outputAvailable = new AtomicBoolean(false); + this.closeHandshakeSource = new AtomicReference<>(CloseHandshakeSource.NONE); + this.closeInfo = new AtomicReference<>(); this.cleanClose = new AtomicBoolean(false); } + public void addListener(ConnectionStateListener listener) + { + listeners.add(listener); + } + public void assertInputOpen() throws IOException { - if (isInputClosed()) + if (!isInputAvailable()) { throw new IOException("Connection input is closed"); } @@ -59,15 +99,15 @@ public class IOState public void assertOutputOpen() throws IOException { - if (isOutputClosed()) + if (!isOutputAvailable()) { throw new IOException("Connection output is closed"); } } - public boolean awaitClosed(long duration) + public CloseInfo getCloseInfo() { - return (isInputClosed() && isOutputClosed()); + return closeInfo.get(); } public ConnectionState getConnectionState() @@ -75,90 +115,283 @@ public class IOState return state; } - public ConnectionState getState() - { - return state; - } - public boolean isClosed() { - return (isInputClosed() && isOutputClosed()); + synchronized (state) + { + return (state == ConnectionState.CLOSED); + } } - public boolean isCloseInitiated() + public boolean isInputAvailable() { - return remoteCloseInitiated.get() || localCloseInitiated.get(); - } - - public boolean isInputClosed() - { - return inputClosed.get(); + return inputAvailable.get(); } public boolean isOpen() { - return (getState() != ConnectionState.CLOSED); + return (getConnectionState() != ConnectionState.CLOSED); } - public boolean isOutputClosed() + public boolean isOutputAvailable() { - return outputClosed.get(); + return outputAvailable.get(); + } + + private void notifyStateListeners(ConnectionState state) + { + for (ConnectionStateListener listener : listeners) + { + listener.onConnectionStateChange(state); + } } /** - * Test for if connection should disconnect or response on a close handshake. - * - * @param incoming - * true if incoming close - * @param close - * the close details. - * @return true if connection should be disconnected now, or false if response to close should be issued. + * A websocket connection has been disconnected for abnormal close reasons. + *

+ * This is the low level disconnect of the socket. It could be the result of a normal close operation, from an IO error, or even from a timeout. */ - public boolean onCloseHandshake(boolean incoming) + public void onAbnormalClose(CloseInfo close) { - boolean in = inputClosed.get(); - boolean out = outputClosed.get(); - if (incoming) + ConnectionState event = null; + synchronized (this.state) { - in = true; - this.inputClosed.set(true); - - if (!localCloseInitiated.get()) + if (this.state == ConnectionState.CLOSED) { - remoteCloseInitiated.set(true); + // already closed + return; } - } - else - { - out = true; - this.outputClosed.set(true); - if ( !remoteCloseInitiated.get() ) + if (this.state == ConnectionState.OPEN) { - localCloseInitiated.set(true); + this.cleanClose.set(false); + } + + this.state = ConnectionState.CLOSED; + this.closeInfo.compareAndSet(null,close); + this.inputAvailable.set(false); + this.outputAvailable.set(false); + this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL); + event = this.state; + } + notifyStateListeners(event); + } + + /** + * A close handshake has been issued from the local endpoint + */ + public void onCloseLocal(CloseInfo close) + { + ConnectionState event = null; + ConnectionState initialState = this.state; + if (initialState == ConnectionState.CLOSED) + { + // already closed + return; + } + + if (initialState == ConnectionState.CONNECTED) + { + // fast close. a local close request from end-user onConnected() method + LOG.debug("FastClose in CONNECTED detected"); + // Force the state open (to allow read/write to endpoint) + onOpened(); + } + + synchronized (this.state) + { + closeInfo.compareAndSet(null,close); + + boolean in = inputAvailable.get(); + boolean out = outputAvailable.get(); + closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.LOCAL); + out = false; + outputAvailable.set(false); + + LOG.debug("onCloseLocal(), input={}, output={}",in,out); + + if (!in && !out) + { + LOG.debug("Close Handshake satisfied, disconnecting"); + cleanClose.set(true); + this.state = ConnectionState.CLOSED; + event = this.state; + } + else if (this.state == ConnectionState.OPEN) + { + // We are now entering CLOSING (or half-closed) + this.state = ConnectionState.CLOSING; + event = this.state; } } - LOG.debug("onCloseHandshake({}), input={}, output={}",incoming,in,out); - - if (in && out) + // Only notify on state change events + if (event != null) { - LOG.debug("Close Handshake satisfied, disconnecting"); - cleanClose.set(true); - return true; + notifyStateListeners(event); + + // if SHUTDOWN, we don't expect an answer. + if (close.getStatusCode() == StatusCode.SHUTDOWN) + { + synchronized (this.state) + { + this.state = ConnectionState.CLOSED; + cleanClose.set(false); + outputAvailable.set(false); + inputAvailable.set(false); + this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL); + event = this.state; + } + notifyStateListeners(event); + return; + } + } + } + + /** + * A close handshake has been received from the remote endpoint + */ + public void onCloseRemote(CloseInfo close) + { + ConnectionState event = null; + synchronized (this.state) + { + if (this.state == ConnectionState.CLOSED) + { + // already closed + return; + } + + closeInfo.compareAndSet(null,close); + + boolean in = inputAvailable.get(); + boolean out = outputAvailable.get(); + closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.REMOTE); + in = false; + inputAvailable.set(false); + + LOG.debug("onCloseRemote(), input={}, output={}",in,out); + + if (!in && !out) + { + LOG.debug("Close Handshake satisfied, disconnecting"); + cleanClose.set(true); + this.state = ConnectionState.CLOSED; + event = this.state; + } + else if (this.state == ConnectionState.OPEN) + { + // We are now entering CLOSING (or half-closed) + this.state = ConnectionState.CLOSING; + event = this.state; + } } - return false; + // Only notify on state change events + if (event != null) + { + notifyStateListeners(event); + } } - public void setConnectionState(ConnectionState connectionState) + /** + * WebSocket has successfully upgraded, but the end-user onOpen call hasn't run yet. + *

+ * This is an intermediate state between the RFC's {@link ConnectionState#CONNECTING} and {@link ConnectionState#OPEN} + */ + public void onConnected() { - this.state = connectionState; + if (this.state != ConnectionState.CONNECTING) + { + LOG.debug("Unable to set to connected, not in CONNECTING state: {}",this.state); + return; + } + + ConnectionState event = null; + synchronized (this.state) + { + this.state = ConnectionState.CONNECTED; + this.inputAvailable.set(false); // cannot read (yet) + this.outputAvailable.set(true); // write allowed + event = this.state; + } + notifyStateListeners(event); } - public void setState(ConnectionState state) + /** + * A websocket connection has failed its upgrade handshake, and is now closed. + */ + public void onFailedUpgrade() { - this.state = state; + assert (this.state == ConnectionState.CONNECTING); + ConnectionState event = null; + synchronized (this.state) + { + this.state = ConnectionState.CLOSED; + this.cleanClose.set(false); + this.inputAvailable.set(false); + this.outputAvailable.set(false); + event = this.state; + } + notifyStateListeners(event); + } + + /** + * A websocket connection has finished its upgrade handshake, and is now open. + */ + public void onOpened() + { + if (this.state != ConnectionState.CONNECTED) + { + LOG.debug("Unable to open, not in CONNECTED state: {}",this.state); + return; + } + + assert (this.state == ConnectionState.CONNECTED); + + ConnectionState event = null; + synchronized (this.state) + { + this.state = ConnectionState.OPEN; + this.inputAvailable.set(true); + this.outputAvailable.set(true); + event = this.state; + } + notifyStateListeners(event); + } + + /** + * The local endpoint has reached a read EOF. + *

+ * This could be a normal result after a proper close handshake, or even a premature close due to a connection disconnect. + */ + public void onReadEOF() + { + ConnectionState event = null; + synchronized (this.state) + { + if (this.state == ConnectionState.CLOSED) + { + // already closed + return; + } + + CloseInfo close = new CloseInfo(StatusCode.NO_CLOSE,"Read EOF"); + + this.cleanClose.set(false); + this.state = ConnectionState.CLOSED; + this.closeInfo.compareAndSet(null,close); + this.inputAvailable.set(false); + this.outputAvailable.set(false); + this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL); + event = this.state; + } + notifyStateListeners(event); + } + + public boolean wasAbnormalClose() + { + return closeHandshakeSource.get() == CloseHandshakeSource.ABNORMAL; } public boolean wasCleanClose() @@ -168,11 +401,11 @@ public class IOState public boolean wasLocalCloseInitiated() { - return localCloseInitiated.get(); + return closeHandshakeSource.get() == CloseHandshakeSource.LOCAL; } public boolean wasRemoteCloseInitiated() { - return remoteCloseInitiated.get(); + return closeHandshakeSource.get() == CloseHandshakeSource.REMOTE; } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java index e34e31b695d..1cf29b24378 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java @@ -258,6 +258,10 @@ public class WriteBytesProvider implements Callback private void notifySafeFailure(Callback callback, Throwable t) { + if (callback == null) + { + return; + } try { callback.failed(t); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java index 56bc5138072..ecb5adbbf91 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java @@ -116,7 +116,7 @@ public class EventDriverTest driver.incomingFrame(new WebSocketFrame(OpCode.PING).setPayload("PING")); driver.incomingFrame(WebSocketFrame.text("Text Me")); driver.incomingFrame(WebSocketFrame.binary().setPayload("Hello Bin")); - driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN).asFrame()); + driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN,"testcase").asFrame()); socket.capture.assertEventCount(6); socket.capture.assertEventStartsWith(0,"onConnect("); @@ -148,13 +148,14 @@ public class EventDriverTest } @Test - public void testListener_Text() throws IOException + public void testListener_Text() throws Exception { ListenerBasicSocket socket = new ListenerBasicSocket(); EventDriver driver = wrap(socket); try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver)) { + conn.start(); conn.open(); driver.incomingFrame(WebSocketFrame.text("Hello World")); driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame()); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java new file mode 100644 index 00000000000..24393b12a7e --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/IOStateTest.java @@ -0,0 +1,245 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.websocket.common.io; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.LinkedList; + +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.ConnectionState; +import org.junit.Test; + +public class IOStateTest +{ + public static class StateTracker implements IOState.ConnectionStateListener + { + private LinkedList transitions = new LinkedList<>(); + + public void assertTransitions(ConnectionState ...states) + { + assertThat("Transitions.count",transitions.size(),is(states.length)); + if (states.length > 0) + { + int len = states.length; + for (int i = 0; i < len; i++) + { + assertThat("Transitions[" + i + "]",transitions.get(i),is(states[i])); + } + } + } + + public LinkedList getTransitions() + { + return transitions; + } + + @Override + public void onConnectionStateChange(ConnectionState state) + { + transitions.add(state); + } + } + + private void assertCleanClose(IOState state, boolean expected) + { + assertThat("State.cleanClose",state.wasCleanClose(),is(expected)); + } + + private void assertInputAvailable(IOState state, boolean available) + { + assertThat("State.inputAvailable",state.isInputAvailable(),is(available)); + } + + private void assertLocalInitiated(IOState state, boolean expected) + { + assertThat("State.localCloseInitiated",state.wasLocalCloseInitiated(),is(expected)); + } + + private void assertOutputAvailable(IOState state, boolean available) + { + assertThat("State.outputAvailable",state.isOutputAvailable(),is(available)); + } + + private void assertRemoteInitiated(IOState state, boolean expected) + { + assertThat("State.remoteCloseInitiated",state.wasRemoteCloseInitiated(),is(expected)); + } + + private void assertState(IOState state, ConnectionState expectedState) + { + assertThat("State",state.getConnectionState(),is(expectedState)); + } + + @Test + public void testConnectAbnormalClose() + { + IOState state = new IOState(); + StateTracker tracker = new StateTracker(); + state.addListener(tracker); + assertState(state,ConnectionState.CONNECTING); + + // connect + state.onConnected(); + assertInputAvailable(state,false); + assertOutputAvailable(state,true); + + // open + state.onOpened(); + assertInputAvailable(state,true); + assertOutputAvailable(state,true); + + // disconnect + state.onAbnormalClose(new CloseInfo(StatusCode.NO_CLOSE,"Oops")); + + assertInputAvailable(state,false); + assertOutputAvailable(state,false); + tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSED); + assertState(state,ConnectionState.CLOSED); + + // not clean + assertCleanClose(state,false); + assertLocalInitiated(state,false); + assertRemoteInitiated(state,false); + } + + @Test + public void testConnectCloseLocalInitiated() + { + IOState state = new IOState(); + StateTracker tracker = new StateTracker(); + state.addListener(tracker); + assertState(state,ConnectionState.CONNECTING); + + // connect + state.onConnected(); + assertInputAvailable(state,false); + assertOutputAvailable(state,true); + + // open + state.onOpened(); + assertInputAvailable(state,true); + assertOutputAvailable(state,true); + + // close (local initiated) + state.onCloseLocal(new CloseInfo(StatusCode.NORMAL,"Hi")); + assertInputAvailable(state,true); + assertOutputAvailable(state,false); + assertState(state,ConnectionState.CLOSING); + + // close (remote response) + state.onCloseRemote(new CloseInfo(StatusCode.NORMAL,"Hi")); + + assertInputAvailable(state,false); + assertOutputAvailable(state,false); + tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSING,ConnectionState.CLOSED); + assertState(state,ConnectionState.CLOSED); + + // not clean + assertCleanClose(state,true); + assertLocalInitiated(state,true); + assertRemoteInitiated(state,false); + } + + @Test + public void testConnectCloseRemoteInitiated() + { + IOState state = new IOState(); + StateTracker tracker = new StateTracker(); + state.addListener(tracker); + assertState(state,ConnectionState.CONNECTING); + + // connect + state.onConnected(); + assertInputAvailable(state,false); + assertOutputAvailable(state,true); + + // open + state.onOpened(); + assertInputAvailable(state,true); + assertOutputAvailable(state,true); + + // close (remote initiated) + state.onCloseRemote(new CloseInfo(StatusCode.NORMAL,"Hi")); + assertInputAvailable(state,false); + assertOutputAvailable(state,true); + assertState(state,ConnectionState.CLOSING); + + // close (local response) + state.onCloseLocal(new CloseInfo(StatusCode.NORMAL,"Hi")); + + assertInputAvailable(state,false); + assertOutputAvailable(state,false); + tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSING,ConnectionState.CLOSED); + assertState(state,ConnectionState.CLOSED); + + // not clean + assertCleanClose(state,true); + assertLocalInitiated(state,false); + assertRemoteInitiated(state,true); + } + + @Test + public void testConnectFailure() + { + IOState state = new IOState(); + StateTracker tracker = new StateTracker(); + state.addListener(tracker); + assertState(state,ConnectionState.CONNECTING); + + // fail upgrade + state.onFailedUpgrade(); + + tracker.assertTransitions(ConnectionState.CLOSED); + assertState(state,ConnectionState.CLOSED); + + assertInputAvailable(state,false); + assertOutputAvailable(state,false); + + // not clean + assertCleanClose(state,false); + assertLocalInitiated(state,false); + assertRemoteInitiated(state,false); + } + + @Test + public void testInit() + { + IOState state = new IOState(); + StateTracker tracker = new StateTracker(); + state.addListener(tracker); + assertState(state,ConnectionState.CONNECTING); + + // do nothing + + tracker.assertTransitions(); + assertState(state,ConnectionState.CONNECTING); + + // not connected yet + assertInputAvailable(state,false); + assertOutputAvailable(state,false); + + // no close yet + assertCleanClose(state,false); + assertLocalInitiated(state,false); + assertRemoteInitiated(state,false); + } +} diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java index 5387af30d0b..92e5e775b8f 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketConnection.java @@ -22,22 +22,25 @@ import java.net.InetSocketAddress; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.IncomingFrames; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.ConnectionState; import org.eclipse.jetty.websocket.common.LogicalConnection; import org.eclipse.jetty.websocket.common.WebSocketSession; +import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; import org.junit.rules.TestName; -public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames +public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames, ConnectionStateListener { private static final Logger LOG = Log.getLogger(LocalWebSocketConnection.class); private final String id; private WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); - private boolean open = false; private IncomingFrames incoming; private IOState ioState = new IOState(); @@ -49,29 +52,33 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram public LocalWebSocketConnection(String id) { this.id = id; + this.ioState.addListener(this); } public LocalWebSocketConnection(TestName testname) { this.id = testname.getMethodName(); + this.ioState.addListener(this); } @Override public void close() { - open = false; + close(StatusCode.NORMAL,null); } @Override public void close(int statusCode, String reason) { - open = false; + LOG.debug("close({}, {})",statusCode,reason); + CloseInfo close = new CloseInfo(statusCode,reason); + ioState.onCloseLocal(close); } @Override public void disconnect() { - open = false; + LOG.debug("disconnect()"); } public IncomingFrames getIncoming() @@ -131,7 +138,7 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram @Override public boolean isOpen() { - return open; + return getIOState().isOpen(); } @Override @@ -140,9 +147,31 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram return false; } + @Override + public void onConnectionStateChange(ConnectionState state) + { + LOG.debug("Connection State Change: {}",state); + switch (state) + { + case CLOSED: + this.disconnect(); + break; + case CLOSING: + if (ioState.wasRemoteCloseInitiated()) + { + // send response close frame + CloseInfo close = ioState.getCloseInfo(); + LOG.debug("write close frame: {}",close); + ioState.onCloseLocal(close); + } + default: + break; + } + } + public void onOpen() { LOG.debug("onOpen()"); - open = true; + ioState.onOpened(); } @Override diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java index afe2e78a632..752972d7857 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java @@ -75,13 +75,6 @@ public class WebSocketServerConnection extends AbstractWebSocketConnection super.onOpen(); } - @Override - public void onWriteWebSocketClose() - { - // as server, always disconnect if writing close - disconnect(); - } - @Override public void setNextIncomingFrames(IncomingFrames incoming) { diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java index ce46ba23fe6..fcb40ffe705 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java @@ -65,6 +65,7 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory; import org.eclipse.jetty.websocket.common.io.IOState; +import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser; import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture; import org.junit.Assert; @@ -81,7 +82,7 @@ import org.junit.Assert; * with regards to basic IO behavior, a write should work as expected, a read should work as expected, but what byte it sends or reads is not within its * scope. */ -public class BlockheadClient implements IncomingFrames, OutgoingFrames +public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener { private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ=="; private static final int BUFFER_SIZE = 8192; @@ -140,6 +141,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames this.extensionFactory = new WebSocketExtensionFactory(policy,bufferPool); this.ioState = new IOState(); + this.ioState.addListener(this); } public void addExtensions(String xtension) @@ -175,24 +177,22 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames public void close(int statusCode, String message) { - try - { - CloseInfo close = new CloseInfo(statusCode,message); + CloseInfo close = new CloseInfo(statusCode,message); - if (ioState.onCloseHandshake(false)) + ioState.onCloseLocal(close); + + if (!ioState.isClosed()) + { + WebSocketFrame frame = close.asFrame(); + LOG.debug("Issuing: {}",frame); + try { - this.disconnect(); - } - else - { - WebSocketFrame frame = close.asFrame(); - LOG.debug("Issuing: {}",frame); write(frame); } - } - catch (IOException e) - { - LOG.debug(e); + catch (IOException e) + { + LOG.debug(e); + } } } @@ -278,7 +278,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames // configure parser parser.setIncomingFramesHandler(extensionStack); - ioState.setState(ConnectionState.OPEN); + ioState.onOpened(); LOG.debug("outgoing = {}",outgoing); LOG.debug("incoming = {}",extensionStack); @@ -393,14 +393,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames if (frame.getType() == Frame.Type.CLOSE) { CloseInfo close = new CloseInfo(frame); - if (ioState.onCloseHandshake(true)) - { - this.disconnect(); - } - else - { - close(close.getStatusCode(),close.getReason()); - } + ioState.onCloseRemote(close); } WebSocketFrame copy = new WebSocketFrame(frame); @@ -412,6 +405,27 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames return (socket != null) && (socket.isConnected()); } + @Override + public void onConnectionStateChange(ConnectionState state) + { + switch (state) + { + case CLOSED: + this.disconnect(); + break; + case CLOSING: + if (ioState.wasRemoteCloseInitiated()) + { + CloseInfo close = ioState.getCloseInfo(); + close(close.getStatusCode(),close.getReason()); + } + break; + default: + /* do nothing */ + break; + } + } + @Override public void outgoingFrame(Frame frame, WriteCallback callback) { diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java index a00bc7194cb..d6186814f4a 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java @@ -116,6 +116,9 @@ public class BrowserDebugTool implements WebSocketCreator // Setup the desired Socket to use for all incoming upgrade requests factory.setCreator(BrowserDebugTool.this); + + // Set the timeout + factory.getPolicy().setIdleTimeout(2000); } }; diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java index e9ce0878ce6..f54f12e4b2c 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/examples/echo/ExampleEchoServer.java @@ -31,6 +31,15 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; */ public class ExampleEchoServer { + public final class EchoSocketHandler extends WebSocketHandler + { + @Override + public void configure(WebSocketServletFactory factory) + { + factory.setCreator(new EchoCreator()); + } + } + private static final Logger LOG = Log.getLogger(ExampleEchoServer.class); public static void main(String... args) @@ -96,14 +105,7 @@ public class ExampleEchoServer connector.setPort(port); server.addConnector(connector); - wsHandler = new WebSocketHandler() - { - @Override - public void configure(WebSocketServletFactory factory) - { - factory.setCreator(new EchoCreator()); - } - }; + wsHandler = new EchoSocketHandler(); server.setHandler(wsHandler); @@ -126,6 +128,14 @@ public class ExampleEchoServer public void runForever() throws Exception { server.start(); + String host = connector.getHost(); + if (host == null) + { + host = "localhost"; + } + int port = connector.getLocalPort(); + System.err.printf("Echo Server started on ws://%s:%d/%n",host,port); + System.err.println(server.dump()); server.join(); } diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties index c27be6164a8..e7122e6fe09 100644 --- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties @@ -5,7 +5,6 @@ org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.websocket.LEVEL=WARN # org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG -# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG # org.eclipse.jetty.websocket.server.ab.Fuzzer.LEVEL=DEBUG From 7590ad6795d7a65f3db8068bc3c993ca5d3cf4fd Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 1 May 2013 09:14:05 +1000 Subject: [PATCH 26/28] 406390 406617 removed tiny race from handling of suspend and complete --- .../org/eclipse/jetty/server/HttpChannel.java | 49 +++++++++++++------ .../jetty/server/HttpChannelState.java | 40 +++++++++------ .../eclipse/jetty/server/HttpConnection.java | 5 +- 3 files changed, 63 insertions(+), 31 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 73f7f890831..c32b6aade57 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ChannelEndPoint; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.server.HttpChannelState.Next; import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; @@ -216,6 +217,15 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable @Override public void run() + { + handle(); + } + + /* ------------------------------------------------------------ */ + /** + * @return True if the channel is ready to continue handling (ie it is not suspended) + */ + public boolean handle() { LOG.debug("{} handle enter", this); @@ -228,15 +238,16 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable Thread.currentThread().setName(threadName + " - " + _uri); } + + // Loop here to handle async request redispatches. + // The loop is controlled by the call to async.unhandle in the + // finally block below. Unhandle will return false only if an async dispatch has + // already happened when unhandle is called. + HttpChannelState.Next next = _state.handling(); try { - // Loop here to handle async request redispatches. - // The loop is controlled by the call to async.unhandle in the - // finally block below. Unhandle will return false only if an async dispatch has - // already happened when unhandle is called. - boolean handling = _state.handling(); - while (handling && getServer().isRunning()) + while (next==Next.CONTINUE && getServer().isRunning()) { try { @@ -286,9 +297,11 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable } finally { - handling = !_state.unhandle(); + next = _state.unhandle(); } } + if (next==Next.WAIT) + return false; } finally { @@ -296,7 +309,7 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable Thread.currentThread().setName(threadName); setCurrentHttpChannel(null); - if (_state.isCompleting()) + if (next==Next.COMPLETE) { try { @@ -319,13 +332,21 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable } finally { - _request.setHandled(true); - _transport.completed(); + next=Next.RECYCLE; } } + if (next==Next.RECYCLE) + { + _request.setHandled(true); + _transport.completed(); + } + + LOG.debug("{} handle exit", this); } + + return true; } /** @@ -570,10 +591,9 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable try { - if (_state.handling()) + if (_state.handling()==Next.CONTINUE) { commitResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true); - _state.unhandle(); } } catch (IOException e) @@ -581,8 +601,9 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable LOG.warn(e); } finally - { - _state.completed(); + { + if (_state.unhandle()==Next.COMPLETE) + _state.completed(); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index ee60c80f3ea..79c35b88224 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -71,6 +71,14 @@ public class HttpChannelState COMPLETING, // Request is completable COMPLETED // Request is complete } + + public enum Next + { + CONTINUE, // Continue handling the channel + WAIT, // Wait for further events + COMPLETE, // Complete the channel + RECYCLE, // Channel is completed + } private final HttpChannel _channel; private List _lastAsyncListeners; @@ -154,9 +162,9 @@ public class HttpChannelState } /** - * @return true if the handling of the request should proceed + * @return Next handling of the request should proceed */ - protected boolean handling() + protected Next handling() { synchronized (this) { @@ -178,12 +186,16 @@ public class HttpChannelState case COMPLETECALLED: _state=State.COMPLETING; - return false; + return Next.COMPLETE; - case ASYNCWAIT: case COMPLETING: + return Next.COMPLETE; + + case ASYNCWAIT: + return Next.WAIT; + case COMPLETED: - return false; + return Next.RECYCLE; case REDISPATCH: _state=State.REDISPATCHED; @@ -194,7 +206,7 @@ public class HttpChannelState } _responseWrapped=false; - return true; + return Next.CONTINUE; } } @@ -255,10 +267,10 @@ public class HttpChannelState * Signal that the HttpConnection has finished handling the request. * For blocking connectors, this call may block if the request has * been suspended (startAsync called). - * @return true if handling is complete, false if the request should + * @return next actions * be handled again (eg because of a resume that happened before unhandle was called) */ - protected boolean unhandle() + protected Next unhandle() { synchronized (this) { @@ -267,7 +279,7 @@ public class HttpChannelState case REDISPATCHED: case DISPATCHED: _state=State.COMPLETING; - return true; + return Next.COMPLETE; case IDLE: throw new IllegalStateException(this.getStatusString()); @@ -277,25 +289,25 @@ public class HttpChannelState _state=State.ASYNCWAIT; scheduleTimeout(); if (_state==State.ASYNCWAIT) - return true; + return Next.WAIT; else if (_state==State.COMPLETECALLED) { _state=State.COMPLETING; - return true; + return Next.COMPLETE; } _initial=false; _state=State.REDISPATCHED; - return false; + return Next.CONTINUE; case REDISPATCHING: _initial=false; _state=State.REDISPATCHED; - return false; + return Next.CONTINUE; case COMPLETECALLED: _initial=false; _state=State.COMPLETING; - return true; + return Next.COMPLETE; default: throw new IllegalStateException(this.getStatusString()); 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 17f09a7b913..f7e675eea7c 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 @@ -222,11 +222,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http // The parser returned true, which indicates the channel is ready to handle a request. // Call the channel and this will either handle the request/response to completion OR, // if the request suspends, the request/response will be incomplete so the outer loop will exit. - - _channel.run(); + boolean handle=_channel.handle(); // Return if suspended or upgraded - if (_channel.getState().isSuspended() || getEndPoint().getConnection()!=this) + if (!handle || getEndPoint().getConnection()!=this) return; } else if (BufferUtil.isEmpty(_requestBuffer)) From b99a04c674bce2e1891bb77a04f3f7ea80b179e5 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Wed, 1 May 2013 17:53:33 +1000 Subject: [PATCH 27/28] 406962 Improve attribute names in Request --- .../src/main/java/org/eclipse/jetty/server/Request.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 1b981812552..6831dbcfb11 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 @@ -115,9 +115,9 @@ import org.eclipse.jetty.util.log.Logger; */ public class Request implements HttpServletRequest { - public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig"; - public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.multiPartInputStream"; - public static final String __MULTIPART_CONTEXT = "org.eclipse.multiPartContext"; + public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig"; + public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.jetty.multiPartInputStream"; + public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext"; private static final Logger LOG = Log.getLogger(Request.class); private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault()); From 7ddd2c6cfd0a02ad2d3475d48f60cd7cf57354bd Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 1 May 2013 21:16:30 +1000 Subject: [PATCH 28/28] 406923 Accept CRLF or LF but not CR as line termination --- .../org/eclipse/jetty/http/HttpField.java | 7 +- .../org/eclipse/jetty/http/HttpMethod.java | 5 +- .../org/eclipse/jetty/http/HttpParser.java | 291 ++++++++++-------- .../org/eclipse/jetty/http/HttpTokens.java | 7 +- .../eclipse/jetty/http/HttpParserTest.java | 215 ++++++++----- 5 files changed, 303 insertions(+), 222 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java index c1a9ef2243e..c127e7504b9 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java @@ -33,7 +33,7 @@ import org.eclipse.jetty.util.Trie; */ public class HttpField { - public final static Trie CACHE = new ArrayTrie<>(1024); + public final static Trie CACHE = new ArrayTrie<>(2048); public final static Trie CONTENT_TYPE = new ArrayTrie<>(512); static @@ -77,10 +77,7 @@ public class HttpField } // Add headers with null values so HttpParser can avoid looking up name again for unknown values - Set headers = new HashSet<>(); - for (String key:CACHE.keySet()) - headers.add(CACHE.get(key).getHeader()); - for (HttpHeader h:headers) + for (HttpHeader h:HttpHeader.values()) if (!CACHE.put(new HttpField(h,(String)null))) throw new IllegalStateException("CACHE FULL"); // Add some more common headers diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java index 60dc69d09d1..6b27173c7a8 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java @@ -110,7 +110,10 @@ public enum HttpMethod { if (buffer.hasArray()) return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit()); - return CACHE.getBest(buffer,0,buffer.remaining()); + + // TODO use cache and check for space + // return CACHE.getBest(buffer,0,buffer.remaining()); + return 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 82ea39ac0c8..218ce47134b 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 @@ -18,7 +18,6 @@ package org.eclipse.jetty.http; -import java.io.IOException; import java.nio.ByteBuffer; import org.eclipse.jetty.http.HttpTokens.EndOfContent; @@ -26,6 +25,7 @@ import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Trie; +import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -80,13 +80,13 @@ public class HttpParser private String _methodString; private HttpVersion _version; private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune? - private byte _eol; private EndOfContent _endOfContent; private long _contentLength; private long _contentPosition; private int _chunkLength; private int _chunkPosition; private boolean _headResponse; + private boolean _cr; private ByteBuffer _contentChunk; private Trie _connectionFields; @@ -204,11 +204,70 @@ public class HttpParser return _state == state; } + /* ------------------------------------------------------------------------------- */ + private byte next(ByteBuffer buffer) + { + byte ch=buffer.get(); + + // If not a special character + if (ch>=HttpTokens.SPACE || ch<0) + { + if (_cr) + { + badMessage(buffer,400,"Bad EOL"); + return -1; + } + return ch; + } + + + // Only a LF acceptable after CR + if (_cr) + { + _cr=false; + if (ch==HttpTokens.LINE_FEED) + return ch; + + badMessage(buffer,400,"Bad EOL"); + return -1; + } + + // If it is a CR + if (ch==HttpTokens.CARRIAGE_RETURN) + { + // Skip CR and look for a LF + if (buffer.hasRemaining()) + { + if(_maxHeaderBytes>0 && _state.ordinal() HttpTokens.SPACE || ch<0) + byte ch=next(buffer); + + if (ch > HttpTokens.SPACE) { _string.setLength(0); _string.append((char)ch); setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION); - return; + return false; } + if (ch==-1) + return true; } + return false; } private String takeString() @@ -281,7 +336,11 @@ public class HttpParser while (_state.ordinal()0 && ++_headerBytes>_maxHeaderBytes) { @@ -301,13 +360,6 @@ public class HttpParser return true; } - if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED) - { - _eol=HttpTokens.LINE_FEED; - continue; - } - _eol=0; - switch (_state) { case METHOD: @@ -413,7 +465,6 @@ public class HttpParser else if (ch < HttpTokens.SPACE && ch>=0) { return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, null); - _eol=ch; setState(State.HEADER); } else @@ -464,28 +515,39 @@ public class HttpParser { setState(State.REQUEST_VERSION); - // try quick look ahead + // try quick look ahead for HTTP Version if (buffer.position()>0 && buffer.hasArray()) { - _version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit()); - if (_version!=null) + HttpVersion version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit()); + if (version!=null) { - _string.setLength(0); - buffer.position(buffer.position()+_version.asString().length()-1); - _eol=buffer.get(); - setState(State.HEADER); - _uri.flip(); - return_from_parse|=_requestHandler.startRequest(_method,_methodString,_uri, _version); + int pos = buffer.position()+version.asString().length()-1; + if (pos(header_cache); } - _eol=ch; setState(State.HEADER); _uri.flip(); return_from_parse|=_requestHandler.startRequest(_method,_methodString,_uri, _version); @@ -532,11 +593,10 @@ public class HttpParser break; case REASON: - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + if (ch == HttpTokens.LINE_FEED) { String reason=takeLengthString(); - _eol=ch; setState(State.HEADER); return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, reason); continue; @@ -692,7 +752,12 @@ public class HttpParser while (_state.ordinal()0 && ++_headerBytes>_maxHeaderBytes) { LOG.warn("Header is too large >"+_maxHeaderBytes); @@ -700,13 +765,6 @@ public class HttpParser return true; } - if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED) - { - _eol=HttpTokens.LINE_FEED; - continue; - } - _eol=0; - switch (_state) { case HEADER: @@ -751,10 +809,8 @@ public class HttpParser _field=null; // now handle the ch - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + if (ch == HttpTokens.LINE_FEED) { - consumeCRLF(ch,buffer); - _contentPosition=0; // End of headers! @@ -815,41 +871,52 @@ public class HttpParser if (buffer.remaining()>6) { // Try a look ahead for the known header name and value. - _field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining()); - if (_field==null) - _field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining()); + HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining()); + if (field==null) + field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining()); - if (_field!=null) + if (field!=null) { - _header=_field.getHeader(); - _headerString=_field.getName(); - _valueString=_field.getValue(); - if (_valueString==null) + String n=field.getName(); + String v=field.getValue(); + + if (v==null) { + // Header only + int pos=buffer.position()+n.length()+1; + byte b=buffer.get(pos); + _header=field.getHeader(); + _headerString=n; setState(State.HEADER_VALUE); - buffer.position(buffer.position()+_headerString.length()+1); _string.setLength(0); _length=0; - _field=null; + buffer.position(pos); + break; } else { - setState(State.HEADER_IN_VALUE); - buffer.position(buffer.position()+_headerString.length()+_valueString.length()+1); - } - break; - } + // Header and value + int pos=buffer.position()+n.length()+v.length()+1; + byte b=buffer.get(pos); - // Try a look ahead for the known header name. - _header=HttpHeader.CACHE.getBest(buffer,-1,buffer.remaining()); - //_header=HttpHeader.CACHE.getBest(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.remaining()+1); - if (_header!=null) - { - _headerString=_header.asString(); - _string.setLength(0); - setState(State.HEADER_IN_NAME); - buffer.position(buffer.position()+_headerString.length()-1); - break; + if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED) + { + _field=field; + _header=_field.getHeader(); + _headerString=n; + _valueString=v; + setState(State.HEADER_IN_VALUE); + + if (b==HttpTokens.CARRIAGE_RETURN) + { + _cr=true; + buffer.position(pos+1); + } + else + buffer.position(pos); + break; + } + } } } @@ -867,9 +934,7 @@ public class HttpParser case HEADER_NAME: switch(ch) { - case HttpTokens.CARRIAGE_RETURN: case HttpTokens.LINE_FEED: - consumeCRLF(ch,buffer); if (_headerString==null) { _headerString=takeLengthString(); @@ -884,6 +949,8 @@ public class HttpParser { _headerString=takeLengthString(); _header=HttpHeader.CACHE.get(_headerString); + if (_header!=null) + System.err.println(_header); } setState(State.HEADER_VALUE); break; @@ -904,9 +971,7 @@ public class HttpParser case HEADER_IN_NAME: switch(ch) { - case HttpTokens.CARRIAGE_RETURN: case HttpTokens.LINE_FEED: - consumeCRLF(ch,buffer); _headerString=takeString(); _length=-1; _header=HttpHeader.CACHE.get(_headerString); @@ -952,9 +1017,7 @@ public class HttpParser case HEADER_VALUE: switch(ch) { - case HttpTokens.CARRIAGE_RETURN: case HttpTokens.LINE_FEED: - consumeCRLF(ch,buffer); if (_length > 0) { if (_valueString!=null) @@ -991,9 +1054,7 @@ public class HttpParser case HEADER_IN_VALUE: switch(ch) { - case HttpTokens.CARRIAGE_RETURN: case HttpTokens.LINE_FEED: - consumeCRLF(ch,buffer); if (_length > 0) { if (HttpHeaderValue.hasKnownValues(_header)) @@ -1046,17 +1107,6 @@ public class HttpParser return return_from_parse; } - /* ------------------------------------------------------------------------------- */ - private void consumeCRLF(byte ch, ByteBuffer buffer) - { - _eol=ch; - if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED) - { - buffer.get(); - _eol=0; - } - } - /* ------------------------------------------------------------------------------- */ /** * Parse until next Event. @@ -1075,7 +1125,8 @@ public class HttpParser _methodString=null; _endOfContent=EndOfContent.UNKNOWN_CONTENT; _header=null; - quickStart(buffer); + if(quickStart(buffer)) + return true; break; case CONTENT: @@ -1131,13 +1182,6 @@ public class HttpParser byte ch; while (_state.ordinal() > State.END.ordinal() && buffer.hasRemaining()) { - if (_eol == HttpTokens.CARRIAGE_RETURN && buffer.get(buffer.position()) == HttpTokens.LINE_FEED) - { - _eol=buffer.get(); - continue; - } - _eol=0; - switch (_state) { case EOF_CONTENT: @@ -1187,31 +1231,24 @@ public class HttpParser case CHUNKED_CONTENT: { - ch=buffer.get(buffer.position()); - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) - _eol=buffer.get(); - else if (ch <= HttpTokens.SPACE) - buffer.get(); - else + ch=next(buffer); + if (ch>HttpTokens.SPACE) { - _chunkLength=0; + _chunkLength=TypeUtil.convertHexDigit(ch); _chunkPosition=0; setState(State.CHUNK_SIZE); } + break; } case CHUNK_SIZE: { - ch=buffer.get(); - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + ch=next(buffer); + if (ch == HttpTokens.LINE_FEED) { - _eol=ch; - if (_chunkLength == 0) { - if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED) - _eol=buffer.get(); setState(State.END); if (_handler.messageComplete()) return true; @@ -1221,27 +1258,18 @@ public class HttpParser } else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON) setState(State.CHUNK_PARAMS); - else if (ch >= '0' && ch <= '9') - _chunkLength=_chunkLength * 16 + (ch - '0'); - else if (ch >= 'a' && ch <= 'f') - _chunkLength=_chunkLength * 16 + (10 + ch - 'a'); - else if (ch >= 'A' && ch <= 'F') - _chunkLength=_chunkLength * 16 + (10 + ch - 'A'); - else - throw new IOException("bad chunk char: " + ch); + else + _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch); break; } case CHUNK_PARAMS: { - ch=buffer.get(); - if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED) + ch=next(buffer); + if (ch == HttpTokens.LINE_FEED) { - _eol=ch; if (_chunkLength == 0) { - if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED) - _eol=buffer.get(); setState(State.END); if (_handler.messageComplete()) return true; @@ -1290,6 +1318,7 @@ public class HttpParser } catch(Exception e) { + e.printStackTrace(); BufferUtil.clear(buffer); if (isClosed()) { diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java index e3d13d56abc..d54efffc7fe 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java @@ -25,13 +25,12 @@ public interface HttpTokens { // Terminal symbols. static final byte COLON= (byte)':'; - static final byte SPACE= 0x20; - static final byte CARRIAGE_RETURN= 0x0D; + static final byte TAB= 0x09; static final byte LINE_FEED= 0x0A; + static final byte CARRIAGE_RETURN= 0x0D; + static final byte SPACE= 0x20; static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED}; static final byte SEMI_COLON= (byte)';'; - static final byte TAB= 0x09; - public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT,SELF_DEFINING_CONTENT } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index 33f6a9b8297..24950a50433 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -50,11 +50,13 @@ public class HttpParserTest throw new IllegalStateException("!START"); // continue parsing - while (!parser.isState(State.END) && buffer.hasRemaining()) + int remaining=buffer.remaining(); + while (!parser.isState(State.END) && remaining>0) { - int remaining=buffer.remaining(); + int was_remaining=remaining; parser.parseNext(buffer); - if (remaining==buffer.remaining()) + remaining=buffer.remaining(); + if (remaining==was_remaining) break; } } @@ -64,8 +66,8 @@ public class HttpParserTest { ByteBuffer buffer= BufferUtil.toBuffer("POST /foo HTTP/1.0\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("POST", _methodOrVersion); assertEquals("/foo", _uriOrStatus); @@ -79,8 +81,8 @@ public class HttpParserTest ByteBuffer buffer= BufferUtil.toBuffer("GET /999\015\012"); _versionOrReason= null; - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("GET", _methodOrVersion); assertEquals("/999", _uriOrStatus); @@ -94,8 +96,8 @@ public class HttpParserTest ByteBuffer buffer= BufferUtil.toBuffer("POST /222 \015\012"); _versionOrReason= null; - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("POST", _methodOrVersion); assertEquals("/222", _uriOrStatus); @@ -108,8 +110,8 @@ public class HttpParserTest { ByteBuffer buffer= BufferUtil.toBuffer("POST /fo\u0690 HTTP/1.0\015\012" + "\015\012",StringUtil.__UTF8_CHARSET); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("POST", _methodOrVersion); assertEquals("/fo\u0690", _uriOrStatus); @@ -122,8 +124,8 @@ public class HttpParserTest { ByteBuffer buffer= BufferUtil.toBuffer("POST /foo?param=\u0690 HTTP/1.0\015\012" + "\015\012",StringUtil.__UTF8_CHARSET); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("POST", _methodOrVersion); assertEquals("/foo?param=\u0690", _uriOrStatus); @@ -136,8 +138,8 @@ public class HttpParserTest { ByteBuffer buffer= BufferUtil.toBuffer("POST /123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/ HTTP/1.0\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("POST", _methodOrVersion); assertEquals("/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/", _uriOrStatus); @@ -149,10 +151,9 @@ public class HttpParserTest public void testConnect() throws Exception { ByteBuffer buffer= BufferUtil.toBuffer("CONNECT 192.168.1.2:80 HTTP/1.1\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); - assertTrue(handler.request); assertEquals("CONNECT", _methodOrVersion); assertEquals("192.168.1.2:80", _uriOrStatus); assertEquals("HTTP/1.1", _versionOrReason); @@ -182,8 +183,8 @@ public class HttpParserTest BufferUtil.put(b0,buffer); BufferUtil.flipToFlush(buffer,pos); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("GET", _methodOrVersion); @@ -230,8 +231,8 @@ public class HttpParserTest "Accept-Encoding: gzip, deflated\015\012" + "Accept: unknown\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("GET", _methodOrVersion); @@ -278,8 +279,8 @@ public class HttpParserTest "Accept-Encoding: gzip, deflated\n" + "Accept: unknown\n" + "\n"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("GET", _methodOrVersion); @@ -328,9 +329,10 @@ public class HttpParserTest for (int i=0;i handler = new Handler(); + HttpParser parser= new HttpParser(handler); + System.err.println(BufferUtil.toDetailString(buffer)); buffer.position(2); buffer.limit(2+i); @@ -377,8 +379,8 @@ public class HttpParserTest + "1a\015\012" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012" + "0\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parseAll(parser,buffer); assertEquals("GET", _methodOrVersion); @@ -421,8 +423,8 @@ public class HttpParserTest + "0123456789\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("GET", _methodOrVersion); assertEquals("/mp", _uriOrStatus); @@ -466,8 +468,8 @@ public class HttpParserTest + "\015\012" + "0123456789\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("HTTP/1.1", _methodOrVersion); assertEquals("200", _uriOrStatus); @@ -485,8 +487,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("HTTP/1.1", _methodOrVersion); assertEquals("304", _uriOrStatus); @@ -509,8 +511,8 @@ public class HttpParserTest + "\015\012" + "0123456789\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("HTTP/1.1", _methodOrVersion); assertEquals("204", _uriOrStatus); @@ -542,8 +544,8 @@ public class HttpParserTest + "\015\012" + "0123456789\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("HTTP/1.1", _methodOrVersion); assertEquals("200", _uriOrStatus); @@ -563,8 +565,8 @@ public class HttpParserTest + "\015\012" + "0123456789\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("HTTP/1.1", _methodOrVersion); assertEquals("200", _uriOrStatus); @@ -582,8 +584,8 @@ public class HttpParserTest + "Content-Length: 10\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("HTTP/1.1", _methodOrVersion); assertEquals("304", _uriOrStatus); @@ -601,8 +603,8 @@ public class HttpParserTest + "Transfer-Encoding: chunked\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("HTTP/1.1", _methodOrVersion); assertEquals("101", _uriOrStatus); @@ -624,8 +626,8 @@ public class HttpParserTest + "HTTP/1.1 400 OK\015\012"); // extra data causes close - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("HTTP/1.1", _methodOrVersion); @@ -647,8 +649,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals(null,_methodOrVersion); @@ -667,8 +669,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals(null,_methodOrVersion); @@ -686,8 +688,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals(null,_methodOrVersion); @@ -705,8 +707,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals(null,_methodOrVersion); @@ -724,9 +726,9 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); - + HttpParser.ResponseHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); + parser.parseNext(buffer); assertEquals(null,_methodOrVersion); assertEquals("No Status",_bad); @@ -743,8 +745,23 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); + + parser.parseNext(buffer); + assertEquals(null,_methodOrVersion); + assertEquals("Unknown Version",_bad); + assertFalse(buffer.hasRemaining()); + assertEquals(HttpParser.State.CLOSED,parser.getState()); + + buffer= BufferUtil.toBuffer( + "GET / HTTP/1.01\015\012" + + "Content-Length: 0\015\012" + + "Connection: close\015\012" + + "\015\012"); + + handler = new Handler();handler = new Handler(); + parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals(null,_methodOrVersion); @@ -752,6 +769,42 @@ public class HttpParserTest assertFalse(buffer.hasRemaining()); assertEquals(HttpParser.State.CLOSED,parser.getState()); } + + @Test + public void testBadCR() throws Exception + { + ByteBuffer buffer= BufferUtil.toBuffer( + "GET / HTTP/1.0\r\n" + + "Content-Length: 0\r" + + "Connection: close\r" + + "\r"); + + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); + + parser.parseNext(buffer); + assertEquals("Bad EOL",_bad); + assertFalse(buffer.hasRemaining()); + assertEquals(HttpParser.State.CLOSED,parser.getState()); + + + buffer= BufferUtil.toBuffer( + "GET / HTTP/1.0\r" + + "Content-Length: 0\r" + + "Connection: close\r" + + "\r"); + + handler = new Handler(); + parser= new HttpParser(handler); + + parser.parseNext(buffer); + assertEquals("Bad EOL",_bad); + assertFalse(buffer.hasRemaining()); + assertEquals(HttpParser.State.CLOSED,parser.getState()); + } + + + @Test public void testBadContentLength0() throws Exception @@ -762,8 +815,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("GET",_methodOrVersion); @@ -781,8 +834,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("GET",_methodOrVersion); @@ -800,8 +853,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("GET",_methodOrVersion); @@ -819,8 +872,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("host",_host); assertEquals(0,_port); @@ -835,8 +888,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("192.168.0.1",_host); assertEquals(0,_port); @@ -851,8 +904,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("::1",_host); assertEquals(0,_port); @@ -867,8 +920,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("Bad IPv6 Host header",_bad); } @@ -882,8 +935,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("myhost",_host); assertEquals(8888,_port); @@ -898,8 +951,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("Bad Host header",_bad); } @@ -913,8 +966,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("192.168.0.1",_host); assertEquals(8888,_port); @@ -929,8 +982,8 @@ public class HttpParserTest + "Connection: close\015\012" + "\015\012"); - Handler handler = new Handler(); - HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser= new HttpParser(handler); parser.parseNext(buffer); assertEquals("::1",_host); assertEquals(8888,_port);