Merge branch 'jetty-9.4.x'
This commit is contained in:
commit
fa6d9029fc
|
@ -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) {
|
||||
|
|
|
@ -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
10985
VERSION.txt
File diff suppressed because it is too large
Load Diff
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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."));
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()]));
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
....
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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[]
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
@ -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
|
@ -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[]
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)]);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
[description]
|
||||
Enable use of DefaultCache for session data storage
|
||||
|
||||
[xml]
|
||||
etc/sessions/infinispan/default.xml
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -120,6 +120,14 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the housekeeper
|
||||
*/
|
||||
public HouseKeeper getSessionHouseKeeper()
|
||||
{
|
||||
return _houseKeeper;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue