Merge branch 'master' into jetty-9.4.x-Feature

This commit is contained in:
Greg Wilkins 2016-03-15 18:21:56 +11:00
commit ea1deda4f9
11 changed files with 1552 additions and 1198 deletions

View File

@ -21,6 +21,8 @@ package org.eclipse.jetty.client;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
/**
* <p>A protocol handler that handles redirect status codes 301, 302, 303, 307 and 308.</p>
@ -54,6 +56,14 @@ public class RedirectProtocolHandler extends Response.Listener.Adapter implement
return this;
}
@Override
public boolean onHeader(Response response, HttpField field)
{
// Avoid that the content is decoded, which could generate
// errors, since we are discarding the content anyway.
return field.getHeader() != HttpHeader.CONTENT_ENCODING;
}
@Override
public void onComplete(Result result)
{

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.UnresolvedAddressException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -44,7 +45,6 @@ import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@ -55,15 +55,11 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
super(sslContextFactory);
}
@Before
public void prepare() throws Exception
{
start(new RedirectHandler());
}
@Test
public void test_303() throws Exception
{
start(new RedirectHandler());
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/localhost/done")
@ -77,6 +73,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void test_303_302() throws Exception
{
start(new RedirectHandler());
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/localhost/302/localhost/done")
@ -90,6 +88,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void test_303_302_OnDifferentDestinations() throws Exception
{
start(new RedirectHandler());
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/127.0.0.1/302/localhost/done")
@ -103,6 +103,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void test_301() throws Exception
{
start(new RedirectHandler());
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.HEAD)
@ -117,6 +119,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void test_301_WithWrongMethod() throws Exception
{
start(new RedirectHandler());
try
{
client.newRequest("localhost", connector.getLocalPort())
@ -140,6 +144,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void test_307_WithRequestContent() throws Exception
{
start(new RedirectHandler());
byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
@ -157,6 +163,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void testMaxRedirections() throws Exception
{
start(new RedirectHandler());
client.setMaxRedirects(1);
try
@ -181,6 +188,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void test_303_WithConnectionClose_WithBigRequest() throws Exception
{
start(new RedirectHandler());
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/localhost/done?close=true")
@ -194,6 +203,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void testDontFollowRedirects() throws Exception
{
start(new RedirectHandler());
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.followRedirects(false)
@ -208,6 +219,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void testRelativeLocation() throws Exception
{
start(new RedirectHandler());
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/localhost/done?relative=true")
@ -221,6 +234,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void testAbsoluteURIPathWithSpaces() throws Exception
{
start(new RedirectHandler());
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/localhost/a+space?decode=true")
@ -234,6 +249,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void testRelativeURIPathWithSpaces() throws Exception
{
start(new RedirectHandler());
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/localhost/a+space?relative=true&decode=true")
@ -247,7 +264,6 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void testRedirectWithWrongScheme() throws Exception
{
dispose();
start(new AbstractHandler()
{
@Override
@ -264,14 +280,10 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
.scheme(scheme)
.path("/path")
.timeout(5, TimeUnit.SECONDS)
.send(new Response.CompleteListener()
.send(result ->
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
latch.countDown();
}
Assert.assertTrue(result.isFailed());
latch.countDown();
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ -281,6 +293,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
public void testRedirectFailed() throws Exception
{
// TODO this test is failing with timout after an ISP upgrade?? DNS dependent?
start(new RedirectHandler());
try
{
client.newRequest("localhost", connector.getLocalPort())
@ -370,6 +384,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
@Test
public void testHttpRedirector() throws Exception
{
start(new RedirectHandler());
final HttpRedirector redirector = new HttpRedirector(client);
org.eclipse.jetty.client.api.Request request1 = client.newRequest("localhost", connector.getLocalPort())
@ -390,20 +405,52 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
Assert.assertTrue(redirector.isRedirect(response2));
final CountDownLatch latch = new CountDownLatch(1);
redirector.redirect(request2, response2, new Response.CompleteListener()
redirector.redirect(request2, response2, r ->
{
@Override
public void onComplete(Result result)
{
Response response3 = result.getResponse();
Assert.assertEquals(200, response3.getStatus());
Assert.assertFalse(redirector.isRedirect(response3));
latch.countDown();
}
Response response3 = r.getResponse();
Assert.assertEquals(200, response3.getStatus());
Assert.assertFalse(redirector.isRedirect(response3));
latch.countDown();
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testRedirectWithCorruptedBody() throws Exception
{
byte[] bytes = "ok".getBytes(StandardCharsets.UTF_8);
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (target.startsWith("/redirect"))
{
response.setStatus(HttpStatus.SEE_OTHER_303);
response.setHeader(HttpHeader.LOCATION.asString(), scheme + "://localhost:" + connector.getLocalPort() + "/ok");
// Say that we send gzipped content, but actually don't.
response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip");
response.getOutputStream().write("redirect".getBytes(StandardCharsets.UTF_8));
}
else
{
response.setStatus(HttpStatus.OK_200);
response.getOutputStream().write(bytes);
}
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/redirect")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
Assert.assertArrayEquals(bytes, response.getContent());
}
private void testSameMethodRedirect(final HttpMethod method, int redirectCode) throws Exception
{
testMethodRedirect(method, method, redirectCode);
@ -416,6 +463,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
private void testMethodRedirect(final HttpMethod requestMethod, final HttpMethod redirectMethod, int redirectCode) throws Exception
{
start(new RedirectHandler());
final AtomicInteger passes = new AtomicInteger();
client.getRequestListeners().add(new org.eclipse.jetty.client.api.Request.Listener.Adapter()
{

View File

@ -78,8 +78,8 @@ import static org.eclipse.jetty.http.HttpTokens.TAB;
* <dl>
* <dt>RFC7230</dt><dd>(default) Compliance with RFC7230</dd>
* <dt>RFC2616</dt><dd>Wrapped headers and HTTP/0.9 supported</dd>
* <dt>LEGACY</dt><dd>(aka STRICT) Adherence to Servlet Specification requirement for
* exact case of header names, bypassing the header caches, which are case insensitive,
* <dt>LEGACY</dt><dd>(aka STRICT) Adherence to Servlet Specification requirement for
* exact case of header names, bypassing the header caches, which are case insensitive,
* otherwise equivalent to RFC2616</dd>
* </dl>
* @see <a href="http://tools.ietf.org/html/rfc7230">RFC 7230</a>
@ -221,10 +221,10 @@ public class HttpParser
CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
}
private static HttpCompliance compliance()
{
Boolean strict = Boolean.getBoolean(__STRICT);
Boolean strict = Boolean.getBoolean(__STRICT);
return strict?HttpCompliance.LEGACY:HttpCompliance.RFC7230;
}
@ -258,7 +258,7 @@ public class HttpParser
{
this(handler,maxHeaderBytes,strict?HttpCompliance.LEGACY:compliance());
}
/* ------------------------------------------------------------------------------- */
@Deprecated
public HttpParser(ResponseHandler handler,int maxHeaderBytes,boolean strict)
@ -271,7 +271,7 @@ public class HttpParser
{
this(handler,-1,compliance);
}
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler handler,int maxHeaderBytes,HttpCompliance compliance)
{
@ -841,17 +841,13 @@ public class HttpParser
switch (_header)
{
case CONTENT_LENGTH:
if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
if (_endOfContent == EndOfContent.CONTENT_LENGTH)
{
try
{
_contentLength=Long.parseLong(_valueString);
}
catch(NumberFormatException e)
{
LOG.ignore(e);
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
}
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Duplicate Content-Length");
}
else if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
{
_contentLength=convertContentLength(_valueString);
if (_contentLength <= 0)
_endOfContent=EndOfContent.NO_CONTENT;
else
@ -861,15 +857,16 @@ public class HttpParser
case TRANSFER_ENCODING:
if (_value==HttpHeaderValue.CHUNKED)
{
_endOfContent=EndOfContent.CHUNKED_CONTENT;
_contentLength=-1;
}
else
{
if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
_endOfContent=EndOfContent.CHUNKED_CONTENT;
else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
{
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad chunking");
}
}
break;
@ -919,6 +916,18 @@ public class HttpParser
_field=null;
}
private long convertContentLength(String valueString)
{
try
{
return Long.parseLong(valueString);
}
catch(NumberFormatException e)
{
LOG.ignore(e);
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Invalid Content-Length Value");
}
}
/* ------------------------------------------------------------------------------- */
/*
@ -970,12 +979,12 @@ public class HttpParser
setState(State.HEADER_VALUE);
break;
}
case HttpTokens.LINE_FEED:
{
// process previous header
parsedHeader();
_contentPosition=0;
// End of headers!
@ -1039,7 +1048,7 @@ public class HttpParser
// process previous header
parsedHeader();
// handle new header
if (buffer.hasRemaining())
{

View File

@ -415,32 +415,35 @@ public class LdapLoginModule extends AbstractLoginModule
return isAuthenticated();
}
boolean authed = false;
if (_forceBindingLogin)
{
return bindingLogin(webUserName, webCredential);
authed = bindingLogin(webUserName, webCredential);
}
// This sets read and the credential
UserInfo userInfo = getUserInfo(webUserName);
if (userInfo == null)
{
setAuthenticated(false);
return false;
}
setCurrentUser(new JAASUserInfo(userInfo));
boolean authed = false;
if (webCredential instanceof String)
authed = credentialLogin(Credential.getCredential((String) webCredential));
else
authed = credentialLogin(webCredential);
{
// This sets read and the credential
UserInfo userInfo = getUserInfo(webUserName);
if (userInfo == null)
{
setAuthenticated(false);
return false;
}
setCurrentUser(new JAASUserInfo(userInfo));
if (webCredential instanceof String)
authed = credentialLogin(Credential.getCredential((String) webCredential));
else
authed = credentialLogin(webCredential);
}
//only fetch roles if authenticated
if (authed)
getCurrentUser().fetchRoles();
return authed;
}
catch (UnsupportedCallbackException e)

View File

@ -24,12 +24,14 @@ import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -98,11 +100,10 @@ public class FileSessionDataStore extends AbstractSessionDataStore
File file = null;
if (_storeDir != null)
{
file = new File(_storeDir, getFileName(id));
if (file.exists() && file.getParentFile().equals(_storeDir))
file = getFile(_storeDir, id);
if (file != null && file.exists() && file.getParentFile().equals(_storeDir))
{
file.delete();
return true;
return file.delete();
}
}
@ -114,12 +115,55 @@ public class FileSessionDataStore extends AbstractSessionDataStore
* @see org.eclipse.jetty.server.session.SessionDataStore#getExpired(Set, int)
*/
@Override
public Set<String> doGetExpired(Set<String> candidates, int expiryTimeoutSec)
public Set<String> doGetExpired(final Set<String> candidates, final int expiryTimeoutSec)
{
//we don't want to open up each file and check, so just leave it up to the SessionStore
//TODO as the session manager is likely to be a lazy loader, if a session is never requested, its
//file will stay forever after a restart
return candidates;
final long now = System.currentTimeMillis();
HashSet<String> expired = new HashSet<String>();
File[] files = _storeDir.listFiles(new FilenameFilter()
{
@Override
public boolean accept(File dir, String name)
{
if (dir != _storeDir)
return false;
String s = name.substring(0, name.indexOf('_'));
long expiry = (s==null?0:Long.parseLong(s));
if (expiry > 0 && expiry < now)
return true;
else
return false;
}
});
if (files != null)
{
for (File f:files)
{
expired.add(getIdFromFile(f));
}
}
//check candidates that were not found to be expired, perhaps they no
//longer exist and they should be expired
for (String c:candidates)
{
if (!expired.contains(c))
{
//check if the file exists
File f = getFile(_storeDir, c);
if (f == null || !f.exists())
expired.add(c);
}
}
return expired;
}
@ -136,9 +180,9 @@ public class FileSessionDataStore extends AbstractSessionDataStore
{
public void run ()
{
File file = new File(_storeDir,getFileName(id));
File file = getFile(_storeDir,id);
if (!file.exists())
if (file == null || !file.exists())
{
if (LOG.isDebugEnabled())
LOG.debug("No file: {}",file);
@ -187,9 +231,13 @@ public class FileSessionDataStore extends AbstractSessionDataStore
File file = null;
if (_storeDir != null)
{
file = new File(_storeDir, getFileName(id));
if (file.exists())
//remove any existing file for the session
file = getFile(_storeDir, id);
if (file != null && file.exists())
file.delete();
//make a fresh file using the latest session expiry
file = new File(_storeDir, getFileNameWithExpiry(data));
try(FileOutputStream fos = new FileOutputStream(file,false))
{
@ -264,7 +312,50 @@ public class FileSessionDataStore extends AbstractSessionDataStore
{
return _context.getCanonicalContextPath()+"_"+_context.getVhost()+"_"+id;
}
private String getFileNameWithExpiry (SessionData data)
{
return ""+data.getExpiry()+"_"+getFileName(data.getId());
}
private String getIdFromFile (File file)
{
if (file == null)
return null;
String name = file.getName();
return name.substring(name.lastIndexOf('_')+1);
}
/**
* Find a File for the session id for the current context.
*
* @param storeDir
* @param id
* @return
*/
private File getFile (final File storeDir, final String id)
{
File[] files = storeDir.listFiles (new FilenameFilter() {
/**
* @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
*/
@Override
public boolean accept(File dir, String name)
{
if (dir != storeDir)
return false;
return (name.contains(getFileName(id)));
}
});
if (files == null || files.length < 1)
return null;
return files[0];
}
/**
* @param is inputstream containing session data

View File

@ -408,6 +408,8 @@ public class Session implements SessionManager.SessionIf
_sessionData.setDirty(true);
if (secs <= 0)
LOG.warn("Session {} is now immortal (maxInactiveInterval={})", _sessionData.getId(), secs);
else if (LOG.isDebugEnabled())
LOG.debug("Session {} maxInactiveInterval={}", _sessionData.getId(), secs);
}
}

View File

@ -663,9 +663,10 @@ public class SessionManager extends ContainerLifeCycle implements org.eclipse.je
public void setMaxInactiveInterval(int seconds)
{
_dftMaxIdleSecs=seconds;
if (_dftMaxIdleSecs < 0)
if (_dftMaxIdleSecs <= 0)
LOG.warn("Sessions created by this manager are immortal (default maxInactiveInterval={})"+_dftMaxIdleSecs);
else if (LOG.isDebugEnabled())
LOG.debug("SessionManager default maxInactiveInterval={}", _dftMaxIdleSecs);
}
/* ------------------------------------------------------------ */

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.server.session;
import java.io.File;
import java.io.FilenameFilter;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
@ -153,9 +154,22 @@ public class FileSessionManagerTest
manager.setMaxInactiveInterval(30); // change max inactive interval for *new* sessions
manager.stop();
String expectedFilename = "_0.0.0.0_"+session.getId();
Assert.assertTrue("File should exist!", new File(testDir, expectedFilename).exists());
final String expectedFilename = "_0.0.0.0_"+session.getId();
File[] files = testDir.listFiles(new FilenameFilter(){
@Override
public boolean accept(File dir, String name)
{
return name.contains(expectedFilename);
}
});
Assert.assertNotNull(files);
Assert.assertEquals(1, files.length);
Assert.assertTrue("File should exist!", files[0].exists());
manager.start();

View File

@ -269,7 +269,7 @@
<plugin>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-version-maven-plugin</artifactId>
<version>1.0.10</version>
<version>1.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -215,6 +215,57 @@ public class SessionExpiryTest extends AbstractSessionExpiryTest
}
@Test
public void testChangeNewSessionTimeout () throws Exception
{
String contextPath = "";
String servletMapping = "/server";
int inactivePeriod = 10;
int scavengePeriod = 1;
int inspectPeriod = 1;
int idlePassivatePeriod = 0;
AbstractTestServer server1 = createServer(0, inactivePeriod, scavengePeriod,inspectPeriod, idlePassivatePeriod);
ImmediateChangeTimeoutServlet servlet = new ImmediateChangeTimeoutServlet();
ServletHolder holder = new ServletHolder(servlet);
ServletContextHandler context = server1.addContext(contextPath);
context.addServlet(holder, servletMapping);
TestHttpSessionListener listener = new TestHttpSessionListener();
context.getSessionHandler().addEventListener(listener);
server1.start();
int port1 = server1.getPort();
try
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port1 + contextPath + servletMapping;
inactivePeriod = 5; //change from the sessionmanager configured default
//make a request to set up a session on the server and change its inactive setting straight away
ContentResponse response1 = client.GET(url + "?action=init&val="+inactivePeriod);
assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
String sessionCookie = response1.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
// Mangle the cookie, replacing Path with $Path, etc.
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
String sessionId = AbstractTestServer.extractSessionId(sessionCookie);
DBCollection sessions = ((MongoSessionIdManager)((MongoTestServer)server1).getServer().getSessionIdManager()).getSessions();
verifySessionCreated(listener,sessionId);
//verify that the session timeout is the new value and not the default
verifySessionTimeout(sessions, sessionId, inactivePeriod);
}
finally
{
server1.stop();
}
}
public void verifySessionTimeout (DBCollection sessions, String id, int sec) throws Exception
{
long val;
@ -299,5 +350,31 @@ public class SessionExpiryTest extends AbstractSessionExpiryTest
}
}
}
public static class ImmediateChangeTimeoutServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
{
String action = request.getParameter("action");
if ("init".equals(action))
{
HttpSession session = request.getSession(true);
assertNotNull(session);
String tmp = request.getParameter("val");
int val = (StringUtil.isBlank(tmp)?0:Integer.valueOf(tmp.trim()));
session.setMaxInactiveInterval(val);
}
else if ("change".equals(action))
{
String tmp = request.getParameter("val");
int val = (StringUtil.isBlank(tmp)?0:Integer.valueOf(tmp.trim()));
HttpSession session = request.getSession(false);
assertNotNull(session);
session.setMaxInactiveInterval(val);
}
}
}
}