Merge branch 'jetty-9.4.x'

This commit is contained in:
Joakim Erdfelt 2016-11-15 14:30:19 -07:00
commit fa6d9029fc
200 changed files with 13917 additions and 6043 deletions

93
Jenkinsfile vendored
View File

@ -9,8 +9,9 @@ node {
try
{
stage 'Checkout'
checkout scm
stage('Checkout') {
checkout scm
}
} catch (Exception e) {
notifyBuild("Checkout Failure")
throw e
@ -18,10 +19,11 @@ node {
try
{
stage 'Compile'
withEnv(mvnEnv) {
timeout(time: 15, unit: 'MINUTES') {
sh "mvn -B clean install -Dtest=None"
stage('Compile') {
withEnv(mvnEnv) {
timeout(time: 15, unit: 'MINUTES') {
sh "mvn -B clean install -Dtest=None"
}
}
}
} catch(Exception e) {
@ -31,10 +33,11 @@ node {
try
{
stage 'Javadoc'
withEnv(mvnEnv) {
timeout(time: 15, unit: 'MINUTES') {
sh "mvn -B javadoc:javadoc"
stage('Javadoc') {
withEnv(mvnEnv) {
timeout(time: 15, unit: 'MINUTES') {
sh "mvn -B javadoc:javadoc"
}
}
}
} catch(Exception e) {
@ -44,31 +47,51 @@ node {
try
{
stage 'Test'
withEnv(mvnEnv) {
timeout(time: 60, unit: 'MINUTES') {
// Run test phase / ignore test failures
sh "mvn -B install -Dmaven.test.failure.ignore=true"
// Report failures in the jenkins UI
step([$class: 'JUnitResultArchiver',
testResults: '**/target/surefire-reports/TEST-*.xml'])
// Collect up the jacoco execution results
step([$class: 'JacocoPublisher',
inclusionPattern: "**/org/eclipse/jetty/**/*.class",
execPattern: '**/target/jacoco.exec',
classPattern: '**/target/classes',
sourcePattern: '**/src/main/java'])
// Report on Maven and Javadoc warnings
step([$class: 'WarningsPublisher',
consoleParsers: [
[parserName: 'Maven'],
[parserName: 'JavaDoc'],
[parserName: 'JavaC']
]])
}
if(isUnstable())
{
notifyBuild("Unstable / Test Errors")
stage('Test') {
withEnv(mvnEnv) {
timeout(time: 60, unit: 'MINUTES') {
// Run test phase / ignore test failures
sh "mvn -B install -Dmaven.test.failure.ignore=true"
// Report failures in the jenkins UI
step([$class: 'JUnitResultArchiver',
testResults: '**/target/surefire-reports/TEST-*.xml'])
// Collect up the jacoco execution results
def jacocoExcludes =
// build tools
"**/org/eclipse/jetty/ant/**" +
",**/org/eclipse/jetty/maven/**" +
",**/org/eclipse/jetty/jspc/**" +
// example code / documentation
",**/org/eclipse/jetty/embedded/**" +
",**/org/eclipse/jetty/asyncrest/**" +
",**/org/eclipse/jetty/demo/**" +
// special environments / late integrations
",**/org/eclipse/jetty/gcloud/**" +
",**/org/eclipse/jetty/infinispan/**" +
",**/org/eclipse/jetty/osgi/**" +
",**/org/eclipse/jetty/spring/**" +
",**/org/eclipse/jetty/http/spi/**" +
// test classes
",**/org/eclipse/jetty/tests/**" +
",**/org/eclipse/jetty/test/**";
step([$class: 'JacocoPublisher',
inclusionPattern: '**/org/eclipse/jetty/**/*.class',
exclusionPattern: jacocoExcludes,
execPattern: '**/target/jacoco.exec',
classPattern: '**/target/classes',
sourcePattern: '**/src/main/java'])
// Report on Maven and Javadoc warnings
step([$class: 'WarningsPublisher',
consoleParsers: [
[parserName: 'Maven'],
[parserName: 'JavaDoc'],
[parserName: 'JavaC']
]])
}
if(isUnstable())
{
notifyBuild("Unstable / Test Errors")
}
}
}
} catch(Exception e) {

6
KEYS.txt Normal file
View File

@ -0,0 +1,6 @@
# GPG Release Key Fingerprints
Jan Bartel AED5 EE6C 45D0 FE8D 5D1B 164F 27DE D4BF 6216 DB
Simone Bordet 8B09 6546 B1A8 F026 56B1 5D3B 1677 D141 BCF3 58
Joakim Erdfelt <joakim@erdfelt.com> BFBB 21C2 46D7 7768 3628 7A48 A04E 0C74 ABB3 5FEA
Joakim Erdfelt <joakim@apache.org> B59B 67FD 7904 9843 67F9 3180 0818 D9D6 8FB6 7BAC
Jesse McConnell 2A68 4B57 436A 81FA 8706 B53C 61C3 351A 438A 3B7D

10985
VERSION.txt

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,31 @@
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>nolog-jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>nolog</classifier>
<excludes>
<exclude>META-INF/services/org.apache.juli.logging.Log</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
@ -39,11 +64,6 @@
<artifactId>jetty-util</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Schemas -->
<dependency>
@ -68,5 +88,25 @@
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
</dependency>
<!-- tests -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -29,8 +29,9 @@ import javax.servlet.ServletContext;
import org.apache.jasper.servlet.JasperInitializer;
import org.apache.jasper.servlet.TldPreScanned;
import org.apache.jasper.servlet.TldScanner;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.xml.sax.SAXException;
/**
@ -38,8 +39,7 @@ import org.xml.sax.SAXException;
*/
public class JettyJasperInitializer extends JasperInitializer
{
private static final Logger LOG = Log.getLogger(JettyJasperInitializer.class);
private static final Log LOG = LogFactory.getLog(JasperInitializer.class);
/**
* NullTldScanner
*
@ -111,6 +111,4 @@ public class JettyJasperInitializer extends JasperInitializer
if (LOG.isDebugEnabled()) LOG.debug("Defaulting to jasper tld scanning");
return super.newTldScanner(context, namespaceAware, validate, blockExternal);
}
}

View File

@ -19,6 +19,9 @@
package org.eclipse.jetty.jsp;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
@ -26,9 +29,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.jasper.servlet.JspServlet;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
/**
* JettyJspServlet
@ -76,7 +77,7 @@ public class JettyJspServlet extends JspServlet
pathInfo = request.getPathInfo();
}
String pathInContext = URIUtil.addPaths(servletPath,pathInfo);
String pathInContext = addPaths(servletPath,pathInfo);
String jspFile = getInitParameter("jspFile");
@ -84,7 +85,7 @@ public class JettyJspServlet extends JspServlet
//otherwise the default servlet might handle it
if (jspFile == null)
{
if (pathInContext.endsWith("/"))
if (pathInContext != null && pathInContext.endsWith("/"))
{
//dispatch via forward to the default servlet
getServletContext().getNamedDispatcher("default").forward(req, resp);
@ -93,13 +94,16 @@ public class JettyJspServlet extends JspServlet
else
{
//check if it resolves to a directory
Resource resource = ((ContextHandler.Context)getServletContext()).getContextHandler().getResource(pathInContext);
if (resource!=null && resource.isDirectory())
String realPath = getServletContext().getRealPath(pathInContext);
if (realPath != null)
{
//dispatch via forward to the default servlet
getServletContext().getNamedDispatcher("default").forward(req, resp);
return;
Path asPath = Paths.get(realPath);
if (Files.exists(asPath) && Files.isDirectory(asPath))
{
//dispatch via forward to the default servlet
getServletContext().getNamedDispatcher("default").forward(req, resp);
return;
}
}
}
}
@ -108,5 +112,19 @@ public class JettyJspServlet extends JspServlet
super.service(req, resp);
}
/**
* @param servletPath the servletPath of the request
* @param pathInfo the pathInfo of the request
* @return servletPath with pathInfo appended
*/
private String addPaths(String servletPath, String pathInfo)
{
if (servletPath.length()==0)
return pathInfo;
if (pathInfo==null)
return servletPath;
return servletPath+pathInfo;
}
}

View File

@ -0,0 +1,123 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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.jsp;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspFactory;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletTester;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.apache.jasper.runtime.JspFactoryImpl;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.SimpleInstanceManager;
import org.eclipse.jetty.servlet.ServletContextHandler;
public class TestJettyJspServlet
{
File _dir;
ServletTester _tester;
public static class DfltServlet extends HttpServlet
{
public DfltServlet()
{
super();
}
/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setContentType("html/text");
resp.getOutputStream().println("This.Is.The.Default.");
}
}
@Before
public void setUp () throws Exception
{
JspFactory.setDefaultFactory(new JspFactoryImpl());
_dir = MavenTestingUtils.getTestResourceDir("base");
_tester = new ServletTester("/context");
_tester.getContext().setClassLoader(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
ServletHolder jspHolder = _tester.getContext().addServlet(JettyJspServlet.class, "/*");
jspHolder.setInitParameter("scratchdir", MavenTestingUtils.getTargetTestingDir().getAbsolutePath());
_tester.getContext().setResourceBase(_dir.getAbsolutePath());
_tester.getContext().setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
ServletHolder dfltHolder = new ServletHolder();
dfltHolder.setName("default");
dfltHolder.setHeldClass( DfltServlet.class);
_tester.getContext().addServlet(dfltHolder, "/");
_tester.start();
}
@After
public void tearDown() throws Exception
{
if (_tester != null)
_tester.stop();
}
@Test
public void testWithJsp() throws Exception
{
//test that an ordinary jsp is served by jsp servlet
String request = "" +
"GET /context/foo.jsp HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n" +
"\r\n";
String response = _tester.getResponses(request);
assertTrue(!response.contains("This.Is.The.Default."));
}
@Test
public void testWithDirectory() throws Exception
{
//test that a dir is served by the default servlet
String request = "" +
"GET /context/dir HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n" +
"\r\n";
String response = _tester.getResponses(request);
assertTrue(response.contains("This.Is.The.Default."));
}
}

View File

@ -0,0 +1,23 @@
<html><head>
<%@ page import="java.util.Enumeration" %>
</head><body>
<h1>JSP Dump</h1>
<table border="1">
<tr><th>Request URI:</th><td><%= request.getRequestURI() %></td></tr>
<tr><th>ServletPath:</th><td><%= request.getServletPath() %></td></tr>
<tr><th>PathInfo:</th><td><%= request.getPathInfo() %></td></tr>
<%
Enumeration e =request.getParameterNames();
while(e.hasMoreElements())
{
String name = (String)e.nextElement();
%>
<tr>
<th>getParameter("<%= name %>")</th>
<td><%= request.getParameter(name) %></td></tr>
<% } %>
</table>
</body></html>

View File

@ -22,7 +22,13 @@
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -0,0 +1,5 @@
[files]
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.9.v20160720/alpn-boot-8.1.9.v20160720.jar|lib/alpn/alpn-boot-8.1.9.v20160720.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.9.v20160720.jar

View File

@ -0,0 +1,5 @@
[files]
http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.10.v20161026/alpn-boot-8.1.10.v20161026.jar|lib/alpn/alpn-boot-8.1.10.v20161026.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.10.v20161026.jar

View File

@ -44,7 +44,7 @@ public class PostConstructAnnotationHandler extends AbstractIntrospectableAnnota
public void doHandle(Class clazz)
{
//Check that the PostConstruct is on a class that we're interested in
if (Util.supportsPostConstructPreDestroy(clazz))
if (supportsPostConstruct(clazz))
{
Method[] methods = clazz.getDeclaredMethods();
for (int i=0; i<methods.length; i++)
@ -84,4 +84,27 @@ public class PostConstructAnnotationHandler extends AbstractIntrospectableAnnota
}
}
}
/**
* Check if the given class is permitted to have PostConstruct annotation.
* @param c the class
* @return true if the spec permits the class to have PostConstruct, false otherwise
*/
public boolean supportsPostConstruct (Class c)
{
if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
javax.servlet.Filter.class.isAssignableFrom(c) ||
javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionIdListener.class.isAssignableFrom(c) ||
javax.servlet.AsyncListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpUpgradeHandler.class.isAssignableFrom(c))
return true;
return false;
}
}

View File

@ -43,7 +43,7 @@ public class PreDestroyAnnotationHandler extends AbstractIntrospectableAnnotatio
public void doHandle(Class clazz)
{
//Check that the PreDestroy is on a class that we're interested in
if (Util.supportsPostConstructPreDestroy(clazz))
if (supportsPreDestroy(clazz))
{
Method[] methods = clazz.getDeclaredMethods();
for (int i=0; i<methods.length; i++)
@ -85,4 +85,27 @@ public class PreDestroyAnnotationHandler extends AbstractIntrospectableAnnotatio
}
}
}
/**
* Check if the spec permits the given class to use the PreDestroy annotation.
* @param c the class
* @return true if permitted, false otherwise
*/
public boolean supportsPreDestroy (Class c)
{
if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
javax.servlet.Filter.class.isAssignableFrom(c) ||
javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionIdListener.class.isAssignableFrom(c) ||
javax.servlet.AsyncListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpUpgradeHandler.class.isAssignableFrom(c))
return true;
return false;
}
}

View File

@ -21,6 +21,8 @@ package org.eclipse.jetty.annotations;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.annotation.Resource;
@ -40,6 +42,10 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
{
private static final Logger LOG = Log.getLogger(ResourceAnnotationHandler.class);
protected static final List<Class<?>> ENV_ENTRY_TYPES =
Arrays.asList(new Class[] {String.class, Character.class, Integer.class, Boolean.class, Double.class, Byte.class, Short.class, Long.class, Float.class});
protected WebAppContext _context;
@ -57,7 +63,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
*/
public void doHandle(Class<?> clazz)
{
if (Util.supportsResourceInjection(clazz))
if (supportsResourceInjection(clazz))
{
handleClass(clazz);
@ -182,7 +188,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
metaData.setOrigin("resource-ref."+name+".injection",resource,clazz);
}
else if (!Util.isEnvEntryType(type))
else if (!isEnvEntryType(type))
{
//if this is an env-entry type resource and there is no value bound for it, it isn't
//an error, it just means that perhaps the code will use a default value instead
@ -196,7 +202,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//if this is an env-entry type resource and there is no value bound for it, it isn't
//an error, it just means that perhaps the code will use a default value instead
// JavaEE Spec. sec 5.4.1.3
if (!Util.isEnvEntryType(type))
if (!isEnvEntryType(type))
throw new IllegalStateException(e);
}
}
@ -339,7 +345,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
metaData.setOrigin("resource-ref."+name+".injection",resource,clazz);
}
else if (!Util.isEnvEntryType(paramType))
else if (!isEnvEntryType(paramType))
{
//if this is an env-entry type resource and there is no value bound for it, it isn't
@ -353,11 +359,47 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
//if this is an env-entry type resource and there is no value bound for it, it isn't
//an error, it just means that perhaps the code will use a default value instead
// JavaEE Spec. sec 5.4.1.3
if (!Util.isEnvEntryType(paramType))
if (!isEnvEntryType(paramType))
throw new IllegalStateException(e);
}
}
}
}
/**
* Check if the given Class is one that the specification allows to have a Resource annotation.
*
* @param c the class
* @return true if Resource annotation permitted, false otherwise
*/
public boolean supportsResourceInjection (Class<?> c)
{
if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
javax.servlet.Filter.class.isAssignableFrom(c) ||
javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionIdListener.class.isAssignableFrom(c) ||
javax.servlet.AsyncListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpUpgradeHandler.class.isAssignableFrom(c))
return true;
return false;
}
/**
* Check if the class is one of the basic java types permitted as
* env-entries.
* @param clazz the class to check
* @return true if class is permitted by the spec to be an env-entry value
*/
public boolean isEnvEntryType (Class<?> clazz)
{
return ENV_ENTRY_TYPES.contains(clazz);
}
}

View File

@ -1,275 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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.annotations;
import java.lang.reflect.Array;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.TypeUtil;
import org.objectweb.asm.Type;
/**
* Annotation Processing Utilities
*/
public class Util
{
private static Class[] __envEntryClassTypes =
new Class[] {String.class, Character.class, Integer.class, Boolean.class, Double.class, Byte.class, Short.class, Long.class, Float.class};
private static String[] __envEntryTypes =
new String[] { Type.getDescriptor(String.class), Type.getDescriptor(Character.class), Type.getDescriptor(Integer.class), Type.getDescriptor(Boolean.class),
Type.getDescriptor(Double.class), Type.getDescriptor(Byte.class), Type.getDescriptor(Short.class), Type.getDescriptor(Long.class), Type.getDescriptor(Float.class)};
/**
* Check if the presented method belongs to a class that is one
* of the classes with which a servlet container should be concerned.
* @param c the class
* @return true if class is a type of one of the following:
* ({@link javax.servlet.Servlet},
* {@link javax.servlet.Filter},
* {@link javax.servlet.ServletContextListener},
* {@link javax.servlet.ServletContextAttributeListener},
* {@link javax.servlet.ServletRequestListener},
* {@link javax.servlet.ServletRequestAttributeListener},
* {@link javax.servlet.http.HttpSessionListener},
* {@link javax.servlet.http.HttpSessionAttributeListener})
*/
public static boolean isServletType (Class c)
{
boolean isServlet = false;
if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
javax.servlet.Filter.class.isAssignableFrom(c) ||
javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.AsyncListener.class.isAssignableFrom(c))
isServlet=true;
return isServlet;
}
public static boolean supportsResourceInjection (Class c)
{
if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
javax.servlet.Filter.class.isAssignableFrom(c) ||
javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionIdListener.class.isAssignableFrom(c) ||
javax.servlet.AsyncListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpUpgradeHandler.class.isAssignableFrom(c))
return true;
return false;
}
public static boolean supportsPostConstructPreDestroy (Class c)
{
if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
javax.servlet.Filter.class.isAssignableFrom(c) ||
javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpSessionIdListener.class.isAssignableFrom(c) ||
javax.servlet.AsyncListener.class.isAssignableFrom(c) ||
javax.servlet.http.HttpUpgradeHandler.class.isAssignableFrom(c))
return true;
return false;
}
public static boolean isEnvEntryType (Class type)
{
boolean result = false;
for (int i=0;i<__envEntryClassTypes.length && !result;i++)
{
result = (type.equals(__envEntryClassTypes[i]));
}
return result;
}
public static boolean isEnvEntryType (String desc)
{
boolean result = false;
for (int i=0;i<__envEntryTypes.length && !result;i++)
{
result = (desc.equals(__envEntryTypes[i]));
}
return result;
}
public static String normalizePattern(String p)
{
if (p!=null && p.length()>0 && !p.startsWith("/") && !p.startsWith("*"))
return "/"+p;
return p;
}
public static Class[] convertTypes (String params)
throws Exception
{
return convertTypes(Type.getArgumentTypes(params));
}
public static Class[] convertTypes (Type[] types)
throws Exception
{
if (types==null)
return new Class[0];
Class[] classArray = new Class[types.length];
for (int i=0; i<types.length; i++)
{
classArray[i] = convertType(types[i]);
}
return classArray;
}
public static Class convertType (Type t)
throws Exception
{
if (t == null)
return (Class)null;
switch (t.getSort())
{
case Type.BOOLEAN:
{
return Boolean.TYPE;
}
case Type.ARRAY:
{
Class clazz = convertType(t.getElementType());
return Array.newInstance(clazz, 0).getClass();
}
case Type.BYTE:
{
return Byte.TYPE;
}
case Type.CHAR:
{
return Character.TYPE;
}
case Type.DOUBLE:
{
return Double.TYPE;
}
case Type.FLOAT:
{
return Float.TYPE;
}
case Type.INT:
{
return Integer.TYPE;
}
case Type.LONG:
{
return Long.TYPE;
}
case Type.OBJECT:
{
return (Loader.loadClass(t.getClassName()));
}
case Type.SHORT:
{
return Short.TYPE;
}
case Type.VOID:
{
return null;
}
default:
return null;
}
}
public static String asCanonicalName (Type t)
{
if (t == null)
return null;
switch (t.getSort())
{
case Type.BOOLEAN:
{
return TypeUtil.toName(Boolean.TYPE);
}
case Type.ARRAY:
{
return t.getElementType().getClassName();
}
case Type.BYTE:
{
return TypeUtil.toName(Byte.TYPE);
}
case Type.CHAR:
{
return TypeUtil.toName(Character.TYPE);
}
case Type.DOUBLE:
{
return TypeUtil.toName(Double.TYPE);
}
case Type.FLOAT:
{
return TypeUtil.toName(Float.TYPE);
}
case Type.INT:
{
return TypeUtil.toName(Integer.TYPE);
}
case Type.LONG:
{
return TypeUtil.toName(Long.TYPE);
}
case Type.OBJECT:
{
return t.getClassName();
}
case Type.SHORT:
{
return TypeUtil.toName(Short.TYPE);
}
case Type.VOID:
{
return null;
}
default:
return null;
}
}
}

View File

@ -26,6 +26,7 @@ import javax.servlet.Filter;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.Source;
@ -117,7 +118,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
ArrayList<String> paths = new ArrayList<String>();
for (String s:urlPatterns)
{
paths.add(Util.normalizePattern(s));
paths.add(ServletPathSpec.normalize(s));
}
mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
}
@ -188,7 +189,7 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
ArrayList<String> paths = new ArrayList<String>();
for (String s:urlPatterns)
{
paths.add(Util.normalizePattern(s));
paths.add(ServletPathSpec.normalize(s));
}
mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
}

View File

@ -27,6 +27,8 @@ import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.servlet.Holder;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.servlet.Source;
@ -102,7 +104,7 @@ public class WebServletAnnotation extends DiscoveredAnnotation
//canonicalize the patterns
ArrayList<String> urlPatternList = new ArrayList<String>();
for (String p : urlPatterns)
urlPatternList.add(Util.normalizePattern(p));
urlPatternList.add(ServletPathSpec.normalize(p));
String servletName = (annotation.name().equals("")?clazz.getName():annotation.name());

View File

@ -33,6 +33,13 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>

View File

@ -1,10 +1,10 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.jboss.LEVEL=DEBUG
# org.jboss.LEVEL=DEBUG
org.eclipse.jetty.LEVEL=INFO
org.eclipse.jetty.util.DecoratedObjectFactory.LEVEL=DEBUG
# org.eclipse.jetty.util.DecoratedObjectFactory.LEVEL=DEBUG
# org.eclipse.jetty.LEVEL=DEBUG
org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.client.LEVEL=DEBUG

View File

@ -112,7 +112,7 @@ public abstract class HttpConnection implements Connection
}
// If we are HTTP 1.1, add the Host header
if (version.getVersion() == 11)
if (version.getVersion() <= 11)
{
if (!headers.containsKey(HttpHeader.HOST.asString()))
headers.put(getHttpDestination().getHostField());
@ -121,14 +121,15 @@ public abstract class HttpConnection implements Connection
// Add content headers
if (content != null)
{
if (content instanceof ContentProvider.Typed)
if (!headers.containsKey(HttpHeader.CONTENT_TYPE.asString()))
{
if (!headers.containsKey(HttpHeader.CONTENT_TYPE.asString()))
{
String contentType = ((ContentProvider.Typed)content).getContentType();
if (contentType != null)
headers.put(HttpHeader.CONTENT_TYPE, contentType);
}
String contentType = null;
if (content instanceof ContentProvider.Typed)
contentType = ((ContentProvider.Typed)content).getContentType();
if (contentType != null)
headers.put(HttpHeader.CONTENT_TYPE, contentType);
else
headers.put(HttpHeader.CONTENT_TYPE, "application/octet-stream");
}
long contentLength = content.getLength();
if (contentLength >= 0)
@ -136,11 +137,6 @@ public abstract class HttpConnection implements Connection
if (!headers.containsKey(HttpHeader.CONTENT_LENGTH.asString()))
headers.put(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength));
}
else
{
if (!headers.containsKey(HttpHeader.TRANSFER_ENCODING.asString()))
headers.put(CHUNKED_FIELD);
}
}
// Cookies

View File

@ -143,6 +143,8 @@ public class HttpChannelOverHTTP extends HttpChannel
closeReason = "failure";
else if (receiver.isShutdown())
closeReason = "server close";
else if (sender.isShutdown())
closeReason = "client close";
if (closeReason == null)
{
@ -157,7 +159,7 @@ public class HttpChannelOverHTTP extends HttpChannel
}
else
{
// HTTP 1.1 or greater closes only if it has an explicit close.
// HTTP 1.1 closes only if it has an explicit close.
if (responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()))
closeReason = "http/1.1";
}

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.util.IteratingCallback;
public class HttpSenderOverHTTP extends HttpSender
{
private final HttpGenerator generator = new HttpGenerator();
private boolean shutdown;
public HttpSenderOverHTTP(HttpChannelOverHTTP channel)
{
@ -149,7 +150,12 @@ public class HttpSenderOverHTTP extends HttpSender
{
if (LOG.isDebugEnabled())
LOG.debug("Request shutdown output {}", getHttpExchange().getRequest());
getHttpChannel().getHttpConnection().getEndPoint().shutdownOutput();
shutdown = true;
}
protected boolean isShutdown()
{
return shutdown;
}
@Override

View File

@ -99,7 +99,10 @@ public class DigestAuthentication extends AbstractAuthentication
clientQOP = "auth-int";
}
return new DigestResult(headerInfo.getHeader(), response.getContent(), getRealm(), user, password, algorithm, nonce, clientQOP, opaque);
String realm = getRealm();
if (ANY_REALM.equals(realm))
realm = headerInfo.getRealm();
return new DigestResult(headerInfo.getHeader(), response.getContent(), realm, user, password, algorithm, nonce, clientQOP, opaque);
}
private Map<String, String> parseParameters(String wwwAuthenticate)

View File

@ -19,26 +19,26 @@
package org.eclipse.jetty.client;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Test;
@ -51,43 +51,16 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
}
@Test
public void testClientConnectionCloseShutdownOutputWithoutRequestContent() throws Exception
public void test_ClientConnectionClose_ServerConnectionClose_ClientClosesAfterExchange() throws Exception
{
testClientConnectionCloseShutdownOutput(null);
}
@Test
public void testClientConnectionCloseShutdownOutputWithRequestContent() throws Exception
{
testClientConnectionCloseShutdownOutput(new StringContentProvider("data", StandardCharsets.UTF_8));
}
@Test
public void testClientConnectionCloseShutdownOutputWithChunkedRequestContent() throws Exception
{
DeferredContentProvider content = new DeferredContentProvider()
{
@Override
public long getLength()
{
return -1;
}
};
content.offer(ByteBuffer.wrap("data".getBytes(StandardCharsets.UTF_8)));
content.close();
testClientConnectionCloseShutdownOutput(content);
}
private void testClientConnectionCloseShutdownOutput(ContentProvider content) throws Exception
{
AtomicReference<EndPoint> ref = new AtomicReference<>();
byte[] data = new byte[128 * 1024];
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
ref.set(baseRequest.getHttpChannel().getEndPoint());
ServletInputStream input = request.getInputStream();
while (true)
{
@ -95,28 +68,190 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
if (read < 0)
break;
}
response.setStatus(HttpStatus.OK_200);
response.setContentLength(data.length);
response.getOutputStream().write(data);
try
{
// Delay the server from sending the TCP FIN.
Thread.sleep(1000);
}
catch (InterruptedException x)
{
throw new InterruptedIOException();
}
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
ContentResponse response = client.newRequest(host, port)
.scheme(scheme)
.path("/ctx/path")
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
.content(content)
.content(new StringContentProvider("0"))
.onRequestSuccess(request ->
{
HttpConnectionOverHTTP connection = (HttpConnectionOverHTTP)connectionPool.getActiveConnections().iterator().next();
Assert.assertFalse(connection.getEndPoint().isOutputShutdown());
})
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertArrayEquals(data, response.getContent());
Assert.assertEquals(0, connectionPool.getConnectionCount());
}
// Wait for the FIN to arrive to the server
Thread.sleep(1000);
@Test
public void test_ClientConnectionClose_ServerDoesNotRespond_ClientIdleTimeout() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
request.startAsync();
// Do not respond.
}
});
// Do not read from the server because it will trigger
// the send of the TLS Close Message before the response.
String host = "localhost";
int port = connector.getLocalPort();
EndPoint serverEndPoint = ref.get();
ByteBuffer buffer = BufferUtil.allocate(1);
int read = serverEndPoint.fill(buffer);
Assert.assertEquals(-1, read);
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
CountDownLatch resultLatch = new CountDownLatch(1);
long idleTimeout = 1000;
client.newRequest(host, port)
.scheme(scheme)
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
.onRequestSuccess(request ->
{
HttpConnectionOverHTTP connection = (HttpConnectionOverHTTP)connectionPool.getActiveConnections().iterator().next();
Assert.assertFalse(connection.getEndPoint().isOutputShutdown());
})
.send(result ->
{
if (result.isFailed())
resultLatch.countDown();
});
Assert.assertTrue(resultLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
Assert.assertEquals(0, connectionPool.getConnectionCount());
}
@Test
public void test_ClientConnectionClose_ServerPartialResponse_ClientIdleTimeout() throws Exception
{
long idleTimeout = 1000;
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
ServletInputStream input = request.getInputStream();
while (true)
{
int read = input.read();
if (read < 0)
break;
}
response.getOutputStream().print("Hello");
response.flushBuffer();
try
{
Thread.sleep(2 * idleTimeout);
}
catch (InterruptedException x)
{
throw new InterruptedIOException();
}
}
});
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.allocate(8));
CountDownLatch resultLatch = new CountDownLatch(1);
client.newRequest(host, port)
.scheme(scheme)
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
.content(content)
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
.onRequestSuccess(request ->
{
HttpConnectionOverHTTP connection = (HttpConnectionOverHTTP)connectionPool.getActiveConnections().iterator().next();
Assert.assertFalse(connection.getEndPoint().isOutputShutdown());
})
.send(result ->
{
if (result.isFailed())
resultLatch.countDown();
});
content.offer(ByteBuffer.allocate(8));
content.close();
Assert.assertTrue(resultLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
Assert.assertEquals(0, connectionPool.getConnectionCount());
}
@Test
public void test_ClientConnectionClose_ServerNoConnectionClose_ClientCloses() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setContentLength(0);
response.flushBuffer();
try
{
// Delay the server from sending the TCP FIN.
Thread.sleep(1000);
}
catch (InterruptedException x)
{
throw new InterruptedIOException();
}
}
});
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
ContentResponse response = client.newRequest(host, port)
.scheme(scheme)
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
.onRequestSuccess(request ->
{
HttpConnectionOverHTTP connection = (HttpConnectionOverHTTP)connectionPool.getActiveConnections().iterator().next();
Assert.assertFalse(connection.getEndPoint().isOutputShutdown());
})
.onResponseHeaders(r -> r.getHeaders().remove(HttpHeader.CONNECTION))
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertEquals(0, connectionPool.getConnectionCount());
}
}

View File

@ -135,6 +135,14 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
test_Authentication(new DigestAuthentication(uri, realm, "digest", "digest"));
}
@Test
public void test_DigestAnyRealm() throws Exception
{
startDigest(new EmptyServerHandler());
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
test_Authentication(new DigestAuthentication(uri, Authentication.ANY_REALM, "digest", "digest"));
}
private void test_Authentication(Authentication authentication) throws Exception
{
AuthenticationStore authenticationStore = client.getAuthenticationStore();

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.client;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -71,6 +73,7 @@ import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
@ -1289,13 +1292,29 @@ public class HttpClientTest extends AbstractHttpClientServerTest
@Test
public void testSmallContentDelimitedByEOFWithSlowRequestHTTP10() throws Exception
{
testContentDelimitedByEOFWithSlowRequest(HttpVersion.HTTP_1_0, 1024);
try
{
testContentDelimitedByEOFWithSlowRequest(HttpVersion.HTTP_1_0, 1024);
}
catch(ExecutionException e)
{
assertThat(e.getCause(), Matchers.instanceOf(BadMessageException.class));
assertThat(e.getCause().getMessage(), Matchers.containsString("Unknown content"));
}
}
@Test
public void testBigContentDelimitedByEOFWithSlowRequestHTTP10() throws Exception
{
testContentDelimitedByEOFWithSlowRequest(HttpVersion.HTTP_1_0, 128 * 1024);
try
{
testContentDelimitedByEOFWithSlowRequest(HttpVersion.HTTP_1_0, 128 * 1024);
}
catch(ExecutionException e)
{
assertThat(e.getCause(), Matchers.instanceOf(BadMessageException.class));
assertThat(e.getCause().getMessage(), Matchers.containsString("Unknown content"));
}
}
@Test
@ -1563,8 +1582,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
}
@Test
public void testCopyRequest()
throws Exception
public void testCopyRequest() throws Exception
{
startClient();
@ -1611,6 +1629,28 @@ public class HttpClientTest extends AbstractHttpClientServerTest
.header("X-Custom-Header-2", "value"));
}
@Test
public void testHostWithHTTP10() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertThat(request.getHeader("Host"), Matchers.notNullValue());
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.version(HttpVersion.HTTP_1_0)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
}
private void assertCopyRequest(Request original)
{
Request copy = client.copyRequest((HttpRequest) original, original.getURI());

View File

@ -36,6 +36,7 @@ import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.Promise;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -258,7 +259,7 @@ public class HttpSenderOverHTTPTest
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content1 + content2));
Assert.assertThat(requestString,Matchers.endsWith("\r\n\r\n" + content1 + content2));
Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}

View File

@ -127,7 +127,7 @@ public class TypedContentProviderTest extends AbstractHttpClientServerTest
{
baseRequest.setHandled(true);
Assert.assertEquals("GET", request.getMethod());
Assert.assertNull(request.getContentType());
Assert.assertNotNull(request.getContentType());
Assert.assertEquals(content, IO.toString(request.getInputStream()));
}
});

View File

@ -2,3 +2,4 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
#org.eclipse.jetty.http.LEVEL=DEBUG

View File

@ -27,9 +27,11 @@ Enabling these frameworks in the Jetty distribution is as easy as activating any
[source, screen, subs="{sub-order}"]
....
[my-base]$ java -jar ../start.jar --add-to-start=logging-jetty
INFO : logging-jetty initialized in ${jetty.base}/start.d/logging-jetty.ini
INFO : resources transitive
INFO : Base directory was modified
INFO : logging-jetty initialized in ${jetty.base}/start.d/logging-jetty.ini
INFO : resources transitively enabled
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/logging-jetty/resources/jetty-logging.properties to ${jetty.base}/resources/jetty-logging.properties
INFO : Base directory was modified
....
As noted above, Jetty supports a wide array of logging technologies.
@ -78,7 +80,6 @@ Most other top level logging modules work in the same way: `logging-jcl`, `loggi
Jetty uses the SLF4J api as a binding to provide logging information to additional frameworks such as Log4j or Logback.
It can also be used on it's own to provide simple server logging.
To enable the SLF4J framework, you need to activate the `logging-slf4j` module.
By default, log files will be stored in `${jetty.base}/logs`.
[source, screen, subs="{sub-order}"]
....
@ -115,9 +116,9 @@ Proceed (y/N)? y
INFO : slf4j-api transitively enabled
INFO : logging-slf4j initialized in ${jetty.base}/start.d/logging-slf4j.ini
MKDIR : ${jetty.base}/lib/slf4j
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
INFO : Base directory was modified
ERROR : Module logging-slf4j requires a `slf4j-impl` module from one of [slf4j-simple-impl, slf4j-logback, slf4j-jul, slf4j-jcl, slf4j-log4j2, slf4j-log4j]
ERROR : Module logging-slf4j requires a module providing slf4j-impl from one of [slf4j-simple-impl, slf4j-logback, slf4j-jul, slf4j-log4j2, slf4j-log4j]
ERROR : Unsatisfied module dependencies: logging-slf4j
@ -134,10 +135,9 @@ To enable the simple SLF4J implementation, we will also need to activate the `sl
[my-base]$ java -jar ../start.jar --add-to-start=slf4j-simple-impl
INFO : slf4j-simple-impl initialized in ${jetty.base}/start.d/slf4j-simple-impl.ini
INFO : resources transitively enabled
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-simple/1.7.21/slf4j-simple-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-simple-1.7.21.jar
DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-simple/1.7.21/slf4j-simple-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-simple-1.7.21.jar
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/slf4j/simplelogger.properties to ${jetty.base}/resources/simplelogger.properties
MKDIR : ${jetty.base}/logs
COPY : ${jetty.home}/modules/slf4j-simple-impl/resources/simplelogger.properties to ${jetty.base}/resources/simplelogger.properties
INFO : Base directory was modified
[my-base]$ tree
@ -146,7 +146,6 @@ INFO : Base directory was modified
│   └── slf4j
│   ├── slf4j-api-1.7.21.jar
│   └── slf4j-simple-1.7.21.jar
├── logs
├── resources
│   └── simplelogger.properties
└── start.d
@ -154,13 +153,15 @@ INFO : Base directory was modified
└── slf4j-simple-impl.ini
....
Jetty is now configured to log using the SLF4J framework.
A standard SLF4J properties file is located in `${jetty.base}/resources/simplelogger.properties`.
[[example-logging-log4j]]
==== Logging with Log4j and Log4j2
It is possible to have the Jetty Server logging configured so that Log4j or Log4j2 controls the output of logging events produced by Jetty.
This is accomplished by configuring Jetty for logging to http://logging.apache.org/log4j/[Apache Log4j] via http://slf4j.org/manual.html[Slf4j] and the http://slf4j.org/manual.html#swapping[Slf4j binding layer for Log4j].
Implementation of Log4j can be done by enabling the `logging-log4j` module.
By default, log files will be stored in `${jetty.base}/logs`.
[source, screen, subs="{sub-order}"]
....
@ -204,13 +205,12 @@ INFO : resources transitively enabled
INFO : slf4j-log4j transitively enabled
INFO : logging-log4j initialized in ${jetty.base}/start.d/logging-log4j.ini
MKDIR : ${jetty.base}/lib/slf4j
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/log4j/log4j.properties to ${jetty.base}/resources/log4j.properties
DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
MKDIR : ${jetty.base}/lib/log4j
DOWNLOAD: http://central.maven.org/maven2/log4j/log4j/1.2.17/log4j-1.2.17.jar to ${jetty.base}/lib/log4j/log4j-1.2.17.jar
MKDIR : ${jetty.base}/logs
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-log4j12-1.7.21.jar
COPY : /Users/chris/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar to ${jetty.base}/lib/log4j/log4j-1.2.17.jar
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/log4j-impl/resources/log4j.xml to ${jetty.base}/resources/log4j.xml
DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-log4j12-1.7.21.jar
INFO : Base directory was modified
[my-base]$ tree
@ -221,19 +221,20 @@ INFO : Base directory was modified
│   └── slf4j
│   ├── slf4j-api-1.7.21.jar
│   └── slf4j-log4j12-1.7.21.jar
├── logs
├── resources
│   └── log4j.properties
│   └── log4j.xml
└── start.d
└── logging-log4j.ini
....
Or, to enable Log4j2, simply enable the `logging-log4j2` module.
By default, log files will be stored in `${jetty.base}/logs`.
Jetty is now configured to log using the Log4j framework.
A standard Log4j configuration file is located in `${jetty.base}/resources/log4j.xml`.
Or, to set up Log4j2, enable the `logging-log4j2` module.
[source, screen, subs="{sub-order}"]
....
[my-base]$ java -jar ../start.jar --add-to-start=logging-log4j2
[my-base]$ java -jar ../start.jar --add-to-start=logging-log4j2
ALERT: There are enabled module(s) with licenses.
The following 2 module(s):
@ -274,34 +275,32 @@ INFO : resources transitively enabled
INFO : slf4j-log4j2 transitively enabled
INFO : log4j2-impl transitively enabled
MKDIR : ${jetty.base}/lib/slf4j
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
MKDIR : ${jetty.base}/lib/log4j
DOWNLOAD: http://central.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.6.1/log4j-api-2.6.1.jar to ${jetty.base}/lib/log4j/log4j-api-2.6.1.jar
MKDIR : ${jetty.base}/resources
DOWNLOAD: http://central.maven.org/maven2/org/apache/logging/log4j/log4j-slf4j-impl/2.6.1/log4j-slf4j-impl-2.6.1.jar to ${jetty.base}/lib/log4j/log4j-slf4j-impl-2.6.1.jar
DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
MKDIR : ${jetty.base}/lib/log4j2
DOWNLOAD: http://central.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.6.1/log4j-core-2.6.1.jar to ${jetty.base}/lib/log4j2/log4j-core-2.6.1.jar
COPY : ${jetty.home}/modules/log4j2/log4j2.xml to ${jetty.base}/resources/log4j2.xml
MKDIR : ${jetty.base}/logs
DOWNLD: http://central.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.6.1/log4j-api-2.6.1.jar to ${jetty.base}/lib/log4j2/log4j-api-2.6.1.jar
MKDIR : ${jetty.base}/resources
DOWNLD: http://central.maven.org/maven2/org/apache/logging/log4j/log4j-slf4j-impl/2.6.1/log4j-slf4j-impl-2.6.1.jar to ${jetty.base}/lib/log4j2/log4j-slf4j-impl-2.6.1.jar
DOWNLD: http://central.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.6.1/log4j-core-2.6.1.jar to ${jetty.base}/lib/log4j2/log4j-core-2.6.1.jar
COPY : ${jetty.home}/modules/log4j2-impl/resources/log4j2.xml to ${jetty.base}/resources/log4j2.xml
INFO : Base directory was modified
[my-base]$ tree
.
├── lib
│   ├── log4j
│   │   ├── log4j-api-2.6.1.jar
│   │   └── log4j-slf4j-impl-2.6.1.jar
│   ├── log4j2
│   │   └── log4j-core-2.6.1.jar
│   │   ├── log4j-api-2.6.1.jar
│   │   ├── log4j-core-2.6.1.jar
│   │   └── log4j-slf4j-impl-2.6.1.jar
│   └── slf4j
│   └── slf4j-api-1.7.21.jar
├── logs
├── resources
│   └── log4j2.xml
└── start.d
└── logging-log4j2.ini
....
At this point Jetty is configured so that the Jetty server itself will log using Log4j2, using the Log4j2 configuration found in `{$jetty.base}/resources/log4j2.xml`.
[[example-logging-logback]]
==== Logging with Logback
@ -309,7 +308,6 @@ It is possible to have the Jetty Server logging configured so that Logback contr
This is accomplished by configuring Jetty for logging to `Logback`, which uses http://slf4j.org/manual.html[Slf4j] and the http://logback.qos.ch/[Logback Implementation for Slf4j].
To set up Jetty logging via Logback, enable the `logging-logback` module.
By default, log files will be stored in `${jetty.base}/logs`.
[source, screen, subs="{sub-order}"]
....
@ -362,13 +360,12 @@ INFO : slf4j-logback transitively enabled
INFO : logging-logback initialized in ${jetty.base}/start.d/logging-logback.ini
INFO : resources transitively enabled
MKDIR : ${jetty.base}/lib/slf4j
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
MKDIR : ${jetty.base}/lib/logback
DOWNLOAD: http://central.maven.org/maven2/ch/qos/logback/logback-core/1.1.7/logback-core-1.1.7.jar to ${jetty.base}/lib/logback/logback-core-1.1.7.jar
DOWNLD: http://central.maven.org/maven2/ch/qos/logback/logback-core/1.1.7/logback-core-1.1.7.jar to ${jetty.base}/lib/logback/logback-core-1.1.7.jar
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/logback/logback.xml to ${jetty.base}/resources/logback.xml
MKDIR : ${jetty.base}/logs
DOWNLOAD: http://central.maven.org/maven2/ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar to ${jetty.base}/lib/logback/logback-classic-1.1.7.jar
COPY : ${jetty.home}/modules/logback-impl/resources/logback.xml to ${jetty.base}/resources/logback.xml
DOWNLD: http://central.maven.org/maven2/ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar to ${jetty.base}/lib/logback/logback-classic-1.1.7.jar
INFO : Base directory was modified
[my-base]$ tree
@ -379,7 +376,6 @@ INFO : Base directory was modified
│   │   └── logback-core-1.1.7.jar
│   └── slf4j
│   └── slf4j-api-1.7.21.jar
├── logs
├── resources
│   └── logback.xml
└── start.d
@ -387,7 +383,6 @@ INFO : Base directory was modified
....
At this point Jetty is configured so that the Jetty server itself will log using Logback, using the Logback configuration found in `{$jetty.base}/resources/logback.xml`.
Log files will be stored in `${jetty.base}/logs`.
==== Logging with Java Util Logging
@ -395,8 +390,7 @@ Log files will be stored in `${jetty.base}/logs`.
===== Java Util Logging with SLF4J
It is possible to have the Jetty Server logging configured so that `java.util.logging` controls the output of logging events produced by Jetty.
This example demonstrates how to configuring Jetty for logging to `java.util.logging` via http://slf4j.org/manual.html[Slf4j] and the http://slf4j.org/manual.html#swapping[Slf4j binding layer for java.util.logging].
By default, log files will be stored in `${jetty.base}/logs`.
This example demonstrates how to configuring Jetty for logging to `java.util.logging` via http://slf4j.org/manual.html[SLF4J] as a binding layer.
[source, screen, subs="{sub-order}"]
....
@ -436,12 +430,10 @@ INFO : slf4j-jul transitively enabled
INFO : logging-jul initialized in ${jetty.base}/start.d/logging-jul.ini
INFO : resources transitively enabled
MKDIR : ${jetty.base}/etc
COPY : ${jetty.home}/modules/jul-impl/java-util-logging.properties to ${jetty.base}/etc/java-util-logging.properties
MKDIR : ${jetty.base}/logs
COPY : ${jetty.home}/modules/jul-impl/etc/java-util-logging.properties to ${jetty.base}/etc/java-util-logging.properties
MKDIR : ${jetty.base}/lib/slf4j
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-jdk14/1.7.21/slf4j-jdk14-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-jdk14-1.7.21.jar
MKDIR : ${jetty.base}/resources
DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
DOWNLD: http://central.maven.org/maven2/org/slf4j/slf4j-jdk14/1.7.21/slf4j-jdk14-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-jdk14-1.7.21.jar
INFO : Base directory was modified
[my-base]$ tree
@ -452,76 +444,49 @@ INFO : Base directory was modified
│   └── slf4j
│   ├── slf4j-api-1.7.21.jar
│   └── slf4j-jdk14-1.7.21.jar
├── logs
├── resources
└── start.d
└── logging-jul.ini
....
[[example-logging-java-commons-logging]]
==== Logging with Java Commons Logging
Jetty provides support of the Java Commons Logging (jcl) through the `logging-jcl` module, using Slf4j as a binding.
This is enabled by activating the `logging-jcl` module.
By default, log files will be stored in `${jetty.base}/logs`.
Jetty is now configured to log using the JUL framework.
A standard JUL properties file is located in `${jetty.base}/etc/java-util-logging.properties`.
==== Capturing Console Output
By default, enabling the above modules will output log information to the console.
Included in the distribution is the `console-capture` module, which can be used in lieu of additional configuration to the selected logging module to capture this output to a `logs` directory in your `${jetty.base}`.
To enable this functionality, activate the `console-capture` module.
[source, screen, subs="{sub-order}"]
....
[my-base]$ java -jar ../start.jar --add-to-start=logging-jcl
ALERT: There are enabled module(s) with licenses.
The following 2 module(s):
+ contains software not provided by the Eclipse Foundation!
+ contains software not covered by the Eclipse Public License!
+ has not been audited for compliance with its license
Module: jcl-impl
+ Log4j is released under the Apache 2.0 license.
+ http://www.apache.org/licenses/LICENSE-2.0.html
Module: slf4j-api
+ SLF4J is distributed under the MIT License.
+ Copyright (c) 2004-2013 QOS.ch
+ All rights reserved.
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Proceed (y/N)? y
INFO : slf4j-api transitively enabled
INFO : jcl-impl transitively enabled
INFO : resources transitively enabled
INFO : slf4j-jcl transitively enabled
INFO : logging-jcl initialized in ${jetty.base}/start.d/logging-jcl.ini
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.21.jar
DOWNLOAD: http://central.maven.org/maven2/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar to ${jetty.base}/lib/jcl/commons-logging-1.1.3.jar
MKDIR: ${jetty.base}/logs
DOWNLOAD: http://central.maven.org/maven2/org/slf4j/slf4j-jcl/1.7.21/slf4j-jcl-1.7.21.jar to ${jetty.base}/lib/slf4j/slf4j-jcl-1.7.21.jar
INFO : Base directory was modified
[my-base]$ java -jar ../start.jar --add-to-start=console-capture
INFO : console-capture initialized in ${jetty.base}/start.d/console-capture.ini
MKDIR : ${jetty.base}/logs
INFO : Base directory was modified
[my-base]$ tree
.
├── lib
│   ├── jcl
│   │   └── commons-logging-1.1.3.jar
│   └── slf4j
│   ├── slf4j-api-1.7.21.jar
│   └── slf4j-jcl-1.7.21.jar
├── logs
├── resources
│   └── commons-logging.properties
└── start.d
└── logging-jcl.ini
└── console-capture.ini
....
As an example, here is the output from Logback before using the `console-capture` module:
[source, screen, subs="{sub-order}"]
....
[my-base]$ java -jar ../start.jar
419 [main] INFO org.eclipse.jetty.util.log - Logging initialized @508ms to org.eclipse.jetty.util.log.Slf4jLog
540 [main] INFO org.eclipse.jetty.server.Server - jetty-9.4.0-SNAPSHOT
575 [main] INFO o.e.jetty.server.AbstractConnector - Started ServerConnector@3c0ecd4b{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
575 [main] INFO org.eclipse.jetty.server.Server - Started @668ms
....
After enabling `console-capture`, the output is as follows, which displays the location the log is being saved to:
[source, screen, subs="{sub-order}"]
....
[my-base]$ java -jar ../start.jar
151 [main] INFO org.eclipse.jetty.util.log - Logging initialized @238ms to org.eclipse.jetty.util.log.Slf4jLog
196 [main] INFO org.eclipse.jetty.util.log - Console stderr/stdout captured to /installs/jetty-distribution/mybase/logs/2016_10_21.jetty.log
....

View File

@ -23,21 +23,39 @@
If you do nothing to configure a separate logging framework, Jetty will default to using an internal `org.eclipse.jetty.util.log.StdErrLog` implementation.
This will output all logging events to STDERR (aka `System.err`).
Simply use Jetty and `StdErrLog` based logging is output to the console.
Simply use Jetty and `StdErrLog`-based logging is output to the console.
Included in the Jetty distribution is a logging module that is capable of performing simple capturing of all STDOUT (`System.out`) and STDERR (`System.err`) output to a file that is rotated daily.
Included in the Jetty distribution is a logging module named `console-capture` that is capable of performing simple capturing of all STDOUT (`System.out`) and STDERR (`System.err`) output to a file that is rotated daily.
To enable on this feature via the command line:
To enable this feature, simply activate the `console-capture` module on the command line:
[source, screen, subs="{sub-order}"]
....
[my-base]$ java -jar ../start.jar --add-to-start=console-capture
INFO : console-capture initialized in ${jetty.base}/start.d/console-capture.ini
MKDIR : ${jetty.base}/logs
INFO : Base directory was modified
[my-base]$ tree
.
├── logs
└── start.d
└── console-capture.ini
....
The default configuration for logging output will create a file `${jetty.base}/logs/yyyy_mm_dd.stderrout.log` which allows configuration of the output directory by setting the `jetty.logs` property.
Just enabling the `console-capture` will simply output the values of STDERR and STDOUT to a log file.
To customize the log further, a module named `logging-jetty` is available to provides a default properties file to configure.
As with `console-capture`, you activate the `logging-jetty` on the command line.
[source, screen, subs="{sub-order}"]
....
[my-base]$ java -jar ../start.jar --add-to-start=logging-jetty
INFO : logging-jetty initialized in ${jetty.base}/start.d/logging-jetty.ini
INFO : console-capture transitively enabled, ini template available with --add-to-start=console-capture
INFO : resources transitively enabled
MKDIR : ${jetty.base}/resources
COPY : ${jetty.home}/modules/logging-jetty/jetty-logging.properties to ${jetty.base}/resources/jetty-logging.properties
MKDIR : ${jetty.base}/logs
COPY : ${jetty.home}/modules/logging-jetty/resources/jetty-logging.properties to ${jetty.base}/resources/jetty-logging.properties
INFO : Base directory was modified
[my-base]$ tree
@ -46,26 +64,31 @@ INFO : Base directory was modified
├── resources
│   └── jetty-logging.properties
└── start.d
├── console-capture.ini
└── logging-jetty.ini
....
The default configuration for logging output will create a file `${jetty.base}/logs/yyyy_mm_dd.stderrout.log` which allows configuration of the output directory by setting the `jetty.logs` property.
For more advanced logging configurations, please consider use of a separate logging library.
The recommended way to configure `StdErrLog` is to create a `${jetty.base}/resources/jetty-logging.properties` file, specify the log implementation to `StdErrLog` and then setup logging levels.
Once activated, you can find the properties file at `${jetty.base}/resources/jetty-logging.properties`.
By default, the following parameters are defined.
To change them, un-comment the line and substitute your naming scheme and configuration choices.
[source, properties, subs="{sub-order}"]
....
# Configure Jetty for StdErrLog Logging
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StrErrLog
# Overall Logging Level is INFO
org.eclipse.jetty.LEVEL=INFO
# Detail Logging for WebSocket
org.eclipse.jetty.websocket.LEVEL=DEBUG
## Force jetty logging implementation
#org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
## Set logging levels from: ALL, DEBUG, INFO, WARN, OFF
#org.eclipse.jetty.LEVEL=INFO
#com.example.LEVEL=INFO
## Hide stacks traces in logs?
#com.example.STACKS=false
## Show the source file of a log location?
#com.example.SOURCE=false
....
There are a number of properties that can be defined in the configuration that will affect the behavior of `StdErrLog`.
There are a number of properties that can be defined in the configuration that will affect the behavior of StdErr logging with `console-capture`.
`<name>.LEVEL=<level>`::
Sets the logging level for all loggers within the `name` specified to the level, which can be (in increasing order of restriction) `ALL`, `DEBUG`, `INFO`, `WARN`, `OFF`.
@ -95,22 +118,20 @@ There are a number of properties that can be defined in the configuration that w
+
[source, screen, subs="{sub-order}"]
....
2014-06-03 14:36:16.013:INFO:oejs.Server:main: jetty-9.2.0.v20140526
2014-06-03 14:36:16.028:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:/opt/jetty/demo-base/webapps/] at interval 1
2014-06-03 14:36:16.051:INFO:oejsh.ContextHandler:main: Started o.e.j.s.h.MovedContextHandler@7d256e50{/oldContextPath,null,AVAILABLE}
2014-06-03 14:36:17.880:INFO:oejs.ServerConnector:main: Started ServerConnector@34f2d11a{HTTP/1.1}{0.0.0.0:8080}
2014-06-03 14:36:17.888:INFO:oejs.Server:main: Started @257ms
2016-10-21 15:31:01.248:INFO::main: Logging initialized @332ms to org.eclipse.jetty.util.log.StdErrLog
2016-10-21 15:31:01.370:INFO:oejs.Server:main: jetty-9.4.0-SNAPSHOT
2016-10-21 15:31:01.400:INFO:oejs.AbstractConnector:main: Started ServerConnector@2c330fbc{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2016-10-21 15:31:01.400:INFO:oejs.Server:main: Started @485ms
....
+
* Example when set to true:
+
[source, screen, subs="{sub-order}"]
....
2014-06-03 14:38:19.019:INFO:org.eclipse.jetty.server.Server:main: jetty-9.2.0.v20140526
2014-06-03 14:38:19.032:INFO:org.eclipse.jetty.deploy.providers.ScanningAppProvider:main: Deployment monitor [file:/opt/jetty/demo-base/webapps/] at interval 1
2014-06-03 14:38:19.054:INFO:org.eclipse.jetty.server.handler.ContextHandler:main: Started o.e.j.s.h.MovedContextHandler@246d8660{/oldContextPath,null,AVAILABLE}
2014-06-03 14:38:20.715:INFO:org.eclipse.jetty.server.ServerConnector:main: Started ServerConnector@59f625be{HTTP/1.1}{0.0.0.0:8080}
2014-06-03 14:38:20.723:INFO:org.eclipse.jetty.server.Server:main: Started @243ms
2016-10-21 15:31:35.020:INFO::main: Logging initialized @340ms to org.eclipse.jetty.util.log.StdErrLog
2016-10-21 15:31:35.144:INFO:org.eclipse.jetty.server.Server:main: jetty-9.4.0-SNAPSHOT
2016-10-21 15:31:35.174:INFO:org.eclipse.jetty.server.AbstractConnector:main: Started ServerConnector@edf4efb{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2016-10-21 15:31:35.175:INFO:org.eclipse.jetty.server.Server:main: Started @495ms
....
[[deprecated-parameters]]

View File

@ -15,7 +15,7 @@
// ========================================================================
[[example-logging-logback-centralized]]
=== Example: Centralized Logging with Logback
=== Centralized Logging using Logback
The term _Centralized Logging_ refers to a forced logging configuration for the Jetty Server and all web applications that are deployed on the server.
It routes all logging events from the web applications to a single configuration on the Server side.

View File

@ -24,6 +24,7 @@ If you need a session manager that can work in a clustered scenario with multipl
Jetty also offers more niche session managers that leverage backends such as MongoDB, Inifinispan, or even Google's Cloud Data Store.
include::session-hierarchy.adoc[]
include::sessions-details.adoc[]
include::session-configuration-file-system.adoc[]
include::session-configuration-jdbc.adoc[]
include::session-configuration-mongodb.adoc[]

View File

@ -44,7 +44,9 @@ The Google deployment tools will automatically configure the project and authent
==== Configuring Indexes for Session Data
Regardless of whether you're running inside or outside google infrastructure you will need to upload a file that defines some indexes that are needed by the GCloud datastore session data store.
Using some special, composite indexes can speed up session search operations, although it may make write operations slower.
By default, indexes will not be used.
In order to use them, you will need to manually upload a file that defines the indexes.
This file is named `index.yaml` and you can find it in your distribution in `${jetty.base}/etc/sessions/gcloud/index.yaml`.
//TODO - Add index.yaml properties? Test with new 9.4.x. It needs uploaded to Google as part of config

View File

@ -46,7 +46,7 @@ There is only one (1) `SessionDataStore` per `SessionCache`.
// Null cache, memcache, non-sticky load-balancer
// in-memory caching
Visually the Session Hierarchy can be represented like this:
Visually the session architecture can be represented like this:
image::images/SessionsHierarchy.png[]

View File

@ -0,0 +1,131 @@
// ========================================================================
// Copyright (c) 1995-2016 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.
// ========================================================================
[[sessions-details]]
=== Session Configuration and Use Cases
==== Configuration
===== SessionIdManager
There is a maximum of 1 SessionIdManager per jetty Server instance. Its purpose is to generate fresh, unique session ids and to coordinate the re-use of session ids amongst co-operating contexts.
Unlike in previous versions of jetty, the SessionIdManager is agnostic with respect to the type of clustering technology chosen.
Jetty provides a default implementation - the DefaultSessionIdManager - which should meet most users' needs.
If you do not explicitly enable one of the session modules, or otherwise configure a SessionIdManager, the DefaultSessionIdManager will be used.
If the DefaultSessionIdManager does not meet your needs, you can extend the org.eclipse.jetty.server.session.AbstractSessionIdManager or do a fresh implementation of the org.eclipse.jetty.server.session.SessionIdManager interface.
===== HouseKeeper
There is a maximum of 1 HouseKeeper per SessionIdManager. Its purpose is to periodically poll the SessionHandlers to clean out expired sessions.
By default the HouseKeeper will poll the SessionHandlers every 10 mins to find and delete expired sessions, although this interval is configurable.
===== SessionCache
There is 1 SessionCache per context. Its purpose is to provide an L1 cache of Session objects.
Having a working set of Session objects in memory allows multiple simultaneous requests for the same session to share the same Session object.
Jetty provides 2 SessionCache implementations: the DefaultSessionCache and the NullSessionCache.
The DefaultSessionCache retains Session objects in memory in a cache and has a number of configuration options to control cache behaviour.
It is the default that is used if no other SessionCache has been configured.
It is suitable for non-clustered and clustered deployments with a sticky load balancer, as well as clustered deployments with a non-sticky load balancer, with some caveats.
The NullSessionCache does not actually cache any objects: each request uses a fresh Session object.
It is suitable for clustered deployments without a sticky load balancer and non-clustered deployments when purely minimal support for sessions is needed.
SessionCaches always write out a Session to the SessionDataStore whenever the last request for the Session exits.
They can also be configured to do an immediate, eager write of a freshly created session.
This can be useful if you are likely to experience multiple, near simultaneous requests referencing the same session, eg with HTTP2 and you don't have a sticky load balancer.
Alternatively, if the eager write is not done, application paths which create and then invalidate a session within a single request never incur the cost of writing to persistent storage.
Additionally, if the EVICT_ON_INACTIVITY eviction policy is in use, you can configure the DefaultSessionCache to force a write of the Session to the SessionDataStore just before the Session is evicted.
===== SessionDataStore
There is 1 SessionDataStore per context. Its purpose is to handle all persistance related operations on sessions.
The common characteristics for all SessionDataStores are whether or not they support passivation, and the length of the grace period.
Supporting passivation means that session data is serialized.
Some persistence mechanisms serialize, such as JDBC, GCloud Datastore etc, whereas others may store an object in shared memory eg Infinispan when configured with a local cache.
Whether or not a clustering technology entails passivation controls whether or not the session passivation/activation listeners will be called.
The grace period is an interval, configured in seconds, that attempts to deal with the non-transactional nature of sessions with regard to finding sessions that have expired.
Due to the lack of transactionality, in a clustered configuration, even with a sticky load balancer, it is always possible that a Session is live on a node but has not yet been updated in the persistent store.
When SessionDataStores search their persistant store to find sessions that have expired, they typically perform a few sequential searches:
. the first verifies the expiration of a list of candidate session ids suggested by the SessionCache
. the second finds sessions in the store that have expired which were last live on the current node
. the third finds sessions that expired a "while" ago, irrespective of on which node they were last used: the definition of "a while" is based on the grace period.
===== CachingSessionDataStore
The CachingSessionDataStore is a special type of SessionDataStore that inserts an L2 cache of SessionData - the SessionDataMap - in front of a delegate SessionDataStore.
The SessionDataMap is preferentially consulted before the actual SessionDataStore on reads.
This can improve the performance of slow stores.
At the time of writing, jetty provides one implementation of the this L2 cache based on Memcached, the MemcachedSessionDataMap.
==== Use Cases
===== Clustering with a Sticky Load Balancer
Preferably, your cluster will utilize a sticky load balancer.
This will route requests for the same session to the same jetty instance.
In this case, the DefaultSessionCache can be used to keep in-use Session objects in memory.
You can fine-tune the cache by controlling how long Session objects remain in memory with the eviction policy settings.
If you have a large number of Sessions or very large Session objects, then you might want to manage your memory allocation by controlling the amount of time Session objects spend in the cache.
The EVICT_ON_SESSION_EXIT eviction policy will remove a Session object from the cache as soon as the last simultaneous request referencing it exits.
Alternatively, the EVICT_ON_INACTIVITY policy will remove a Session object from the cache after a configurable amount of time has passed without a request referencing it.
If your Sessions are very long lived and infrequently referenced, you might use the EVICT_ON_INACTIVITY_POLICY to control the size of the cache.
If your Sessions are small, or relatively few or stable in number or they are read-mostly, then you might select the NEVER_EVICT policy.
With this policy, Session objects will remain in the cache until they either expire or are explicitly invalidated.
If you have a high likelihood of simultaneous requests for the same session object, then the EVICT_ON_SESSION_EXIT policy will ensure the Session object stays in the cache as long as it is needed.
===== Clustering without a Sticky Load Balancer
Without a sticky load balancer requests for the same session may arrive on any node in the cluster.
This means it is likely that the copy of the Session object in any SessionCache is likely to be out-of-date, as the Session was probably last accessed on a different node.
In this case, your choices are to use either the NullSessionCache or to de-tuned the DefaultSessionCache.
If you use the NullSessionCache all Session object caching is avoided.
This means that every time a request references a session it must be brought in from persistent storage.
It also means that there can be no sharing of Session objects for multiple requests for the same session: each will have their own Session object.
Furthermore, the outcome of session writes are indeterminate because the Servlet Specification does not mandate ACID transactions for sessions.
If you use the DefaultSessionCache, there is a risk that the caches on some nodes will contain out-of-date session information as simultaneous requests for the same session are scattered over the cluster.
To mitigate this somewhat you can use the EVICT_ON_SESSION_EXIT eviction policy: this will ensure that the Session is removed from the cache as soon as the last simultaneous request for it exits.
Again, due to the lack of session transactionality, the ordering outcome of write operations cannot be guaranteed.
As the Session is cached while at least one request is accessing it, it is possible for multiple simultaneous requests to share the same Session object.
===== Handling corrupted or unloadable session data
For various reasons it might not be possible for the SessionDataStore to re-read a stored session.
One scenario is that the session stores a serialized object in it's attributes, and after a redeployment there in an incompatible class change.
Using the setter SessionCache.setRemoveUnloadableSessions(true) will allow the SessionDataStore to delete the unreadable session from persistent storage.
This can be useful from preventing the scavenger from continually erroring on the same expired, but unrestorable session.

View File

@ -131,8 +131,8 @@ Jetty attempts to gently close all TCP/IP connections with proper half close sem
[[jetty-connectors-http-configuration]]
==== HTTP Configuration
The link:{JDURL}/org/eclipse/jetty/server/HttpConfiguration.html[HttpConfiguration] class holds the configuration for link:{JDURL}/org/eclipse/jetty/server/HttpChannel.html[`HTTPChannel`]s, which you can create 1:1 with each HTTP connection or 1:n on a multiplexed HTTP/2 connection.
Thus a `HTTPConfiguration` object is injected into both the HTTP and HTTP/2 connection factories.
The link:{JDURL}/org/eclipse/jetty/server/HttpConfiguration.html[`HttpConfiguration`] class holds the configuration for link:{JDURL}/org/eclipse/jetty/server/HttpChannel.html[`HttpChannel`]s, which you can create 1:1 with each HTTP connection or 1:n on a multiplexed HTTP/2 connection.
Thus a `HttpConfiguration` object is injected into both the HTTP and HTTP/2 connection factories.
To avoid duplicate configuration, the standard Jetty distribution creates the common `HttpConfiguration` instance in link:{SRCDIR}/jetty-server/src/main/config/etc/jetty.xml[`jetty.xml`], which is a `Ref` element then used in link:{SRCDIR}/jetty-server/src/main/config/etc/jetty-http.xml[`jetty-http.xml`], link:{SRCDIR}/jetty-server/src/main/config/etc/jetty-https.xml[`jetty-https.xml`] and in link:{SRCDIR}/jetty-http2/http2-server/src/main/config/etc/jetty-http2.xml[`jetty-http2.xml`].
A typical configuration of link:{JDURL}/org/eclipse/jetty/server/HttpConfiguration.html[HttpConfiguration] is:
@ -171,6 +171,9 @@ This example HttpConfiguration may be used by reference to the ID "`httpConfig`"
</Call>
----
This same `httpConfig` is referenced by the link:{JDURL}/org/eclipse/jetty/server/handler/SecuredRedirectHandler.html[`SecuredRedirectHandler`] when redirecting secure requests.
Please note that if your `httpConfig` does not include a `secureScheme` or `securePort` or there is no `HttpConfiguration` present these types of secured requests will be returned a `403` error.
For SSL based connectors (in `jetty-https.xml` and `jetty-http2.xml`), the common "`httpConfig`" instance is used as the basis to create an SSL specific configuration with ID "`sslHttpConfig`":
[source, xml, subs="{sub-order}"]

File diff suppressed because one or more lines are too long

View File

@ -24,3 +24,4 @@ include::jetty-xml/chapter.adoc[]
include::troubleshooting/chapter.adoc[]
include::debugging/chapter.adoc[]
include::contributing/chapter.adoc[]
include::upgrading/chapter.adoc[]

View File

@ -24,14 +24,24 @@ https://wiki.debian.org/LSBInitScripts[LSB tags].
You can safely replace Jetty 9.3's `jetty.sh` with 9.4's.
==== Modules No Longer Available
==== Removed Classes
`ConcurrentArrayQueue` was removed from use in Jetty 9.3 and the class has been removed entirely as part of Jetty 9.4.
==== Module Changes in Jetty 9.4
[cols="1,1", options="header"]
|===
| 9.3 Module | 9.4 Module
| logging | console-capture
| `logging` | `console-capture`
| `infinispan` | `session-store-infinispan-embedded` or `session-store-infinispan-remote`
| `jdbc-sessions` | `session-store-jdbc`
| `gcloud-memcached-sessions`, `gcloud-session-idmgr` and `gcloud-sessions` | `session-store-gcloud` and `session-store-cache`
| `nosql` | `session-store-mongo`
|===
===== Logging Modules
The module `logging` is no longer available in Jetty 9.4.
The logging module structure present in Jetty 9.3 has been replaced with
@ -45,7 +55,7 @@ If you have a Jetty 9.3 installation, and you have both
`$jetty.base/modules/logging.mod` and `$jetty.base/etc/jetty-logging.xml`,
then this module is local to your `$jetty.base` setup and will be used
by Jetty 9.4 as before.
No changes required on your part.
No changes are required for your implementation.
If either `$jetty.base/modules/logging.mod` or `$jetty.base/etc/jetty-logging.xml`
are missing, then you were relying on those present in `$jetty.home`,
@ -55,23 +65,191 @@ The Jetty 9.3 `logging` module has been renamed to `console-capture` in Jetty 9.
You need to open your Jetty 9.3 `start.ini` and replace the references to the
`logging` modules with `console-capture`.
For example:
For example, in an existing 9.3 `start.ini` file the module declaration for logging would look like this:
.start.ini
[source, screen, subs="{sub-order}"]
----
--module=logging
jetty.logging.retainDays=7
----
should be replaced by:
In 9.4, it should be replaced by:
.start.ini
[source, screen, subs="{sub-order}"]
----
--module=console-capture
jetty.console-capture.retainDays=7
----
The properties that may be present in your Jetty 9.3's `start.ini`, such as
`jetty.logging.retainDays` will still be working in Jetty 9.4, but a warning
will be printed at Jetty 9.4 startup, saying to replace them with correspondent
`jetty.console-capture.*` properties such as `jetty.console-capture.retainDays`.
The properties that may be present in your Jetty 9.3's `start.ini`, such as `jetty.logging.retainDays` will still be working in Jetty 9.4, but a warning will be printed at Jetty 9.4 startup, saying to replace them with correspondent `jetty.console-capture.*` properties such as `jetty.console-capture.retainDays`.
For information on logging modules in the Jetty 9.4 architecture please see the section on link:#configuring-logging-modules[configuring logging modules.]
===== Session Management
Session management received a significant overhaul in Jetty 9.4.
Session functionality has been refactored to promote code-reuse, easier configuration and easier customization.
Whereas previously users needed to edit xml configuration files, in Jetty 9.4 all session behavior is controlled by properties that are exposed by the various session modules.
Users now configure session management by selecting a composition of session modules.
====== Change Overview
SessionIdManager:: Previously there was a different class of SessionIdManager - with different configuration options - depending upon which type of clustering technology chosen.
In Jetty 9.4, there is only one type, the link:{JDURL}/org/eclipse/jetty/server/session/DefaultSessionIdManager.html[org.eclipse.jetty.server.session.DefaultSessionIdManager].
SessionManager:: Previously, there was a different class of SessionManager depending upon which the type of clustering technology chosen.
In Jetty 9.4 we have removed the SessionManager class and split its functionality into different, more easily extensible and composable classes:
General setters:::
All of the common setup of sessions such as the maxInactiveInterval and session cookie-related configuration has been moved to the link:{JDURL}/org/eclipse/jetty/server/session/SessionHandler.html[org.eclipse.jetty.server.session.SessionHandler]
[cols="1,1", options="header"]
|===
| 9.3 SessionManager | 9.4 SessionHandler
| setMaxInactiveInterval(sec) | setMaxInactiveInterval(sec)
| setSessionCookie(String) | setSessionCookie(String)
| setRefreshCookieAge(sec) | setRefreshCookieAge(sec)
| setSecureRequestOnly(boolean) | setSecureRequestOnly(boolean
| setSessionIdPathParameterName(String) | setSessionIdPathParameterName(String)
| setSessionTrackingModes(Set<SessionTrackingMode>) | setSessionTrackingModes(Set<SessionTrackingMode>)
| setHttpOnly(boolean) | setHttpOnly(boolean)
| setUsingCookies(boolean) | setUsingCookies(boolean)
| setCheckingRemoteSessionIdEncoding(boolean) | setCheckingRemoteSessionIdEncoding(boolean)
|===
Persistence:::
In Jetty 9.3 SessionManagers (and sometimes SessionIdManagers) implemented the persistence mechanism.
In Jetty 9.4 we have moved this functionality into the link:{JDURL}/org/eclipse/jetty/server/session/SessionDataStore.html[org.eclipse.jetty.server.session.SessionDataStore].
Session cache:::
In Jetty 9.3 the SessionManager held a map of session objects in memory.
In Jetty 9.4 this has been moved into the new link:{JDURL}/org/eclipse/jetty/server/session/SessionCache.html[org.eclipse.jetty.server.session.SessionCache] interface.
For more information, please refer to the documentation on link:#jetty-sessions-architecture[Jetty Session Architecture.]
====== Default
As with earlier versions of Jetty, if you do not explicitly configure any session modules, the default session infrastructure will be enabled.
In previous versions of Jetty this was referred to as "hash" session management.
The new default provides similar features to the old hash session management:
* a session scavenger thread that runs every 10mins and removes expired sessions
* a session id manager that generates unique session ids and handles session id sharing during context forwarding
* an in-memory cache of session objects.
Requests for the same session in the same context share the same session object.
Session objects remain in the cache until they expire or are explicitly invalidated.
If you wish to configure the default setup further, enable the `session-cache-default` module.
Compatibility
As Session objects do not persist beyond a server restart, there are no compatibility issues.
====== Filesystem
In earlier versions of Jetty, persisting sessions to the local filesystem was an option of the "hash" session manager.
In Jetty 9.4 this has been refactored to its own configurable module `session-store-file`.
Compatibility
Sessions stored to files by earlier versions of jetty are not compatible with jetty-9.4 sessions.
Here is a comparison of file formats, note that the file contents are listed in order of file output:
[cols="1,1", options="header"]
|===
| 9.3 | 9.4
| File name: sessionid | File name: expirytime_contextpath_vhost_sessionid
| sessionid (utf) | sessionid (utf)
| | contextpath (uft)
| | vhost (utf)
| nodeid (utf) | lastnode (utlf)
| createtime (long) | createtime (long)
| accessed (long) | accessed (long)
| | lastaccessed (long)
| | cookiesettime (long)
| | expiry (long)
| requests (int) |
| | maxInactive (long)
| attributes size (int) | attributes size (int)
| attributes serialized (obj) | attributes serialized (obj)
| maxInactive (long) |
|===
====== JDBC
As with earlier versions of Jetty, sessions may be persisted to a relational database.
Enable the `session-store-jdbc` module.
Compatibility
Sessions stored to the database by earlier versions of jetty are not compatible with jetty-9.4 sessions.
The incompatibility is minor: in jetty-9.4 the `rowid` primary key column is no longer used, and the primary key is a composite of `(sessionid,contextpath,vhost)` columns.
====== NoSQL
As with earlier versions of Jetty, sessions may be persisted to a document database.
Jetty supports the Mongo document database.
Enable the `session-store-mongo` module.
Compatibility
Sessions stored to mongo by earlier versions of jetty are not compatible with jetty-9.4 sessions.
The key for each subdocument that represents the session information for a context is different between jetty-9.3 and 9.4:
[cols="1,1", options="header"]
|===
| 9.3 | 9.4
|Each context key is: vhost+context+path, where empty vhosts="::" and root context = "*" and / is replaced by _
|Each context key is: vhost:contextpath, where empty vhosts="0_0_0_0" and root context = "" and / replaced by _
| eg "::/contextA" | eg " 0_0_0_0:_contextA"
|===
====== Infinispan
As with earlier versions of Jetty, sessions may be clustered via Infinispan to either an in-process or remote infinispan instance.
Enable the `session-store-infinispan` module.
Compatibility
Sessions stored in infinispan by jetty-9.3 are incompatible with jetty-9.4.
In jetty-9.3 the serialized object stored to represent the session data was `org.eclipse.jetty.session.infinispan.SerializableSessionData`.
In jetty-9.4 the serialized object is `org.eclipse.jetty.serer.session.SessionData`.
====== GCloud Datastore
As with earlier versions of Jetty, sessions may be persisted to Google's GCloud Datastore.
Enable the `session-store-gcloud` module.
Compatibility
Sessions stored into gcloud datastore by jetty-9.3 are incompatible with jetty-9.4, although the incompatibility is trivial: the name of the session id entity property has changed:
[cols="1,1", options="header"]
|===
|9.3 | 9.4
|Kind: GCloudSession | Kind: GCloudSession
|key: contextpath_vhost_sessionid | key: contextpath_vhost_sessionid
|*"clusterId": sessionId* | *"id" : sessionId*
|"contextPath" : contextpath | "contextPath": contextpath
|"vhost" :vhost | "vhost": vhost
|"accessed":accesstime | "accessed": accesstime
|"lastAccessed": lastaccesstime | "lastAccessed": lastaccesstime
|"createTime": createtime | "createTime": createtime
|"cookieSetTime": cookiesettime | "cookieSetTime": cookiesettime
|"lastNode": lastnode | "lastNode": lastnode
|"expiry": expiry | "expiry": expiry
|"maxInactive": maxInactive | "maxInactive": maxInactive
|"attributes": blob | "attributes": blob
|===
====== GCloud Datastore with Memcached
As with earlier versions of Jetty, sessions can be both persisted to Google's GCloud Datastore, and cached into Memcached for faster access.
Enable the `session-store-gcloud` and `session-store-cache` modules.
Compatibility
Sessions stored into memcached by earlier versions of jetty are incompatible with jetty-9.4. Previous versions of jetty stored `org.eclipse.jetty.gcloud.memcached.session.SerializableSessionData` whereas jetty-9.4 stores `org.eclipse.jetty.server.session.SessionData`.

View File

@ -30,9 +30,12 @@ import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.server.HttpTransport;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class HttpTransportOverFCGI implements HttpTransport
{
private static final Logger LOG = Log.getLogger(HttpTransportOverFCGI.class);
private final ServerGenerator generator;
private final Flusher flusher;
private final int request;
@ -97,6 +100,8 @@ public class HttpTransportOverFCGI implements HttpTransport
private void commit(MetaData.Response info, boolean head, ByteBuffer content, boolean lastContent, Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("commit {} {} l={}",this,info,lastContent);
boolean shutdown = this.shutdown = info.getFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
if (head)
@ -137,7 +142,10 @@ public class HttpTransportOverFCGI implements HttpTransport
@Override
public void abort(Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("abort {} {}",this,failure);
aborted = true;
flusher.shutdown();
}
@Override

View File

@ -608,7 +608,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.timeout(60, TimeUnit.SECONDS)
.send();
Assert.fail();
}

View File

@ -8,68 +8,50 @@ gcloud
[depends]
gcloud
jcl-slf4j
jul-slf4j
jul-impl
[files]
maven://com.google.cloud/google-cloud-datastore/0.4.0|lib/gcloud/google-cloud-datastore-0.4.0.jar
maven://com.google.cloud/google-cloud-core/0.4.0|lib/gcloud/google-cloud-core-0.4.0.jar
maven://com.google.auth/google-auth-library-credentials/0.3.1|lib/gcloud/google-auth-library-credentials-0.3.1.jar
maven://com.google.auth/google-auth-library-oauth2-http/0.3.1|lib/gcloud/google-auth-library-oauth2-http-0.3.1.jar
maven://com.google.http-client/google-http-client-jackson2/1.19.0|lib/gcloud/google-http-client-jackson2-1.19.0.jar
maven://com.fasterxml.jackson.core/jackson-core/2.1.3|lib/gcloud/jackson-core-2.1.3.jar
maven://com.google.http-client/google-http-client/1.21.0|lib/gcloud/google-http-client-1.21.0.jar
maven://com.google.code.findbugs/jsr305/1.3.9|lib/gcloud/jsr305-1.3.9.jar
maven://org.apache.httpcomponents/httpclient/4.0.1|lib/gcloud/httpclient-4.0.1.jar
maven://org.apache.httpcomponents/httpcore/4.0.1|lib/gcloud/httpcore-4.0.1.jar
maven://commons-codec/commons-codec/1.3|lib/gcloud/commons-codec-1.3.jar
maven://com.google.oauth-client/google-oauth-client/1.21.0|lib/gcloud/google-oauth-client-1.21.0.jar
maven://com.google.guava/guava/19.0|lib/gcloud/guava-19.0.jar
maven://com.google.api-client/google-api-client-appengine/1.21.0|lib/gcloud/google-api-client-appengine-1.21.0.jar
maven://com.google.oauth-client/google-oauth-client-appengine/1.21.0|lib/gcloud/google-oauth-client-appengine-1.21.0.jar
maven://com.google.oauth-client/google-oauth-client-servlet/1.21.0|lib/gcloud/google-oauth-client-servlet-1.21.0.jar
maven://com.google.http-client/google-http-client-jdo/1.21.0|lib/gcloud/google-http-client-jdo-1.21.0.jar
maven://com.google.api-client/google-api-client-servlet/1.21.0|lib/gcloud/google-api-client-servlet-1.21.0.jar
maven://javax.jdo/jdo2-api/2.3-eb|lib/gcloud/jdo2-api-2.3-eb.jar
maven://javax.transaction/transaction-api/1.1|lib/gcloud/transaction-api-1.1.jar
maven://com.google.http-client/google-http-client-appengine/1.21.0|lib/gcloud/google-http-client-appengine-1.21.0.jar
maven://com.google.http-client/google-http-client-jackson/1.21.0|lib/gcloud/google-http-client-jackson-1.21.0.jar
maven://org.codehaus.jackson/jackson-core-asl/1.9.11|lib/gcloud/jackson-core-asl-1.9.11.jar
maven://joda-time/joda-time/2.9.2|lib/gcloud/joda-time-2.9.2.jar
maven://org.json/json/20151123|lib/gcloud/json-20151123.jar
maven://com.google.protobuf/protobuf-java/3.0.0|lib/gcloud/protobuf-java-3.0.0.jar
maven://com.google.api/gax/0.0.18|lib/gcloud/gax-0.0.18.jar
maven://com.google.auto.value/auto-value/1.1|lib/gcloud/auto-value-1.1.jar
maven://io.grpc/grpc-all/1.0.1|lib/gcloud/grpc-all-1.0.1.jar
maven://io.grpc/grpc-auth/1.0.1|lib/gcloud/grpc-auth-1.0.1.jar
maven://io.grpc/grpc-context/1.0.1|lib/gcloud/grpc-context-1.0.1.jar
maven://io.grpc/grpc-protobuf/1.0.1|lib/gcloud/grpc-protobuf-1.0.1.jar
maven://com.google.protobuf/protobuf-java-util/3.0.0|lib/gcloud/protobuf-java-util-3.0.0.jar
maven://com.google.code.gson/gson/2.3|lib/gcloud/gson-2.3.jar
maven://io.grpc/grpc-netty/1.0.1|lib/gcloud/grpc-netty-1.0.1.jar
maven://io.netty/netty-codec-http2/4.1.3.Final|lib/gcloud/netty-codec-http2-4.1.3.jar
maven://io.netty/netty-codec-http/4.1.3.Final|lib/gcloud/netty-codec-http-4.1.3.Final.jar
maven://io.netty/netty-codec/4.1.3.Final|lib/gcloud/netty-codec-4.1.3.Final.jar
maven://io.netty/netty-handler/4.1.3.Final|lib/gcloud/netty-handler-4.1.3.Final.jar
maven://io.netty/netty-buffer/4.1.3.Final|lib/gcloud/netty-buffer-4.1.3.Final.jar
maven://io.netty/netty-common/4.1.3.Final|lib/gcloud/netty-common-4.1.3.Final.jar
maven://io.netty/netty-transport/4.1.3.Final|lib/gcloud/netty-transport-4.1.3.Final.jar
maven://io.netty/netty-resolver/4.1.3.Final|lib/gcloud/netty-resolver-4.1.3.Final.jar
maven://io.grpc/grpc-stub/1.0.1|lib/gcloud/grpc-stub-1.0.1.jar
maven://io.grpc/grpc-protobuf-nano/1.0.1|lib/gcloud/grpc-protobuf-nano-1.0.1.jar
maven://com.google.protobuf.nano/protobuf-javanano/3.0.0-alpha-5|lib/gcloud/protobuf-javanano/3.0.0-alpha-5.jar
maven://io.grpc/grpc-core/1.0.1|lib/gcloud/grpc-core-1.0.1.jar
maven://io.grpc/grpc-okhttp/1.0.1|lib/gcloud/grpc-okhttp-1.0.1.jar
maven://com.squareup.okio/okio/1.6.0|lib/gcloud/okio-1.6.0.jar
maven://com.squareup.okhttp/okhttp/2.5.0|lib/gcloud/okhttp-2.5.0.jar
maven://io.grpc/grpc-protobuf-lite/1.0.1|lib/gcloud/grpc-protobuf-lite-1.0.1.jar
maven://com.google.protobuf/protobuf-lite/3.0.1|lib/gcloud/protobuf-lite-3.0.1.jar
maven://com.google.inject/guice/4.0|lib/gcloud/guice-4.0.jar
maven://javax.inject/javax.inject/1|lib/gcloud/javax.inject-1.jar
maven://aopalliance/aopalliance/1.0|lib/gcloud/aopalliance-1.0.jar
maven://com.fasterxml.jackson.core/jackson-core/2.1.3|lib/gcloud/jackson-core-2.1.3.jar
maven://com.google.api-client/google-api-client-appengine/1.21.0|lib/gcloud/google-api-client-appengine-1.21.0.jar
maven://com.google.api-client/google-api-client/1.20.0|lib/gcloud/google-api-client-1.20.0.jar
maven://com.google.api-client/google-api-client-servlet/1.21.0|lib/gcloud/google-api-client-servlet-1.21.0.jar
maven://com.google.api/gax/0.0.21|lib/gcloud/gax-0.0.21.jar
maven://com.google.api.grpc/grpc-google-common-protos/0.1.0|lib/gcloud/grpc-google-common-protos-0.1.0.jar
maven://com.google.api.grpc/grpc-google-iam-v1/0.1.0|lib/gcloud/grpc-google-iam-v1-0.1.0.jar
maven://com.google.cloud.datastore/datastore-v1-protos/1.2.0|lib/gcloud/datastore-v1-protos-1.2.0.jar
maven://com.google.cloud.datastore/datastore-v1-proto-client/1.2.0|lib/gcloud/datastore-v1-proto-client-1.2.0.jar
maven://com.google.auth/google-auth-library-credentials/0.3.1|lib/gcloud/google-auth-library-credentials-0.3.1.jar
maven://com.google.auth/google-auth-library-oauth2-http/0.3.1|lib/gcloud/google-auth-library-oauth2-http-0.3.1.jar
maven://com.google.auto.value/auto-value/1.2|lib/gcloud/auto-value-1.2.jar
maven://com.google.cloud.datastore/datastore-v1-proto-client/1.3.0|lib/gcloud/datastore-v1-proto-client-1.3.0.jar
maven://com.google.cloud.datastore/datastore-v1-protos/1.3.0|lib/gcloud/datastore-v1-protos-1.3.0.jar
maven://com.google.cloud/google-cloud-core/0.5.1|lib/gcloud/google-cloud-core-0.5.0.jar
maven://com.google.cloud/google-cloud-datastore/0.5.1|lib/gcloud/google-cloud-datastore-0.5.1.jar
maven://com.google.code.findbugs/jsr305/1.3.9|lib/gcloud/jsr305-1.3.9.jar
maven://com.google.code.gson/gson/2.3|lib/gcloud/gson-2.3.jar
maven://com.google.guava/guava/19.0|lib/gcloud/guava-19.0.jar
maven://com.google.http-client/google-http-client-appengine/1.21.0|lib/gcloud/google-http-client-appengine-1.21.0.jar
maven://com.google.http-client/google-http-client-jackson2/1.19.0|lib/gcloud/google-http-client-jackson2-1.19.0.jar
maven://com.google.http-client/google-http-client-jackson/1.21.0|lib/gcloud/google-http-client-jackson-1.21.0.jar
maven://com.google.http-client/google-http-client/1.21.0|lib/gcloud/google-http-client-1.21.0.jar
maven://com.google.http-client/google-http-client-jdo/1.21.0|lib/gcloud/google-http-client-jdo-1.21.0.jar
maven://com.google.http-client/google-http-client-protobuf/1.20.0|lib/gcloud/google-http-client-protobuf-1.20.0.jar
maven://com.google.api-client/google-api-client/1.20.0|lib/gcloud/google-api-client/1.20.0
maven://com.google.inject/guice/4.0|lib/gcloud/guice-4.0.jar
maven://com.google.oauth-client/google-oauth-client-appengine/1.21.0|lib/gcloud/google-oauth-client-appengine-1.21.0.jar
maven://com.google.oauth-client/google-oauth-client/1.21.0|lib/gcloud/google-oauth-client-1.21.0.jar
maven://com.google.oauth-client/google-oauth-client-servlet/1.21.0|lib/gcloud/google-oauth-client-servlet-1.21.0.jar
maven://com.google.protobuf/protobuf-java/3.0.0|lib/gcloud/protobuf-java-3.0.0.jar
maven://com.google.protobuf/protobuf-java-util/3.0.0|lib/gcloud/protobuf-java-util-3.0.0.jar
maven://commons-codec/commons-codec/1.3|lib/gcloud/commons-codec-1.3.jar
maven://io.grpc/grpc-context/1.0.1|lib/gcloud/grpc-context-1.0.1.jar
maven://io.grpc/grpc-core/1.0.1|lib/gcloud/grpc-core-1.0.1.jar
maven://io.grpc/grpc-protobuf/1.0.1|lib/gcloud/grpc-protobuf-1.0.1.jar
maven://io.grpc/grpc-protobuf-lite/1.0.1|lib/gcloud/grpc-protobuf-lite-1.0.1.jar
maven://javax.inject/javax.inject/1|lib/gcloud/javax.inject-1.jar
maven://javax.jdo/jdo2-api/2.3-eb|lib/gcloud/jdo2-api-2.3-eb.jar
maven://javax.transaction/transaction-api/1.1|lib/gcloud/transaction-api-1.1.jar
maven://joda-time/joda-time/2.9.2|lib/gcloud/joda-time-2.9.2.jar
maven://org.apache.httpcomponents/httpclient/4.0.1|lib/gcloud/httpclient-4.0.1.jar
maven://org.apache.httpcomponents/httpcore/4.0.1|lib/gcloud/httpcore-4.0.1.jar
maven://org.codehaus.jackson/jackson-core-asl/1.9.11|lib/gcloud/jackson-core-asl-1.9.11.jar
maven://org.json/json/20151123|lib/gcloud/json-20151123.jar

View File

@ -13,7 +13,7 @@
<name>Jetty :: GCloud</name>
<properties>
<gcloud.version>0.4.0</gcloud.version>
<gcloud.version>0.5.1</gcloud.version>
</properties>
<modules>

View File

@ -101,6 +101,13 @@
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -18,16 +18,18 @@
package org.eclipse.jetty.http;
import static org.eclipse.jetty.http.HttpStatus.INTERNAL_SERVER_ERROR_500;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -46,11 +48,10 @@ public class HttpGenerator
public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");
private final static byte[] __colon_space = new byte[] {':',' '};
private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
public static final MetaData.Response CONTINUE_100_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,100,null,null,-1);
public static final MetaData.Response PROGRESS_102_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,102,null,null,-1);
public final static MetaData.Response RESPONSE_500_INFO =
new MetaData.Response(HttpVersion.HTTP_1_1,HttpStatus.INTERNAL_SERVER_ERROR_500,null,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0);
new MetaData.Response(HttpVersion.HTTP_1_1,INTERNAL_SERVER_ERROR_500,null,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0);
// states
public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
@ -63,13 +64,18 @@ public class HttpGenerator
private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
private long _contentPrepared = 0;
private boolean _noContent = false;
private boolean _noContentResponse = false;
private Boolean _persistent = null;
private final int _send;
private final static int SEND_SERVER = 0x01;
private final static int SEND_XPOWEREDBY = 0x02;
private final static Set<String> __assumedContentMethods = new HashSet<>(Arrays.asList(new String[]{HttpMethod.POST.asString(),HttpMethod.PUT.asString()}));
private final static Trie<Boolean> __assumedContentMethods = new ArrayTrie<>(8);
static
{
__assumedContentMethods.put(HttpMethod.POST.asString(),Boolean.TRUE);
__assumedContentMethods.put(HttpMethod.PUT.asString(),Boolean.TRUE);
}
/* ------------------------------------------------------------------------------- */
public static void setJettyVersion(String serverVersion)
@ -101,7 +107,7 @@ public class HttpGenerator
{
_state = State.START;
_endOfContent = EndOfContent.UNKNOWN_CONTENT;
_noContent=false;
_noContentResponse=false;
_persistent = null;
_contentPrepared = 0;
_needCRLF = false;
@ -160,7 +166,7 @@ public class HttpGenerator
/* ------------------------------------------------------------ */
public boolean isNoContent()
{
return _noContent;
return _noContentResponse;
}
/* ------------------------------------------------------------ */
@ -214,7 +220,7 @@ public class HttpGenerator
// If we have not been told our persistence, set the default
if (_persistent==null)
{
_persistent=info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal();
_persistent=info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal();
if (!_persistent && HttpMethod.CONNECT.is(info.getMethod()))
_persistent=true;
}
@ -226,8 +232,8 @@ public class HttpGenerator
// generate ResponseLine
generateRequestLine(info,header);
if (info.getVersion()==HttpVersion.HTTP_0_9)
throw new BadMessageException(500,"HTTP/0.9 not supported");
if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"HTTP/0.9 not supported");
generateHeaders(info,header,content,last);
@ -252,10 +258,17 @@ public class HttpGenerator
return Result.FLUSH;
}
catch(BadMessageException e)
{
throw e;
}
catch(BufferOverflowException e)
{
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"Request header too large",e);
}
catch(Exception e)
{
String message= (e instanceof BufferOverflowException)?"Request header too large":e.getMessage();
throw new BadMessageException(500,message,e);
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,e.getMessage(),e);
}
finally
{
@ -342,9 +355,9 @@ public class HttpGenerator
{
if (info==null)
return Result.NEED_INFO;
HttpVersion version=info.getVersion();
HttpVersion version=info.getHttpVersion();
if (version==null)
throw new BadMessageException(500,"No version");
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"No version");
switch(version)
{
case HTTP_1_0:
@ -381,7 +394,7 @@ public class HttpGenerator
int status=info.getStatus();
if (status>=100 && status<200 )
{
_noContent=true;
_noContentResponse=true;
if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
{
@ -392,7 +405,7 @@ public class HttpGenerator
}
else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
{
_noContent=true;
_noContentResponse=true;
}
generateHeaders(info,header,content,last);
@ -407,10 +420,17 @@ public class HttpGenerator
}
_state = last?State.COMPLETING:State.COMMITTED;
}
catch(BadMessageException e)
{
throw e;
}
catch(BufferOverflowException e)
{
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"Request header too large",e);
}
catch(Exception e)
{
String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
throw new BadMessageException(500,message,e);
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,e.getMessage(),e);
}
finally
{
@ -523,7 +543,7 @@ public class HttpGenerator
header.put((byte)' ');
header.put(StringUtil.getBytes(request.getURIString()));
header.put((byte)' ');
header.put(request.getVersion().toBytes());
header.put(request.getHttpVersion().toBytes());
header.put(HttpTokens.CRLF);
}
@ -578,22 +598,29 @@ public class HttpGenerator
}
/* ------------------------------------------------------------ */
private void generateHeaders(MetaData _info,ByteBuffer header,ByteBuffer content,boolean last)
private void generateHeaders(MetaData info,ByteBuffer header,ByteBuffer content,boolean last)
{
final MetaData.Request request=(_info instanceof MetaData.Request)?(MetaData.Request)_info:null;
final MetaData.Response response=(_info instanceof MetaData.Response)?(MetaData.Response)_info:null;
final MetaData.Request request=(info instanceof MetaData.Request)?(MetaData.Request)info:null;
final MetaData.Response response=(info instanceof MetaData.Response)?(MetaData.Response)info:null;
if (LOG.isDebugEnabled())
{
LOG.debug("generateHeaders {} last={} content={}",info,last,BufferUtil.toDetailString(content));
LOG.debug(info.getFields().toString());
}
// default field values
int send=_send;
HttpField transfer_encoding=null;
boolean keep_alive=false;
boolean close=false;
boolean content_type=false;
StringBuilder connection = null;
long content_length = _info.getContentLength();
boolean http11 = info.getHttpVersion() == HttpVersion.HTTP_1_1;
boolean close = false;
boolean chunked = false;
boolean content_type = false;
long content_length = info.getContentLength();
boolean content_length_field = false;
// Generate fields
HttpFields fields = _info.getFields();
HttpFields fields = info.getFields();
if (fields != null)
{
int n=fields.size();
@ -612,10 +639,11 @@ public class HttpGenerator
switch (h)
{
case CONTENT_LENGTH:
_endOfContent=EndOfContent.CONTENT_LENGTH;
if (content_length<0)
content_length=Long.valueOf(field.getValue());
// handle setting the field specially below
content_length = field.getLongValue();
else if (content_length!=field.getLongValue())
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,String.format("Incorrect Content-Length %d!=%d",content_length,field.getLongValue()));
content_length_field = true;
break;
case CONTENT_TYPE:
@ -628,81 +656,29 @@ public class HttpGenerator
case TRANSFER_ENCODING:
{
if (_info.getVersion() == HttpVersion.HTTP_1_1)
if (http11)
{
// Don't add yet, treat this only as a hint that there is content
// with a preference to chunk if we can
transfer_encoding = field;
// Do NOT add yet!
chunked = field.contains(HttpHeaderValue.CHUNKED.asString());
}
break;
}
case CONNECTION:
{
if (request!=null)
putTo(field,header);
// Lookup and/or split connection value field
HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
String[] split = null;
if (values[0]==null)
putTo(field,header);
if (field.contains(HttpHeaderValue.CLOSE.asString()))
{
split = StringUtil.csvSplit(field.getValue());
if (split.length>0)
{
values=new HttpHeaderValue[split.length];
for (int i=0;i<split.length;i++)
values[i]=HttpHeaderValue.CACHE.get(split[i]);
}
close=true;
_persistent=false;
}
// Handle connection values
for (int i=0;i<values.length;i++)
if (!http11 && field.contains(HttpHeaderValue.KEEP_ALIVE.asString()))
{
HttpHeaderValue value=values[i];
switch (value==null?HttpHeaderValue.UNKNOWN:value)
{
case UPGRADE:
{
// special case for websocket connection ordering
header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
header.put(CRLF);
break;
}
case CLOSE:
{
close=true;
_persistent=false;
if (response!=null)
{
if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
_endOfContent=EndOfContent.EOF_CONTENT;
}
break;
}
case KEEP_ALIVE:
{
if (_info.getVersion() == HttpVersion.HTTP_1_0)
{
keep_alive = true;
if (response!=null)
_persistent=true;
}
break;
}
default:
{
if (connection==null)
connection=new StringBuilder();
else
connection.append(',');
connection.append(split==null?field.getValue():split[i]);
}
}
_persistent=true;
}
// Do NOT add yet!
break;
}
@ -720,143 +696,91 @@ public class HttpGenerator
}
}
// Can we work out the content length?
if (last && content_length<0)
content_length = _contentPrepared+BufferUtil.length(content);
// Calculate how to end _content and connection, _content length and transfer encoding
// settings.
// From http://tools.ietf.org/html/rfc7230#section-3.3.3
// From RFC 2616 4.4:
// 1. No body for 1xx, 204, 304 & HEAD response
// 3. If Transfer-Encoding==(.*,)?chunked && HTTP/1.1 && !HttpConnection==close then chunk
// 5. Content-Length without Transfer-Encoding
// 6. Request and none over the above, then Content-Length=0 if POST/PUT
// 7. close
// settings from http://tools.ietf.org/html/rfc7230#section-3.3.3
boolean assumed_content_request = request!=null && Boolean.TRUE.equals(__assumedContentMethods.get(request.getMethod()));
boolean assumed_content = assumed_content_request || content_type || chunked;
boolean nocontent_request = request!=null && content_length<=0 && !assumed_content;
int status=response!=null?response.getStatus():-1;
switch (_endOfContent)
// If the message is known not to have content
if (_noContentResponse || nocontent_request)
{
case UNKNOWN_CONTENT:
// It may be that we have no _content, or perhaps _content just has not been
// written yet?
// We don't need to indicate a body length
_endOfContent=EndOfContent.NO_CONTENT;
// Response known not to have a body
if (_contentPrepared == 0 && response!=null && _noContent)
_endOfContent=EndOfContent.NO_CONTENT;
else if (_info.getContentLength()>0)
// But it is an error if there actually is content
if (_contentPrepared>0 || content_length>0)
{
if (_contentPrepared==0 && last)
{
// we have been given a content length
_endOfContent=EndOfContent.CONTENT_LENGTH;
if ((response!=null || content_length>0 || content_type ) && !_noContent)
{
// known length but not actually set.
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtil.putDecLong(header, content_length);
header.put(HttpTokens.CRLF);
}
}
else if (last)
{
// we have seen all the _content there is, so we can be content-length limited.
_endOfContent=EndOfContent.CONTENT_LENGTH;
long actual_length = _contentPrepared+BufferUtil.length(content);
if (content_length>=0 && content_length!=actual_length)
throw new BadMessageException(500,"Content-Length header("+content_length+") != actual("+actual_length+")");
// Do we need to tell the headers about it
putContentLength(header,actual_length,content_type,request,response);
// TODO discard content for backward compatibility with 9.3 releases
// TODO review if it is still needed in 9.4 or can we just throw.
content.clear();
content_length=0;
}
else
{
// No idea, so we must assume that a body is coming.
_endOfContent = EndOfContent.CHUNKED_CONTENT;
// HTTP 1.0 does not understand chunked content, so we must use EOF content.
// For a request with HTTP 1.0 & Connection: keep-alive
// we *must* close the connection, otherwise the client
// has no way to detect the end of the content.
if (!isPersistent() || _info.getVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
_endOfContent = EndOfContent.EOF_CONTENT;
}
break;
case CONTENT_LENGTH:
{
putContentLength(header,content_length,content_type,request,response);
break;
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"Content for no content response");
}
case NO_CONTENT:
throw new BadMessageException(500);
case EOF_CONTENT:
_persistent = request!=null;
break;
case CHUNKED_CONTENT:
break;
default:
break;
}
// Add transfer_encoding if needed
if (isChunking())
// Else if we are HTTP/1.1 and the content length is unknown and we are either persistent
// or it is a request with content (which cannot EOF)
else if (http11 && content_length<0 && (_persistent || assumed_content_request))
{
// we use chunking
_endOfContent = EndOfContent.CHUNKED_CONTENT;
chunked = true;
// try to use user supplied encoding as it may have other values.
if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
if (transfer_encoding == null)
header.put(TRANSFER_ENCODING_CHUNKED);
else if (transfer_encoding.toString().endsWith(HttpHeaderValue.CHUNKED.toString()))
{
String c = transfer_encoding.getValue();
if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
putTo(transfer_encoding,header);
else
throw new BadMessageException(500,"BAD TE");
putTo(transfer_encoding,header);
transfer_encoding = null;
}
else
header.put(TRANSFER_ENCODING_CHUNKED);
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"Bad Transfer-Encoding");
}
// Handle connection if need be
if (_endOfContent==EndOfContent.EOF_CONTENT)
// Else if we known the content length and are a request or a persistent response,
else if (content_length>=0 && (request!=null || _persistent))
{
keep_alive=false;
// Use the content length
_endOfContent = EndOfContent.CONTENT_LENGTH;
putContentLength(header,content_length);
}
// Else if we are a response
else if (response!=null)
{
// We must use EOF - even if we were trying to be persistent
_endOfContent = EndOfContent.EOF_CONTENT;
_persistent=false;
}
if (content_length>=0 && ( content_length> 0 || assumed_content || content_length_field ))
putContentLength(header,content_length);
// If this is a response, work out persistence
if (response!=null)
if (http11 && !close)
header.put(CONNECTION_CLOSE);
}
// Else we must be a request
else
{
if (!isPersistent() && (close || _info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
{
if (connection==null)
header.put(CONNECTION_CLOSE);
else
{
header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
header.put((byte)',');
header.put(StringUtil.getBytes(connection.toString()));
header.put(CRLF);
}
}
else if (keep_alive)
{
if (connection==null)
header.put(CONNECTION_KEEP_ALIVE);
else
{
header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
header.put((byte)',');
header.put(StringUtil.getBytes(connection.toString()));
header.put(CRLF);
}
}
else if (connection!=null)
{
header.put(HttpHeader.CONNECTION.getBytesColonSpace());
header.put(StringUtil.getBytes(connection.toString()));
header.put(CRLF);
}
// with no way to indicate body length
throw new BadMessageException(INTERNAL_SERVER_ERROR_500,"Unknown content length for request");
}
if (LOG.isDebugEnabled())
LOG.debug(_endOfContent.toString());
// Add transfer encoding if it is not chunking
if (transfer_encoding!=null && !chunked)
putTo(transfer_encoding,header);
// Send server?
int status=response!=null?response.getStatus():-1;
if (status>199)
header.put(SEND[send]);
@ -865,19 +789,16 @@ public class HttpGenerator
}
/* ------------------------------------------------------------------------------- */
private void putContentLength(ByteBuffer header, long contentLength, boolean contentType, MetaData.Request request, MetaData.Response response)
private static void putContentLength(ByteBuffer header,long contentLength)
{
if (contentLength>0)
if (contentLength==0)
header.put(CONTENT_LENGTH_0);
else
{
header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
BufferUtil.putDecLong(header, contentLength);
header.put(HttpTokens.CRLF);
}
else if (!_noContent)
{
if (contentType || response!=null || (request!=null && __assumedContentMethods.contains(request.getMethod())))
header.put(CONTENT_LENGTH_0);
}
}
/* ------------------------------------------------------------------------------- */
@ -905,10 +826,8 @@ public class HttpGenerator
// common _content
private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
private static final byte[] CRLF = StringUtil.getBytes("\015\012");
private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
private static final byte[][] SEND = new byte[][]{
new byte[0],

View File

@ -57,10 +57,19 @@ public class MetaData implements Iterable<HttpField>
return false;
}
/**
* @deprecated use {@link #getHttpVersion()} instead
*/
@Deprecated
public HttpVersion getVersion()
{
return getHttpVersion();
}
/**
* @return the HTTP version of this MetaData object
*/
public HttpVersion getVersion()
public HttpVersion getHttpVersion()
{
return _httpVersion;
}
@ -155,7 +164,7 @@ public class MetaData implements Iterable<HttpField>
public Request(Request request)
{
this(request.getMethod(),new HttpURI(request.getURI()), request.getVersion(), new HttpFields(request.getFields()), request.getContentLength());
this(request.getMethod(),new HttpURI(request.getURI()), request.getHttpVersion(), new HttpFields(request.getFields()), request.getContentLength());
}
public void recycle()
@ -216,8 +225,8 @@ public class MetaData implements Iterable<HttpField>
public String toString()
{
HttpFields fields = getFields();
return String.format("%s{u=%s,%s,h=%d}",
getMethod(), getURI(), getVersion(), fields == null ? -1 : fields.size());
return String.format("%s{u=%s,%s,h=%d,cl=%d}",
getMethod(), getURI(), getHttpVersion(), fields == null ? -1 : fields.size(), getContentLength());
}
}
@ -291,7 +300,7 @@ public class MetaData implements Iterable<HttpField>
public String toString()
{
HttpFields fields = getFields();
return String.format("%s{s=%d,h=%d}", getVersion(), getStatus(), fields == null ? -1 : fields.size());
return String.format("%s{s=%d,h=%d,cl=%d}", getHttpVersion(), getStatus(), fields == null ? -1 : fields.size(), getContentLength());
}
}
}

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@ -30,14 +29,11 @@ import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
@ -400,6 +396,12 @@ public class MimeTypes
continue;
}
if(';'==b && state<=8)
{
state = 1;
continue;
}
switch(state)
{
case 0:
@ -408,8 +410,6 @@ public class MimeTypes
quote=true;
break;
}
if (';'==b)
state=1;
break;
case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
@ -51,30 +52,56 @@ public class PreEncodedHttpField extends HttpField
{
try
{
encoders.add(iter.next());
HttpFieldPreEncoder encoder = iter.next();
if (index(encoder.getHttpVersion())>=0)
encoders.add(encoder);
}
catch(Error|RuntimeException e)
{
LOG.debug(e);
}
}
// TODO avoid needing this catch all
if (encoders.size()==0)
encoders.add(new Http1FieldPreEncoder());
LOG.debug("HttpField encoders loaded: {}",encoders);
__encoders = encoders.toArray(new HttpFieldPreEncoder[encoders.size()]);
int size=encoders.size();
__encoders = new HttpFieldPreEncoder[size==0?1:size];
for (HttpFieldPreEncoder e:encoders)
{
int i = index(e.getHttpVersion());
if (__encoders[i]==null)
__encoders[i] = e;
else
LOG.warn("multiple PreEncoders for "+e.getHttpVersion());
}
// Always support HTTP1
if (__encoders[0]==null)
__encoders[0] = new Http1FieldPreEncoder();
}
private final byte[][] _encodedField=new byte[2][];
private static int index(HttpVersion version)
{
switch (version)
{
case HTTP_1_0:
case HTTP_1_1:
return 0;
case HTTP_2:
return 1;
default:
return -1;
}
}
private final byte[][] _encodedField=new byte[__encoders.length][];
public PreEncodedHttpField(HttpHeader header,String name,String value)
{
super(header,name, value);
for (HttpFieldPreEncoder e:__encoders)
{
_encodedField[e.getHttpVersion()==HttpVersion.HTTP_2?1:0]=e.getEncodedField(header,header.asString(),value);
}
for (int i=0;i<__encoders.length;i++)
_encodedField[i]=__encoders[i].getEncodedField(header,header.asString(),value);
}
public PreEncodedHttpField(HttpHeader header,String value)
@ -89,6 +116,6 @@ public class PreEncodedHttpField extends HttpField
public void putTo(ByteBuffer bufferInFillMode, HttpVersion version)
{
bufferInFillMode.put(_encodedField[version==HttpVersion.HTTP_2?1:0]);
bufferInFillMode.put(_encodedField[index(version)]);
}
}

View File

@ -18,10 +18,26 @@
package org.eclipse.jetty.http.pathmap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
public class ServletPathSpec extends PathSpec
{
/**
* If a servlet or filter path mapping isn't a suffix mapping, ensure
* it starts with '/'
*
* @param pathSpec the servlet or filter mapping pattern
* @return the pathSpec prefixed by '/' if appropriate
*/
public static String normalize(String pathSpec)
{
if (StringUtil.isNotBlank(pathSpec) && !pathSpec.startsWith("/") && !pathSpec.startsWith("*"))
return "/" + pathSpec;
return pathSpec;
}
public ServletPathSpec(String servletPathSpec)
{
super();

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
@ -27,6 +28,7 @@ import static org.junit.Assert.assertThat;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
@ -229,7 +231,7 @@ public class HttpGeneratorServerTest
}
@Test
public void testResponseNoContent() throws Exception
public void testResponseIncorrectContentLength() throws Exception
{
ByteBuffer header = BufferUtil.allocate(8096);
@ -239,7 +241,36 @@ public class HttpGeneratorServerTest
assertEquals(HttpGenerator.Result.NEED_INFO, result);
assertEquals(HttpGenerator.State.START, gen.getState());
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1);
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 10);
info.getFields().add("Last-Modified", DateGenerator.__01Jan1970);
info.getFields().add("Content-Length", "11");
result = gen.generateResponse(info, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
try
{
gen.generateResponse(info, header, null, null, true);
Assert.fail();
}
catch(BadMessageException e)
{
assertEquals(e._code,500);
}
}
@Test
public void testResponseNoContentPersistent() throws Exception
{
ByteBuffer header = BufferUtil.allocate(8096);
HttpGenerator gen = new HttpGenerator();
HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_INFO, result);
assertEquals(HttpGenerator.State.START, gen.getState());
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 0);
info.getFields().add("Last-Modified", DateGenerator.__01Jan1970);
result = gen.generateResponse(info, null, null, null, true);
@ -261,6 +292,40 @@ public class HttpGeneratorServerTest
assertThat(head, containsString("Content-Length: 0"));
}
@Test
public void testResponseKnownNoContentNotPersistent() throws Exception
{
ByteBuffer header = BufferUtil.allocate(8096);
HttpGenerator gen = new HttpGenerator();
HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_INFO, result);
assertEquals(HttpGenerator.State.START, gen.getState());
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 0);
info.getFields().add("Last-Modified", DateGenerator.__01Jan1970);
info.getFields().add("Connection", "close");
result = gen.generateResponse(info, null, null, null, true);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
result = gen.generateResponse(info, header, null, null, true);
assertEquals(HttpGenerator.Result.FLUSH, result);
assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
String head = BufferUtil.toString(header);
BufferUtil.clear(header);
result = gen.generateResponse(null, null, null, null, false);
assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result);
assertEquals(HttpGenerator.State.END, gen.getState());
assertEquals(0, gen.getContentPrepared());
assertThat(head, containsString("HTTP/1.1 200 OK"));
assertThat(head, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
assertThat(head, containsString("Connection: close"));
}
@Test
public void testResponseUpgrade() throws Exception
{
@ -344,19 +409,23 @@ public class HttpGeneratorServerTest
assertEquals(HttpGenerator.Result.DONE, result);
assertEquals(HttpGenerator.State.END, gen.getState());
assertThat(out, containsString("HTTP/1.1 200 OK"));
assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
assertThat(out, not(containsString("Content-Length")));
assertThat(out, containsString("Transfer-Encoding: chunked"));
assertThat(out, containsString("\r\n\r\nD\r\n"));
assertThat(out, containsString("\r\nHello World! \r\n"));
assertThat(out, containsString("\r\n2E\r\n"));
assertThat(out, containsString("\r\nThe quick brown fox jumped over the lazy dog. \r\n"));
assertThat(out, containsString("\r\n0\r\n"));
assertThat(out, endsWith(
"\r\n\r\nD\r\n"+
"Hello World! \r\n"+
"2E\r\n"+
"The quick brown fox jumped over the lazy dog. \r\n"+
"0\r\n"+
"\r\n"));
}
@Test
public void testResponseWithKnownContent() throws Exception
public void testResponseWithKnownContentLengthFromMetaData() throws Exception
{
ByteBuffer header = BufferUtil.allocate(4096);
ByteBuffer content0 = BufferUtil.toBuffer("Hello World! ");
@ -403,6 +472,58 @@ public class HttpGeneratorServerTest
assertThat(out, containsString("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. "));
}
@Test
public void testResponseWithKnownContentLengthFromHeader() throws Exception
{
ByteBuffer header = BufferUtil.allocate(4096);
ByteBuffer content0 = BufferUtil.toBuffer("Hello World! ");
ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. ");
HttpGenerator gen = new HttpGenerator();
HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false);
assertEquals(HttpGenerator.Result.NEED_INFO, result);
assertEquals(HttpGenerator.State.START, gen.getState());
MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1);
info.getFields().add("Last-Modified", DateGenerator.__01Jan1970);
info.getFields().add("Content-Length",""+(content0.remaining()+content1.remaining()));
result = gen.generateResponse(info, null, null, content0, false);
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
assertEquals(HttpGenerator.State.START, gen.getState());
result = gen.generateResponse(info, header, null, content0, false);
assertEquals(HttpGenerator.Result.FLUSH, result);
assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
String out = BufferUtil.toString(header);
BufferUtil.clear(header);
out += BufferUtil.toString(content0);
BufferUtil.clear(content0);
result = gen.generateResponse(null, null, null, content1, false);
assertEquals(HttpGenerator.Result.FLUSH, result);
assertEquals(HttpGenerator.State.COMMITTED, gen.getState());
out += BufferUtil.toString(content1);
BufferUtil.clear(content1);
result = gen.generateResponse(null, null, null, null, true);
assertEquals(HttpGenerator.Result.CONTINUE, result);
assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
result = gen.generateResponse(null, null, null, null, true);
assertEquals(HttpGenerator.Result.DONE, result);
assertEquals(HttpGenerator.State.END, gen.getState());
assertThat(out, containsString("HTTP/1.1 200 OK"));
assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT"));
assertThat(out, not(containsString("chunked")));
assertThat(out, containsString("Content-Length: 59"));
assertThat(out, containsString("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. "));
}
@Test
public void test100ThenResponseWithContent() throws Exception
{

View File

@ -215,14 +215,20 @@ public class HttpTester
ByteBuffer buffer = in.getBuffer();
int len=0;
while(len>=0)
while(true)
{
if (BufferUtil.hasContent(buffer))
if (parser.parseNext(buffer))
break;
if (in.fillBuffer()<=0)
int len=in.fillBuffer();
if (len==0)
break;
if (len<=0)
{
parser.atEOF();
parser.parseNext(buffer);
break;
}
}
if (r.isComplete())

View File

@ -18,9 +18,11 @@
package org.eclipse.jetty.http;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import org.junit.Test;
@ -76,24 +78,33 @@ public class MimeTypesTest
assertEquals(prefix,expectedMimeType,contentType);
}
private void assertCharsetFromContentType(String contentType, String expectedCharset)
{
assertThat("getCharsetFromContentType(\"" + contentType + "\")",
MimeTypes.getCharsetFromContentType(contentType), is(expectedCharset));
}
@Test
public void testCharsetFromContentType()
{
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar;charset=abc;some=else"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar;charset=abc"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar ; charset = abc"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar ; charset = abc ; some=else"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar;other=param;charset=abc;some=else"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar;other=param;charset=abc"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = abc"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = abc ; some=else"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = abc"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = \"abc\" ; some=else"));
assertEquals(null,MimeTypes.getCharsetFromContentType("foo/bar"));
assertEquals("utf-8",MimeTypes.getCharsetFromContentType("foo/bar;charset=uTf8"));
assertEquals("utf-8",MimeTypes.getCharsetFromContentType("foo/bar;other=\"charset=abc\";charset=uTf8"));
assertEquals("utf-8",MimeTypes.getCharsetFromContentType("text/html;charset=utf-8"));
assertCharsetFromContentType("foo/bar;charset=abc;some=else", "abc");
assertCharsetFromContentType("foo/bar;charset=abc", "abc");
assertCharsetFromContentType("foo/bar ; charset = abc", "abc");
assertCharsetFromContentType("foo/bar ; charset = abc ; some=else", "abc");
assertCharsetFromContentType("foo/bar;other=param;charset=abc;some=else", "abc");
assertCharsetFromContentType("foo/bar;other=param;charset=abc", "abc");
assertCharsetFromContentType("foo/bar other = param ; charset = abc", "abc");
assertCharsetFromContentType("foo/bar other = param ; charset = abc ; some=else", "abc");
assertCharsetFromContentType("foo/bar other = param ; charset = abc", "abc");
assertCharsetFromContentType("foo/bar other = param ; charset = \"abc\" ; some=else", "abc");
assertCharsetFromContentType("foo/bar", null);
assertCharsetFromContentType("foo/bar;charset=uTf8", "utf-8");
assertCharsetFromContentType("foo/bar;other=\"charset=abc\";charset=uTf8", "utf-8");
assertCharsetFromContentType("application/pdf; charset=UTF-8", "utf-8");
assertCharsetFromContentType("application/pdf;; charset=UTF-8", "utf-8");
assertCharsetFromContentType("application/pdf;;; charset=UTF-8", "utf-8");
assertCharsetFromContentType("application/pdf;;;; charset=UTF-8", "utf-8");
assertCharsetFromContentType("text/html;charset=utf-8", "utf-8");
}
@Test

View File

@ -0,0 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG

View File

@ -1,3 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.alpn.LEVEL=DEBUG
org.eclipse.jetty.http2.LEVEL=DEBUG
# org.eclipse.jetty.alpn.LEVEL=DEBUG
# org.eclipse.jetty.http2.LEVEL=DEBUG

View File

@ -799,4 +799,90 @@ public class PushCacheFilterTest extends AbstractTest
Assert.assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testPOSTRequestIsNotPushed() throws Exception
{
final String primaryResource = "/primary.html";
final String secondaryResource = "/secondary.png";
final byte[] secondaryData = "SECONDARY".getBytes("UTF-8");
start(new HttpServlet()
{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String requestURI = req.getRequestURI();
ServletOutputStream output = resp.getOutputStream();
if (requestURI.endsWith(primaryResource))
output.print("<html><head></head><body>PRIMARY</body></html>");
else if (requestURI.endsWith(secondaryResource))
output.write(secondaryData);
}
});
final Session session = newClient(new Session.Listener.Adapter());
// Request for the primary and secondary resource to build the cache.
final String referrerURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
{
// Request for the secondary resource.
HttpFields secondaryFields = new HttpFields();
secondaryFields.put(HttpHeader.REFERER, referrerURI);
MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
warmupLatch.countDown();
}
});
}
}
});
Assert.assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
// Request again the primary resource with POST, we should not get the secondary resource pushed.
primaryRequest = newRequest("POST", primaryResource, primaryFields);
final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
final CountDownLatch pushLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
{
@Override
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
{
return new Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
pushLatch.countDown();
}
};
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
primaryResponseLatch.countDown();
}
});
Assert.assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
Assert.assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
}
}

View File

@ -19,7 +19,6 @@
package org.eclipse.jetty.http2;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
@ -46,7 +45,7 @@ public class HTTP2Flusher extends IteratingCallback
private final HTTP2Session session;
private final ByteBufferPool.Lease lease;
private Entry stalled;
private boolean terminated;
private Throwable terminated;
public HTTP2Flusher(HTTP2Session session)
{
@ -56,52 +55,54 @@ public class HTTP2Flusher extends IteratingCallback
public void window(IStream stream, WindowUpdateFrame frame)
{
boolean closed;
Throwable closed;
synchronized (this)
{
closed = terminated;
if (!closed)
if (closed == null)
windows.offer(new WindowEntry(stream, frame));
}
// Flush stalled data.
if (!closed)
if (closed == null)
iterate();
}
public boolean prepend(Entry entry)
{
boolean closed;
Throwable closed;
synchronized (this)
{
closed = terminated;
if (!closed)
if (closed == null)
{
frames.offerFirst(entry);
if (LOG.isDebugEnabled())
LOG.debug("Prepended {}, frames={}", entry, frames.size());
}
}
if (closed)
closed(entry, new ClosedChannelException());
return !closed;
if (closed == null)
return true;
closed(entry, closed);
return false;
}
public boolean append(Entry entry)
{
boolean closed;
Throwable closed;
synchronized (this)
{
closed = terminated;
if (!closed)
if (closed == null)
{
frames.offer(entry);
if (LOG.isDebugEnabled())
LOG.debug("Appended {}, frames={}", entry, frames.size());
}
}
if (closed)
closed(entry, new ClosedChannelException());
return !closed;
if (closed == null)
return true;
closed(entry, closed);
return false;
}
public int getQueueSize()
@ -113,15 +114,15 @@ public class HTTP2Flusher extends IteratingCallback
}
@Override
protected Action process() throws Exception
protected Action process() throws Throwable
{
if (LOG.isDebugEnabled())
LOG.debug("Flushing {}", session);
synchronized (this)
{
if (terminated)
throw new ClosedChannelException();
if (terminated != null)
throw terminated;
while (!windows.isEmpty())
{
@ -251,13 +252,13 @@ public class HTTP2Flusher extends IteratingCallback
{
lease.recycle();
boolean closed;
Throwable closed;
synchronized (this)
{
closed = terminated;
terminated = true;
terminated = x;
if (LOG.isDebugEnabled())
LOG.debug("{}, active/queued={}/{}", closed ? "Closing" : "Failing", actives.size(), frames.size());
LOG.debug("{}, active/queued={}/{}", closed != null ? "Closing" : "Failing", actives.size(), frames.size());
actives.addAll(frames);
frames.clear();
}
@ -267,21 +268,21 @@ public class HTTP2Flusher extends IteratingCallback
// If the failure came from within the
// flusher, we need to close the connection.
if (!closed)
if (closed == null)
session.abort(x);
}
void terminate()
void terminate(Throwable cause)
{
boolean closed;
Throwable closed;
synchronized (this)
{
closed = terminated;
terminated = true;
terminated = cause;
if (LOG.isDebugEnabled())
LOG.debug("{}", closed ? "Terminated" : "Terminating");
LOG.debug("{}", closed != null ? "Terminated" : "Terminating");
}
if (!closed)
if (closed == null)
iterate();
}

View File

@ -965,7 +965,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
endPoint.close();
}
private void terminate()
private void terminate(Throwable cause)
{
while (true)
{
@ -978,7 +978,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
{
if (closed.compareAndSet(current, CloseState.CLOSED))
{
flusher.terminate();
flusher.terminate(cause);
for (IStream stream : streams.values())
stream.close();
streams.clear();
@ -998,7 +998,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
protected void abort(Throwable failure)
{
notifyFailure(this, failure);
terminate();
terminate(failure);
}
public boolean isDisconnected()
@ -1206,7 +1206,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
}
case DISCONNECT:
{
terminate();
terminate(new ClosedChannelException());
break;
}
default:

View File

@ -18,10 +18,6 @@
package org.eclipse.jetty.http2.hpack;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.BadMessageException;
@ -38,6 +34,10 @@ import org.eclipse.jetty.util.BufferUtil;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class HpackTest
{
final static HttpField ServerJetty = new PreEncodedHttpField(HttpHeader.SERVER,"jetty");
@ -187,7 +187,7 @@ public class HpackTest
private void assertMetadataSame(MetaData expected, MetaData actual)
{
assertThat("Metadata.contentLength",actual.getContentLength(),is(expected.getContentLength()));
assertThat("Metadata.version" + ".version", actual.getVersion(), is(expected.getVersion()));
assertThat("Metadata.version" + ".version", actual.getHttpVersion(),is(expected.getHttpVersion()));
assertHttpFieldsSame("Metadata.fields",expected.getFields(),actual.getFields());
}

View File

@ -67,7 +67,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
HttpResponse response = exchange.getResponse();
MetaData.Response metaData = (MetaData.Response)frame.getMetaData();
response.version(metaData.getVersion()).status(metaData.getStatus()).reason(metaData.getReason());
response.version(metaData.getHttpVersion()).status(metaData.getStatus()).reason(metaData.getReason());
if (responseBegin(exchange))
{

View File

@ -159,15 +159,18 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
if (LOG.isDebugEnabled())
LOG.debug("Processing {} on {}", frame, stream);
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
Runnable task = channel.onRequestContent(frame, callback);
if (task != null)
offerTask(task, false);
if (channel != null)
{
Runnable task = channel.onRequestContent(frame, callback);
if (task != null)
offerTask(task, false);
}
}
public boolean onStreamTimeout(IStream stream, Throwable failure)
{
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
boolean result = channel.onStreamTimeout(failure);
boolean result = channel != null && channel.onStreamTimeout(failure);
if (LOG.isDebugEnabled())
LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", stream, failure);
return result;
@ -178,7 +181,8 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
if (LOG.isDebugEnabled())
LOG.debug("Processing failure on {}: {}", stream, failure);
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
channel.onFailure(failure);
if (channel != null)
channel.onFailure(failure);
}
public boolean onSessionTimeout(Throwable failure)
@ -188,7 +192,8 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
for (Stream stream : session.getStreams())
{
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
result &= !channel.isRequestHandled();
if (channel != null)
result &= !channel.isRequestHandled();
}
if (LOG.isDebugEnabled())
LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", session, failure);

View File

@ -126,7 +126,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
LOG.debug("HTTP2 Request #{}/{}, delayed={}:{}{} {} {}{}{}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
_delayedUntilContent, System.lineSeparator(),
request.getMethod(), request.getURI(), request.getVersion(),
request.getMethod(), request.getURI(), request.getHttpVersion(),
System.lineSeparator(), fields);
}
@ -157,7 +157,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
Stream stream = getStream();
LOG.debug("HTTP2 PUSH Request #{}/{}:{}{} {} {}{}{}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(),
request.getMethod(), request.getURI(), request.getVersion(),
request.getMethod(), request.getURI(), request.getHttpVersion(),
System.lineSeparator(), request.getFields());
}
@ -199,7 +199,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
{
Stream stream = getStream();
LOG.debug("HTTP2 Commit Response #{}/{}:{}{} {} {}{}{}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(), info.getVersion(), info.getStatus(), info.getReason(),
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(), info.getHttpVersion(), info.getStatus(), info.getReason(),
System.lineSeparator(), info.getFields());
}
}

View File

@ -217,7 +217,8 @@ public class HttpTransportOverHTTP2 implements HttpTransport
// Consume the existing queued data frames to
// avoid stalling the session flow control.
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
channel.consumeInput();
if (channel != null)
channel.consumeInput();
}
@Override

View File

@ -9,11 +9,13 @@ session-store
[depend]
sessions
sessions/infinispan/default
[files]
maven://org.infinispan/infinispan-embedded/7.1.1.Final|lib/infinispan/infinispan-embedded-7.1.1.Final.jar
[xml]
etc/sessions/infinispan/default.xml
[lib]
lib/jetty-infinispan-${jetty.version}.jar
lib/infinispan/*.jar
@ -22,3 +24,4 @@ lib/infinispan/*.jar
Infinispan is an open source project hosted on Github and released under the Apache 2.0 license.
http://infinispan.org/
http://www.apache.org/licenses/LICENSE-2.0.html

View File

@ -9,10 +9,13 @@ session-store
[depend]
sessions
sessions/infinispan/remote
[files]
maven://org.infinispan/infinispan-remote/7.1.1.Final|lib/infinispan/infinispan-remote-7.1.1.Final.jar
basehome:modules/session-store-infinispan-remote/
[xml]
etc/sessions/infinispan/remote.xml
[lib]
lib/jetty-infinispan-${jetty.version}.jar
@ -28,3 +31,8 @@ http://www.apache.org/licenses/LICENSE-2.0.html
#jetty.session.infinispan.remoteCacheName=sessions
#jetty.session.infinispan.idleTimeout.seconds=0
#jetty.session.gracePeriod.seconds=3600

View File

@ -1,6 +0,0 @@
[description]
Enable use of DefaultCache for session data storage
[xml]
etc/sessions/infinispan/default.xml

View File

@ -1,9 +0,0 @@
[description]
Enable use of HotRod remote cache for session data storage
[files]
https://raw.githubusercontent.com/eclipse/jetty.project/master/jetty-infinispan/src/main/infinispan-resources/hotrod-client.properties?id=${jetty.tag.version}|resources/hotrod-client.properties
[xml]
etc/sessions/infinispan/remote.xml

View File

@ -331,7 +331,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
* @param time Time to wait
* @param unit Units for time to wait
* @return The buffer of output
* @throws InterruptedException
* @throws InterruptedException if interrupted
*/
public ByteBuffer waitForOutput(long time,TimeUnit unit) throws InterruptedException
{

View File

@ -34,6 +34,7 @@ import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@ -69,86 +70,14 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
private final ExecutionStrategy _lowPriorityStrategy;
private Selector _selector;
private final Runnable _runStrategy = new Runnable()
{
@Override
public void run()
{
_strategy.produce();
}
};
private final Runnable _runLowPriorityStrategy = new Runnable()
{
@Override
public void run()
{
Thread current = Thread.currentThread();
String name = current.getName();
int priority = current.getPriority();
try
{
while (isRunning())
{
try
{
current.setPriority(Thread.MIN_PRIORITY);
current.setName(name+"-lowPrioSelector");
_lowPriorityStrategy.produce();
}
catch (Throwable th)
{
LOG.warn(th);
}
}
}
finally
{
current.setPriority(priority);
current.setName(name);
}
}
};
public ManagedSelector(SelectorManager selectorManager, int id)
{
_selectorManager = selectorManager;
_id = id;
SelectorProducer producer = new SelectorProducer();
_strategy = new ExecuteProduceConsume(producer, selectorManager.getExecutor(), Invocable.InvocationType.BLOCKING);
_lowPriorityStrategy = new ProduceExecuteConsume(producer, selectorManager.getExecutor(), Invocable.InvocationType.BLOCKING)
{
@Override
protected boolean execute(Runnable task)
{
try
{
Invocable.InvocationType invocation=Invocable.getInvocationType(task);
if (LOG.isDebugEnabled())
LOG.debug("Low Prio Selector execute {} {}",invocation,task);
switch (Invocable.getInvocationType(task))
{
case NON_BLOCKING:
task.run();
return true;
case EITHER:
Invocable.invokeNonBlocking(task);
return true;
default:
}
return super.execute(task);
}
finally
{
// Allow opportunity for main strategy to take over
Thread.yield();
}
}
};
Executor executor = selectorManager.getExecutor();
_strategy = new ExecuteProduceConsume(producer, executor, Invocable.InvocationType.BLOCKING);
_lowPriorityStrategy = new LowPriorityProduceExecuteConsume(producer, executor);
setStopTimeout(5000);
}
@ -156,9 +85,38 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
protected void doStart() throws Exception
{
super.doStart();
_selector = _selectorManager.newSelector();
_selectorManager.execute(_runStrategy);
_selectorManager.execute(_runLowPriorityStrategy);
// The producer used by the strategies will never
// be idle (either produces a task or blocks).
// The normal strategy obtains the produced task, schedules
// a new thread to produce more, runs the task and then exits.
_selectorManager.execute(_strategy::produce);
// The low priority strategy knows the producer will never
// be idle, that tasks are scheduled to run in different
// threads, therefore lowPriorityProduce() never exits.
_selectorManager.execute(this::lowPriorityProduce);
}
private void lowPriorityProduce()
{
Thread current = Thread.currentThread();
String name = current.getName();
int priority = current.getPriority();
current.setPriority(Thread.MIN_PRIORITY);
current.setName(name+"-lowPrioritySelector");
try
{
_lowPriorityStrategy.produce();
}
finally
{
current.setPriority(priority);
current.setName(name);
}
}
public int size()
@ -227,28 +185,75 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
void updateKey();
}
private static class LowPriorityProduceExecuteConsume extends ProduceExecuteConsume
{
private LowPriorityProduceExecuteConsume(SelectorProducer producer, Executor executor)
{
super(producer, executor, InvocationType.BLOCKING);
}
@Override
protected boolean execute(Runnable task)
{
try
{
InvocationType invocation=Invocable.getInvocationType(task);
if (LOG.isDebugEnabled())
LOG.debug("Low Priority Selector executing {} {}",invocation,task);
switch (invocation)
{
case NON_BLOCKING:
task.run();
return true;
case EITHER:
Invocable.invokeNonBlocking(task);
return true;
default:
return super.execute(task);
}
}
finally
{
// Allow opportunity for main strategy to take over.
Thread.yield();
}
}
}
private class SelectorProducer implements ExecutionStrategy.Producer
{
private Set<SelectionKey> _keys = Collections.emptySet();
private Iterator<SelectionKey> _cursor = Collections.emptyIterator();
@Override
public synchronized Runnable produce()
public Runnable produce()
{
while (true)
// This method is called from both the
// normal and low priority strategies.
// Only one can produce at a time, so it's synchronized
// to enforce that only one strategy actually produces.
// When idle in select(), this method blocks;
// the other strategy's thread will be blocked
// waiting for this lock to be released.
synchronized (this)
{
Runnable task = processSelected();
if (task != null)
return task;
while (true)
{
Runnable task = processSelected();
if (task != null)
return task;
Runnable action = nextAction();
if (action != null)
return action;
Runnable action = nextAction();
if (action != null)
return action;
update();
update();
if (!select())
return null;
if (!select())
return null;
}
}
}
@ -492,7 +497,7 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
public void destroyEndPoint(final EndPoint endPoint)
{
final Connection connection = endPoint.getConnection();
submit((Runnable)() ->
submit(() ->
{
if (LOG.isDebugEnabled())
LOG.debug("Destroyed {}", endPoint);

View File

@ -39,7 +39,6 @@ import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.WriteFlusher;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
@ -51,7 +50,7 @@ import org.eclipse.jetty.util.log.Logger;
* and another consumer of an EndPoint (typically an {@link Connection} like HttpConnection) that
* wants unencrypted data.
* <p>
* The connector uses an {@link EndPoint} (typically {@link SelectChannelEndPoint}) as
* The connector uses an {@link EndPoint} (typically SocketChannelEndPoint) as
* it's source/sink of encrypted data. It then provides an endpoint via {@link #getDecryptedEndPoint()} to
* expose a source/sink of unencrypted data to another connection (eg HttpConnection).
* <p>
@ -111,6 +110,33 @@ public class SslConnection extends AbstractConnection
}
};
Callback _sslReadCallback = new Callback()
{
@Override
public void succeeded()
{
onFillable();
}
@Override
public void failed(final Throwable x)
{
onFillInterestedFailed(x);
}
@Override
public InvocationType getInvocationType()
{
return getDecryptedEndPoint().getFillInterest().getCallbackInvocationType();
}
@Override
public String toString()
{
return String.format("SSLC.NBReadCB@%x{%s}", SslConnection.this.hashCode(),SslConnection.this);
}
};
public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
{
// This connection does not execute calls to onFillable(), so they will be called by the selector thread.
@ -961,7 +987,11 @@ public class SslConnection extends AbstractConnection
private void ensureFillInterested()
{
if (!SslConnection.this.isFillInterested())
SslConnection.this.fillInterested();
{
if (LOG.isDebugEnabled())
LOG.debug("fillInterested SSL NB {}",SslConnection.this);
SslConnection.this.getEndPoint().fillInterested(_sslReadCallback);
}
}
@Override

View File

@ -34,6 +34,13 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>

View File

@ -35,6 +35,13 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>

View File

@ -70,6 +70,13 @@
</sourceExcludes>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>

View File

@ -257,8 +257,11 @@ public class ProxyServletFailureTest
public void testProxyRequestStallsContentServerIdlesTimeout() throws Exception
{
final byte[] content = new byte[]{'C', '0', 'F', 'F', 'E', 'E'};
int expected = -1;
if (proxyServlet instanceof AsyncProxyServlet)
{
// TODO should this be a 502 also???
expected = 500;
proxyServlet = new AsyncProxyServlet()
{
@Override
@ -281,6 +284,7 @@ public class ProxyServletFailureTest
}
else
{
expected = 502;
proxyServlet = new ProxyServlet()
{
@Override
@ -310,7 +314,7 @@ public class ProxyServletFailureTest
.content(new BytesContentProvider(content))
.send();
Assert.assertEquals(500, response.getStatus());
Assert.assertEquals(expected, response.getStatus());
}
}

View File

@ -360,13 +360,18 @@ public class ProxyServletTest
resp.addHeader(PROXIED_HEADER, "true");
InputStream input = req.getInputStream();
int index = 0;
byte[] buffer = new byte[16*1024];
while (true)
{
int value = input.read();
int value = input.read(buffer);
if (value < 0)
break;
Assert.assertEquals("Content mismatch at index=" + index, content[index] & 0xFF, value);
++index;
for (int i=0;i<value;i++)
{
Assert.assertEquals("Content mismatch at index=" + index, content[index] & 0xFF, buffer[i] & 0xFF);
++index;
}
}
}
});
@ -931,9 +936,10 @@ public class ProxyServletTest
Assert.assertArrayEquals(content, response.getContent());
}
@Test(expected = TimeoutException.class)
@Test
public void testWrongContentLength() throws Exception
{
startServer(new HttpServlet()
{
@Override
@ -948,11 +954,17 @@ public class ProxyServletTest
startProxy();
startClient();
client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(1, TimeUnit.SECONDS)
try
{
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.fail();
Assert.assertThat(response.getStatus(),Matchers.greaterThanOrEqualTo(500));
}
catch(ExecutionException e)
{
Assert.assertThat(e.getCause(),Matchers.instanceOf(IOException.class));
}
}
@Test

View File

@ -2,3 +2,4 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.proxy.LEVEL=DEBUG
#org.eclipse.jetty.server.HttpInput.LEVEL=DEBUG

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.quickstart;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@ -28,12 +29,14 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
@ -44,10 +47,16 @@ import org.eclipse.jetty.util.resource.Resource;
* Replaces and expands:
* <ul>
* <li>${WAR}</li>
* <li>${WAR.path}</li>
* <li>${WAR.uri}</li>
* <li>${jetty.base}</li>
* <li>${jetty.base.uri}</li>
* <li>${jetty.home}</li>
* <li>${jetty.home.uri}</li>
* <li>${user.home}</li>
* <li>${user.home.uri}</li>
* <li>${user.dir}</li>
* <li>${user.dir.uri}</li>
* </ul>
*/
public class AttributeNormalizer
@ -55,88 +64,130 @@ public class AttributeNormalizer
private static final Logger LOG = Log.getLogger(AttributeNormalizer.class);
private static final Pattern __propertyPattern = Pattern.compile("(?<=[^$]|^)\\$\\{([^}]*)\\}");
private static class PathAttribute
private static class Attribute
{
public final Path path;
public final String key;
private int weight = -1;
final String key;
final String value;
final int weight;
public PathAttribute(String key, Path path) throws IOException
public Attribute(String key, String value, int weight)
{
this.key = key;
this.path = toCanonicalPath(path);
// TODO: Don't allow non-directory paths? (but what if the path doesn't exist?)
this.value = value;
this.weight = weight;
}
}
public PathAttribute(String key, String systemPropertyKey) throws IOException
private static URI toCanonicalURI(URI uri)
{
uri = uri.normalize();
String ascii = uri.toASCIIString();
if (ascii.endsWith("/"))
{
this(key, toCanonicalPath(System.getProperty(systemPropertyKey)));
}
private static Path toCanonicalPath(String path) throws IOException
{
if (path == null)
try
{
return null;
uri = new URI(ascii.substring(0,ascii.length()-1));
}
return toCanonicalPath(FileSystems.getDefault().getPath(path));
}
private static Path toCanonicalPath(Path path) throws IOException
{
if (path == null)
catch(URISyntaxException e)
{
return null;
throw new IllegalArgumentException(e);
}
if (Files.exists(path))
}
return uri;
}
private static Path toCanonicalPath(String path)
{
if (path == null)
return null;
if (path.length()>1 && path.endsWith("/"))
path = path.substring(0,path.length()-1);
return toCanonicalPath(FileSystems.getDefault().getPath(path));
}
private static Path toCanonicalPath(Path path)
{
if (path == null)
{
return null;
}
if (Files.exists(path))
{
try
{
return path.toRealPath();
}
return path.toAbsolutePath();
catch (IOException e)
{
throw new IllegalArgumentException(e);
}
}
return path.toAbsolutePath();
}
public PathAttribute weight(int newweight)
private static class PathAttribute extends Attribute
{
public final Path path;
public PathAttribute(String key, Path path, int weight)
{
this.weight = newweight;
return this;
super(key,path.toString(),weight);
this.path = path;
}
@Override
public String toString()
{
return String.format("PathAttribute[%s=>%s,%d]",key,path,weight);
return String.format("PathAttribute[%s=>%s]",key,path);
}
}
private static class PathAttributeComparator implements Comparator<PathAttribute>
private static class URIAttribute extends Attribute
{
public final URI uri;
public URIAttribute(String key, URI uri, int weight)
{
super(key,uri.toASCIIString(),weight);
this.uri = uri;
}
@Override
public String toString()
{
return String.format("URIAttribute[%s=>%s]",key,uri);
}
}
private static Comparator<Attribute> attrComparator = new Comparator<Attribute>()
{
@Override
public int compare(PathAttribute o1, PathAttribute o2)
public int compare(Attribute o1, Attribute o2)
{
if( (o1.path == null) && (o2.path != null) )
if( (o1.value == null) && (o2.value != null) )
{
return -1;
}
if( (o1.path != null) && (o2.path == null) )
if( (o1.value != null) && (o2.value == null) )
{
return 1;
}
if( (o1.path == null) && (o2.path == null) )
if( (o1.value == null) && (o2.value == null) )
{
return 0;
}
// Different lengths?
int diff = o2.path.getNameCount() - o1.path.getNameCount();
int diff = o2.value.length() - o1.value.length();
if(diff != 0)
{
return diff;
}
// Different names?
diff = o2.path.compareTo(o1.path);
diff = o2.value.compareTo(o1.value);
if(diff != 0)
{
return diff;
@ -145,99 +196,122 @@ public class AttributeNormalizer
// The paths are the same, base now on weight
return o2.weight - o1.weight;
}
}
};
public static String uriSeparators(String path)
private static void add(List<PathAttribute>paths,List<URIAttribute> uris,String key,int weight)
{
StringBuilder ret = new StringBuilder();
for (char c : path.toCharArray())
String value = System.getProperty(key);
if (value!=null)
{
if ((c == '/') || (c == '\\'))
{
ret.append('/');
}
else
{
ret.append(c);
}
Path path = toCanonicalPath(value);
paths.add(new PathAttribute(key,path,weight));
uris.add(new URIAttribute(key+".uri",toCanonicalURI(path.toUri()),weight));
}
return ret.toString();
}
private URI warURI;
private List<PathAttribute> attributes = new ArrayList<>();
private Map<String,Attribute> attributes = new HashMap<>();
private List<PathAttribute> paths = new ArrayList<>();
private List<URIAttribute> uris = new ArrayList<>();
public AttributeNormalizer(Resource baseResource)
{
// WAR URI is always evaluated before paths.
warURI = baseResource == null ? null : baseResource.getURI();
// We don't normalize or resolve the baseResource URI
if (baseResource==null)
throw new IllegalArgumentException("No base resource!");
warURI = toCanonicalURI(baseResource.getURI());
if (!warURI.isAbsolute())
throw new IllegalArgumentException("WAR URI is not absolute: " + warURI);
try
add(paths,uris,"jetty.base",9);
add(paths,uris,"jetty.home",8);
add(paths,uris,"user.home",7);
add(paths,uris,"user.dir",6);
if (warURI.getScheme().equalsIgnoreCase("file"))
paths.add(new PathAttribute("WAR.path",toCanonicalPath(new File(warURI).toString()),10));
uris.add(new URIAttribute("WAR.uri", warURI,9)); // preferred encoding
uris.add(new URIAttribute("WAR", warURI,8)); // legacy encoding
Collections.sort(paths,attrComparator);
Collections.sort(uris,attrComparator);
Stream.concat(paths.stream(),uris.stream()).forEach(a->attributes.put(a.key,a));
if (LOG.isDebugEnabled())
{
// Track path attributes for expansion
attributes.add(new PathAttribute("jetty.base", "jetty.base").weight(9));
attributes.add(new PathAttribute("jetty.home", "jetty.home").weight(8));
attributes.add(new PathAttribute("user.home", "user.home").weight(7));
attributes.add(new PathAttribute("user.dir", "user.dir").weight(6));
Collections.sort(attributes, new PathAttributeComparator());
if (LOG.isDebugEnabled())
for (Attribute attr : attributes.values())
{
int i = 0;
for (PathAttribute attr : attributes)
{
LOG.debug(" [{}] {}", i++, attr);
}
LOG.debug(attr.toString());
}
}
catch (Exception e)
{
throw new IllegalArgumentException(e);
}
}
/**
* Normalize a URI, URL, or File reference by replacing known attributes with ${key} attributes.
*
* @param o the object to normalize into a string
* @return the string representation of the object, with expansion keys.
*/
public String normalize(Object o)
{
try
{
// Find a URI
URI uri = null;
Path path = null;
if (o instanceof URI)
uri = (URI)o;
uri = toCanonicalURI(((URI)o));
else if (o instanceof Resource)
uri = toCanonicalURI(((Resource)o).getURI());
else if (o instanceof URL)
uri = ((URL)o).toURI();
uri = toCanonicalURI(((URL)o).toURI());
else if (o instanceof File)
uri = ((File)o).toURI();
path = ((File)o).getAbsoluteFile().getCanonicalFile().toPath();
else if (o instanceof Path)
path = (Path)o;
else
{
String s = o.toString();
uri = new URI(s);
if (uri.getScheme() == null)
return s;
}
if ("jar".equalsIgnoreCase(uri.getScheme()))
{
String raw = uri.getRawSchemeSpecificPart();
int bang = raw.indexOf("!/");
String normal = normalize(raw.substring(0,bang));
String suffix = raw.substring(bang);
return "jar:" + normal + suffix;
}
else if ("file".equalsIgnoreCase(uri.getScheme()))
{
return "file:" + normalizePath(new File(uri.getRawSchemeSpecificPart()).toPath());
}
else
{
if(uri.isAbsolute())
try
{
return normalizeUri(uri);
uri = new URI(s);
if (uri.getScheme() == null)
{
// Unknown scheme? not relevant to normalize
return s;
}
}
catch(URISyntaxException e)
{
// This path occurs for many reasons, but most common is when this
// is executed on MS Windows, on a string like "D:\jetty"
// and the new URI() fails for
// java.net.URISyntaxException: Illegal character in opaque part at index 2: D:\jetty
return s;
}
}
if (uri!=null)
{
if ("jar".equalsIgnoreCase(uri.getScheme()))
{
String raw = uri.getRawSchemeSpecificPart();
int bang = raw.indexOf("!/");
String normal = normalize(raw.substring(0,bang));
String suffix = raw.substring(bang);
return "jar:" + normal + suffix;
}
else
{
if(uri.isAbsolute())
{
return normalizeUri(uri);
}
}
}
else if (path!=null)
return normalizePath(path);
}
catch (Exception e)
{
@ -246,38 +320,62 @@ public class AttributeNormalizer
return String.valueOf(o);
}
public String normalizeUri(URI uri)
protected String normalizeUri(URI uri)
{
String uriStr = uri.toASCIIString();
String warStr = warURI.toASCIIString();
if (uriStr.startsWith(warStr))
for (URIAttribute a : uris)
{
return "${WAR}" + uriStr.substring(warStr.length());
}
return uriStr;
}
public String normalizePath(Path path)
{
for (PathAttribute attr : attributes)
{
if (attr.path == null)
continue;
try
{
if (path.startsWith(attr.path) || path.equals(attr.path) || Files.isSameFile(path,attr.path))
{
return uriSeparators(URIUtil.addPaths("${" + attr.key + "}",attr.path.relativize(path).toString()));
}
if (uri.compareTo(a.uri)==0)
return String.format("${%s}",a.key);
if (!a.uri.getScheme().equalsIgnoreCase(uri.getScheme()))
continue;
if (a.uri.getHost()==null && uri.getHost()!=null)
continue;
if (a.uri.getHost()!=null && !a.uri.getHost().equals(uri.getHost()))
continue;
if (a.uri.getPath().equals(uri.getPath()))
return a.value;
if (!uri.getPath().startsWith(a.uri.getPath()))
continue;
String s = uri.getPath().substring(a.uri.getPath().length());
if (s.charAt(0)!='/')
continue;
return String.format("${%s}%s",a.key,new URI(s).toASCIIString());
}
catch(URISyntaxException e)
{
LOG.ignore(e);
}
}
return uri.toASCIIString();
}
protected String normalizePath(Path path)
{
for (PathAttribute a : paths)
{
try
{
if (path.equals(a.path) || Files.isSameFile(path,a.path))
return String.format("${%s}",a.key);
}
catch (IOException ignore)
{
LOG.ignore(ignore);
}
if (path.startsWith(a.path))
return String.format("${%s}/%s",a.key,a.path.relativize(path).toString());
}
return uriSeparators(path.toString());
return path.toString();
}
public String expand(String str)
@ -359,25 +457,14 @@ public class AttributeNormalizer
private String getString(String property)
{
if(property == null)
if(property==null)
{
return null;
}
// Use war path (if known)
if("WAR".equalsIgnoreCase(property))
{
return warURI.toASCIIString();
}
// Use known path attributes
for (PathAttribute attr : attributes)
{
if (attr.key.equalsIgnoreCase(property))
{
return uriSeparators(attr.path.toString());
}
}
Attribute a = attributes.get(property);
if (a!=null)
return a.value;
// Use system properties next
return System.getProperty(property);

View File

@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.BitSet;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -50,8 +51,6 @@ import org.eclipse.jetty.util.security.Constraint;
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". 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.
@ -59,105 +58,58 @@ import org.eclipse.jetty.util.security.Credential;
public class DigestAuthenticator extends LoginAuthenticator
{
private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
SecureRandom _random = new SecureRandom();
private long _maxNonceAgeMs = 60*1000;
private int _maxNC=1024;
private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<String, Nonce>();
private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
private static class Nonce
{
final String _nonce;
final long _ts;
final BitSet _seen;
public Nonce(String nonce, long ts, int size)
{
_nonce=nonce;
_ts=ts;
_seen = new BitSet(size);
}
private final SecureRandom _random = new SecureRandom();
private long _maxNonceAgeMs = 60 * 1000;
private int _maxNC = 1024;
private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<>();
private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<>();
public boolean seen(int count)
{
synchronized (this)
{
if (count>=_seen.size())
return true;
boolean s=_seen.get(count);
_seen.set(count);
return s;
}
}
}
/* ------------------------------------------------------------ */
public DigestAuthenticator()
{
super();
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
*/
@Override
public void setConfiguration(AuthConfiguration configuration)
{
super.setConfiguration(configuration);
String mna=configuration.getInitParameter("maxNonceAge");
if (mna!=null)
{
_maxNonceAgeMs=Long.valueOf(mna);
}
String mnc=configuration.getInitParameter("maxNonceCount");
if (mnc!=null)
{
_maxNC=Integer.valueOf(mnc);
}
String mna = configuration.getInitParameter("maxNonceAge");
if (mna != null)
setMaxNonceAge(Long.valueOf(mna));
String mnc = configuration.getInitParameter("maxNonceCount");
if (mnc != null)
setMaxNonceCount(Integer.valueOf(mnc));
}
/* ------------------------------------------------------------ */
public int getMaxNonceCount()
{
return _maxNC;
}
/* ------------------------------------------------------------ */
public void setMaxNonceCount(int maxNC)
{
_maxNC = maxNC;
}
/* ------------------------------------------------------------ */
public long getMaxNonceAge()
{
return _maxNonceAgeMs;
}
/* ------------------------------------------------------------ */
public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
public void setMaxNonceAge(long maxNonceAgeInMillis)
{
_maxNonceAgeMs = maxNonceAgeInMillis;
}
/* ------------------------------------------------------------ */
@Override
public String getAuthMethod()
{
return Constraint.__DIGEST_AUTH;
}
/* ------------------------------------------------------------ */
@Override
public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
{
return true;
}
/* ------------------------------------------------------------ */
@Override
public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
{
@ -217,20 +169,20 @@ public class DigestAuthenticator extends LoginAuthenticator
digest.uri = tok;
else if ("response".equalsIgnoreCase(name))
digest.response = tok;
name=null;
name = null;
}
}
}
int n = checkNonce(digest,(Request)request);
int n = checkNonce(digest, (Request)request);
if (n > 0)
{
//UserIdentity user = _loginService.login(digest.username,digest);
UserIdentity user = login(digest.username, digest, req);
if (user!=null)
if (user != null)
{
return new UserAuthentication(getAuthMethod(),user);
return new UserAuthentication(getAuthMethod(), user);
}
}
else if (n == 0)
@ -261,10 +213,17 @@ public class DigestAuthenticator extends LoginAuthenticator
{
throw new ServerAuthException(e);
}
}
/* ------------------------------------------------------------ */
@Override
public UserIdentity login(String username, Object credentials, ServletRequest request)
{
Digest digest = (Digest)credentials;
if (!Objects.equals(digest.realm, _loginService.getName()))
return null;
return super.login(username, credentials, request);
}
public String newNonce(Request request)
{
Nonce nonce;
@ -274,41 +233,40 @@ public class DigestAuthenticator extends LoginAuthenticator
byte[] nounce = new byte[24];
_random.nextBytes(nounce);
nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
nonce = new Nonce(new String(B64Code.encode(nounce)), request.getTimeStamp(), getMaxNonceCount());
}
while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
while (_nonceMap.putIfAbsent(nonce._nonce, nonce) != null);
_nonceQueue.add(nonce);
return nonce._nonce;
}
/**
* @param nstring nonce to check
* @param request
* @param digest the digest data to check
* @param request the request object
* @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce
*/
/* ------------------------------------------------------------ */
private int checkNonce(Digest digest, Request request)
{
// firstly let's expire old nonces
long expired = request.getTimeStamp()-_maxNonceAgeMs;
Nonce nonce=_nonceQueue.peek();
while (nonce!=null && nonce._ts<expired)
long expired = request.getTimeStamp() - getMaxNonceAge();
Nonce nonce = _nonceQueue.peek();
while (nonce != null && nonce._ts < expired)
{
_nonceQueue.remove(nonce);
_nonceMap.remove(nonce._nonce);
nonce=_nonceQueue.peek();
nonce = _nonceQueue.peek();
}
// Now check the requested nonce
try
{
nonce = _nonceMap.get(digest.nonce);
if (nonce==null)
if (nonce == null)
return 0;
long count = Long.parseLong(digest.nc,16);
if (count>=_maxNC)
long count = Long.parseLong(digest.nc, 16);
if (count >= _maxNC)
return 0;
if (nonce.seen((int)count))
@ -323,9 +281,32 @@ public class DigestAuthenticator extends LoginAuthenticator
return -1;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private static class Nonce
{
final String _nonce;
final 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;
}
}
}
private static class Digest extends Credential
{
private static final long serialVersionUID = -2484639019549527724L;
@ -350,8 +331,8 @@ public class DigestAuthenticator extends LoginAuthenticator
public boolean check(Object credentials)
{
if (credentials instanceof char[])
credentials=new String((char[])credentials);
String password = (credentials instanceof String) ? (String) credentials : credentials.toString();
credentials = new String((char[])credentials);
String password = (credentials instanceof String) ? (String)credentials : credentials.toString();
try
{
@ -362,22 +343,22 @@ public class DigestAuthenticator extends LoginAuthenticator
// Credentials are already a MD5 digest - assume it's in
// form user:realm:password (we have no way to know since
// it's a digest, alright?)
ha1 = ((Credential.MD5) credentials).getDigest();
ha1 = ((Credential.MD5)credentials).getDigest();
}
else
{
// calc A1 digest
md.update(username.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte) ':');
md.update((byte)':');
md.update(realm.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte) ':');
md.update((byte)':');
md.update(password.getBytes(StandardCharsets.ISO_8859_1));
ha1 = md.digest();
}
// calc A2 digest
md.reset();
md.update(method.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte) ':');
md.update((byte)':');
md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
byte[] ha2 = md.digest();
@ -389,15 +370,15 @@ public class DigestAuthenticator extends LoginAuthenticator
// ) > <">
md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
md.update((byte) ':');
md.update((byte)':');
md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte) ':');
md.update((byte)':');
md.update(nc.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte) ':');
md.update((byte)':');
md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte) ':');
md.update((byte)':');
md.update(qop.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte) ':');
md.update((byte)':');
md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
byte[] digest = md.digest();

View File

@ -27,7 +27,6 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.thread.Scheduler;
public class AsyncContextEvent extends AsyncEvent implements Runnable
@ -157,7 +156,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable
Scheduler.Task task=_timeoutTask;
_timeoutTask=null;
if (task!=null)
_state.onTimeout();
_state.getHttpChannel().execute(() -> _state.onTimeout());
}
public void addThrowable(Throwable e)

View File

@ -402,10 +402,12 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
{
if (!_response.isCommitted() && !_request.isHandled())
_response.sendError(HttpStatus.NOT_FOUND_404);
else if (!_response.isContentComplete(_response.getHttpOutput().getWritten()))
_transport.abort(new IOException("insufficient content written"));
_response.closeOutput();
_request.setHandled(true);
_state.onComplete();
_state.onComplete();
onCompleted();
@ -557,7 +559,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
if (LOG.isDebugEnabled())
LOG.debug("REQUEST for {} on {}{}{} {} {}{}{}",request.getURIString(),this,System.lineSeparator(),
request.getMethod(),request.getURIString(),request.getVersion(),System.lineSeparator(),
request.getMethod(),request.getURIString(),request.getHttpVersion(),System.lineSeparator(),
request.getFields());
}
@ -703,7 +705,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
_committedMetaData=info;
if (LOG.isDebugEnabled())
LOG.debug("COMMIT for {} on {}{}{} {} {}{}{}",getRequest().getRequestURI(),this,System.lineSeparator(),
info.getStatus(),info.getReason(),info.getVersion(),System.lineSeparator(),
info.getStatus(),info.getReason(),info.getHttpVersion(),System.lineSeparator(),
info.getFields());
}

View File

@ -133,7 +133,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
case EXPECT:
{
if (_metadata.getVersion() == HttpVersion.HTTP_1_1)
if (_metadata.getHttpVersion() == HttpVersion.HTTP_1_1)
{
HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
@ -263,7 +263,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
boolean persistent;
switch (_metadata.getVersion())
switch (_metadata.getHttpVersion())
{
case HTTP_0_9:
{
@ -347,7 +347,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
default:
{
throw new IllegalStateException("unsupported version " + _metadata.getVersion());
throw new IllegalStateException("unsupported version " + _metadata.getHttpVersion());
}
}

View File

@ -299,7 +299,7 @@ public class HttpChannelState
{
listener.onStartAsync(event);
}
catch(Exception e)
catch(Throwable e)
{
// TODO Async Dispatch Error
LOG.warn(e);
@ -853,7 +853,7 @@ public class HttpChannelState
{
listener.onComplete(event);
}
catch(Exception e)
catch(Throwable e)
{
LOG.warn(e+" while invoking onComplete listener " + listener);
LOG.debug(e);
@ -1008,6 +1008,14 @@ public class HttpChannelState
}
}
public boolean isAsyncComplete()
{
try(Locker.Lock lock= _locker.lock())
{
return _async==Async.COMPLETE;
}
}
public boolean isAsync()
{
try(Locker.Lock lock= _locker.lock())
@ -1169,6 +1177,31 @@ public class HttpChannelState
return woken;
}
/* ------------------------------------------------------------ */
/** Called to signal that a read has read -1.
* Will wake if the read was called while in ASYNC_WAIT state
* @return true if woken
*/
public boolean onReadEof()
{
boolean woken=false;
try(Locker.Lock lock= _locker.lock())
{
if(DEBUG)
LOG.debug("onReadEof {}",toStringLocked());
if (_state==State.ASYNC_WAIT)
{
_state=State.ASYNC_WOKEN;
_asyncReadUnready=true;
_asyncReadPossible=true;
woken=true;
}
}
return woken;
}
public boolean isReadPossible()
{
try(Locker.Lock lock= _locker.lock())

View File

@ -524,6 +524,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
@Override
public void abort(Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("abort {} {}",this,failure);
// Do a direct close of the output, as this may indicate to a client that the
// response is bad either with RST or by abnormal completion of chunked response.
getEndPoint().close();

View File

@ -119,8 +119,8 @@ public class HttpInput extends ServletInputStream implements Runnable
}
private final static Logger LOG = Log.getLogger(HttpInput.class);
private final static Content EOF_CONTENT = new EofContent("EOF");
private final static Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF");
final static Content EOF_CONTENT = new EofContent("EOF");
final static Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF");
private final byte[] _oneByteBuffer = new byte[1];
private Content _content;
@ -232,7 +232,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return available;
}
private void wake()
protected void wake()
{
HttpChannel channel = _channelState.getHttpChannel();
Executor executor = channel.getConnector().getServer().getThreadPool();
@ -256,8 +256,11 @@ public class HttpInput extends ServletInputStream implements Runnable
@Override
public int read(byte[] b, int off, int len) throws IOException
{
boolean wake = false;
int l;
synchronized (_inputQ)
{
// Setup blocking only if not async
if (!isAsync())
{
if (_blockUntil == 0)
@ -268,6 +271,7 @@ public class HttpInput extends ServletInputStream implements Runnable
}
}
// Caclulate minimum request rate for DOS protection
long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate();
if (minRequestDataRate > 0 && _firstByteTimeStamp != -1)
{
@ -280,25 +284,39 @@ public class HttpInput extends ServletInputStream implements Runnable
}
}
// Consume content looking for bytes to read
while (true)
{
Content item = nextContent();
if (item != null)
{
int l = get(item,b,off,len);
l = get(item,b,off,len);
if (LOG.isDebugEnabled())
LOG.debug("{} read {} from {}",this,l,item);
// Consume any following poison pills
pollReadableContent();
return l;
if (item.isEmpty())
pollReadableContent();
break;
}
// No content, so should we block?
if (!_state.blockForContent(this))
return _state.noContent();
{
// Not blocking, so what should we return?
l = _state.noContent();
// If EOF do we need to wake for allDataRead callback?
if (l<0)
wake = _channelState.onReadEof();
break;
}
}
}
if (wake)
wake();
return l;
}
/**
@ -345,19 +363,14 @@ public class HttpInput extends ServletInputStream implements Runnable
// If it is EOF, consume it here
if (content instanceof SentinelContent)
{
if (content == EARLY_EOF_CONTENT)
_state = EARLY_EOF;
else if (content instanceof EofContent)
if (content instanceof EofContent)
{
if (_listener == null)
if (content == EARLY_EOF_CONTENT)
_state = EARLY_EOF;
else if (_listener == null)
_state = EOF;
else
{
_state = AEOF;
boolean woken = _channelState.onReadReady(); // force callback?
if (woken)
wake();
}
}
// Consume the EOF content, either if it was original content
@ -732,17 +745,25 @@ public class HttpInput extends ServletInputStream implements Runnable
{
if (_listener != null)
throw new IllegalStateException("ReadListener already set");
if (_state != STREAM)
throw new IllegalStateException("State " + STREAM + " != " + _state);
_state = ASYNC;
_listener = readListener;
boolean content = nextContent() != null;
if (content)
Content content = nextReadable();
if (content!=null)
{
_state = ASYNC;
woken = _channelState.onReadReady();
}
else if (_state == EOF)
{
_state = AEOF;
woken = _channelState.onReadReady();
}
else
{
_state = ASYNC;
_channelState.onReadUnready();
}
}
}
catch (IOException e)
@ -780,23 +801,49 @@ public class HttpInput extends ServletInputStream implements Runnable
@Override
public void run()
{
final Throwable error;
final ReadListener listener;
Throwable error;
boolean aeof = false;
synchronized (_inputQ)
{
listener = _listener;
if (_state == EOF)
return;
if (_state == AEOF)
if (_state==AEOF)
{
_state = EOF;
aeof = true;
}
listener = _listener;
error = _state instanceof ErrorState?((ErrorState)_state).getError():null;
error = _state.getError();
if (!aeof && error==null)
{
Content content = pollReadableContent();
// Consume EOF
if (content instanceof EofContent)
{
content.succeeded();
if (_content==content)
_content = null;
if (content == EARLY_EOF_CONTENT)
{
_state = EARLY_EOF;
error = _state.getError();
}
else
{
_state = EOF;
aeof = true;
}
}
else if (content==null)
throw new IllegalStateException();
}
}
try
@ -813,6 +860,16 @@ public class HttpInput extends ServletInputStream implements Runnable
else
{
listener.onDataAvailable();
synchronized (_inputQ)
{
if (_state == AEOF)
{
_state = EOF;
aeof = !_channelState.isAsyncComplete();
}
}
if (aeof)
listener.onAllDataRead();
}
}
catch (Throwable e)
@ -956,6 +1013,11 @@ public class HttpInput extends ServletInputStream implements Runnable
{
return -1;
}
public Throwable getError()
{
return null;
}
}
protected static class EOFState extends State
@ -1027,7 +1089,7 @@ public class HttpInput extends ServletInputStream implements Runnable
@Override
public int noContent() throws IOException
{
throw new EofException("Early EOF");
throw getError();
}
@Override
@ -1035,6 +1097,11 @@ public class HttpInput extends ServletInputStream implements Runnable
{
return "EARLY_EOF";
}
public IOException getError()
{
return new EofException("Early EOF");
}
};
protected static final State EOF = new EOFState()

View File

@ -685,6 +685,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (LOG.isDebugEnabled())
LOG.debug("sendContent({})", BufferUtil.toDetailString(content));
_written += content.remaining();
write(content, true);
closed();
}
@ -766,6 +767,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (LOG.isDebugEnabled())
LOG.debug("sendContent(buffer={},{})", BufferUtil.toDetailString(content), callback);
_written += content.remaining();
write(content, true, new Callback.Nested(callback)
{
@Override
@ -1280,6 +1282,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// write what we have
_buffer.position(0);
_buffer.limit(len);
_written += len;
write(_buffer, _eof, this);
return Action.SCHEDULED;
}
@ -1338,6 +1341,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// write what we have
BufferUtil.flipToFlush(_buffer, 0);
_written += _buffer.remaining();
write(_buffer, _eof, this);
return Action.SCHEDULED;

View File

@ -83,7 +83,6 @@ import org.eclipse.jetty.server.session.Session;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
@ -1057,7 +1056,7 @@ public class Request implements HttpServletRequest
MetaData.Request metadata = _metaData;
if (metadata==null)
return null;
HttpVersion version = metadata.getVersion();
HttpVersion version = metadata.getHttpVersion();
if (version==null)
return null;
return version.toString();
@ -1070,7 +1069,7 @@ public class Request implements HttpServletRequest
public HttpVersion getHttpVersion()
{
MetaData.Request metadata = _metaData;
return metadata==null?null:metadata.getVersion();
return metadata==null?null:metadata.getHttpVersion();
}
/* ------------------------------------------------------------ */
@ -2000,6 +1999,13 @@ public class Request implements HttpServletRequest
metadata.setMethod(method);
}
public void setHttpVersion(HttpVersion version)
{
MetaData.Request metadata = _metaData;
if (metadata!=null)
metadata.setHttpVersion(version);
}
/* ------------------------------------------------------------ */
public boolean isHead()
{

View File

@ -21,12 +21,9 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.channels.IllegalSelectorException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
@ -254,9 +251,6 @@ public class Response implements HttpServletResponse
buf.append('=');
// Remember name= part to look for other matching set-cookie
String name_equals=buf.toString();
// Append the value
boolean quote_value=isQuoteNeededForCookie(value);
quoteOnlyOrAppend(buf,value,quote_value);
@ -879,13 +873,13 @@ public class Response implements HttpServletResponse
if (isCommitted() || isIncluding())
return;
_contentLength = len;
if (_contentLength > 0)
if (len>0)
{
long written = _out.getWritten();
if (written > len)
throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
_contentLength = len;
_fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
if (isAllContentWritten(written))
{
@ -899,15 +893,19 @@ public class Response implements HttpServletResponse
}
}
}
else if (_contentLength==0)
else if (len==0)
{
long written = _out.getWritten();
if (written > 0)
throw new IllegalArgumentException("setContentLength(0) when already written " + written);
_contentLength = len;
_fields.put(HttpHeader.CONTENT_LENGTH, "0");
}
else
{
_contentLength = len;
_fields.remove(HttpHeader.CONTENT_LENGTH);
}
}
public long getContentLength()
@ -920,6 +918,11 @@ public class Response implements HttpServletResponse
return (_contentLength >= 0 && written >= _contentLength);
}
public boolean isContentComplete(long written)
{
return (_contentLength < 0 || written >= _contentLength);
}
public void closeOutput() throws IOException
{
switch (_outputType)
@ -1094,15 +1097,14 @@ public class Response implements HttpServletResponse
_fields.put(_mimeType.getContentTypeField());
}
}
}
@Override
public void setBufferSize(int size)
{
if (isCommitted() || getContentCount() > 0)
throw new IllegalStateException("cannot set buffer size on committed response");
if (size <= 0)
throw new IllegalStateException("cannot set buffer size when response is committed or written to");
if (size < __MIN_BUFFER_SIZE)
size = __MIN_BUFFER_SIZE;
_out.setBufferSize(size);
}

View File

@ -29,6 +29,7 @@ import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@ -376,22 +377,35 @@ public class Server extends HandlerWrapper implements Attributes
}
HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
MultiException mex=new MultiException();
// check size of thread pool
// Check that the thread pool size is enough.
SizedThreadPool pool = getBean(SizedThreadPool.class);
int max=pool==null?-1:pool.getMaxThreads();
int selectors=0;
int acceptors=0;
if (mex.size()==0)
for (Connector connector : _connectors)
{
for (Connector connector : _connectors)
if (connector instanceof AbstractConnector)
{
if (connector instanceof AbstractConnector)
acceptors+=((AbstractConnector)connector).getAcceptors();
AbstractConnector abstractConnector = (AbstractConnector)connector;
Executor connectorExecutor = connector.getExecutor();
if (connectorExecutor != pool)
{
// Do not count the selectors and acceptors from this connector at
// the server level, because the connector uses a dedicated executor.
continue;
}
acceptors += abstractConnector.getAcceptors();
if (connector instanceof ServerConnector)
selectors+=((ServerConnector)connector).getSelectorManager().getSelectorCount();
{
// The SelectorManager uses 2 threads for each selector,
// one for the normal and one for the low priority strategies.
selectors += 2 * ((ServerConnector)connector).getSelectorManager().getSelectorCount();
}
}
}
@ -399,6 +413,7 @@ public class Server extends HandlerWrapper implements Attributes
if (max>0 && needed>max)
throw new IllegalStateException(String.format("Insufficient threads: max=%d < needed(acceptors=%d + selectors=%d + request=1)",max,acceptors,selectors));
MultiException mex=new MultiException();
try
{
super.doStart();

View File

@ -23,6 +23,7 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.server.session.HouseKeeper;
import org.eclipse.jetty.util.component.LifeCycle;
/**
@ -114,4 +115,16 @@ public interface SessionIdManager extends LifeCycle
* @return the set of session handlers
*/
public Set<SessionHandler> getSessionHandlers();
/**
* @param houseKeeper the housekeeper for doing scavenging
*/
public void setSessionHouseKeeper (HouseKeeper houseKeeper);
/**
* @return the housekeeper for doing scavenging
*/
public HouseKeeper getSessionHouseKeeper();
}

View File

@ -28,16 +28,27 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** AbstractHandler.
* <p>A convenience implementation of {@link Handler} that uses the
* {@link ContainerLifeCycle} to provide:<ul>
* <li>start/stop behavior
* <li>a bean container
* <li>basic {@link Dumpable} support
* <li>a {@link Server} reference
* <li>optional error dispatch handling
* </ul>
*/
@ManagedObject("Jetty Handler")
public abstract class AbstractHandler extends ContainerLifeCycle implements Handler
@ -46,7 +57,6 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
private Server _server;
/* ------------------------------------------------------------ */
/**
*
*/
@ -55,20 +65,31 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (baseRequest.getDispatcherType()==DispatcherType.ERROR)
doError(target,baseRequest,request,response);
else
doHandle(target,baseRequest,request,response);
}
public abstract void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
/* ------------------------------------------------------------ */
protected void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
}
/* ------------------------------------------------------------ */
/**
* Convenience method to generate error page.
* <p>This method can be called from {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when an {@link DispatcherType#ERROR} dispatch
* is detected and an error page needs to be generated by calling {@link HttpServletResponse#sendError(int, String)} with the appropriate code and reason,
* which are taken from {@link HttpServletRequest#getAttribute(String)} for {@link RequestDispatcher#ERROR_STATUS_CODE} and {@link RequestDispatcher#ERROR_MESSAGE}
* @see ErrorDispatchHandler for a conveniance class that calls this method.
* @param target
* The target of the request - either a URI or a name.
* @param baseRequest
* The original unwrapped request object.
* @param request
* The request either as the {@link Request} object or a wrapper of that request. The
* <code>{@link HttpConnection#getCurrentConnection()}.{@link HttpConnection#getHttpChannel() getHttpChannel()}.{@link HttpChannel#getRequest() getRequest()}</code>
* method can be used access the Request object if required.
* @param response
* The response as the {@link Response} object or a wrapper of that request. The
* <code>{@link HttpConnection#getCurrentConnection()}.{@link HttpConnection#getHttpChannel() getHttpChannel()}.{@link HttpChannel#getResponse() getResponse()}</code>
* method can be used access the Response object if required.
* @throws IOException
* if unable to handle the request or response processing
* @throws ServletException
* if unable to handle the request or response due to underlying servlet issue
*/
protected void doError(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
Object o = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
@ -79,7 +100,6 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
response.sendError(code,reason);
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.thread.LifeCycle#start()
*/
@ -93,7 +113,6 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
super.doStart();
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.thread.LifeCycle#stop()
*/
@ -105,7 +124,6 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
super.doStop();
}
/* ------------------------------------------------------------ */
@Override
public void setServer(Server server)
{
@ -116,14 +134,12 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
_server=server;
}
/* ------------------------------------------------------------ */
@Override
public Server getServer()
{
return _server;
}
/* ------------------------------------------------------------ */
@Override
public void destroy()
{
@ -132,11 +148,52 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
super.destroy();
}
/* ------------------------------------------------------------ */
@Override
public void dumpThis(Appendable out) throws IOException
{
out.append(toString()).append(" - ").append(getState()).append('\n');
}
/**
* An extension of AbstractHandler that handles {@link DispatcherType#ERROR} dispatches.
* <p>
* {@link DispatcherType#ERROR} dispatches are handled by calling the {@link #doError(String, Request, HttpServletRequest, HttpServletResponse)}
* method. All other dispatches are passed to the abstract {@link #doNonErrorHandle(String, Request, HttpServletRequest, HttpServletResponse)}
* method, which should be implemented with specific handler behavior
*
*/
public static abstract class ErrorDispatchHandler extends AbstractHandler
{
@Override
public final void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (baseRequest.getDispatcherType()==DispatcherType.ERROR)
doError(target,baseRequest,request,response);
else
doNonErrorHandle(target,baseRequest,request,response);
}
/**
* Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
* for all non-{@link DispatcherType#ERROR} dispatches.
* @param target
* The target of the request - either a URI or a name.
* @param baseRequest
* The original unwrapped request object.
* @param request
* The request either as the {@link Request} object or a wrapper of that request. The
* <code>{@link HttpConnection#getCurrentConnection()}.{@link HttpConnection#getHttpChannel() getHttpChannel()}.{@link HttpChannel#getRequest() getRequest()}</code>
* method can be used access the Request object if required.
* @param response
* The response as the {@link Response} object or a wrapper of that request. The
* <code>{@link HttpConnection#getCurrentConnection()}.{@link HttpConnection#getHttpChannel() getHttpChannel()}.{@link HttpChannel#getResponse() getResponse()}</code>
* method can be used access the Response object if required.
* @throws IOException
* if unable to handle the request or response processing
* @throws ServletException
* if unable to handle the request or response due to underlying servlet issue
*/
protected abstract void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
}
}

View File

@ -1127,16 +1127,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (LOG.isDebugEnabled())
LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
// start manual inline of nextScope(target,baseRequest,request,response);
if (never())
nextScope(target,baseRequest,request,response);
else if (_nextScope != null)
_nextScope.doScope(target,baseRequest,request,response);
else if (_outerScope != null)
_outerScope.doHandle(target,baseRequest,request,response);
else
doHandle(target,baseRequest,request,response);
// end manual inline (pathentic attempt to reduce stack depth)
nextScope(target,baseRequest,request,response);
}
finally
{
@ -1160,6 +1151,40 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
/* ------------------------------------------------------------ */
protected void requestInitialized(Request baseRequest, HttpServletRequest request)
{
// Handle the REALLY SILLY request events!
if (!_servletRequestAttributeListeners.isEmpty())
for (ServletRequestAttributeListener l :_servletRequestAttributeListeners)
baseRequest.addEventListener(l);
if (!_servletRequestListeners.isEmpty())
{
final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
for (ServletRequestListener l : _servletRequestListeners)
l.requestInitialized(sre);
}
}
/* ------------------------------------------------------------ */
protected void requestDestroyed(Request baseRequest, HttpServletRequest request)
{
// Handle more REALLY SILLY request events!
if (!_servletRequestListeners.isEmpty())
{
final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
for (int i=_servletRequestListeners.size();i-->0;)
_servletRequestListeners.get(i).requestDestroyed(sre);
}
if (!_servletRequestAttributeListeners.isEmpty())
{
for (int i=_servletRequestAttributeListeners.size();i-->0;)
baseRequest.removeEventListener(_servletRequestAttributeListeners.get(i));
}
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
@ -1173,19 +1198,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
try
{
if (new_context)
{
// Handle the REALLY SILLY request events!
if (!_servletRequestAttributeListeners.isEmpty())
for (ServletRequestAttributeListener l :_servletRequestAttributeListeners)
baseRequest.addEventListener(l);
if (!_servletRequestListeners.isEmpty())
{
final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
for (ServletRequestListener l : _servletRequestListeners)
l.requestInitialized(sre);
}
}
requestInitialized(baseRequest,request);
switch(dispatch)
{
@ -1203,50 +1216,24 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (Boolean.TRUE.equals(baseRequest.getAttribute(Dispatcher.__ERROR_DISPATCH)))
break;
Object error = request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
// We can just call sendError here. If there is no error page, then one will
// We can just call doError here. If there is no error page, then one will
// be generated. If there is an error page, then a RequestDispatcher will be
// used to route the request through appropriate filters etc.
response.sendError((error instanceof Integer)?((Integer)error).intValue():500);
doError(target,baseRequest,request,response);
return;
default:
break;
}
// start manual inline of nextHandle(target,baseRequest,request,response);
// noinspection ConstantIfStatement
if (never())
nextHandle(target,baseRequest,request,response);
else if (_nextScope != null && _nextScope == _handler)
_nextScope.doHandle(target,baseRequest,request,response);
else if (_handler != null)
_handler.handle(target,baseRequest,request,response);
// end manual inline
nextHandle(target,baseRequest,request,response);
}
finally
{
// Handle more REALLY SILLY request events!
if (new_context)
{
if (!_servletRequestListeners.isEmpty())
{
final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
for (int i=_servletRequestListeners.size();i-->0;)
_servletRequestListeners.get(i).requestDestroyed(sre);
}
if (!_servletRequestAttributeListeners.isEmpty())
{
for (int i=_servletRequestAttributeListeners.size();i-->0;)
baseRequest.removeEventListener(_servletRequestAttributeListeners.get(i));
}
}
requestDestroyed(baseRequest,request);
}
}
/**
* @param request A request that is applicable to the scope, or null
* @param reason An object that indicates the reason the scope is being entered.

View File

@ -74,6 +74,12 @@ public class ErrorHandler extends AbstractHandler
*/
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
doError(target,baseRequest,request,response);
}
@Override
public void doError(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
String method = request.getMethod();
if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))

View File

@ -35,94 +35,79 @@ import org.eclipse.jetty.util.InetAddressSet;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* Inet Address Access Handler
* InetAddress Access Handler
* <p>
* Controls access to the wrapped handler by the real remote IP. Control is provided
* Controls access to the wrapped handler using the real remote IP. Control is provided
* by and {@link IncludeExcludeSet} over a {@link InetAddressSet}. This handler
* uses the real internet address of the connection, not one reported in the forwarded
* for headers, as this cannot be as easily forged.
* <p>
*/
public class InetAccessHandler extends HandlerWrapper
{
private static final Logger LOG = Log.getLogger(InetAccessHandler.class);
IncludeExcludeSet<String, InetAddress> _set = new IncludeExcludeSet<>(InetAddressSet.class);
/* ------------------------------------------------------------ */
/**
* Creates new handler object
*/
public InetAccessHandler()
{
super();
}
private final IncludeExcludeSet<String, InetAddress> _set = new IncludeExcludeSet<>(InetAddressSet.class);
/* ------------------------------------------------------------ */
/**
* Include a InetAddress pattern
* Includes an InetAddress pattern
*
* @param pattern InetAddress pattern to include
* @see InetAddressSet
* @param pattern InetAddress pattern to exclude
*/
public void include(String pattern)
{
_set.include(pattern);
}
/* ------------------------------------------------------------ */
/**
* Include a InetAddress pattern
* Includes InetAddress patterns
*
* @param patterns InetAddress patterns to include
* @see InetAddressSet
* @param patterns InetAddress patterns to exclude
*/
public void include(String... patterns)
{
_set.include(patterns);
}
/* ------------------------------------------------------------ */
/**
* Exclude a InetAddress pattern
* @see InetAddressSet
* Excludes an InetAddress pattern
*
* @param pattern InetAddress pattern to exclude
* @see InetAddressSet
*/
public void exclude(String pattern)
{
_set.exclude(pattern);
}
/* ------------------------------------------------------------ */
/**
* Include a InetAddress pattern
* @see InetAddressSet
* Excludes InetAddress patterns
*
* @param patterns InetAddress patterns to exclude
* @see InetAddressSet
*/
public void exclude(String... patterns)
{
_set.exclude(patterns);
}
/* ------------------------------------------------------------ */
/**
* Checks the incoming request against the whitelist and blacklist
*
* @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// Get the real remote IP (not the one set by the forwarded headers (which may be forged))
HttpChannel channel = baseRequest.getHttpChannel();
if (channel!=null)
if (channel != null)
{
EndPoint endp=channel.getEndPoint();
if (endp!=null)
EndPoint endp = channel.getEndPoint();
if (endp != null)
{
InetSocketAddress address = endp.getRemoteAddress();
if (address!=null && !isAllowed(address.getAddress()))
if (address != null && !isAllowed(address.getAddress(), request))
{
response.sendError(HttpStatus.FORBIDDEN_403);
baseRequest.setHandled(true);
@ -131,26 +116,27 @@ public class InetAccessHandler extends HandlerWrapper
}
}
getHandler().handle(target,baseRequest, request, response);
getHandler().handle(target, baseRequest, request, response);
}
/* ------------------------------------------------------------ */
/**
* Check if specified request is allowed by current IPAccess rules.
*
* @param address internet address
* @return true if address is allowed
* Checks if specified address and request are allowed by current InetAddress rules.
*
* @param address the inetAddress to check
* @param request the request to check
* @return true if inetAddress and request are allowed
*/
protected boolean isAllowed(InetAddress address)
protected boolean isAllowed(InetAddress address, HttpServletRequest request)
{
return _set.test(address);
boolean allowed = _set.test(address);
if (LOG.isDebugEnabled())
LOG.debug("{} {} {} for {}", this, allowed ? "allowed" : "denied", address, request);
return allowed;
}
/* ------------------------------------------------------------ */
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpBeans(out,indent,_set.getIncluded(),_set.getExcluded());
dumpBeans(out, indent, _set.getIncluded(), _set.getExcluded());
}
}
}

View File

@ -129,7 +129,7 @@ public abstract class ScopedHandler extends HandlerWrapper
}
}
/* ------------------------------------------------------------ */
/** ------------------------------------------------------------ */
/*
*/
@Override
@ -145,22 +145,23 @@ public abstract class ScopedHandler extends HandlerWrapper
}
/* ------------------------------------------------------------ */
/*
/**
* Scope the handler
* <p>Derived implementations should call {@link #nextScope(String, Request, HttpServletRequest, HttpServletResponse)}
*/
public abstract void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException;
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
nextScope(target,baseRequest,request,response);
}
/* ------------------------------------------------------------ */
/*
/**
* Scope the handler
*/
public final void nextScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
// this method has been manually inlined in several locations, but
// is called protected by an if(never()), so your IDE can find those
// locations if this code is changed.
if (_nextScope!=null)
_nextScope.doScope(target,baseRequest,request, response);
else if (_outerScope!=null)
@ -170,8 +171,9 @@ public abstract class ScopedHandler extends HandlerWrapper
}
/* ------------------------------------------------------------ */
/*
/**
* Do the handler work within the scope.
* <p>Derived implementations should call {@link #nextHandle(String, Request, HttpServletRequest, HttpServletResponse)}
*/
public abstract void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException;
@ -182,19 +184,9 @@ public abstract class ScopedHandler extends HandlerWrapper
*/
public final void nextHandle(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// this method has been manually inlined in several locations, but
// is called protected by an if(never()), so your IDE can find those
// locations if this code is changed.
if (_nextScope!=null && _nextScope==_handler)
_nextScope.doHandle(target,baseRequest,request, response);
else if (_handler!=null)
_handler.handle(target,baseRequest, request, response);
super.handle(target,baseRequest,request,response);
}
/* ------------------------------------------------------------ */
protected boolean never()
{
return false;
}
}

View File

@ -22,6 +22,9 @@ package org.eclipse.jetty.server.session;
import java.util.Set;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* CachingSessionDataStore
@ -44,7 +47,7 @@ import org.eclipse.jetty.util.component.ContainerLifeCycle;
*/
public class CachingSessionDataStore extends ContainerLifeCycle implements SessionDataStore
{
private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
/**
* The actual store for the session data
*/
@ -98,8 +101,15 @@ public class CachingSessionDataStore extends ContainerLifeCycle implements Sessi
SessionData d = null;
//check to see if the session data is already in the cache
d = _cache.load(id);
try
{
//check to see if the session data is already in the cache
d = _cache.load(id);
}
catch (Exception e)
{
LOG.warn(e);
}
if (d != null)
return d; //cache hit
@ -178,10 +188,17 @@ public class CachingSessionDataStore extends ContainerLifeCycle implements Sessi
@Override
public boolean exists(String id) throws Exception
{
//check the cache first
SessionData data = _cache.load(id);
if (data != null)
return true;
try
{
//check the cache first
SessionData data = _cache.load(id);
if (data != null)
return true;
}
catch (Exception e)
{
LOG.warn(e);
}
//then the delegate store
return _store.exists(id);

View File

@ -120,6 +120,14 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
}
/**
* @return the housekeeper
*/
public HouseKeeper getSessionHouseKeeper()
{
return _houseKeeper;
}
/* ------------------------------------------------------------ */
/**

View File

@ -86,12 +86,12 @@ public class Session implements SessionHandler.SessionIf
protected SessionHandler _handler; //the manager of the session
protected String _extendedId; //the _id plus the worker name
protected long _requests;
private boolean _idChanged;
private boolean _newSession;
private State _state = State.VALID; //state of the session:valid,invalid or being invalidated
private Locker _lock = new Locker(); //sync lock
private boolean _resident = false;
private SessionInactivityTimeout _sessionInactivityTimer = null;
protected boolean _idChanged;
protected boolean _newSession;
protected State _state = State.VALID; //state of the session:valid,invalid or being invalidated
protected Locker _lock = new Locker(); //sync lock
protected boolean _resident = false;
protected SessionInactivityTimeout _sessionInactivityTimer = null;
@ -169,6 +169,7 @@ public class Session implements SessionHandler.SessionIf
_handler = handler;
_sessionData = data;
_newSession = true;
_sessionData.setDirty(true);
_requests = 1; //access will not be called on this new session, but we are obviously in a request
}
@ -232,8 +233,7 @@ public class Session implements SessionHandler.SessionIf
long lastAccessed = _sessionData.getAccessed();
_sessionData.setAccessed(time);
_sessionData.setLastAccessed(lastAccessed);
int maxInterval=getMaxInactiveInterval();
_sessionData.setExpiry(maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
_sessionData.calcAndSetExpiry(time);
if (isExpiredAt(time))
{
invalidate();
@ -859,7 +859,7 @@ public class Session implements SessionHandler.SessionIf
if (result)
{
//tell id mgr to remove session from all other contexts
((DefaultSessionIdManager)_handler.getSessionIdManager()).invalidateAll(_sessionData.getId());
_handler.getSessionIdManager().invalidateAll(_sessionData.getId());
}
}
catch (Exception e)

View File

@ -251,6 +251,16 @@ public class SessionData implements Serializable
return (getMaxInactiveMs() <= 0 ? 0 : (System.currentTimeMillis() + getMaxInactiveMs()));
}
public long calcExpiry (long time)
{
return (getMaxInactiveMs() <= 0 ? 0 : (time + getMaxInactiveMs()));
}
public void calcAndSetExpiry (long time)
{
setExpiry(calcExpiry(time));
}
public void calcAndSetExpiry ()
{
setExpiry(calcExpiry());
@ -351,8 +361,8 @@ public class SessionData implements Serializable
public boolean isExpiredAt (long time)
{
if (LOG.isDebugEnabled())
LOG.debug("Testing expiry on session {}: expires at {} now {}", _id, getExpiry(), time);
if (getExpiry() <= 0)
LOG.debug("Testing expiry on session {}: expires at {} now {} maxIdle {}", _id, getExpiry(), time, getMaxInactiveMs());
if (getMaxInactiveMs() <= 0)
return false; //never expires
return (getExpiry() <= time);
}

Some files were not shown because too many files have changed in this diff Show More