Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-4747-WebSocketTCK
|
@ -8,45 +8,51 @@ pipeline {
|
|||
stage("Parallel Stage") {
|
||||
parallel {
|
||||
stage("Build / Test - JDK11") {
|
||||
agent { node { label 'linux' } }
|
||||
agent {
|
||||
node { label 'linux' }
|
||||
}
|
||||
options { timeout(time: 120, unit: 'MINUTES') }
|
||||
steps {
|
||||
mavenBuild("jdk11", "-T3 -Pmongodb clean install", "maven3", true) // -Pautobahn
|
||||
// Collect up the jacoco execution results (only on main build)
|
||||
jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class',
|
||||
exclusionPattern: '' +
|
||||
// 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/**',
|
||||
execPattern: '**/target/jacoco.exec',
|
||||
classPattern: '**/target/classes',
|
||||
sourcePattern: '**/src/main/java'
|
||||
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']]
|
||||
junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml,**/target/autobahntestsuite-reports/*.xml'
|
||||
container('jetty-build') {
|
||||
mavenBuild("jdk11", "-T3 -Pmongodb clean install", "maven3", true) // -Pautobahn
|
||||
// Collect up the jacoco execution results (only on main build)
|
||||
jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class',
|
||||
exclusionPattern: '' +
|
||||
// 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/**',
|
||||
execPattern: '**/target/jacoco.exec',
|
||||
classPattern: '**/target/classes',
|
||||
sourcePattern: '**/src/main/java'
|
||||
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']]
|
||||
junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml,**/target/autobahntestsuite-reports/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("Build / Test - JDK13") {
|
||||
stage("Build / Test - JDK14") {
|
||||
agent { node { label 'linux' } }
|
||||
steps {
|
||||
timeout(time: 120, unit: 'MINUTES') {
|
||||
mavenBuild("jdk13", "-T3 -Pmongodb clean install", "maven3", true)
|
||||
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']]
|
||||
junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml'
|
||||
container('jetty-build') {
|
||||
timeout( time: 120, unit: 'MINUTES' ) {
|
||||
mavenBuild( "jdk14", "-T3 -Pmongodb clean install", "maven3", true )
|
||||
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']]
|
||||
junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,9 +60,13 @@ pipeline {
|
|||
stage("Build Javadoc") {
|
||||
agent { node { label 'linux' } }
|
||||
steps {
|
||||
timeout(time: 30, unit: 'MINUTES') {
|
||||
mavenBuild("jdk11", "package source:jar javadoc:jar javadoc:aggregate-jar -Peclipse-release -DskipTests -Dpmd.skip=true -Dcheckstyle.skip=true", "maven3", true)
|
||||
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'JavaDoc'], [parserName: 'Java']]
|
||||
container('jetty-build') {
|
||||
timeout( time: 30, unit: 'MINUTES' ) {
|
||||
mavenBuild( "jdk11",
|
||||
"package source:jar javadoc:jar javadoc:aggregate-jar -Peclipse-release -DskipTests -Dpmd.skip=true -Dcheckstyle.skip=true",
|
||||
"maven3", true )
|
||||
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'JavaDoc'], [parserName: 'Java']]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
44
VERSION.txt
|
@ -355,6 +355,46 @@ jetty-10.0.0-alpha0 - 11 July 2019
|
|||
+ 3849 ClosedChannelException from jetty-test-webapp javax websocket chat
|
||||
example
|
||||
|
||||
jetty-9.4.28.v20200408 - 08 April 2020
|
||||
+ 847 Setting async timeout on WebSocketClient does not seem to timeout writes
|
||||
+ 2896 Wrong Certificate Selected When Using Multiple Virtual Host Names in
|
||||
Conscrypt
|
||||
+ 4443 Track backport of ALPN APIs to Java 8
|
||||
+ 4529 ErrorHandler showing servlet info, can not be disabled unless
|
||||
overriding most of its functionality
|
||||
+ 4542 servlet context root mapping incorrect
|
||||
+ 4619 Inconsistent library versions notice.
|
||||
+ 4620 Using console-capture with StdErrLog results in empty log file
|
||||
+ 4621 jetty-jaspi in jetty-all uber aggregate artifact requires
|
||||
javax.security.auth.message.AuthException which cannot be included
|
||||
+ 4628 Add support for conditional module dependencies in jetty-start
|
||||
+ 4631 Startup XmlConfiguration WARN on Arg threadpool
|
||||
+ 4638 maxFormContentSize fix in Issue #3856 broke JenkinsCI/Winstone
|
||||
+ 4644 no injection of env-entry if env-entry-value is whitespace only or
|
||||
missing
|
||||
+ 4645 Empty "X-Forwarded-Port" header results in NumberFormatException
|
||||
+ 4647 Hazelcast remote.xml configuration file do not configure hazelcast
|
||||
remote addresses
|
||||
+ 4650 Do not use ServiceLoader every time a WebSocketSession is created
|
||||
+ 4654 Hazelcast configurationLocation is not configurable via mod files
|
||||
+ 4662 Jetty 9.4.x calls ServletContextListener.contextDestroyed() too early
|
||||
+ 4671 CustomRequestLog throws NullPointerException when no request cookie is
|
||||
present
|
||||
+ 4673 Short reads break form-data multipart parsing
|
||||
+ 4676 ALPN support for Java 15
|
||||
+ 4682 "UnreadableSessionDataException Unreadable session ..." after upgrading
|
||||
to 9.4.27
|
||||
+ 4693 Version 9.4.25 breaks binary compatibility by renaming
|
||||
Response.closeOutput()
|
||||
+ 4699 ServletContainerInitializer.onStartUp is not called with maven jar
|
||||
packaging using Jetty Maven Plugin
|
||||
+ 4711 Reset trailers on recycled response
|
||||
+ 4714 Low setMaxConcurrentStreams causes "1/unexpected_data_frame" errors
|
||||
+ 4735 Get env variables in PHP scripts served through FastCGIProxyServlet
|
||||
+ 4737 PreDestroy not called for non-async and run-as servlets
|
||||
+ 4739 @RunAs not honoured on servlets
|
||||
+ 4751 Refresh NetworkTraffic* classes
|
||||
|
||||
jetty-9.4.27.v20200227 - 27 February 2020
|
||||
+ 3247 Generate jetty-maven-plugin website
|
||||
+ 4247 Cookie security attributes are going to mandated by Google Chrome
|
||||
|
@ -398,13 +438,13 @@ jetty-9.4.25.v20191220 - 20 December 2019
|
|||
+ 4269 ResponseWriter should not throw RuntimeIOExceptions
|
||||
+ 4323 QOS Filter does not handle IllegalStateException and never releases
|
||||
passes
|
||||
+ 4329 rewrite prevents URL session tracking.
|
||||
+ 4329 rewrite prevents URL session tracking
|
||||
+ 4331 Improve handling of HttpOutput.close() for pending writes
|
||||
+ 4350 Deprecated MultiPartInputStreamParser still used in jetty-server
|
||||
(MultiPartsUtilParser) but OSGi ExportPackage suppressed
|
||||
+ 4351 Servlet.service called before Servlet.init is finished when servlet is
|
||||
lazily initialized
|
||||
+ 4363 jetty-maven-plugin no longer processes supplied context.xml-file.
|
||||
+ 4363 jetty-maven-plugin no longer processes supplied context.xml-file
|
||||
+ 4366 HTTP client uses SOCKS4 proxy hostname for SSL hostname verification
|
||||
+ 4374 Jetty client: Response.AsyncContentListener.onContent is not called
|
||||
+ 4376 Async Content Complete bug results in
|
||||
|
|
|
@ -19,11 +19,10 @@
|
|||
package org.eclipse.jetty.embedded;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
|
||||
public class ExampleServer
|
||||
|
@ -41,9 +40,7 @@ public class ExampleServer
|
|||
context.addServlet(HelloServlet.class, "/hello");
|
||||
context.addServlet(AsyncEchoServlet.class, "/echo/*");
|
||||
|
||||
HandlerCollection handlers = new HandlerCollection();
|
||||
handlers.setHandlers(new Handler[]{context, new DefaultHandler()});
|
||||
server.setHandler(handlers);
|
||||
server.setHandler(new HandlerList(context, new DefaultHandler()));
|
||||
|
||||
return server;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpOutput;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
@ -62,12 +61,9 @@ public class FastFileServer
|
|||
{
|
||||
Server server = new Server(port);
|
||||
|
||||
HandlerList handlers = new HandlerList();
|
||||
handlers.setHandlers(new Handler[]{
|
||||
server.setHandler(new HandlerList(
|
||||
new FastFileHandler(resourceBase),
|
||||
new DefaultHandler()
|
||||
});
|
||||
server.setHandler(handlers);
|
||||
new DefaultHandler()));
|
||||
return server;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.eclipse.jetty.embedded;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
|
@ -53,9 +52,7 @@ public class FileServer
|
|||
resourceHandler.setBaseResource(baseResource);
|
||||
|
||||
// Add the ResourceHandler to the server.
|
||||
HandlerList handlers = new HandlerList();
|
||||
handlers.setHandlers(new Handler[]{resourceHandler, new DefaultHandler()});
|
||||
server.setHandler(handlers);
|
||||
server.setHandler(new HandlerList(resourceHandler, new DefaultHandler()));
|
||||
|
||||
return server;
|
||||
}
|
||||
|
|
|
@ -51,10 +51,7 @@ public class JarServer
|
|||
context.setBaseResource(base);
|
||||
context.addServlet(new ServletHolder(new DefaultServlet()), "/");
|
||||
|
||||
HandlerList handlers = new HandlerList();
|
||||
handlers.addHandler(context);
|
||||
handlers.addHandler(new DefaultHandler());
|
||||
server.setHandler(handlers);
|
||||
server.setHandler(new HandlerList(context, new DefaultHandler()));
|
||||
return server;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ import org.eclipse.jetty.security.HashLoginService;
|
|||
import org.eclipse.jetty.server.AsyncRequestLogWriter;
|
||||
import org.eclipse.jetty.server.CustomRequestLog;
|
||||
import org.eclipse.jetty.server.DebugListener;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.LowResourceMonitor;
|
||||
|
@ -51,7 +50,7 @@ import org.eclipse.jetty.server.ServerConnector;
|
|||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
|
@ -105,10 +104,8 @@ public class LikeJettyXml
|
|||
// httpConfig.addCustomizer(new ForwardedRequestCustomizer());
|
||||
|
||||
// Handler Structure
|
||||
HandlerCollection handlers = new HandlerCollection();
|
||||
ContextHandlerCollection contexts = new ContextHandlerCollection();
|
||||
handlers.setHandlers(new Handler[]{contexts, new DefaultHandler()});
|
||||
server.setHandler(handlers);
|
||||
server.setHandler(new HandlerList(contexts, new DefaultHandler()));
|
||||
|
||||
// === jetty-jmx.xml ===
|
||||
MBeanContainer mbContainer = new MBeanContainer(
|
||||
|
|
|
@ -124,7 +124,7 @@ public class ManyHandlers
|
|||
CustomRequestLog ncsaLog = new CustomRequestLog(requestLogFile.getAbsolutePath());
|
||||
server.setRequestLog(ncsaLog);
|
||||
|
||||
// create the handler collections
|
||||
// create the handlers list
|
||||
HandlerList handlers = new HandlerList();
|
||||
|
||||
// wrap contexts around specific handlers
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.eclipse.jetty.server.Server;
|
|||
/**
|
||||
* The simplest possible Jetty server.
|
||||
*/
|
||||
// TODO: remove this class, only used in documentation.
|
||||
public class SimplestServer
|
||||
{
|
||||
public static Server createServer(int port)
|
||||
|
|
|
@ -50,11 +50,8 @@ public class WebSocketJsrServer
|
|||
{
|
||||
Server server = new Server(port);
|
||||
|
||||
HandlerList handlers = new HandlerList();
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
handlers.addHandler(context);
|
||||
|
||||
// Enable javax.websocket configuration for the context
|
||||
JavaxWebSocketServletContainerInitializer.configure(context,
|
||||
|
@ -65,8 +62,7 @@ public class WebSocketJsrServer
|
|||
}
|
||||
);
|
||||
|
||||
handlers.addHandler(new DefaultHandler());
|
||||
server.setHandler(handlers);
|
||||
server.setHandler(new HandlerList(context, new DefaultHandler()));
|
||||
|
||||
return server;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</New>
|
||||
|
||||
<Set name="handler">
|
||||
<New class="org.eclipse.jetty.server.handler.HandlerCollection">
|
||||
<New class="org.eclipse.jetty.server.handler.HandlerList">
|
||||
<Set name="handlers">
|
||||
<Array type="org.eclipse.jetty.server.Handler">
|
||||
<Item>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<Configure id="OtherServer" class="org.eclipse.jetty.server.Server">
|
||||
<Set name="handler">
|
||||
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
|
||||
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerList">
|
||||
<Set name="handlers">
|
||||
<Array type="org.eclipse.jetty.server.Handler">
|
||||
<Item>
|
||||
|
|
|
@ -44,9 +44,6 @@ public class ALPNClientConnection extends NegotiatingClientConnection
|
|||
|
||||
public void selected(String protocol)
|
||||
{
|
||||
if (protocol == null || !protocols.contains(protocol))
|
||||
close();
|
||||
else
|
||||
completed(protocol);
|
||||
completed(protocol);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,12 +110,4 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact
|
|||
}
|
||||
throw new IllegalStateException("No ALPNProcessor for " + engine);
|
||||
}
|
||||
|
||||
public static class ALPN extends Info
|
||||
{
|
||||
public ALPN(Executor executor, ClientConnectionFactory factory, List<String> protocols)
|
||||
{
|
||||
super(List.of("alpn"), new ALPNClientConnectionFactory(executor, factory, protocols));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,8 +75,11 @@ public class JDK9ClientALPNProcessor implements ALPNProcessor.Client
|
|||
{
|
||||
String protocol = alpnConnection.getSSLEngine().getApplicationProtocol();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("selected protocol {}", protocol);
|
||||
alpnConnection.selected(protocol);
|
||||
LOG.debug("selected protocol '{}'", protocol);
|
||||
if (protocol != null && !protocol.isEmpty())
|
||||
alpnConnection.selected(protocol);
|
||||
else
|
||||
alpnConnection.selected(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.eclipse.jetty.annotations;
|
|||
import javax.servlet.Servlet;
|
||||
|
||||
import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
|
||||
import org.eclipse.jetty.plus.annotation.RunAsCollection;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.webapp.Descriptor;
|
||||
import org.eclipse.jetty.webapp.MetaData;
|
||||
|
@ -61,14 +60,7 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand
|
|||
if (d == null)
|
||||
{
|
||||
metaData.setOrigin(holder.getName() + ".servlet.run-as", runAs, clazz);
|
||||
org.eclipse.jetty.plus.annotation.RunAs ra = new org.eclipse.jetty.plus.annotation.RunAs(clazz.getName(), role);
|
||||
RunAsCollection raCollection = (RunAsCollection)_context.getAttribute(RunAsCollection.RUNAS_COLLECTION);
|
||||
if (raCollection == null)
|
||||
{
|
||||
raCollection = new RunAsCollection();
|
||||
_context.setAttribute(RunAsCollection.RUNAS_COLLECTION, raCollection);
|
||||
}
|
||||
raCollection.add(ra);
|
||||
holder.setRunAsRole(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.annotations;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
import org.eclipse.jetty.webapp.WebDescriptor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TestRunAsAnnotation
|
||||
{
|
||||
@Test
|
||||
public void testRunAsAnnotation() throws Exception
|
||||
{
|
||||
WebAppContext wac = new WebAppContext();
|
||||
|
||||
//pre-add a servlet but not by descriptor
|
||||
ServletHolder holder = new ServletHolder();
|
||||
holder.setName("foo1");
|
||||
holder.setHeldClass(ServletC.class);
|
||||
holder.setInitOrder(1); //load on startup
|
||||
wac.getServletHandler().addServletWithMapping(holder, "/foo/*");
|
||||
|
||||
//add another servlet of the same class, but as if by descriptor
|
||||
ServletHolder holder2 = new ServletHolder();
|
||||
holder2.setName("foo2");
|
||||
holder2.setHeldClass(ServletC.class);
|
||||
holder2.setInitOrder(1);
|
||||
wac.getServletHandler().addServletWithMapping(holder2, "/foo2/*");
|
||||
Resource fakeXml = Resource.newResource(new File(MavenTestingUtils.getTargetTestingDir("run-as"), "fake.xml"));
|
||||
wac.getMetaData().setOrigin(holder2.getName() + ".servlet.run-as", new WebDescriptor(fakeXml));
|
||||
|
||||
AnnotationIntrospector parser = new AnnotationIntrospector(wac);
|
||||
RunAsAnnotationHandler handler = new RunAsAnnotationHandler(wac);
|
||||
parser.registerHandler(handler);
|
||||
parser.introspect(new ServletC(), null);
|
||||
|
||||
assertEquals("admin", holder.getRunAsRole());
|
||||
assertEquals(null, holder2.getRunAsRole());
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ import org.eclipse.jetty.ant.types.ContextHandlers;
|
|||
import org.eclipse.jetty.ant.utils.ServerProxy;
|
||||
import org.eclipse.jetty.ant.utils.TaskLog;
|
||||
import org.eclipse.jetty.security.LoginService;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.RequestLog;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
|
@ -39,6 +38,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
|
|||
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.util.Scanner;
|
||||
import org.eclipse.jetty.util.resource.PathResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
@ -406,23 +406,15 @@ public class ServerProxyImpl implements ServerProxy
|
|||
if (requestLog != null)
|
||||
server.setRequestLog(requestLog);
|
||||
|
||||
contexts = (ContextHandlerCollection)server
|
||||
.getChildHandlerByClass(ContextHandlerCollection.class);
|
||||
contexts = server.getChildHandlerByClass(ContextHandlerCollection.class);
|
||||
if (contexts == null)
|
||||
{
|
||||
contexts = new ContextHandlerCollection();
|
||||
HandlerCollection handlers = (HandlerCollection)server
|
||||
.getChildHandlerByClass(HandlerCollection.class);
|
||||
HandlerCollection handlers = server.getChildHandlerByClass(HandlerCollection.class);
|
||||
if (handlers == null)
|
||||
{
|
||||
handlers = new HandlerCollection();
|
||||
server.setHandler(handlers);
|
||||
handlers.setHandlers(new Handler[]{contexts, new DefaultHandler()});
|
||||
}
|
||||
server.setHandler(new HandlerList(contexts, new DefaultHandler()));
|
||||
else
|
||||
{
|
||||
handlers.addHandler(contexts);
|
||||
}
|
||||
}
|
||||
|
||||
//if there are any extra contexts to deploy
|
||||
|
|
|
@ -19,12 +19,14 @@
|
|||
package org.eclipse.jetty.client.dynamic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.alpn.client.ALPNClientConnection;
|
||||
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
|
||||
|
@ -37,6 +39,7 @@ import org.eclipse.jetty.client.MultiplexConnectionPool;
|
|||
import org.eclipse.jetty.client.MultiplexHttpDestination;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
|
@ -105,7 +108,7 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
factoryInfos = new Info[]{HttpClientConnectionFactory.HTTP11};
|
||||
this.factoryInfos = Arrays.asList(factoryInfos);
|
||||
this.protocols = Arrays.stream(factoryInfos)
|
||||
.flatMap(info -> info.getProtocols().stream())
|
||||
.flatMap(info -> Stream.concat(info.getProtocols(false).stream(), info.getProtocols(true).stream()))
|
||||
.distinct()
|
||||
.map(p -> p.toLowerCase(Locale.ENGLISH))
|
||||
.collect(Collectors.toList());
|
||||
|
@ -117,9 +120,9 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
@Override
|
||||
public Origin newOrigin(HttpRequest request)
|
||||
{
|
||||
boolean ssl = HttpClient.isSchemeSecure(request.getScheme());
|
||||
boolean secure = HttpClient.isSchemeSecure(request.getScheme());
|
||||
String http1 = "http/1.1";
|
||||
String http2 = ssl ? "h2" : "h2c";
|
||||
String http2 = secure ? "h2" : "h2c";
|
||||
List<String> protocols = List.of();
|
||||
if (request.isVersionExplicit())
|
||||
{
|
||||
|
@ -130,16 +133,23 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
}
|
||||
else
|
||||
{
|
||||
if (ssl)
|
||||
if (secure)
|
||||
{
|
||||
// There may be protocol negotiation, so preserve the order
|
||||
// of protocols chosen by the application.
|
||||
// We need to keep multiple protocols in case the protocol
|
||||
// is negotiated: e.g. [http/1.1, h2] negotiates [h2], but
|
||||
// here we don't know yet what will be negotiated.
|
||||
List<String> http = List.of("http/1.1", "h2c", "h2");
|
||||
protocols = this.protocols.stream()
|
||||
.filter(p -> p.equals(http1) || p.equals(http2))
|
||||
.collect(Collectors.toList());
|
||||
.filter(http::contains)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
// The http/1.1 upgrade to http/2 over TLS implicitly
|
||||
// "negotiates" [h2c], so we need to remove [h2]
|
||||
// because we don't want to negotiate using ALPN.
|
||||
if (request.getHeaders().contains(HttpHeader.UPGRADE, "h2c"))
|
||||
protocols.remove("h2");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -149,7 +159,7 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
}
|
||||
Origin.Protocol protocol = null;
|
||||
if (!protocols.isEmpty())
|
||||
protocol = new Origin.Protocol(protocols, ssl && protocols.contains(http2));
|
||||
protocol = new Origin.Protocol(protocols, secure && protocols.contains(http2));
|
||||
return getHttpClient().createOrigin(request, protocol);
|
||||
}
|
||||
|
||||
|
@ -164,32 +174,33 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Origin.Protocol protocol = destination.getOrigin().getProtocol();
|
||||
ClientConnectionFactory.Info factoryInfo;
|
||||
ClientConnectionFactory factory;
|
||||
if (protocol == null)
|
||||
{
|
||||
// Use the default ClientConnectionFactory.
|
||||
factoryInfo = factoryInfos.get(0);
|
||||
factory = factoryInfos.get(0).getClientConnectionFactory();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (destination.isSecure() && protocol.isNegotiate())
|
||||
{
|
||||
factoryInfo = new ALPNClientConnectionFactory.ALPN(getClientConnector().getExecutor(), this::newNegotiatedConnection, protocol.getProtocols());
|
||||
factory = new ALPNClientConnectionFactory(getClientConnector().getExecutor(), this::newNegotiatedConnection, protocol.getProtocols());
|
||||
}
|
||||
else
|
||||
{
|
||||
factoryInfo = findClientConnectionFactoryInfo(protocol.getProtocols())
|
||||
.orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for " + protocol));
|
||||
factory = findClientConnectionFactoryInfo(protocol.getProtocols(), destination.isSecure())
|
||||
.orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for " + protocol))
|
||||
.getClientConnectionFactory();
|
||||
}
|
||||
}
|
||||
return factoryInfo.getClientConnectionFactory().newConnection(endPoint, context);
|
||||
return factory.newConnection(endPoint, context);
|
||||
}
|
||||
|
||||
public void upgrade(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Origin.Protocol protocol = destination.getOrigin().getProtocol();
|
||||
Info info = findClientConnectionFactoryInfo(protocol.getProtocols())
|
||||
Info info = findClientConnectionFactoryInfo(protocol.getProtocols(), destination.isSecure())
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " to upgrade to " + protocol));
|
||||
info.upgrade(endPoint, context);
|
||||
}
|
||||
|
@ -200,13 +211,22 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
{
|
||||
ALPNClientConnection alpnConnection = (ALPNClientConnection)endPoint.getConnection();
|
||||
String protocol = alpnConnection.getProtocol();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("ALPN negotiated {} among {}", protocol, alpnConnection.getProtocols());
|
||||
if (protocol == null)
|
||||
throw new IOException("Could not negotiate protocol among " + alpnConnection.getProtocols());
|
||||
List<String> protocols = List.of(protocol);
|
||||
Info factoryInfo = findClientConnectionFactoryInfo(protocols)
|
||||
Info factoryInfo;
|
||||
if (protocol != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("ALPN negotiated {} among {}", protocol, alpnConnection.getProtocols());
|
||||
List<String> protocols = List.of(protocol);
|
||||
factoryInfo = findClientConnectionFactoryInfo(protocols, true)
|
||||
.orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for negotiated protocol " + protocol));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Server does not support ALPN, let's try the first protocol.
|
||||
factoryInfo = factoryInfos.get(0);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("No ALPN protocol, using {}", factoryInfo);
|
||||
}
|
||||
return factoryInfo.getClientConnectionFactory().newConnection(endPoint, context);
|
||||
}
|
||||
catch (Throwable failure)
|
||||
|
@ -216,10 +236,10 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
}
|
||||
}
|
||||
|
||||
private Optional<Info> findClientConnectionFactoryInfo(List<String> protocols)
|
||||
private Optional<Info> findClientConnectionFactoryInfo(List<String> protocols, boolean secure)
|
||||
{
|
||||
return factoryInfos.stream()
|
||||
.filter(info -> info.matches(protocols))
|
||||
.filter(info -> info.matches(protocols, secure))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,16 @@ package org.eclipse.jetty.client.http;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
|
||||
public class HttpClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
public static final Info HTTP11 = new Info(List.of("http/1.1"), new HttpClientConnectionFactory());
|
||||
/**
|
||||
* <p>Representation of the {@code HTTP/1.1} application protocol used by {@link HttpClientTransportDynamic}.</p>
|
||||
*/
|
||||
public static final Info HTTP11 = new HTTP11(new HttpClientConnectionFactory());
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
|
@ -34,4 +38,26 @@ public class HttpClientConnectionFactory implements ClientConnectionFactory
|
|||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, context);
|
||||
return customize(connection, context);
|
||||
}
|
||||
|
||||
private static class HTTP11 extends Info
|
||||
{
|
||||
private static final List<String> protocols = List.of("http/1.1");
|
||||
|
||||
private HTTP11(ClientConnectionFactory factory)
|
||||
{
|
||||
super(factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getProtocols(boolean secure)
|
||||
{
|
||||
return protocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x%s", getClass().getSimpleName(), hashCode(), protocols);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,525 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.client.util.FormRequestContent;
|
||||
import org.eclipse.jetty.client.util.StringRequestContent;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ManagedSelector;
|
||||
import org.eclipse.jetty.io.NetworkTrafficListener;
|
||||
import org.eclipse.jetty.io.NetworkTrafficSocketChannelEndPoint;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.NetworkTrafficServerConnector;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class NetworkTrafficListenerTest
|
||||
{
|
||||
private static final String END_OF_CONTENT = "~";
|
||||
|
||||
private Server server;
|
||||
private NetworkTrafficServerConnector connector;
|
||||
private NetworkTrafficHttpClient client;
|
||||
|
||||
private void start(Handler handler) throws Exception
|
||||
{
|
||||
startServer(handler);
|
||||
startClient();
|
||||
}
|
||||
|
||||
private void startServer(Handler handler) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new NetworkTrafficServerConnector(server);
|
||||
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
|
||||
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
|
||||
server.addConnector(connector);
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
}
|
||||
|
||||
private void startClient() throws Exception
|
||||
{
|
||||
client = new NetworkTrafficHttpClient(new AtomicReference<>());
|
||||
client.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
if (client != null)
|
||||
client.stop();
|
||||
if (server != null)
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenedClosedAreInvoked() throws Exception
|
||||
{
|
||||
startServer(null);
|
||||
|
||||
CountDownLatch openedLatch = new CountDownLatch(1);
|
||||
CountDownLatch closedLatch = new CountDownLatch(1);
|
||||
connector.setNetworkTrafficListener(new NetworkTrafficListener()
|
||||
{
|
||||
public volatile Socket socket;
|
||||
|
||||
@Override
|
||||
public void opened(Socket socket)
|
||||
{
|
||||
this.socket = socket;
|
||||
openedLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closed(Socket socket)
|
||||
{
|
||||
if (this.socket == socket)
|
||||
closedLatch.countDown();
|
||||
}
|
||||
});
|
||||
int port = connector.getLocalPort();
|
||||
|
||||
// Connect to the server
|
||||
try (Socket ignored = new Socket("localhost", port))
|
||||
{
|
||||
assertTrue(openedLatch.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
assertTrue(closedLatch.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrafficWithNoResponseContentOnNonPersistentConnection() throws Exception
|
||||
{
|
||||
start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse)
|
||||
{
|
||||
request.setHandled(true);
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> serverIncoming = new AtomicReference<>("");
|
||||
CountDownLatch serverIncomingLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> serverOutgoing = new AtomicReference<>("");
|
||||
CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
|
||||
connector.setNetworkTrafficListener(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
serverIncomingLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
serverOutgoingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> clientIncoming = new AtomicReference<>("");
|
||||
CountDownLatch clientIncomingLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> clientOutgoing = new AtomicReference<>("");
|
||||
CountDownLatch clientOutgoingLatch = new CountDownLatch(1);
|
||||
client.listener.set(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
clientOutgoingLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
clientIncomingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
|
||||
assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertEquals(clientOutgoing.get(), serverIncoming.get());
|
||||
assertEquals(serverOutgoing.get(), clientIncoming.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrafficWithResponseContentOnPersistentConnection() throws Exception
|
||||
{
|
||||
String responseContent = "response_content" + END_OF_CONTENT;
|
||||
start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException
|
||||
{
|
||||
request.setHandled(true);
|
||||
ServletOutputStream output = servletResponse.getOutputStream();
|
||||
output.write(responseContent.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> serverIncoming = new AtomicReference<>("");
|
||||
CountDownLatch serverIncomingLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> serverOutgoing = new AtomicReference<>("");
|
||||
CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
|
||||
connector.setNetworkTrafficListener(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
serverIncomingLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
serverOutgoingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> clientIncoming = new AtomicReference<>("");
|
||||
CountDownLatch clientIncomingLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> clientOutgoing = new AtomicReference<>("");
|
||||
CountDownLatch clientOutgoingLatch = new CountDownLatch(1);
|
||||
client.listener.set(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
clientOutgoingLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
clientIncomingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort()).send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertEquals(responseContent, response.getContentAsString());
|
||||
|
||||
assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertEquals(clientOutgoing.get(), serverIncoming.get());
|
||||
assertEquals(serverOutgoing.get(), clientIncoming.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrafficWithResponseContentChunkedOnPersistentConnection() throws Exception
|
||||
{
|
||||
String responseContent = "response_content";
|
||||
String responseChunk1 = responseContent.substring(0, responseContent.length() / 2);
|
||||
String responseChunk2 = responseContent.substring(responseContent.length() / 2);
|
||||
start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException
|
||||
{
|
||||
request.setHandled(true);
|
||||
ServletOutputStream output = servletResponse.getOutputStream();
|
||||
output.write(responseChunk1.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
output.write(responseChunk2.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> serverIncoming = new AtomicReference<>("");
|
||||
CountDownLatch serverIncomingLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> serverOutgoing = new AtomicReference<>("");
|
||||
CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
|
||||
connector.setNetworkTrafficListener(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
serverIncomingLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
if (serverOutgoing.get().endsWith("\r\n0\r\n\r\n"))
|
||||
serverOutgoingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> clientIncoming = new AtomicReference<>("");
|
||||
CountDownLatch clientIncomingLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> clientOutgoing = new AtomicReference<>("");
|
||||
CountDownLatch clientOutgoingLatch = new CountDownLatch(1);
|
||||
client.listener.set(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
clientOutgoingLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
if (clientIncoming.get().endsWith("\r\n0\r\n\r\n"))
|
||||
clientIncomingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort()).send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
|
||||
assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertEquals(clientOutgoing.get(), serverIncoming.get());
|
||||
assertEquals(serverOutgoing.get(), clientIncoming.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrafficWithRequestContentWithResponseRedirectOnPersistentConnection() throws Exception
|
||||
{
|
||||
String location = "/redirect";
|
||||
start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException
|
||||
{
|
||||
request.setHandled(true);
|
||||
servletResponse.sendRedirect(location);
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> serverIncoming = new AtomicReference<>("");
|
||||
CountDownLatch serverIncomingLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> serverOutgoing = new AtomicReference<>("");
|
||||
CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
|
||||
connector.setNetworkTrafficListener(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
serverIncomingLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
serverOutgoingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> clientIncoming = new AtomicReference<>("");
|
||||
CountDownLatch clientIncomingLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> clientOutgoing = new AtomicReference<>("");
|
||||
CountDownLatch clientOutgoingLatch = new CountDownLatch(1);
|
||||
client.listener.set(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
clientOutgoingLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
clientIncomingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
client.setFollowRedirects(false);
|
||||
Fields fields = new Fields();
|
||||
fields.put("a", "1");
|
||||
fields.put("b", "2");
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||
.body(new FormRequestContent(fields))
|
||||
.send();
|
||||
assertEquals(HttpStatus.FOUND_302, response.getStatus());
|
||||
|
||||
assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertEquals(clientOutgoing.get(), serverIncoming.get());
|
||||
assertEquals(serverOutgoing.get(), clientIncoming.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrafficWithBigRequestContentOnPersistentConnection() throws Exception
|
||||
{
|
||||
start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException
|
||||
{
|
||||
// Read and discard the request body to make the test more
|
||||
// reliable, otherwise there is a race between request body
|
||||
// upload and response download
|
||||
InputStream input = servletRequest.getInputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
while (true)
|
||||
{
|
||||
int read = input.read(buffer);
|
||||
if (read < 0)
|
||||
break;
|
||||
}
|
||||
request.setHandled(true);
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> serverIncoming = new AtomicReference<>("");
|
||||
AtomicReference<String> serverOutgoing = new AtomicReference<>("");
|
||||
CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
|
||||
connector.setNetworkTrafficListener(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
serverOutgoingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
AtomicReference<String> clientIncoming = new AtomicReference<>("");
|
||||
CountDownLatch clientIncomingLatch = new CountDownLatch(1);
|
||||
AtomicReference<String> clientOutgoing = new AtomicReference<>("");
|
||||
client.listener.set(new NetworkTrafficListener()
|
||||
{
|
||||
@Override
|
||||
public void outgoing(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incoming(Socket socket, ByteBuffer bytes)
|
||||
{
|
||||
clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
|
||||
clientIncomingLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
// Generate a large request content.
|
||||
String requestContent = "0123456789ABCDEF";
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
requestContent += requestContent;
|
||||
}
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||
.body(new StringRequestContent(requestContent))
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
|
||||
assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
|
||||
assertEquals(clientOutgoing.get(), serverIncoming.get());
|
||||
assertTrue(clientOutgoing.get().length() > requestContent.length());
|
||||
assertEquals(serverOutgoing.get(), clientIncoming.get());
|
||||
}
|
||||
|
||||
private static class NetworkTrafficHttpClient extends HttpClient
|
||||
{
|
||||
private final AtomicReference<NetworkTrafficListener> listener;
|
||||
|
||||
private NetworkTrafficHttpClient(AtomicReference<NetworkTrafficListener> listener)
|
||||
{
|
||||
super(new HttpClientTransportOverHTTP(new ClientConnector()
|
||||
{
|
||||
@Override
|
||||
protected SelectorManager newSelectorManager()
|
||||
{
|
||||
return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors())
|
||||
{
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
|
||||
{
|
||||
return new NetworkTrafficSocketChannelEndPoint(channel, selector, selectionKey, getScheduler(), getIdleTimeout().toMillis(), listener.get());
|
||||
}
|
||||
};
|
||||
}
|
||||
}));
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
|
@ -69,7 +69,7 @@ public class BadAppDeployTest
|
|||
It is important that this Order be maintained for an accurate test case.
|
||||
### BEAN: QueuedThreadPool[qtp1327763628]@4f2410ac{STOPPED,8<=0<=200,i=0,r=-1,q=0}[NO_TRY]
|
||||
### BEAN: ServerConnector@16f65612{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
|
||||
### BEAN: HandlerCollection@5f150435{STOPPED}
|
||||
### BEAN: HandlerList@5f150435{STOPPED}
|
||||
### BEAN: DeploymentManager@1c53fd30{STOPPED}
|
||||
*/
|
||||
|
||||
|
@ -79,10 +79,8 @@ public class BadAppDeployTest
|
|||
server.addConnector(connector);
|
||||
|
||||
ContextHandlerCollection contexts = new ContextHandlerCollection();
|
||||
HandlerCollection handlers = new HandlerCollection();
|
||||
handlers.addHandler(contexts);
|
||||
handlers.addHandler(new DefaultHandler());
|
||||
server.setHandler(handlers); // this should be done before addBean(deploymentManager)
|
||||
// this should be done before addBean(deploymentManager)
|
||||
server.setHandler(new HandlerList(contexts, new DefaultHandler()));
|
||||
|
||||
DeploymentManager deploymentManager = new DeploymentManager();
|
||||
deploymentManager.setContexts(contexts);
|
||||
|
@ -121,7 +119,7 @@ public class BadAppDeployTest
|
|||
### BEAN: QueuedThreadPool[qtp1530388690]@5b37e0d2{STOPPED,8<=0<=200,i=0,r=-1,q=0}[NO_TRY]
|
||||
### BEAN: ServerConnector@5e265ba4{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
|
||||
### BEAN: DeploymentManager@3419866c{STOPPED}
|
||||
### BEAN: HandlerCollection@63e31ee{STOPPED}
|
||||
### BEAN: HandlerList@63e31ee{STOPPED}
|
||||
*/
|
||||
|
||||
server = new Server();
|
||||
|
@ -146,12 +144,11 @@ public class BadAppDeployTest
|
|||
webAppProvider.setMonitoredDirName(webappsDir.toString());
|
||||
webAppProvider.setScanInterval(1);
|
||||
|
||||
server.addBean(deploymentManager); // this should be done before setHandler(handlers)
|
||||
// this must be done before setHandler(handlers)
|
||||
server.addBean(deploymentManager);
|
||||
|
||||
HandlerCollection handlers = new HandlerCollection();
|
||||
handlers.addHandler(contexts);
|
||||
handlers.addHandler(new DefaultHandler());
|
||||
server.setHandler(handlers); // this should be done after addBean(deploymentManager)
|
||||
// this must be done after addBean(deploymentManager)
|
||||
server.setHandler(new HandlerList(contexts, new DefaultHandler()));
|
||||
|
||||
assertTimeoutPreemptively(ofSeconds(10), () ->
|
||||
{
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
<!-- RequestLogHandler after the default handler -->
|
||||
<!-- =========================================================== -->
|
||||
<Set name="handler">
|
||||
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
|
||||
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerList">
|
||||
<Set name="handlers">
|
||||
<Array type="org.eclipse.jetty.server.Handler">
|
||||
<Item>
|
||||
|
|
|
@ -1,24 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~
|
||||
~ ========================================================================
|
||||
~ Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
~
|
||||
~ This program and the accompanying materials are made available under
|
||||
~ the terms of the Eclipse Public License 2.0 which is available at
|
||||
~ https://www.eclipse.org/legal/epl-2.0
|
||||
~
|
||||
~ This Source Code may also be made available under the following
|
||||
~ Secondary Licenses when the conditions for such availability set
|
||||
~ forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
~ the Apache License v2.0 which is available at
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
~ ========================================================================
|
||||
~
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
|
@ -41,7 +21,7 @@
|
|||
<asciidoctorj.epub.version>1.5.0-alpha.8.1</asciidoctorj.epub.version>
|
||||
<asciidoctorj.version>1.5.8.1</asciidoctorj.version>
|
||||
<jruby.version>1.7.27</jruby.version>
|
||||
<web.resources.version>1.1</web.resources.version>
|
||||
<web.resources.version>1.2</web.resources.version>
|
||||
<web.resources.directory>${project.build.directory}/web-resources</web.resources.directory>
|
||||
<!-- old properties -->
|
||||
<html.directory>${project.build.directory}/current</html.directory>
|
||||
|
@ -49,16 +29,66 @@
|
|||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-rewrite</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||
<artifactId>fcgi-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-http-client-transport</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -23,8 +23,11 @@
|
|||
:revdate: {TIMESTAMP}
|
||||
:toc: left
|
||||
:toc-title: Contribution Guide
|
||||
:toc-image: ../../common/images/jetty-logo.svg
|
||||
:toc-image-url: /jetty/index.html
|
||||
:toc-style:
|
||||
|
||||
:header-style: eclipse-thin
|
||||
:breadcrumb-style: eclipse-thin
|
||||
:footer-style: default
|
||||
:breadcrumb: Home:../index.html | Contribution Guide:./index.html
|
||||
|
||||
// docinfo lets you pull in shared content and/or influence via render type
|
||||
|
@ -43,12 +46,6 @@ endif::[]
|
|||
// options for special blocks, code snippets, screen, etc
|
||||
:sub-order: attributes+
|
||||
|
||||
// suppress document footer generation
|
||||
//:nofooter:
|
||||
|
||||
// suppress Eclipse footer
|
||||
:no-eclipse-footer:
|
||||
|
||||
// uncomment to allow include::https:// style content inclusion
|
||||
//:allow-uri-read: true
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ If `true`, welcome files are redirected rather that forwarded.
|
|||
welcomeServlets::
|
||||
If `true`, attempt to dispatch to welcome files that are servlets, but only after no matching static
|
||||
resources could be found. If `false`, then a welcome file must exist on disk. If `exact`, then exact
|
||||
servlet matches are supported without an existing file. Default is `true`. This must be `false` if you want directory listings,
|
||||
servlet matches are supported without an existing file. Default is `false`. This must be `false` if you want directory listings,
|
||||
but have index.jsp in your welcome file list.
|
||||
precompressed::
|
||||
If set to a comma separated list of encoding types (that may be listed in a requests Accept-Encoding header) to file extension mappings to look for and serve.
|
||||
|
|
|
@ -50,5 +50,5 @@ The Jetty HTTP/2 implementation consists of the following sub-projects (each pro
|
|||
2. `http2-hpack`: Contains the HTTP/2 HPACK implementation for HTTP header compression.
|
||||
3. `http2-server`: Provides the server-side implementation of HTTP/2.
|
||||
4. `http2-client`: Provides the implementation of HTTP/2 client with a low level HTTP/2 API, dealing with HTTP/2 streams, frames, etc.
|
||||
5. `http2-http-client-transport`: Provides the implementation of the HTTP/2 transport for `HttpClient` (see xref:http-client[]).
|
||||
5. `http2-http-client-transport`: Provides the implementation of the HTTP/2 transport for `HttpClient` (see xref:client-http[this section]).
|
||||
Applications can use the higher level API provided by `HttpClient` to send HTTP requests and receive HTTP responses, and the HTTP/2 transport will take care of converting them in HTTP/2 format (see also https://webtide.com/http2-support-for-httpclient/[this blog entry]).
|
||||
|
|
|
@ -23,8 +23,11 @@
|
|||
:revdate: {TIMESTAMP}
|
||||
:toc: left
|
||||
:toc-title: Distribution Guide
|
||||
:toc-image: ../../common/images/jetty-logo.svg
|
||||
:toc-image-url: /jetty/index.html
|
||||
:toc-style:
|
||||
|
||||
:header-style: eclipse-thin
|
||||
:breadcrumb-style: eclipse-thin
|
||||
:footer-style: default
|
||||
:breadcrumb: Home:../index.html | Distribution Guide:./index.html
|
||||
|
||||
// docinfo lets you pull in shared content and/or influence via render type
|
||||
|
@ -43,12 +46,6 @@ endif::[]
|
|||
// options for special blocks, code snippets, screen, etc
|
||||
:sub-order: attributes+
|
||||
|
||||
// suppress document footer generation
|
||||
//:nofooter:
|
||||
|
||||
// suppress Eclipse footer
|
||||
:no-eclipse-footer:
|
||||
|
||||
// uncomment to allow include::https:// style content inclusion
|
||||
//:allow-uri-read: true
|
||||
|
||||
|
|
|
@ -34,74 +34,105 @@ This configuration is essentially the multiple logger configuration with added c
|
|||
|
||||
The technique used by this configuration is to provide an link:{JDURL}org/eclipse/jetty/deploy/AppLifeCycle.Binding.html[AppLifeCycle.Binding] against the link:{JDURL}/org/eclipse/jetty/deploy/AppLifeCycle.html[`"deploying"`node] that modifies the
|
||||
link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html#getSystemClasspathPattern()[WebAppContext.getSystemClasspathPattern().add(String)] for the common logging classes.
|
||||
See https://github.com/jetty-project/jetty-webapp-logging/blob/master/src/main/java/org/eclipse/jetty/webapp/logging/CentralizedWebAppLoggingBinding.java[org.eclipse.jetty.logging.CentralizedWebAppLoggingBinding] for actual implementation.
|
||||
See https://github.com/jetty-project/jetty-webapp-logging/blob/master/jetty-webapp-logging/src/main/java/org/eclipse/jetty/webapp/logging/CentralizedWebAppLoggingBinding.java[org.eclipse.jetty.logging.CentralizedWebAppLoggingBinding] for actual implementation.
|
||||
|
||||
A convenient replacement `logging` module has been created to bootstrap your `${jetty.base}` directory for capturing all Jetty server logging from multiple logging frameworks into a single logging output file managed by Logback.
|
||||
|
||||
[source, screen, subs="{sub-order}"]
|
||||
[source,screen,subs="{sub-order}"]
|
||||
....
|
||||
[mybase]$ mkdir modules
|
||||
[mybase]$ cd modules
|
||||
|
||||
[modules]$ curl -O https://raw.githubusercontent.com/jetty-project/logging-modules/master/capture-all/logging.mod
|
||||
[mybase]$ curl -O https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-webapp-logging/9.4.27/jetty-webapp-logging-9.4.27-config.jar
|
||||
% Total % Received % Xferd Average Speed Time Time Time Current
|
||||
Dload Upload Total Spent Left Speed
|
||||
100 1416 100 1416 0 0 4241 0 --:--:-- --:--:-- --:--:-- 4252
|
||||
100 3402 100 3402 0 0 15823 0 --:--:-- --:--:-- --:--:-- 15750
|
||||
|
||||
[master]$ curl -O https://raw.githubusercontent.com/jetty-project/logging-modules/master/centralized/webapp-logging.mod
|
||||
% Total % Received % Xferd Average Speed Time Time Time Current
|
||||
Dload Upload Total Spent Left Speed
|
||||
100 660 100 660 0 0 2032 0 --:--:-- --:--:-- --:--:-- 2037
|
||||
[modules]$ cd ..
|
||||
[mybase]$ jar -xf jetty-webapp-logging-9.4.27-config.jar
|
||||
|
||||
[mybase]$ java -jar /opt/jetty-dist/start.jar --add-to-start=logging,webapp-logging
|
||||
INFO: logging initialised in ${jetty.base}/start.ini (appended)
|
||||
MKDIR: ${jetty.base}/logs
|
||||
DOWNLOAD: https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.6/slf4j-api-1.6.6.jar to lib/logging/slf4j-api-1.6.6.jar
|
||||
DOWNLOAD: https://repo1.maven.org/maven2/org/slf4j/log4j-over-slf4j/1.6.6/log4j-over-slf4j-1.6.6.jar to lib/logging/log4j-over-slf4j-1.6.6.jar
|
||||
DOWNLOAD: https://repo1.maven.org/maven2/org/slf4j/jul-to-slf4j/1.6.6/jul-to-slf4j-1.6.6.jar to lib/logging/jul-to-slf4j-1.6.6.jar
|
||||
DOWNLOAD: https://repo1.maven.org/maven2/org/slf4j/jcl-over-slf4j/1.6.6/jcl-over-slf4j-1.6.6.jar to lib/logging/jcl-over-slf4j-1.6.6.jar
|
||||
DOWNLOAD: https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.0.7/logback-core-1.0.7.jar to lib/logging/logback-core-1.0.7.jar
|
||||
DOWNLOAD: https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.0.7/logback-classic-1.0.7.jar to lib/logging/logback-classic-1.0.7.jar
|
||||
DOWNLOAD: https://raw.githubusercontent.com/jetty-project/logging-modules/master/capture-all/logback.xml to resources/logback.xml
|
||||
DOWNLOAD: https://raw.githubusercontent.com/jetty-project/logging-modules/master/capture-all/jetty-logging.properties to resources/jetty-logging.properties
|
||||
DOWNLOAD: https://raw.githubusercontent.com/jetty-project/logging-modules/master/capture-all/jetty-logging.xml to etc/jetty-logging.xml
|
||||
INFO: resources initialised transitively
|
||||
INFO: resources enabled in ${jetty.base}/start.ini
|
||||
INFO: webapp-logging initialised in ${jetty.base}/start.ini (appended)
|
||||
DOWNLOAD: https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-webapp-logging/9.0.0/jetty-webapp-logging-9.0.0.jar to lib/webapp-logging/jetty-webapp-logging-9.0.0.jar
|
||||
DOWNLOAD: https://raw.githubusercontent.com/jetty-project/jetty-webapp-logging/master/src/main/config/etc/jetty-webapp-logging.xml to etc/jetty-webapp-logging.xml
|
||||
DOWNLOAD: https://raw.githubusercontent.com/jetty-project/jetty-webapp-logging/master/src/main/config/etc/jetty-mdc-handler.xml to etc/jetty-mdc-handler.xml
|
||||
INFO: deploy initialised transitively
|
||||
INFO: deploy enabled in ${jetty.base}/start.ini
|
||||
INFO: logging initialised transitively
|
||||
INFO: resources initialised transitively
|
||||
INFO: resources enabled in ${jetty.base}/start.ini
|
||||
[mybase]$ java -jar /opt/jetty-hom/start.jar --create-startd --add-to-start=centralized-webapp-logging
|
||||
|
||||
[mybase]$ java -jar /opt/jetty-dist/start.jar
|
||||
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: logback-impl
|
||||
+ Logback: the reliable, generic, fast and flexible logging framework.
|
||||
+ Copyright (C) 1999-2012, QOS.ch. All rights reserved.
|
||||
+ This program and the accompanying materials are dual-licensed under
|
||||
+ either:
|
||||
+ the terms of the Eclipse Public License v1.0
|
||||
+ as published by the Eclipse Foundation:
|
||||
+ http://www.eclipse.org/legal/epl-v10.html
|
||||
+ or (per the licensee's choosing) under
|
||||
+ the terms of the GNU Lesser General Public License version 2.1
|
||||
+ as published by the Free Software Foundation:
|
||||
+ http://www.gnu.org/licenses/old-licenses/lgpl-2.1.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 : log4j-over-slf4j transitively enabled
|
||||
INFO : jcl-slf4j transitively enabled
|
||||
INFO : logback-impl transitively enabled
|
||||
INFO : jul-slf4j transitively enabled
|
||||
INFO : slf4j-logback transitively enabled
|
||||
INFO : centralized-webapp-logging initialized in ${jetty.base}/start.d/centralized-webapp-logging.ini
|
||||
INFO : logging-logback transitively enabled
|
||||
INFO : resources transitively enabled
|
||||
MKDIR : ${jetty.base}/lib/slf4j
|
||||
DOWNLD: https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar to ${jetty.base}/lib/slf4j/slf4j-api-1.7.25.jar
|
||||
MKDIR : ${jetty.base}/lib/logging
|
||||
DOWNLD: https://repo1.maven.org/maven2/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.jar to ${jetty.base}/lib/logging/log4j-over-slf4j-1.7.25.jar
|
||||
DOWNLD: https://repo1.maven.org/maven2/org/slf4j/jcl-over-slf4j/1.7.25/jcl-over-slf4j-1.7.25.jar to ${jetty.base}/lib/slf4j/jcl-over-slf4j-1.7.25.jar
|
||||
MKDIR : ${jetty.base}/lib/logback
|
||||
DOWNLD: https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar to ${jetty.base}/lib/logback/logback-core-1.2.3.jar
|
||||
DOWNLD: https://repo1.maven.org/maven2/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar to ${jetty.base}/lib/slf4j/jul-to-slf4j-1.7.25.jar
|
||||
COPY : ${jetty.home}/modules/jul-slf4j/etc/java-util-logging.properties to ${jetty.base}/etc/java-util-logging.properties
|
||||
DOWNLD: https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar to ${jetty.base}/lib/logback/logback-classic-1.2.3.jar
|
||||
MKDIR : ${jetty.base}/logs
|
||||
DOWNLD: https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-webapp-logging/9.4.27/jetty-webapp-logging-9.4.27.jar to ${jetty.base}/lib/logging/jetty-webapp-logging-9.4.27.jar
|
||||
INFO : Base directory was modified
|
||||
|
||||
$
|
||||
....
|
||||
|
||||
The replacement `logging.mod` performs a number of tasks.
|
||||
This replacement `centralized-webapp-logging.mod` performs a number of tasks.
|
||||
|
||||
. `mybase` is a `${jetty.base}` directory.
|
||||
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/` and becomes the `${jetty.home}` directory for this demonstration.
|
||||
. The `curl` command downloads the replacement `logging.mod` and puts it into the `${jetty.base}/modules/` directory for use by mybase only.
|
||||
. The `start.jar --add-to-start=logging,webapp-logging` command performs a number of steps to make the logging module available to the `${jetty.base}` configuration.
|
||||
.. Several entries are added to the `${jetty.base}/start.ini` configuration.
|
||||
* `--module=logging` is added to enable the logging module.
|
||||
* `--module=webapp-logging` is added to enable the webapp-logging module.
|
||||
.. Required `${jetty.base}` directories are created: `${jetty.base}/logs` and `${jetty.base}/resources`.
|
||||
.. Required logging libraries are downloaded (if not present already) to the `${jetty.base}/lib/logging/` directory:
|
||||
. `mybase` is a `${jetty.base}` directory.
|
||||
. The jetty-distribution is unpacked (and untouched) into `/opt/jetty-dist/` and becomes the `${jetty.home}` directory for this demonstration.
|
||||
. The `curl` command downloads the replacement config overlay for the `${jetty.base}/modules/` directory to use.
|
||||
. The `start.jar --add-to-start=centralized-webapp-logging` command performs a number of steps to make the centralized-webapp-logging module available to the `${jetty.base}` configuration.
|
||||
.. A new `${jetty.base}/start.d/centralized-webapp-logging.ini` configuration was created.
|
||||
.. Required `${jetty.base}` directories are created: `${jetty.base}/logs` and `${jetty.base}/resources`.
|
||||
.. Required logging libraries are downloaded (if not present already) to the `${jetty.base}/lib/logging/` directory:
|
||||
* `slf4j-api.jar` - API jar for Slf4j (used by most of the rest of the jars)
|
||||
* `log4j-over-slf4j.jar` - Slf4j jar that captures all log4j emitted logging events
|
||||
* `jul-to-slf4j.jar` - Slf4j jar that captures all java.util.logging events
|
||||
* `jcl-over-slf4j.jar` - Slf4j jar that captures all commons-logging events
|
||||
* `logback-classic.jar` - the Slf4j adapter jar that routes all of the captured logging events to logback itself.
|
||||
* `logback-core.jar` - the logback implementation jar, that handles all of the filtering and output of the logging events.
|
||||
.. Required webapp-logging library is downloaded (if not present already) to the `${jetty.base}/lib/webapp-logging/` directory:
|
||||
.. Required webapp-logging library is downloaded (if not present already) to the `${jetty.base}/lib/webapp-logging/` directory:
|
||||
* `jetty-webapp-logging.jar` - the Jetty side deployment manger app-lifecycle bindings for modifying the `WebAppClassloaders` of deployed webapps.
|
||||
.. Required configuration files are downloaded (if not present already) to the `${jetty.base}/resources/` directory: `jetty-logging.properties`, and `logback.xml`.
|
||||
.. Required initialization commands are downloaded (if not present already) to the `${jetty.base}/etc/` directory: `jetty-logging.xml`, `jetty-webapp-logging.xml`, and `jetty-mdc-handler.xml`.
|
||||
|
||||
At this point the Jetty `mybase` is configured so that the jetty server itself will log using slf4j, and all other logging events from other Jetty Server components (such as database drivers, security layers, jsp, mail, and other 3rd party server components) are routed to logback for filtering and output.
|
||||
|
||||
|
@ -109,4 +140,5 @@ All webapps deployed via the `DeploymentManager` have their `WebAppClassLoader`
|
|||
|
||||
The server classpath can be verified by using the `start.jar --list-config` command.
|
||||
|
||||
In essence, Jetty is now configured to emit its own logging events to slf4j, and various slf4j bridge jars are acting on behalf of log4j, `java.util.logging`, and `commons-logging`, routing all of the logging events to logback (a slf4j adapter) for routing (to console, file, etc...).
|
||||
In essence, Jetty is now configured to emit its own logging events to slf4j, and various slf4j bridge jars are acting on behalf of `log4j`, `java.util.logging`, and `commons-logging`, routing all of the logging events to `logback`
|
||||
(a slf4j implementation) for routing (to console, file, etc...).
|
||||
|
|
|
@ -16,47 +16,35 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client-io-arch]]
|
||||
=== Client Libraries Architecture
|
||||
[[eg-client-io-arch]]
|
||||
=== Client Libraries I/O Architecture
|
||||
|
||||
The Jetty client libraries provide the basic components and APIs to implement
|
||||
a network client.
|
||||
The Jetty client libraries provide the basic components and APIs to implement a network client.
|
||||
|
||||
They build on the common link:#io-arch[Jetty I/O Architecture] and provide client
|
||||
specific concepts (such as establishing a connection to a server).
|
||||
They build on the common xref:eg-io-arch[Jetty I/O Architecture] and provide client specific concepts (such as establishing a connection to a server).
|
||||
|
||||
There are conceptually two layers that compose the Jetty client libraries:
|
||||
|
||||
. link:#client-io-arch-network[The network layer], that handles the low level
|
||||
I/O and deals with buffers, threads, etc.
|
||||
. link:#client-io-arch-protocol[The protocol layer], that handles the parsing
|
||||
of bytes read from the network and the generation of bytes to write to the
|
||||
network.
|
||||
. xref:eg-client-io-arch-network[The network layer], that handles the low level I/O and deals with buffers, threads, etc.
|
||||
. xref:eg-client-io-arch-protocol[The protocol layer], that handles the parsing of bytes read from the network and the generation of bytes to write to the network.
|
||||
|
||||
[[client-io-arch-network]]
|
||||
[[eg-client-io-arch-network]]
|
||||
==== Client Libraries Network Layer
|
||||
|
||||
The Jetty client libraries use the common I/O design described in
|
||||
link:#io-arch[this section].
|
||||
The main client-side component is the
|
||||
link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[`ClientConnector`].
|
||||
The Jetty client libraries use the common I/O design described in link:#eg-io-arch[this section].
|
||||
The main client-side component is the link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[`ClientConnector`].
|
||||
|
||||
The `ClientConnector` primarily wraps the
|
||||
link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`]
|
||||
and aggregates other four components:
|
||||
The `ClientConnector` primarily wraps the link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`] and aggregates other four components:
|
||||
|
||||
* a thread pool (in form of an `java.util.concurrent.Executor`)
|
||||
* a scheduler (in form of `org.eclipse.jetty.util.thread.Scheduler`)
|
||||
* a byte buffer pool (in form of `org.eclipse.jetty.io.ByteBufferPool`)
|
||||
* a TLS factory (in form of `org.eclipse.jetty.util.ssl.SslContextFactory.Client`)
|
||||
|
||||
The `ClientConnector` is where you want to set those components after you
|
||||
have configured them.
|
||||
If you don't explicitly set those components on the `ClientConnector`, then
|
||||
appropriate defaults will be chosen when the `ClientConnector` starts.
|
||||
The `ClientConnector` is where you want to set those components after you have configured them.
|
||||
If you don't explicitly set those components on the `ClientConnector`, then appropriate defaults will be chosen when the `ClientConnector` starts.
|
||||
|
||||
The simplest example that creates and starts a `ClientConnector` is the
|
||||
following:
|
||||
The simplest example that creates and starts a `ClientConnector` is the following:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
@ -70,119 +58,72 @@ A more typical example:
|
|||
include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=typical]
|
||||
----
|
||||
|
||||
A more advanced example that customizes the `ClientConnector` by overriding
|
||||
some of its methods:
|
||||
A more advanced example that customizes the `ClientConnector` by overriding some of its methods:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=advanced]
|
||||
----
|
||||
|
||||
Since `ClientConnector` is the component that handles the low-level network, it
|
||||
is also the component where you want to configure the low-level network configuration.
|
||||
Since `ClientConnector` is the component that handles the low-level network, it is also the component where you want to configure the low-level network configuration.
|
||||
|
||||
The most common parameters are:
|
||||
|
||||
* `ClientConnector.selectors`: the number of ``java.nio.Selector``s components
|
||||
(defaults to `1`) that are present to handle the ``SocketChannel``s opened by
|
||||
the `ClientConnector`. You typically want to increase the number of selectors
|
||||
only for those use cases where each selector should handle more than few hundreds
|
||||
_concurrent_ socket events.
|
||||
For example, one selector typically runs well for `250` _concurrent_ socket
|
||||
events; as a rule of thumb, you can multiply that number by `10` to obtain the
|
||||
number of opened sockets a selector can handle (`2500`), based on the assumption
|
||||
that not all the `2500` sockets will be active _at the same time_.
|
||||
* `ClientConnector.idleTimeout`: the duration of time after which
|
||||
`ClientConnector` closes a socket due to inactivity (defaults to `30` seconds).
|
||||
This is an important parameter to configure, and you typically want the client
|
||||
idle timeout to be shorter than the server idle timeout, to avoid race
|
||||
conditions where the client attempts to use a socket just before the client-side
|
||||
idle timeout expires, but the server-side idle timeout has already expired and
|
||||
the is already closing the socket.
|
||||
* `ClientConnector.connectBlocking`: whether the operation of connecting a
|
||||
socket to the server (i.e. `SocketChannel.connect(SocketAddress)`) must be a
|
||||
blocking or a non-blocking operation (defaults to `false`).
|
||||
* `ClientConnector.selectors`: the number of ``java.nio.Selector``s components (defaults to `1`) that are present to handle the ``SocketChannel``s opened by the `ClientConnector`.
|
||||
You typically want to increase the number of selectors only for those use cases where each selector should handle more than few hundreds _concurrent_ socket events.
|
||||
For example, one selector typically runs well for `250` _concurrent_ socket events; as a rule of thumb, you can multiply that number by `10` to obtain the number of opened sockets a selector can handle (`2500`), based on the assumption that not all the `2500` sockets will be active _at the same time_.
|
||||
* `ClientConnector.idleTimeout`: the duration of time after which `ClientConnector` closes a socket due to inactivity (defaults to `30` seconds).
|
||||
This is an important parameter to configure, and you typically want the client idle timeout to be shorter than the server idle timeout, to avoid race conditions where the client attempts to use a socket just before the client-side idle timeout expires, but the server-side idle timeout has already expired and the is already closing the socket.
|
||||
* `ClientConnector.connectBlocking`: whether the operation of connecting a socket to the server (i.e. `SocketChannel.connect(SocketAddress)`) must be a blocking or a non-blocking operation (defaults to `false`).
|
||||
For `localhost` or same datacenter hosts you want to set this parameter to
|
||||
`true` because DNS resolution will be immediate (and likely never fail).
|
||||
For generic Internet hosts (e.g. when you are implementing a web spider) you
|
||||
want to set this parameter to `false`.
|
||||
* `ClientConnector.connectTimeout`: the duration of time after which
|
||||
`ClientConnector` aborts a connection attempt to the server (defaults to `5`
|
||||
seconds).
|
||||
For generic Internet hosts (e.g. when you are implementing a web spider) you want to set this parameter to `false`.
|
||||
* `ClientConnector.connectTimeout`: the duration of time after which `ClientConnector` aborts a connection attempt to the server (defaults to `5` seconds).
|
||||
This time includes the DNS lookup time _and_ the TCP connect time.
|
||||
|
||||
Please refer to the `ClientConnector`
|
||||
link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[javadocs]
|
||||
for the complete list of configurable parameters.
|
||||
Please refer to the `ClientConnector` link:{JDURL}/org/eclipse/jetty/io/ClientConnector.html[javadocs] for the complete list of configurable parameters.
|
||||
|
||||
[[client-io-arch-protocol]]
|
||||
[[eg-client-io-arch-protocol]]
|
||||
==== Client Libraries Protocol Layer
|
||||
|
||||
The protocol layer builds on top of the network layer to generate the
|
||||
bytes to be written to the network and to parse the bytes read from the
|
||||
network.
|
||||
The protocol layer builds on top of the network layer to generate the bytes to be written to the network and to parse the bytes read from the network.
|
||||
|
||||
Recall from link:#io-arch-connection[this section] that Jetty uses the
|
||||
`Connection` abstraction to produce and interpret the network bytes.
|
||||
Recall from link:#eg-io-arch-connection[this section] that Jetty uses the `Connection` abstraction to produce and interpret the network bytes.
|
||||
|
||||
On the client side, a `ClientConnectionFactory` implementation is the
|
||||
component that creates `Connection` instances based on the protocol that
|
||||
the client wants to "speak" with the server.
|
||||
On the client side, a `ClientConnectionFactory` implementation is the component that creates `Connection` instances based on the protocol that the client wants to "speak" with the server.
|
||||
|
||||
Applications use `ClientConnector.connect(SocketAddress, Map<String, Object>)`
|
||||
to establish a TCP connection to the server, and must tell
|
||||
`ClientConnector` how to create the `Connection` for that particular
|
||||
TCP connection, and how to notify back the application when the connection
|
||||
creation succeeds or fails.
|
||||
Applications use `ClientConnector.connect(SocketAddress, Map<String, Object>)` to establish a TCP connection to the server, and must tell `ClientConnector` how to create the `Connection` for that particular TCP connection, and how to notify back the application when the connection creation succeeds or fails.
|
||||
|
||||
This is done by passing a
|
||||
link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`]
|
||||
(that creates `Connection` instances) and a
|
||||
link:{JDURL}/org/eclipse/jetty/util/Promise.html[`Promise`] (that is notified
|
||||
of connection creation success or failure) in the context `Map` as follows:
|
||||
This is done by passing a link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`] (that creates `Connection` instances) and a link:{JDURL}/org/eclipse/jetty/util/Promise.html[`Promise`] (that is notified of connection creation success or failure) in the context `Map` as follows:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=connect]
|
||||
----
|
||||
|
||||
When a `Connection` is created successfully, its `onOpen()` method is invoked,
|
||||
and then the promise is completed successfully.
|
||||
When a `Connection` is created successfully, its `onOpen()` method is invoked, and then the promise is completed successfully.
|
||||
|
||||
It is now possible to write a super-simple `telnet` client that reads and writes
|
||||
string lines:
|
||||
It is now possible to write a super-simple `telnet` client that reads and writes string lines:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/embedded/client/ClientConnectorDocs.java[tags=telnet]
|
||||
----
|
||||
|
||||
Note how a very basic "telnet" API that applications could use is implemented
|
||||
in the form of the `onLine(Consumer<String>)` for the non-blocking receiving
|
||||
side and `writeLine(String, Callback)` for the non-blocking sending side.
|
||||
Note also how the `onFillable()` method implements some basic "parsing"
|
||||
by looking up the `\n` character in the buffer.
|
||||
Note how a very basic "telnet" API that applications could use is implemented in the form of the `onLine(Consumer<String>)` for the non-blocking receiving side and `writeLine(String, Callback)` for the non-blocking sending side.
|
||||
Note also how the `onFillable()` method implements some basic "parsing" by looking up the `\n` character in the buffer.
|
||||
|
||||
NOTE: The "telnet" client above looks like a super-simple HTTP client because
|
||||
HTTP/1.0 can be seen as a line-based protocol. HTTP/1.0 was used just as an
|
||||
example, but we could have used any other line-based protocol such as
|
||||
link:https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol[SMTP],
|
||||
provided that the server was able to understand it.
|
||||
NOTE: The "telnet" client above looks like a super-simple HTTP client because HTTP/1.0 can be seen as a line-based protocol.
|
||||
HTTP/1.0 was used just as an example, but we could have used any other line-based protocol such as link:https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol[SMTP], provided that the server was able to understand it.
|
||||
|
||||
This is very similar to what the Jetty client implementation does for real
|
||||
network protocols.
|
||||
Real network protocols are of course more complicated and so is the implementation
|
||||
code that handles them, but the general ideas are similar.
|
||||
This is very similar to what the Jetty client implementation does for real network protocols.
|
||||
Real network protocols are of course more complicated and so is the implementation code that handles them, but the general ideas are similar.
|
||||
|
||||
The Jetty client implementation provides a number of `ClientConnectionFactory`
|
||||
implementations that can be composed to produce and interpret the network bytes.
|
||||
The Jetty client implementation provides a number of `ClientConnectionFactory` implementations that can be composed to produce and interpret the network bytes.
|
||||
|
||||
For example, it is simple to modify the above example to use the TLS protocol
|
||||
so that you will be able to connect to the server on port `443`, typically
|
||||
reserved for the encrypted HTTP protocol.
|
||||
For example, it is simple to modify the above example to use the TLS protocol so that you will be able to connect to the server on port `443`, typically reserved for the encrypted HTTP protocol.
|
||||
|
||||
The differences between the clear-text version and the TLS encrypted version
|
||||
are minimal:
|
||||
The differences between the clear-text version and the TLS encrypted version are minimal:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
|
|
@ -16,28 +16,23 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client]]
|
||||
[[eg-client]]
|
||||
== Client Libraries
|
||||
|
||||
The Eclipse Jetty Project provides also provides client-side libraries
|
||||
that allow you to embed a client in your applications.
|
||||
A typical example is an application that needs to contact a third party
|
||||
service via HTTP (for example a REST service).
|
||||
Another example is a proxy application that receives HTTP requests and
|
||||
forwards them as FCGI requests to a PHP application such as WordPress,
|
||||
or receives HTTP/1.1 requests and converts them to HTTP/2.
|
||||
The Eclipse Jetty Project provides also provides client-side libraries that allow you to embed a client in your applications.
|
||||
A typical example is a client application that needs to contact a third party service via HTTP (for example a REST service).
|
||||
Another example is a proxy application that receives HTTP requests and forwards them as FCGI requests to a PHP application such as WordPress, or receives HTTP/1.1 requests and converts them to HTTP/2. Yet another example is a client application that needs to received events from a WebSocket server.
|
||||
|
||||
The client libraries are designed to be non-blocking and offer both synchronous
|
||||
and asynchronous APIs and come with a large number of configuration options.
|
||||
The client libraries are designed to be non-blocking and offer both synchronous and asynchronous APIs and come with a large number of configuration options.
|
||||
|
||||
There are primarily two client libraries:
|
||||
These are the available client libraries:
|
||||
|
||||
* link:#client-http[The HTTP client library]
|
||||
* link:#client-websocket[The WebSocket client library]
|
||||
* xref:eg-client-http[The HTTP Client Library]
|
||||
* xref:eg-client-http2[The HTTP/2 Client Library]
|
||||
* xref:eg-client-websocket[The WebSocket client library]
|
||||
|
||||
If you are interested in the low-level details of how the Eclipse Jetty
|
||||
client libraries work, or are interested in writing a custom protocol,
|
||||
look at the link:#client-io-arch[Client I/O Architecture].
|
||||
If you are interested in the low-level details of how the Eclipse Jetty client libraries work, or are interested in writing a custom protocol, look at the xref:eg-client-io-arch[Client I/O Architecture].
|
||||
|
||||
include::http/client-http.adoc[]
|
||||
include::http2/client-http2.adoc[]
|
||||
include::client-io-arch.adoc[]
|
||||
|
|
|
@ -16,12 +16,12 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client-http-api]]
|
||||
[[eg-client-http-api]]
|
||||
=== HttpClient API Usage
|
||||
|
||||
`HttpClient` provides two types of APIs: a blocking API and a non-blocking API.
|
||||
|
||||
[[client-http-blocking]]
|
||||
[[eg-client-http-blocking]]
|
||||
==== HttpClient Blocking APIs
|
||||
|
||||
The simpler way to perform a HTTP request is the following:
|
||||
|
@ -34,7 +34,7 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=simpleBl
|
|||
The method `HttpClient.GET(...)` performs a HTTP `GET` request to the given URI and returns a `ContentResponse` when the request/response conversation completes successfully.
|
||||
|
||||
The `ContentResponse` object contains the HTTP response information: status code, headers and possibly content.
|
||||
The content length is limited by default to 2 MiB; for larger content see xref:http-client-response-content[].
|
||||
The content length is limited by default to 2 MiB; for larger content see xref:client-http-content-response[].
|
||||
|
||||
If you want to customize the request, for example by issuing a `HEAD` request instead of a `GET`, and simulating a browser user agent, you can do it in this way:
|
||||
|
||||
|
@ -81,7 +81,7 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=totalTim
|
|||
|
||||
In the example above, when the 5 seconds expire, the request is aborted and a `java.util.concurrent.TimeoutException` is thrown.
|
||||
|
||||
[[client-http-non-blocking]]
|
||||
[[eg-client-http-non-blocking]]
|
||||
==== HttpClient Non-Blocking APIs
|
||||
|
||||
So far we have shown how to use Jetty HTTP client in a blocking style - that is, the thread that issues the request blocks until the request/response conversation is complete.
|
||||
|
@ -139,10 +139,10 @@ This makes Jetty HTTP client suitable for HTTP load testing because, for example
|
|||
|
||||
Have a look at the link:{JDURL}/org/eclipse/jetty/client/api/Request.Listener.html[`Request.Listener`] class to know about request events, and to the link:{JDURL}/org/eclipse/jetty/client/api/Response.Listener.html[`Response.Listener`] class to know about response events.
|
||||
|
||||
[[client-http-content]]
|
||||
[[eg-client-http-content]]
|
||||
==== HttpClient Content Handling
|
||||
|
||||
[[client-http-content-request]]
|
||||
[[eg-client-http-content-request]]
|
||||
===== Request Content Handling
|
||||
|
||||
Jetty's `HttpClient` provides a number of utility classes off the shelf to handle request content.
|
||||
|
@ -182,15 +182,14 @@ While the request content is awaited and consequently uploaded by the client app
|
|||
In this case, `Response.Listener` callbacks will be invoked before the request is fully sent.
|
||||
This allows fine-grained control of the request/response conversation: for example the server may reject contents that are too big, send a response to the client, which in turn may stop the content upload.
|
||||
|
||||
Another way to provide request content is by using an `OutputStreamRequestContent`,
|
||||
which allows applications to write request content when it is available to the `OutputStream` provided by `OutputStreamRequestContent`:
|
||||
Another way to provide request content is by using an `OutputStreamRequestContent`, which allows applications to write request content when it is available to the `OutputStream` provided by `OutputStreamRequestContent`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=outputStreamRequestContent]
|
||||
----
|
||||
|
||||
[[http-client-response-content]]
|
||||
[[eg-client-http-content-response]]
|
||||
===== Response Content Handling
|
||||
|
||||
Jetty's `HttpClient` allows applications to handle response content in different ways.
|
||||
|
@ -222,74 +221,42 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=inputStr
|
|||
|
||||
Finally, let's look at the advanced usage of the response content handling.
|
||||
|
||||
The response content is provided by the `HttpClient` implementation to application
|
||||
listeners following a reactive model similar to that of `java.util.concurrent.Flow`.
|
||||
The response content is provided by the `HttpClient` implementation to application listeners following a reactive model similar to that of `java.util.concurrent.Flow`.
|
||||
|
||||
The listener that follows this model is `Response.DemandedContentListener`.
|
||||
|
||||
After the response headers have been processed by the `HttpClient` implementation,
|
||||
`Response.DemandedContentListener.onBeforeContent(response, demand)` is
|
||||
invoked. This allows the application to control whether to demand the first
|
||||
content or not. The default implementation of this method calls `demand.accept(1)`,
|
||||
which demands one chunk of content to the implementation.
|
||||
After the response headers have been processed by the `HttpClient` implementation, `Response.DemandedContentListener.onBeforeContent(response, demand)` is invoked.
|
||||
This allows the application to control whether to demand the first content or not.
|
||||
The default implementation of this method calls `demand.accept(1)`, which demands one chunk of content to the implementation.
|
||||
The implementation will deliver the chunk of content as soon as it is available.
|
||||
|
||||
The chunks of content are delivered to the application by invoking
|
||||
`Response.DemandedContentListener.onContent(response, demand, buffer, callback)`.
|
||||
The chunks of content are delivered to the application by invoking `Response.DemandedContentListener.onContent(response, demand, buffer, callback)`.
|
||||
Applications implement this method to process the content bytes in the `buffer`.
|
||||
Succeeding the `callback` signals to the implementation that the application
|
||||
has consumed the `buffer` so that the implementation can dispose/recycle the
|
||||
`buffer`. Failing the `callback` signals to the implementation to fail the
|
||||
response (no more content will be delivered, and the _response failed_ event
|
||||
will be emitted).
|
||||
Succeeding the `callback` signals to the implementation that the application has consumed the `buffer` so that the implementation can dispose/recycle the `buffer`.
|
||||
Failing the `callback` signals to the implementation to fail the response (no more content will be delivered, and the _response failed_ event will be emitted).
|
||||
|
||||
IMPORTANT: Succeeding the `callback` must be done only after the `buffer`
|
||||
bytes have been consumed. When the `callback` is succeeded, the `HttpClient`
|
||||
implementation may reuse the `buffer` and overwrite the bytes with different
|
||||
bytes; if the application looks at the `buffer` _after_ having succeeded
|
||||
the `callback` is may see other, unrelated, bytes.
|
||||
IMPORTANT: Succeeding the `callback` must be done only after the `buffer` bytes have been consumed.
|
||||
When the `callback` is succeeded, the `HttpClient` implementation may reuse the `buffer` and overwrite the bytes with different bytes; if the application looks at the `buffer` _after_ having succeeded the `callback` is may see other, unrelated, bytes.
|
||||
|
||||
The application uses the `demand` object to demand more content chunks.
|
||||
Applications will typically demand for just one more content via
|
||||
`demand.accept(1)`, but may decide to demand for more via `demand.accept(2)`
|
||||
or demand "infinitely" once via `demand.accept(Long.MAX_VALUE)`.
|
||||
Applications that demand for more than 1 chunk of content must be prepared
|
||||
to receive all the content that they have demanded.
|
||||
Applications will typically demand for just one more content via `demand.accept(1)`, but may decide to demand for more via `demand.accept(2)` or demand "infinitely" once via `demand.accept(Long.MAX_VALUE)`.
|
||||
Applications that demand for more than 1 chunk of content must be prepared to receive all the content that they have demanded.
|
||||
|
||||
Demanding for content and consuming the content are orthogonal activities.
|
||||
|
||||
An application can demand "infinitely" and store aside the pairs
|
||||
`(buffer, callback)` to consume them later.
|
||||
If not done carefully, this may lead to excessive memory consumption, since
|
||||
the ``buffer``s are not consumed.
|
||||
Succeeding the ``callback``s will result in the ``buffer``s to be
|
||||
disposed/recycled and may be performed at any time.
|
||||
An application can demand "infinitely" and store aside the pairs `(buffer, callback)` to consume them later.
|
||||
If not done carefully, this may lead to excessive memory consumption, since the ``buffer``s are not consumed.
|
||||
Succeeding the ``callback``s will result in the ``buffer``s to be disposed/recycled and may be performed at any time.
|
||||
|
||||
An application can also demand one chunk of content, consume it (by
|
||||
succeeding the associated `callback`) and then _not_ demand for more content
|
||||
until a later time.
|
||||
An application can also demand one chunk of content, consume it (by succeeding the associated `callback`) and then _not_ demand for more content until a later time.
|
||||
|
||||
Subclass `Response.AsyncContentListener` overrides the behavior of
|
||||
`Response.DemandedContentListener`; when an application implementing its
|
||||
`onContent(response, buffer, callback)` succeeds the `callback`, it
|
||||
will have _both_ the effect of disposing/recycling the `buffer` _and_ the
|
||||
effect of demanding one more chunk of content.
|
||||
Subclass `Response.AsyncContentListener` overrides the behavior of `Response.DemandedContentListener`; when an application implementing its `onContent(response, buffer, callback)` succeeds the `callback`, it will have _both_ the effect of disposing/recycling the `buffer` _and_ the effect of demanding one more chunk of content.
|
||||
|
||||
Subclass `Response.ContentListener` overrides the behavior of
|
||||
`Response.AsyncContentListener`; when an application implementing its
|
||||
`onContent(response, buffer)` returns from the method itself, it will
|
||||
_both_ the effect of disposing/recycling the `buffer` _and_ the effect
|
||||
of demanding one more chunk of content.
|
||||
Subclass `Response.ContentListener` overrides the behavior of `Response.AsyncContentListener`; when an application implementing its `onContent(response, buffer)` returns from the method itself, it will _both_ the effect of disposing/recycling the `buffer` _and_ the effect of demanding one more chunk of content.
|
||||
|
||||
Previous examples of response content handling were inefficient because they
|
||||
involved copying the `buffer` bytes, either to accumulate them aside so that
|
||||
the application could use them when the request was completed, or because
|
||||
they were provided to an API such as `InputStream` that made use of `byte[]`
|
||||
(and therefore a copy from `ByteBuffer` to `byte[]` is necessary).
|
||||
Previous examples of response content handling were inefficient because they involved copying the `buffer` bytes, either to accumulate them aside so that the application could use them when the request was completed, or because they were provided to an API such as `InputStream` that made use of `byte[]` (and therefore a copy from `ByteBuffer` to `byte[]` is necessary).
|
||||
|
||||
An application that implements a forwarder between two servers can be
|
||||
implemented efficiently by handling the response content without copying
|
||||
the `buffer` bytes as in the following example:
|
||||
An application that implements a forwarder between two servers can be implemented efficiently by handling the response content without copying the `buffer` bytes as in the following example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
|
|
@ -16,76 +16,66 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client-http-authentication]]
|
||||
=== Authentication Support
|
||||
[[eg-client-http-authentication]]
|
||||
=== HttpClient Authentication Support
|
||||
|
||||
Jetty's HTTP client supports the `BASIC` and `DIGEST` authentication mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235].
|
||||
Jetty's `HttpClient` supports the `BASIC` and `DIGEST` authentication mechanisms defined by link:https://tools.ietf.org/html/rfc7235[RFC 7235], as well as the SPNEGO authentication mechanism defined in link:https://tools.ietf.org/html/rfc4559[RFC 4559].
|
||||
|
||||
You can configure authentication credentials in the HTTP client instance as follows:
|
||||
The HTTP _conversation_ - the sequence of related HTTP requests - for a request that needs authentication is the following:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[plantuml]
|
||||
----
|
||||
URI uri = new URI("http://domain.com/secure");
|
||||
String realm = "MyRealm";
|
||||
String user = "username";
|
||||
String pass = "password";
|
||||
skinparam backgroundColor transparent
|
||||
skinparam monochrome true
|
||||
skinparam shadowing false
|
||||
|
||||
// Add authentication credentials
|
||||
AuthenticationStore auth = httpClient.getAuthenticationStore();
|
||||
auth.addAuthentication(new BasicAuthentication(uri, realm, user, pass));
|
||||
participant Application
|
||||
participant HttpClient
|
||||
participant Server
|
||||
|
||||
ContentResponse response = httpClient
|
||||
.newRequest(uri)
|
||||
.send()
|
||||
.get(5, TimeUnit.SECONDS);
|
||||
Application -> Server : GET /path
|
||||
Server -> HttpClient : 401 + WWW-Authenticate
|
||||
HttpClient -> Server : GET + Authentication
|
||||
Server -> Application : 200 OK
|
||||
----
|
||||
|
||||
Jetty's HTTP client tests authentication credentials against the challenge(s) the server issues (see our section here on link:#configuring-security-secure-passwords[secure password obfuscation]), and if they match it automatically sends the right authentication headers to the server for authentication.
|
||||
If the authentication is successful, it caches the result and reuses it for subsequent requests for the same domain and matching URIs.
|
||||
Upon receiving a HTTP 401 response code, `HttpClient` looks at the `WWW-Authenticate` response header (the server _challenge_) and then tries to match configured authentication credentials to produce an `Authentication` header that contains the authentication credentials to access the resource.
|
||||
|
||||
The HTTP conversation for a successful match is the following:
|
||||
You can configure authentication credentials in the `HttpClient` instance as follows:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
Application HttpClient Server
|
||||
| | |
|
||||
|--- GET ---|------------ GET ----------->|
|
||||
| | |
|
||||
| |<-- 401 + WWW-Authenticate --|
|
||||
| | |
|
||||
| |--- GET + Authentication --->|
|
||||
| | |
|
||||
|<-- 200 ---|------------ 200 ------------|
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=addAuthentication]
|
||||
----
|
||||
|
||||
The application does not receive events related to the response with code 401, they are handled internally by `HttpClient` which produces a request similar to the original but with the correct `Authorization` header, and then relays the response with code 200 to the application.
|
||||
``Authentication``s are matched against the server challenge first by mechanism (e.g. `BASIC` or `DIGEST`), then by realm and then by URI.
|
||||
|
||||
Successful authentications are cached, but it is possible to clear them in order to force authentication again:
|
||||
If an `Authentication` match is found, the application does not receive events related to the HTTP 401 response.
|
||||
These events are handled internally by `HttpClient` which produces another (internal) request similar to the original request but with an additional `Authorization` header.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
If the authentication is successful, the server responds with a HTTP 200 and `HttpClient` caches the `Authentication.Result` so that subsequent requests for a matching URI will not incur in the additional rountrip caused by the HTTP 401 response.
|
||||
|
||||
It is possible to clear ``Authentication.Result``s in order to force authentication again:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
httpClient.getAuthenticationStore().clearAuthenticationResults();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=clearResults]
|
||||
----
|
||||
|
||||
Authentications may be preempted to avoid the additional roundtrip due to the server challenge in this way:
|
||||
Authentication results may be preempted to avoid the additional roundtrip due to the server challenge in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
AuthenticationStore auth = httpClient.getAuthenticationStore();
|
||||
URI uri = URI.create("http://domain.com/secure");
|
||||
auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "username", "password"));
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=preemptedResult]
|
||||
----
|
||||
|
||||
In this way, requests for the given URI are enriched by `HttpClient` immediately with the `Authorization` header, and the server should respond with a 200 and the resource content rather than with the 401 and the challenge.
|
||||
In this way, requests for the given URI are enriched immediately with the `Authorization` header, and the server should respond with HTTP 200 (and the resource content) rather than with the 401 and the challenge.
|
||||
|
||||
It is also possible to preempt the authentication for a single request only, in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
URI uri = URI.create("http://domain.com/secure");
|
||||
Authentication.Result authn = new BasicAuthentication.BasicResult(uri, "username", "password")
|
||||
Request request = httpClient.newRequest(uri);
|
||||
authn.apply(request);
|
||||
request.send();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=requestPreemptedResult]
|
||||
----
|
||||
|
||||
See also the link:#client-http-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies.
|
||||
See also the xref:eg-client-http-proxy-authentication[proxy authentication section] for further information about how authentication works with HTTP proxies.
|
||||
|
|
|
@ -16,58 +16,43 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client-http-configuration]]
|
||||
[[eg-client-http-configuration]]
|
||||
=== HttpClient Configuration
|
||||
|
||||
`HttpClient` has a quite large number of configuration parameters.
|
||||
Please refer to the `HttpClient`
|
||||
link:{JDURL}/org/eclipse/jetty/client/HttpClient.html[javadocs]
|
||||
for the complete list of configurable parameters.
|
||||
Please refer to the `HttpClient` link:{JDURL}/org/eclipse/jetty/client/HttpClient.html[javadocs] for the complete list of configurable parameters.
|
||||
|
||||
The most common parameters are:
|
||||
|
||||
* `HttpClient.idleTimeout`: same as `ClientConnector.idleTimeout`
|
||||
described in link:#client-io-arch-network[this section].
|
||||
* `HttpClient.connectBlocking`: same as `ClientConnector.connectBlocking`
|
||||
described in link:#client-io-arch-network[this section].
|
||||
* `HttpClient.connectTimeout`: same as `ClientConnector.connectTimeout`
|
||||
described in link:#client-io-arch-network[this section].
|
||||
* `HttpClient.maxConnectionsPerDestination`: the max number of TCP
|
||||
connections that are opened for a particular destination (defaults to 64).
|
||||
* `HttpClient.maxRequestsQueuedPerDestination`: the max number of requests
|
||||
queued (defaults to 1024).
|
||||
* `HttpClient.idleTimeout`: same as `ClientConnector.idleTimeout` described in xref:eg-client-io-arch-network[this section].
|
||||
* `HttpClient.connectBlocking`: same as `ClientConnector.connectBlocking` described in xref:eg-client-io-arch-network[this section].
|
||||
* `HttpClient.connectTimeout`: same as `ClientConnector.connectTimeout` described in xref:eg-client-io-arch-network[this section].
|
||||
* `HttpClient.maxConnectionsPerDestination`: the max number of TCP connections that are opened for a particular destination (defaults to 64).
|
||||
* `HttpClient.maxRequestsQueuedPerDestination`: the max number of requests queued (defaults to 1024).
|
||||
|
||||
[[client-http-configuration-tls]]
|
||||
[[eg-client-http-configuration-tls]]
|
||||
==== HttpClient TLS Configuration
|
||||
|
||||
`HttpClient` supports HTTPS requests out-of-the-box like a browser does.
|
||||
|
||||
The support for HTTPS request is provided by a `SslContextFactory.Client`,
|
||||
typically configured in the `ClientConnector`.
|
||||
If not explicitly configured, the `ClientConnector` will allocate a default
|
||||
one when started.
|
||||
The support for HTTPS request is provided by a `SslContextFactory.Client`, typically configured in the `ClientConnector`.
|
||||
If not explicitly configured, the `ClientConnector` will allocate a default one when started.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsExplicit]
|
||||
----
|
||||
|
||||
The default `SslContextFactory.Client` verifies the certificate sent by the
|
||||
server by verifying the certificate chain.
|
||||
This means that requests to public websites that have a valid certificates
|
||||
(such as ``https://google.com``) will work out-of-the-box.
|
||||
The default `SslContextFactory.Client` verifies the certificate sent by the server by verifying the certificate chain.
|
||||
This means that requests to public websites that have a valid certificate (such as ``https://google.com``) will work out-of-the-box.
|
||||
|
||||
However, requests made to sites (typically ``localhost``) that have invalid
|
||||
(for example, expired or with a wrong host) or self-signed certificates will
|
||||
fail (like they will in a browser).
|
||||
However, requests made to sites (typically ``localhost``) that have an invalid (for example, expired or with a wrong host) or self-signed certificate will fail (like they will in a browser).
|
||||
|
||||
Certificate validation is performed at two levels: at the TLS implementation
|
||||
level (in the JDK) and - optionally - at the application level.
|
||||
Certificate validation is performed at two levels: at the TLS implementation level (in the JDK) and - optionally - at the application level.
|
||||
|
||||
By default, certificate validation at the TLS level is enabled, while
|
||||
certificate validation at the application level is disabled.
|
||||
By default, certificate validation at the TLS level is enabled, while certificate validation at the application level is disabled.
|
||||
|
||||
You can configure the `SslContextFactory.Client` to skip certificate validation
|
||||
at the TLS level:
|
||||
You can configure the `SslContextFactory.Client` to skip certificate validation at the TLS level:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
@ -81,6 +66,4 @@ You can enable certificate validation at the application level:
|
|||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=tlsAppValidation]
|
||||
----
|
||||
|
||||
Please refer to the `SslContextFactory.Client`
|
||||
link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.Client.html[javadocs]
|
||||
for the complete list of configurable parameters.
|
||||
Please refer to the `SslContextFactory.Client` link:{JDURL}/org/eclipse/jetty/util/ssl/SslContextFactory.Client.html[javadocs] for the complete list of configurable parameters.
|
||||
|
|
|
@ -16,104 +16,88 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client-http-cookie]]
|
||||
=== Cookies Support
|
||||
[[eg-client-http-cookie]]
|
||||
=== HttpClient Cookie Support
|
||||
|
||||
Jetty HTTP client supports cookies out of the box.
|
||||
Jetty's `HttpClient` supports cookies out of the box.
|
||||
The `HttpClient` instance receives cookies from HTTP responses and stores them in a `java.net.CookieStore`, a class that is part of the JDK.
|
||||
When new requests are made, the cookie store is consulted and if there are matching cookies (that is, cookies that are not expired and that match domain and path of the request) then they are added to the requests.
|
||||
|
||||
Applications can programmatically access the cookie store to find the cookies that have been set:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
CookieStore cookieStore = httpClient.getCookieStore();
|
||||
List<HttpCookie> cookies = cookieStore.get(URI.create("http://domain.com/path"));
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=getCookies]
|
||||
----
|
||||
|
||||
Applications can also programmatically set cookies as if they were returned from a HTTP response:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
CookieStore cookieStore = httpClient.getCookieStore();
|
||||
HttpCookie cookie = new HttpCookie("foo", "bar");
|
||||
cookie.setDomain("domain.com");
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(TimeUnit.DAYS.toSeconds(1));
|
||||
cookieStore.add(URI.create("http://domain.com"), cookie);
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=setCookie]
|
||||
----
|
||||
|
||||
Cookies may be added only for a particular request:
|
||||
Cookies may be added explicitly only for a particular request:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
ContentResponse response = httpClient.newRequest("http://domain.com/path")
|
||||
.cookie(new HttpCookie("foo", "bar"))
|
||||
.send();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=requestCookie]
|
||||
----
|
||||
|
||||
You can remove cookies that you do not want to be sent in future HTTP requests:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
CookieStore cookieStore = httpClient.getCookieStore();
|
||||
URI uri = URI.create("http://domain.com");
|
||||
List<HttpCookie> cookies = cookieStore.get(uri);
|
||||
for (HttpCookie cookie : cookies)
|
||||
cookieStore.remove(uri, cookie);
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=removeCookie]
|
||||
----
|
||||
|
||||
If you want to totally disable cookie handling, you can install a `HttpCookieStore.Empty` instance in this way:
|
||||
If you want to totally disable cookie handling, you can install a `HttpCookieStore.Empty`.
|
||||
This must be done when `HttpClient` is used in a proxy application, in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
httpClient.setCookieStore(new HttpCookieStore.Empty());
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=emptyCookieStore]
|
||||
----
|
||||
|
||||
You can enable cookie filtering by installing a cookie store that performs the filtering logic in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
httpClient.setCookieStore(new GoogleOnlyCookieStore());
|
||||
|
||||
public class GoogleOnlyCookieStore extends HttpCookieStore
|
||||
{
|
||||
@Override
|
||||
public void add(URI uri, HttpCookie cookie)
|
||||
{
|
||||
if (uri.getHost().endsWith("google.com"))
|
||||
super.add(uri, cookie);
|
||||
}
|
||||
}
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=filteringCookieStore]
|
||||
----
|
||||
|
||||
The example above will retain only cookies that come from the `google.com` domain or sub-domains.
|
||||
|
||||
// TODO: move this section to server-side
|
||||
==== Special Characters in Cookies
|
||||
|
||||
Jetty is compliant with link:https://tools.ietf.org/html/rfc6265[RFC6265], and as such care must be taken when setting a cookie value that includes special characters such as `;`.
|
||||
|
||||
Previously, Version=1 cookies defined in link:https://tools.ietf.org/html/rfc2109[RFC2109] (and continued in link:https://tools.ietf.org/html/rfc2965[RFC2965]) allowed for special/reserved characters to be enclosed within double quotes when declared in a `Set-Cookie` response header:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,screen]
|
||||
----
|
||||
Set-Cookie: foo="bar;baz";Version=1;Path="/secur"
|
||||
----
|
||||
|
||||
This was added to the HTTP Response header as follows:
|
||||
This was added to the HTTP Response as follows:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java]
|
||||
----
|
||||
Cookie cookie = new Cookie("foo", "bar;baz");
|
||||
cookie.setPath("/secur");
|
||||
response.addCookie(cookie);
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
javax.servlet.http.Cookie cookie = new Cookie("foo", "bar;baz");
|
||||
cookie.setPath("/secur");
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
----
|
||||
|
||||
The introduction of RFC6265 has rendered this approach no longer possible; users are now required to encode cookie values that use these special characters.
|
||||
This can be done utilizing `javax.servlet.http.Cookie` as follows:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java]
|
||||
----
|
||||
Cookie cookie = new Cookie("foo", URLEncoder.encode("bar;baz", "utf-8"));
|
||||
javax.servlet.http.Cookie cookie = new Cookie("foo", URLEncoder.encode("bar;baz", "UTF-8"));
|
||||
----
|
||||
|
||||
Jetty validates all cookie names and values being added to the `HttpServletResponse` via the `addCookie(Cookie)` method.
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client-http-intro]]
|
||||
[[eg-client-http-intro]]
|
||||
=== HttpClient Introduction
|
||||
|
||||
The Jetty HTTP client module provides easy-to-use APIs and utility classes to perform HTTP (or HTTPS) requests.
|
||||
|
@ -24,13 +24,10 @@ The Jetty HTTP client module provides easy-to-use APIs and utility classes to pe
|
|||
Jetty's HTTP client is non-blocking and asynchronous.
|
||||
It offers an asynchronous API that never blocks for I/O, making it very efficient in thread utilization and well suited for high performance scenarios such as load testing or parallel computation.
|
||||
|
||||
However, when all you need to do is to perform a `GET` request to a resource, Jetty's HTTP client offers also a synchronous API; a programming interface
|
||||
where the thread that issued the request blocks until the request/response conversation is complete.
|
||||
However, when all you need to do is to perform a `GET` request to a resource, Jetty's HTTP client offers also a synchronous API; a programming interface where the thread that issued the request blocks until the request/response conversation is complete.
|
||||
|
||||
Jetty's HTTP client supports link:#http-client-transport[different transports]: HTTP/1.1, FastCGI and HTTP/2.
|
||||
This means that the semantic of a HTTP request (that is, " `GET` me the resource `/index.html` ") can be carried over the network in different formats.
|
||||
The most common and default format is HTTP/1.1.
|
||||
That said, Jetty's HTTP client can carry the same request using the FastCGI format or the HTTP/2 format.
|
||||
Jetty's HTTP client supports xref:#eg-client-http-transport[different transports]: HTTP/1.1, FastCGI and HTTP/2. This means that the semantic of a HTTP request (that is, " `GET` me the resource `/index.html` ") can be carried over the network in different formats.
|
||||
The most common and default format is HTTP/1.1. That said, Jetty's HTTP client can carry the same request using the FastCGI format or the HTTP/2 format.
|
||||
|
||||
The FastCGI transport is heavily used in Jetty's link:#fastcgi[FastCGI support] that allows Jetty to work as a reverse proxy to PHP (exactly like Apache or Nginx do) and therefore be able to serve - for example - WordPress websites.
|
||||
|
||||
|
@ -43,14 +40,25 @@ Out of the box features that you get with the Jetty HTTP client include:
|
|||
* Authentication support - HTTP "Basic" and "Digest" authentications are supported, others are pluggable.
|
||||
* Forward proxy support - HTTP proxying and SOCKS4 proxying.
|
||||
|
||||
[[client-http-start]]
|
||||
[[eg-client-http-start]]
|
||||
==== Starting HttpClient
|
||||
|
||||
The Jetty artifact that provides the main HTTP client implementation is `jetty-client`.
|
||||
The Maven artifact coordinates are the following:
|
||||
|
||||
[source,xml,subs=normal]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<version>{version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
The main class is named `org.eclipse.jetty.client.HttpClient`.
|
||||
|
||||
You can think of a `HttpClient` instance as a browser instance.
|
||||
Like a browser it can make requests to different domains, it manages redirects, cookies and authentication, you can configure it with a proxy, and
|
||||
it provides you with the responses to the requests you make.
|
||||
Like a browser it can make requests to different domains, it manages redirects, cookies and authentication, you can configure it with a proxy, and it provides you with the responses to the requests you make.
|
||||
|
||||
In order to use `HttpClient`, you must instantiate it, configure it, and then start it:
|
||||
|
||||
|
@ -64,15 +72,12 @@ There are several reasons for having multiple `HttpClient` instances including,
|
|||
|
||||
* You want to specify different configuration parameters (for example, one instance is configured with a forward proxy while another is not).
|
||||
* You want the two instances to behave like two different browsers and hence have different cookies, different authentication credentials, etc.
|
||||
* You want to use link:#http-client-transport[different transports].
|
||||
* You want to use link:#eg-client-http-transport[different transports].
|
||||
|
||||
Like browsers, HTTPS requests are supported out-of-the-box, as long as the server
|
||||
provides a valid certificate.
|
||||
In case the server does not provide a valid certificate (or in case it is self-signed)
|
||||
you want to customize ``HttpClient``'s TLS configuration as described in
|
||||
link:#client-http-configuration-tls[this section].
|
||||
Like browsers, HTTPS requests are supported out-of-the-box, as long as the server provides a valid certificate.
|
||||
In case the server does not provide a valid certificate (or in case it is self-signed) you want to customize ``HttpClient``'s TLS configuration as described in xref:eg-client-http-configuration-tls[this section].
|
||||
|
||||
[[client-http-stop]]
|
||||
[[eg-client-http-stop]]
|
||||
==== Stopping HttpClient
|
||||
|
||||
It is recommended that when your application stops, you also stop the `HttpClient` instance (or instances) that you are using.
|
||||
|
@ -84,92 +89,109 @@ include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=stop]
|
|||
|
||||
Stopping `HttpClient` makes sure that the memory it holds (for example, authentication credentials, cookies, etc.) is released, and that the thread pool and scheduler are properly stopped allowing all threads used by `HttpClient` to exit.
|
||||
|
||||
[[client-http-arch]]
|
||||
[[eg-client-http-arch]]
|
||||
==== HttpClient Architecture
|
||||
|
||||
A `HttpClient` instance can be thought as a browser instance, and it manages the
|
||||
following components:
|
||||
A `HttpClient` instance can be thought as a browser instance, and it manages the following components:
|
||||
|
||||
* a `CookieStore` (see link:#client-http-cookie[this section]).
|
||||
* a `AuthenticationStore` (see link:#client-http-authentication[this section]).
|
||||
* a `ProxyConfiguration` (see link:#client-http-proxy[this section]).
|
||||
* a `CookieStore` (see xref:eg-client-http-cookie[this section]).
|
||||
* a `AuthenticationStore` (see xref:eg-client-http-authentication[this section]).
|
||||
* a `ProxyConfiguration` (see xref:eg-client-http-proxy[this section]).
|
||||
* a set of _destinations_.
|
||||
|
||||
A _destination_ is the client-side component that represent an _origin_ on
|
||||
a server, and manages a queue of requests for that origin, and a pool of
|
||||
connections to that origin.
|
||||
A _destination_ is the client-side component that represent an _origin_ on a server, and manages a queue of requests for that origin, and a xref:eg-client-http-connection-pool[pool of connections] to that origin.
|
||||
|
||||
An _origin_ may be simply thought as the tuple `(scheme, host, port)` and it
|
||||
is where the client connects to in order to communicate with the server.
|
||||
An _origin_ may be simply thought as the tuple `(scheme, host, port)` and it is where the client connects to in order to communicate with the server.
|
||||
However, this is not enough.
|
||||
|
||||
If you use `HttpClient` to write a proxy you may have different clients that
|
||||
want to contact the same server.
|
||||
In this case, you may not want to use the same proxy-to-server connection to
|
||||
proxy requests for both clients, for example for authentication reasons: the
|
||||
server may associate the connection with authentication credentials and you
|
||||
do not want to use the same connection for two different users that have
|
||||
different credentials.
|
||||
Instead, you want to use different connections for different clients and
|
||||
this can be achieved by "tagging" a destination with a tag object that
|
||||
represents the remote client (for example, it could be the remote client IP
|
||||
address).
|
||||
If you use `HttpClient` to write a proxy you may have different clients that want to contact the same server.
|
||||
In this case, you may not want to use the same proxy-to-server connection to proxy requests for both clients, for example for authentication reasons: the server may associate the connection with authentication credentials and you do not want to use the same connection for two different users that have different credentials.
|
||||
Instead, you want to use different connections for different clients and this can be achieved by "tagging" a destination with a tag object that represents the remote client (for example, it could be the remote client IP address).
|
||||
|
||||
Two origins with the same `(scheme, host, port)` but different `tag`
|
||||
create two different destinations and therefore two different connection pools.
|
||||
Two origins with the same `(scheme, host, port)` but different `tag` create two different destinations and therefore two different connection pools.
|
||||
However, also this is not enough.
|
||||
|
||||
It is possible that a server speaks different protocols on the same `port`.
|
||||
A connection may start by speaking one protocol, for example HTTP/1.1, but
|
||||
then be upgraded to speak a different protocol, for example HTTP/2.
|
||||
After a connection has been upgraded to a second protocol, it cannot speak
|
||||
the first protocol anymore, so it can only be used to communicate using
|
||||
the second protocol.
|
||||
A connection may start by speaking one protocol, for example HTTP/1.1, but then be upgraded to speak a different protocol, for example HTTP/2. After a connection has been upgraded to a second protocol, it cannot speak the first protocol anymore, so it can only be used to communicate using the second protocol.
|
||||
|
||||
Two origins with the same `(scheme, host, port)` but different
|
||||
`protocol` create two different destinations and therefore two different
|
||||
connection pools.
|
||||
Two origins with the same `(scheme, host, port)` but different `protocol` create two different destinations and therefore two different connection pools.
|
||||
|
||||
Therefore an origin is identified by the tuple
|
||||
`(scheme, host, port, tag, protocol)`.
|
||||
Therefore an origin is identified by the tuple `(scheme, host, port, tag, protocol)`.
|
||||
|
||||
[[client-http-request-processing]]
|
||||
[[eg-client-http-connection-pool]]
|
||||
==== HttpClient Connection Pooling
|
||||
|
||||
A destination manages a `org.eclipse.jetty.client.ConnectionPool`, where connections to a particular origin are pooled for performance reasons:
|
||||
opening a connection is a costly operation and it's better to reuse them for multiple requests.
|
||||
|
||||
NOTE: Remember that to select a specific destination you must select a specific origin, and that an origin is identified by the tuple `(scheme, host, port, tag, protocol)`, so you can have multiple destinations for the same `host` and `port`.
|
||||
|
||||
You can access the `ConnectionPool` in this way:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=getConnectionPool]
|
||||
----
|
||||
|
||||
Jetty's client library provides the following `ConnectionPool` implementations:
|
||||
|
||||
* `DuplexConnectionPool`, historically the first implementation, only used by the HTTP/1.1 transport.
|
||||
* `MultiplexConnectionPool`, the generic implementation valid for any transport where connections are reused with a MRU (most recently used) algorithm (that is, the connections most recently returned to the connection pool are the more likely to be used again).
|
||||
* `RoundRobinConnectionPool`, similar to `MultiplexConnectionPool` but where connections are reused with a round-robin algorithm.
|
||||
|
||||
The `ConnectionPool` implementation can be customized for each destination in by setting a `ConnectionPool.Factory` on the `HttpClientTransport`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tags=setConnectionPool]
|
||||
----
|
||||
|
||||
[[eg-client-http-request-processing]]
|
||||
==== HttpClient Request Processing
|
||||
|
||||
When a request is sent, an origin is computed from the request; `HttpClient`
|
||||
uses that origin to find (or create if it does not exist) the correspondent
|
||||
destination.
|
||||
The request is then queued onto the destination, and this causes the
|
||||
destination to ask its connection pool for a free connection.
|
||||
If a connection is available, it is returned, otherwise a new connection is
|
||||
created.
|
||||
Once the destination has obtained the connection, it dequeues the request
|
||||
and sends it over the connection.
|
||||
[plantuml]
|
||||
----
|
||||
skinparam backgroundColor transparent
|
||||
skinparam monochrome true
|
||||
skinparam shadowing false
|
||||
|
||||
The first request to a destination triggers the opening of the first
|
||||
connection.
|
||||
A second request with the same origin sent _after_ the first will reuse the
|
||||
same connection.
|
||||
A second request with the same origin sent _concurrently_ with the first
|
||||
request will cause the opening of a second connection.
|
||||
The configuration parameter `HttpClient.maxConnectionsPerDestination`
|
||||
(see also the link:#client-http-configuration[configuration section]) controls
|
||||
the max number of connections that can be opened for a destination.
|
||||
participant Application
|
||||
participant Request
|
||||
participant HttpClient
|
||||
participant Destination
|
||||
participant ConnectionPool
|
||||
participant Connection
|
||||
|
||||
NOTE: If opening connections to a given origin takes a long time, then
|
||||
requests for that origin will queue up in the corresponding destination.
|
||||
Application -> HttpClient : newRequest()
|
||||
HttpClient -> Request **
|
||||
Application -> Request : send()
|
||||
Request -> HttpClient : send()
|
||||
HttpClient -> Destination ** : get or create
|
||||
Destination -> ConnectionPool ** : create
|
||||
HttpClient -> Destination : send(Request)
|
||||
Destination -> Destination : enqueue(Request)
|
||||
Destination -> ConnectionPool : acquire()
|
||||
ConnectionPool -> Connection ** : create
|
||||
Destination -> Destination : dequeue(Request)
|
||||
Destination -> Connection : send(Request)
|
||||
----
|
||||
|
||||
Each connection can handle a limited number of requests.
|
||||
For HTTP/1.1, this number is always `1`: there can only be one outstanding
|
||||
request for each connection.
|
||||
For HTTP/2 this number is determined by the server `max_concurrent_stream`
|
||||
setting (typically around `100`, i.e. there can be up to `100` outstanding
|
||||
requests for every connection).
|
||||
When a request is sent, an origin is computed from the request; `HttpClient` uses that origin to find (or create if it does not exist) the correspondent destination.
|
||||
The request is then queued onto the destination, and this causes the destination to ask its connection pool for a free connection.
|
||||
If a connection is available, it is returned, otherwise a new connection is created.
|
||||
Once the destination has obtained the connection, it dequeues the request and sends it over the connection.
|
||||
|
||||
When a destination has maxed out its number of connections, and all
|
||||
connections have maxed out their number of outstanding requests, more requests
|
||||
sent to that destination will be queued.
|
||||
The first request to a destination triggers the opening of the first connection.
|
||||
A second request with the same origin sent _after_ the first request/response cycle is completed will reuse the same connection.
|
||||
A second request with the same origin sent _concurrently_ with the first request will cause the opening of a second connection.
|
||||
The configuration parameter `HttpClient.maxConnectionsPerDestination` (see also the xref:eg-client-http-configuration[configuration section]) controls the max number of connections that can be opened for a destination.
|
||||
|
||||
NOTE: If opening connections to a given origin takes a long time, then requests for that origin will queue up in the corresponding destination.
|
||||
|
||||
Each connection can handle a limited number of concurrent requests.
|
||||
For HTTP/1.1, this number is always `1`: there can only be one outstanding request for each connection.
|
||||
For HTTP/2 this number is determined by the server `max_concurrent_stream` setting (typically around `100`, i.e. there can be up to `100` outstanding requests for every connection).
|
||||
|
||||
When a destination has maxed out its number of connections, and all connections have maxed out their number of outstanding requests, more requests sent to that destination will be queued.
|
||||
When the request queue is full, the request will be failed.
|
||||
The configuration parameter `HttpClient.maxRequestsQueuedPerDestination`
|
||||
(see also the link:#client-http-configuration[configuration section]) controls
|
||||
the max number of requests that can be queued for a destination.
|
||||
The configuration parameter `HttpClient.maxRequestsQueuedPerDestination` (see also the xref:eg-client-http-configuration[configuration section]) controls the max number of requests that can be queued for a destination.
|
||||
|
|
|
@ -16,88 +16,65 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client-http-proxy]]
|
||||
=== Proxy Support
|
||||
[[eg-client-http-proxy]]
|
||||
=== HttpClient Proxy Support
|
||||
|
||||
Jetty's HTTP client can be configured to use proxies to connect to destinations.
|
||||
Jetty's `HttpClient` can be configured to use proxies to connect to destinations.
|
||||
|
||||
Two types of proxies are available out of the box: a HTTP proxy (provided by class `org.eclipse.jetty.client.HttpProxy`) and a SOCKS 4 proxy (provided by class `org.eclipse.jetty.client.Socks4Proxy`).
|
||||
Other implementations may be written by subclassing `ProxyConfiguration.Proxy`.
|
||||
|
||||
The following is a typical configuration:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
|
||||
HttpProxy proxy = new HttpProxy("proxyHost", proxyPort);
|
||||
|
||||
// Do not proxy requests for localhost:8080
|
||||
proxy.getExcludedAddresses().add("localhost:8080");
|
||||
|
||||
// add the new proxy to the list of proxies already registered
|
||||
proxyConfig.getProxies().add(proxy);
|
||||
|
||||
ContentResponse response = httpClient.GET(uri);
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=proxy]
|
||||
----
|
||||
|
||||
You specify the proxy host and port, and optionally also the addresses that you do not want to be proxied, and then add the proxy configuration on the `ProxyConfiguration` instance.
|
||||
You specify the proxy host and proxy port, and optionally also the addresses that you do not want to be proxied, and then add the proxy configuration on the `ProxyConfiguration` instance.
|
||||
|
||||
Configured in this way, `HttpClient` makes requests to the HTTP proxy (for plain-text HTTP requests) or establishes a tunnel via `HTTP CONNECT` (for encrypted HTTPS requests).
|
||||
Configured in this way, `HttpClient` makes requests to the HTTP proxy (for plain-text HTTP requests) or establishes a tunnel via HTTP `CONNECT` (for encrypted HTTPS requests).
|
||||
|
||||
[[client-http-proxy-authentication]]
|
||||
Proxying is supported for both HTTP/1.1 and HTTP/2.
|
||||
|
||||
[[eg-client-http-proxy-authentication]]
|
||||
==== Proxy Authentication Support
|
||||
|
||||
Jetty's HTTP client support proxy authentication in the same way it supports link:#client-http-authentication[server authentication].
|
||||
Jetty's `HttpClient` supports proxy authentication in the same way it supports xref:eg-client-http-authentication[server authentication].
|
||||
|
||||
In the example below, the proxy requires Basic authentication, but the server requires Digest authentication, and therefore:
|
||||
In the example below, the proxy requires `BASIC` authentication, but the server requires `DIGEST` authentication, and therefore:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
URI proxyURI = new URI("http://proxy.net:8080");
|
||||
URI serverURI = new URI("http://domain.com/secure");
|
||||
|
||||
AuthenticationStore auth = httpClient.getAuthenticationStore();
|
||||
|
||||
// Proxy credentials.
|
||||
auth.addAuthentication(new BasicAuthentication(proxyURI, "ProxyRealm", "proxyUser", "proxyPass"));
|
||||
|
||||
// Server credentials.
|
||||
auth.addAuthentication(new DigestAuthentication(serverURI, "ServerRealm", "serverUser", "serverPass"));
|
||||
|
||||
// Proxy configuration.
|
||||
ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
|
||||
HttpProxy proxy = new HttpProxy("proxy.net", 8080);
|
||||
proxyConfig.getProxies().add(proxy);
|
||||
|
||||
ContentResponse response = httpClient.newRequest(serverURI)
|
||||
.send()
|
||||
.get(5, TimeUnit.SECONDS);
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=proxyAuthentication]
|
||||
----
|
||||
|
||||
The HTTP conversation for successful authentications on both the proxy and the server is the following:
|
||||
|
||||
[plantuml]
|
||||
----
|
||||
Application HttpClient Proxy Server
|
||||
| | | |
|
||||
|--- GET -->|------------- GET ------------->| |
|
||||
| | | |
|
||||
| |<----- 407 + Proxy-Authn -------| |
|
||||
| | | |
|
||||
| |------ GET + Proxy-Authz ------>| |
|
||||
| | | |
|
||||
| | |---------- GET --------->|
|
||||
| | | |
|
||||
| | |<--- 401 + WWW-Authn ----|
|
||||
| | | |
|
||||
| |<------ 401 + WWW-Authn --------| |
|
||||
| | | |
|
||||
| |-- GET + Proxy-Authz + Authz -->| |
|
||||
| | | |
|
||||
| | |------ GET + Authz ----->|
|
||||
| | | |
|
||||
|<-- 200 ---|<------------ 200 --------------|<--------- 200 ----------|
|
||||
skinparam backgroundColor transparent
|
||||
skinparam monochrome true
|
||||
skinparam shadowing false
|
||||
|
||||
participant Application
|
||||
participant HttpClient
|
||||
participant Proxy
|
||||
participant Server
|
||||
|
||||
Application -> Proxy : GET /path
|
||||
Proxy -> HttpClient : 407 + Proxy-Authenticate
|
||||
HttpClient -> Proxy : GET /path + Proxy-Authorization
|
||||
Proxy -> Server : GET /path
|
||||
Server -> Proxy : 401 + WWW-Authenticate
|
||||
Proxy -> HttpClient : 401 + WWW-Authenticate
|
||||
HttpClient -> Proxy : GET /path + Proxy-Authorization + Authorization
|
||||
Proxy -> Server : GET /path + Authorization
|
||||
Server -> Proxy : 200 OK
|
||||
Proxy -> HttpClient : 200 OK
|
||||
HttpClient -> Application : 200 OK
|
||||
----
|
||||
|
||||
The application does not receive events related to the responses with code 407 and 401 since they are handled internally by `HttpClient`.
|
||||
|
||||
Similarly to the link:#client-http-authentication[authentication section], the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips.
|
||||
Similarly to the xref:eg-client-http-authentication[authentication section], the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips.
|
||||
|
|
|
@ -16,21 +16,18 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[http-client-transport]]
|
||||
=== Pluggable Transports
|
||||
[[eg-client-http-transport]]
|
||||
=== HttpClient Pluggable Transports
|
||||
|
||||
Jetty's HTTP client can be configured to use different transports to carry the semantic of HTTP requests and responses.
|
||||
Jetty's `HttpClient` can be configured to use different transports to carry the semantic of HTTP requests and responses.
|
||||
|
||||
This means that the intention of a client to request resource `/index.html` using the `GET` method can be carried over the network in different formats.
|
||||
|
||||
A HTTP client transport is the component that is in charge of converting a high-level, semantic, HTTP requests such as "GET resource /index.html" into the specific format understood by the server (for example, HTTP/2), and to convert the server response from the specific format (HTTP/2) into high-level, semantic objects that can be used by applications.
|
||||
A `HttpClient` transport is the component that is in charge of converting a high-level, semantic, HTTP requests such as "`GET` resource ``/index.html``" into the specific format understood by the server (for example, HTTP/2), and to convert the server response from the specific format (HTTP/2) into high-level, semantic objects that can be used by applications.
|
||||
|
||||
In this way, applications are not aware of the actual protocol being used.
|
||||
This allows them to write their logic against a high-level API that hides the details of the specific protocol being used over the network.
|
||||
The most common protocol format is HTTP/1.1, a textual protocol with lines separated by `\r\n`:
|
||||
|
||||
The most common protocol format is HTTP/1.1, a text-based protocol with lines separated by `\r\n`:
|
||||
|
||||
[source, screen, subs="{sub-order}"]
|
||||
[source,screen]
|
||||
----
|
||||
GET /index.html HTTP/1.1\r\n
|
||||
Host: domain.com\r\n
|
||||
|
@ -40,7 +37,7 @@ Host: domain.com\r\n
|
|||
|
||||
However, the same request can be made using FastCGI, a binary protocol:
|
||||
|
||||
[source, screen, subs="{sub-order}"]
|
||||
[source,screen]
|
||||
----
|
||||
x01 x01 x00 x01 x00 x08 x00 x00
|
||||
x00 x01 x01 x00 x00 x00 x00 x00
|
||||
|
@ -54,62 +51,111 @@ x0C x0B D O C U M E
|
|||
|
||||
Similarly, HTTP/2 is a binary protocol that transports the same information in a yet different format.
|
||||
|
||||
A protocol may be _negotiated_ between client and server.
|
||||
A request for a resource may be sent using one protocol (for example, HTTP/1.1), but the response may arrive in a different protocol (for example, HTTP/2).
|
||||
|
||||
`HttpClient` supports 3 static transports, each speaking only one protocol: xref:eg-client-http-transport-http11[HTTP/1.1], xref:eg-client-http-transport-http2[HTTP/2] and xref:eg-client-http-transport-fcgi[FastCGI], all of them with 2 variants: clear-text and TLS encrypted.
|
||||
|
||||
`HttpClient` also supports one xref:eg-client-http-transport-dynamic[dynamic transport], that can speak different protocols and can select the right protocol by negotiating it with the server or by explicit indication from applications.
|
||||
|
||||
Applications are typically not aware of the actual protocol being used.
|
||||
This allows them to write their logic against a high-level API that hides the details of the specific protocol being used over the network.
|
||||
|
||||
[[eg-client-http-transport-http11]]
|
||||
==== HTTP/1.1 Transport
|
||||
|
||||
HTTP/1.1 is the default transport.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
// No transport specified, using default.
|
||||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=defaultTransport]
|
||||
----
|
||||
|
||||
If you want to customize the HTTP/1.1 transport, you can explicitly configure `HttpClient` in this way:
|
||||
If you want to customize the HTTP/1.1 transport, you can explicitly configure it in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
int selectors = 1;
|
||||
HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP(selectors);
|
||||
|
||||
HttpClient client = new HttpClient(transport, null);
|
||||
client.start();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=http11Transport]
|
||||
----
|
||||
|
||||
The example above allows you to customize the number of NIO selectors that `HttpClient` will be using.
|
||||
|
||||
[[eg-client-http-transport-http2]]
|
||||
==== HTTP/2 Transport
|
||||
|
||||
The HTTP/2 transport can be configured in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
HTTP2Client h2Client = new HTTP2Client();
|
||||
h2Client.setSelectors(1);
|
||||
HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client);
|
||||
|
||||
HttpClient client = new HttpClient(transport, null);
|
||||
client.start();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=http2Transport]
|
||||
----
|
||||
|
||||
`HTTP2Client` is the lower-level client that provides an API based on HTTP/2 concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2.
|
||||
`HTTP2Client` is the lower-level client that provides an API based on HTTP/2 concepts such as _sessions_, _streams_ and _frames_ that are specific to HTTP/2. See xref:eg-client-http2[the HTTP/2 client section] for more information.
|
||||
|
||||
`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic HTTP requests ("GET resource /index.html") into the HTTP/2 specific format.
|
||||
`HttpClientTransportOverHTTP2` uses `HTTP2Client` to format high-level semantic HTTP requests (like "GET resource /index.html") into the HTTP/2 specific format.
|
||||
|
||||
[[eg-client-http-transport-fcgi]]
|
||||
==== FastCGI Transport
|
||||
|
||||
The FastCGI transport can be configured in this way:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
int selectors = 1;
|
||||
String scriptRoot = "/var/www/wordpress";
|
||||
HttpClientTransportOverFCGI transport = new HttpClientTransportOverFCGI(selectors, false, scriptRoot);
|
||||
|
||||
HttpClient client = new HttpClient(transport, null);
|
||||
client.start();
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=fcgiTransport]
|
||||
----
|
||||
|
||||
In order to make requests using the FastCGI transport, you need to have a FastCGI server such as https://en.wikipedia.org/wiki/PHP#PHPFPM[PHP-FPM] (see also http://php.net/manual/en/install.fpm.php).
|
||||
|
||||
The FastCGI transport is primarily used by Jetty's link:#fastcgi[FastCGI support] to serve PHP pages (WordPress for example).
|
||||
|
||||
[[eg-client-http-transport-dynamic]]
|
||||
==== Dynamic Transport
|
||||
|
||||
The static transports work well if you know in advance the protocol you want to speak with the server, or if the server only supports one protocol (such as FastCGI).
|
||||
|
||||
With the advent of HTTP/2, however, servers are now able to support multiple protocols, at least both HTTP/1.1 and HTTP/2.
|
||||
|
||||
The HTTP/2 protocol is typically negotiated between client and server.
|
||||
This negotiation can happen via ALPN, a TLS extension that allows the client to tell the server the list of protocol that the client supports, so that the server can pick one of the client supported protocols that also the server supports; or via HTTP/1.1 upgrade by means of the `Upgrade` header.
|
||||
|
||||
Applications can configure the dynamic transport with one or more _application_ protocols such as HTTP/1.1 or HTTP/2. The implementation will take care of using TLS for HTTPS URIs, using ALPN, negotiating protocols, upgrading from one protocol to another, etc.
|
||||
|
||||
By default, the dynamic transport only speaks HTTP/1.1:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicDefault]
|
||||
----
|
||||
|
||||
The dynamic transport can be configured with just one protocol, making it equivalent to the corresponding static transport:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicOneProtocol]
|
||||
----
|
||||
|
||||
The dynamic transport, however, has been implemented to support multiple transports, in particular both HTTP/1.1 and HTTP/2:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicH1H2]
|
||||
----
|
||||
|
||||
IMPORTANT: The order in which the protocols are specified to `HttpClientTransportDynamic` indicates what is the client preference.
|
||||
If the protocol is negotiated via ALPN, it is the server that decides what is the protocol to use for the communication, regardless of the client preference.
|
||||
If the protocol is not negotiated, the client preference is honored.
|
||||
|
||||
Provided that the server supports both HTTP/1.1 and HTTP/2 clear-text, client applications can explicitly hint the version they want to use:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http/HTTPClientDocs.java[tag=dynamicClearText]
|
||||
----
|
||||
|
||||
In case of TLS encrypted communication using the HTTPS scheme, things are a little more complicated.
|
||||
|
||||
If the client application explicitly specifies the HTTP version, then ALPN is not used on the client.
|
||||
By specifying the HTTP version explicitly, the client application has prior-knowledge of what HTTP version the server supports, and therefore ALPN is not needed.
|
||||
If the server does not support the HTTP version chosen by the client, then the communication will fail.
|
||||
|
||||
If the client application does not explicitly specify the HTTP version, then ALPN will be used on the client.
|
||||
If the server also supports ALPN, then the protocol will be negotiated via ALPN and the server will choose the protocol to use.
|
||||
If the server does not support ALPN, the client will try to use the first protocol configured in `HttpClientTransportDynamic`, and the communication may succeed or fail depending on whether the server supports the protocol chosen by the client.
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
[[client-http]]
|
||||
[[eg-client-http]]
|
||||
=== HTTP Client
|
||||
|
||||
include::client-http-intro.adoc[]
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[eg-client-http2]]
|
||||
=== HTTP/2 Client Library
|
||||
|
||||
In the vast majority of cases, client applications should use the generic, high-level, xref:eg-client-http[HTTP client library] that also provides HTTP/2 support via the pluggable xref:eg-client-http-transport-http2[HTTP/2 transport] or the xref:eg-client-http-transport-dynamic[dynamic transport].
|
||||
|
||||
The high-level HTTP library supports cookies, authentication, redirection, connection pooling and a number of other features that are absent in the low-level HTTP/2 library.
|
||||
|
||||
The HTTP/2 client library has been designed for those applications that need low-level access to HTTP/2 features such as _sessions_, _streams_ and _frames_, and this is quite a rare use case.
|
||||
|
||||
See also the correspondent xref:eg-server-http2[HTTP/2 server library].
|
||||
|
||||
[[eg-client-http2-intro]]
|
||||
==== Introducing HTTP2Client
|
||||
|
||||
The Maven artifact coordinates for the HTTP/2 client library are the following:
|
||||
|
||||
[source,xml,subs=normal]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-client</artifactId>
|
||||
<version>{version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
The main class is named `org.eclipse.jetty.http2.client.HTTP2Client`, and must be created, configured and started before use:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=start]
|
||||
----
|
||||
|
||||
When your application stops, or otherwise does not need `HTTP2Client` anymore, it should stop the `HTTP2Client` instance (or instances) that were started:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=stop]
|
||||
----
|
||||
|
||||
`HTTP2Client` allows client applications to connect to an HTTP/2 server.
|
||||
A _session_ represents a single TCP connection to an HTTP/2 server and is defined by class `org.eclipse.jetty.http2.api.Session`.
|
||||
A _session_ typically has a long life - once the TCP connection is established, it remains open until it is not used anymore (and therefore it is closed by the idle timeout mechanism), until a fatal error occurs (for example, a network failure), or if one of the peers decides unilaterally to close the TCP connection.
|
||||
|
||||
include::../../http2.adoc[tag=multiplex]
|
||||
|
||||
[[eg-client-http2-flow-control]]
|
||||
===== HTTP/2 Flow Control
|
||||
|
||||
include::../../http2.adoc[tag=flowControl]
|
||||
|
||||
How a client application should handle HTTP/2 flow control is discussed in details in xref:eg-client-http2-response[this section].
|
||||
|
||||
[[eg-client-http2-connect]]
|
||||
==== Connecting to the Server
|
||||
|
||||
The first thing an application should do is to connect to the server and obtain a `Session`.
|
||||
The following example connects to the server on a clear-text port:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=clearTextConnect]
|
||||
----
|
||||
|
||||
The following example connects to the server on an encrypted port:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=encryptedConnect]
|
||||
----
|
||||
|
||||
IMPORTANT: Applications must know in advance whether they want to connect to a clear-text or encrypted port, and pass the `SslContextFactory` parameter accordingly to the `connect(...)` method.
|
||||
|
||||
[[eg-client-http2-configure]]
|
||||
===== Configuring the Session
|
||||
|
||||
The `connect(...)` method takes a `Session.Listener` parameter.
|
||||
This listener's `onPreface(...)` method is invoked just before establishing the connection to the server to gather the client configuration to send to the server.
|
||||
Client applications can override this method to change the default configuration:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=configure]
|
||||
----
|
||||
|
||||
The `Session.Listener` is notified of session events originated by the server such as receiving a `SETTINGS` frame from the server, or the server closing the connection, or the client timing out the connection due to idleness.
|
||||
Please refer to the `Session.Listener` link:{JDURL}/org/eclipse/jetty/http2/api/Session.Listener.html[javadocs] for the complete list of events.
|
||||
|
||||
Once a `Session` has been established, the communication with the server happens by exchanging _frames_, as specified in the link:https://tools.ietf.org/html/rfc7540#section-4[HTTP/2 specification].
|
||||
|
||||
[[eg-client-http2-request]]
|
||||
==== Sending a Request
|
||||
|
||||
Sending an HTTP request to the server, and receiving a response, creates a _stream_ that encapsulates the exchange of HTTP/2 frames that compose the request and the response.
|
||||
|
||||
In order to send an HTTP request to the server, the client must send a `HEADERS` frame.
|
||||
`HEADERS` frames carry the request method, the request URI and the request headers.
|
||||
Sending the `HEADERS` frame opens the `Stream`:
|
||||
|
||||
[source,java,indent=0,subs=normal]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=newStream]
|
||||
----
|
||||
|
||||
Note how `Session.newStream(...)` takes a `Stream.Listener` parameter.
|
||||
This listener is notified of stream events originated by the server such as receiving `HEADERS` or `DATA` frames that are part of the response, discussed in more details in the xref:eg-client-http2-response[section below].
|
||||
Please refer to the `Stream.Listener` link:{JDURL}/org/eclipse/jetty/http2/api/Stream.Listener.html[javadocs] for the complete list of events.
|
||||
|
||||
HTTP requests may have content, which is sent using the `Stream` APIs:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=newStreamWithData]
|
||||
----
|
||||
|
||||
IMPORTANT: When sending two `DATA` frames consecutively, the second call to `Stream.data(...)` must be done only when the first is completed, or a `WritePendingException` will be thrown.
|
||||
Use the `Callback` APIs or `CompletableFuture` APIs to ensure that the second `Stream.data(...)` call is performed when the first completed successfully.
|
||||
|
||||
[[eg-client-http2-response]]
|
||||
==== Receiving a Response
|
||||
|
||||
Response events are delivered to the `Stream.Listener` passed to `Session.newStream(...)`.
|
||||
|
||||
An HTTP response is typically composed of a `HEADERS` frame containing the HTTP status code and the response headers, and optionally one or more `DATA` frames containing the response content bytes.
|
||||
|
||||
The HTTP/2 protocol also supports response trailers (that is, headers that are sent after the response content) that also are sent using a `HEADERS` frame.
|
||||
|
||||
A client application can therefore receive the HTTP/2 frames sent by the server by implementing the relevant methods in `Stream.Listener`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=responseListener]
|
||||
----
|
||||
|
||||
include::../../http2.adoc[tag=apiFlowControl]
|
||||
|
||||
[[eg-client-http2-reset]]
|
||||
==== Resetting a Request or Response
|
||||
|
||||
In HTTP/2, clients and servers have the ability to tell to the other peer that they are not interested anymore in either the request or the response, using a `RST_STREAM` frame.
|
||||
|
||||
The `HTTP2Client` APIs allow client applications to send and receive this "reset" frame:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=reset]
|
||||
----
|
||||
|
||||
[[eg-client-http2-push]]
|
||||
==== Receiving HTTP/2 Pushes
|
||||
|
||||
HTTP/2 servers have the ability to push resources related to a primary resource.
|
||||
When an HTTP/2 server pushes a resource, it sends to the client a `PUSH_PROMISE` frame that contains the request URI and headers that a client would use to request explicitly that resource.
|
||||
|
||||
Client applications can be configured to tell the server to never push resources, see xref:eg-client-http2-configure[this section].
|
||||
|
||||
Client applications can listen to the push events, and act accordingly:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=push]
|
||||
----
|
||||
|
||||
If a client application does not want to handle a particular HTTP/2 push, it can just reset the pushed stream to tell the server to stop sending bytes for the pushed stream:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/embedded/client/http2/HTTP2ClientDocs.java[tags=pushReset]
|
||||
----
|
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
// Snippets of HTTP/2 documentation that are common between client and server.
|
||||
|
||||
tag::multiplex[]
|
||||
HTTP/2 is a multiplexed protocol: it allows multiple HTTP/2 requests to be sent on the same TCP connection.
|
||||
Each request/response cycle is represented by a _stream_.
|
||||
Therefore, a single _session_ manages multiple concurrent _streams_.
|
||||
A _stream_ has typically a very short life compared to the _session_: a _stream_ only exists for the duration of the request/response cycle and then disappears.
|
||||
end::multiplex[]
|
||||
|
||||
tag::flowControl[]
|
||||
The HTTP/2 protocol is _flow controlled_ (see link:https://tools.ietf.org/html/rfc7540#section-5.2[the specification]).
|
||||
This means that a sender and a receiver maintain a _flow control window_ that tracks the number of data bytes sent and received, respectively.
|
||||
When a sender sends data bytes, it reduces its flow control window.
|
||||
When a receiver receives data bytes, it also reduces its flow control window, and then passes the received data bytes to the application.
|
||||
The application consumes the data bytes and tells back the receiver that it has consumed the data bytes.
|
||||
The receiver then enlarges the flow control window, and arranges to send a message to the sender with the number of bytes consumed, so that the sender can enlarge its flow control window.
|
||||
|
||||
A sender can send data bytes up to its whole flow control window, then it must stop sending until it receives a message from the receiver that the data bytes have been consumed, which enlarges the flow control window, which allows the sender to send more data bytes.
|
||||
|
||||
HTTP/2 defines _two_ flow control windows: one for each _session_, and one for each _stream_.
|
||||
Let's see with an example how they interact, assuming that in this example the session flow control window is 120 bytes and the stream flow control window is 100 bytes.
|
||||
|
||||
The sender opens a session, and then opens `stream_1` on that session, and sends `80` data bytes.
|
||||
At this point the session flow control window is `40` bytes (`120 - 80`), and ``stream_1``'s flow control window is `20` bytes (`100 - 80`).
|
||||
The sender now opens `stream_2` on the same session and sends `40` data bytes.
|
||||
At this point, the session flow control window is `0` bytes (`40 - 40`), while ``stream_2``'s flow control window is `60` (`100 - 40`).
|
||||
Since now the session flow control window is `0`, the sender cannot send more data bytes, neither on `stream_1` nor on `stream_2` despite both have their stream flow control windows greater than `0`.
|
||||
|
||||
The receiver consumes ``stream_2``'s `40` data bytes and sends a message to the sender with this information.
|
||||
At this point, the session flow control window is `40` (`0 40`), ``stream_1``'s flow control window is still `20` and ``stream_2``'s flow control window is `100` (`60 40`).
|
||||
If the sender opens `stream_3` and would like to send 50 data bytes, it would only be able to send `40` because that is the maximum allowed by the session flow control window at this point.
|
||||
|
||||
It is therefore very important that applications notify the fact that they have consumed data bytes as soon as possible, so that the implementation (the receiver) can send a message to the sender (in the form of a `WINDOW_UPDATE` frame) with the information to enlarge the flow control window, therefore reducing the possibility that sender stalls due to the flow control windows being reduced to `0`.
|
||||
end::flowControl[]
|
||||
|
||||
tag::apiFlowControl[]
|
||||
NOTE: Returning from the `onData(...)` method implicitly demands for more `DATA` frames (unless the one just delivered was the last).
|
||||
Additional `DATA` frames may be delivered immediately if they are available or later, asynchronously, when they arrive.
|
||||
|
||||
Applications that consume the content buffer within `onData(...)` (for example, writing it to a file, or copying the bytes to another storage) should succeed the callback as soon as they have consumed the content buffer.
|
||||
This allows the implementation to reuse the buffer, reducing the memory requirements needed to handle the content buffers.
|
||||
|
||||
Alternatively, a client application may store away _both_ the buffer and the callback to consume the buffer bytes later, or pass _both_ the buffer and the callback to another asynchronous API (this is typical in proxy applications).
|
||||
|
||||
IMPORTANT: Completing the `Callback` is very important not only to allow the implementation to reuse the buffer, but also tells the implementation to enlarge the stream and session flow control windows so that the sender will be able to send more `DATA` frames without stalling.
|
||||
|
||||
Applications can also precisely control _when_ to demand more `DATA` frames, by implementing the `onDataDemanded(...)` method instead of `onData(...)`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/embedded/HTTP2Docs.java[tags=dataDemanded]
|
||||
----
|
||||
|
||||
IMPORTANT: Applications that implement `onDataDemanded(...)` must remember to call `Stream.demand(...)`.
|
||||
If they don't, the implementation will not deliver `DATA` frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session.
|
||||
end::apiFlowControl[]
|
|
@ -23,8 +23,12 @@
|
|||
:revdate: {TIMESTAMP}
|
||||
:toc: left
|
||||
:toc-title: Embedded Guide
|
||||
:toc-image: ../../common/images/jetty-logo.svg
|
||||
:toc-image-url: /jetty/index.html
|
||||
:toc-style:
|
||||
|
||||
:header-style: eclipse-thin
|
||||
:breadcrumb-style: eclipse-thin
|
||||
:footer-style: default
|
||||
|
||||
:breadcrumb: Home:../index.html | Embedded Guide:./index.html
|
||||
|
||||
// docinfo lets you pull in shared content and/or influence via render type
|
||||
|
@ -43,12 +47,6 @@ endif::[]
|
|||
// options for special blocks, code snippets, screen, etc
|
||||
:sub-order: attributes+
|
||||
|
||||
// suppress document footer generation
|
||||
//:nofooter:
|
||||
|
||||
// suppress Eclipse footer
|
||||
:no-eclipse-footer:
|
||||
|
||||
// uncomment to allow include::https:// style content inclusion
|
||||
//:allow-uri-read: true
|
||||
|
||||
|
|
|
@ -17,36 +17,25 @@
|
|||
//
|
||||
|
||||
[appendix]
|
||||
[[io-arch]]
|
||||
[[eg-io-arch]]
|
||||
== Jetty I/O Architecture
|
||||
|
||||
Jetty libraries (both client and server) use Java NIO to handle I/O, so that
|
||||
at its core Jetty I/O is completely non-blocking.
|
||||
Jetty libraries (both client and server) use Java NIO to handle I/O, so that at its core Jetty I/O is completely non-blocking.
|
||||
|
||||
[[io-arch-selector-manager]]
|
||||
[[eg-io-arch-selector-manager]]
|
||||
=== Jetty I/O: `SelectorManager`
|
||||
|
||||
The core class of Jetty I/O is
|
||||
link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`].
|
||||
The core class of Jetty I/O is link:{JDURL}/org/eclipse/jetty/io/SelectorManager.html[`SelectorManager`].
|
||||
|
||||
`SelectorManager` manages internally a configurable number of
|
||||
link:{JDURL}/org/eclipse/jetty/io/ManagedSelector.html[`ManagedSelector`]s.
|
||||
Each `ManagedSelector` wraps an instance of `java.nio.channels.Selector` that
|
||||
in turn manages a number of `java.nio.channels.SocketChannel` instances.
|
||||
`SelectorManager` manages internally a configurable number of link:{JDURL}/org/eclipse/jetty/io/ManagedSelector.html[`ManagedSelector`]s.
|
||||
Each `ManagedSelector` wraps an instance of `java.nio.channels.Selector` that in turn manages a number of `java.nio.channels.SocketChannel` instances.
|
||||
|
||||
NOTE: TODO: add image
|
||||
|
||||
`SocketChannel` instances can be created by network clients when connecting
|
||||
to a server and by a network server when accepting connections from network
|
||||
clients.
|
||||
In both cases the `SocketChannel` instance is passed to `SelectorManager`
|
||||
(which passes it to `ManagedSelector` and eventually to
|
||||
`java.nio.channels.Selector`) to be registered for use within Jetty.
|
||||
`SocketChannel` instances can be created by network clients when connecting to a server and by a network server when accepting connections from network clients.
|
||||
In both cases the `SocketChannel` instance is passed to `SelectorManager` (which passes it to `ManagedSelector` and eventually to `java.nio.channels.Selector`) to be registered for use within Jetty.
|
||||
|
||||
It is possible for an application to create the `SocketChannel`
|
||||
instances outside Jetty, even perform some initial network traffic also
|
||||
outside Jetty (for example for authentication purposes), and then pass the
|
||||
`SocketChannel` instance to `SelectorManager` for use within Jetty.
|
||||
It is possible for an application to create the `SocketChannel` instances outside Jetty, even perform some initial network traffic also outside Jetty (for example for authentication purposes), and then pass the `SocketChannel` instance to `SelectorManager` for use within Jetty.
|
||||
|
||||
This example shows how a client can connect to a server:
|
||||
|
||||
|
@ -62,146 +51,84 @@ This example shows how a server accepts a client connection:
|
|||
include::{doc_code}/embedded/SelectorManagerDocs.java[tags=accept]
|
||||
----
|
||||
|
||||
[[io-arch-endpoint-connection]]
|
||||
[[eg-io-arch-endpoint-connection]]
|
||||
=== Jetty I/O: `EndPoint` and `Connection`
|
||||
|
||||
``SocketChannel``s that are passed to `SelectorManager` are wrapped into two
|
||||
related components:
|
||||
an link:{JDURL}/org/eclipse/jetty/io/EndPoint.html[`EndPoint`] and a
|
||||
link:{JDURL}/org/eclipse/jetty/io/Connection.html[`Connection`].
|
||||
``SocketChannel``s that are passed to `SelectorManager` are wrapped into two related components: an link:{JDURL}/org/eclipse/jetty/io/EndPoint.html[`EndPoint`] and a link:{JDURL}/org/eclipse/jetty/io/Connection.html[`Connection`].
|
||||
|
||||
`EndPoint` is the Jetty abstraction for a `SocketChannel`: you can read bytes
|
||||
from an `EndPoint` via `EndPoint.fill(ByteBuffer)`, you can write bytes to an
|
||||
`EndPoint` via `EndPoint.flush(ByteBuffer...)` and
|
||||
`EndPoint.write(Callback, ByteBuffer...)`, you can close an `EndPoint` via
|
||||
`EndPoint.close()`, etc.
|
||||
`EndPoint` is the Jetty abstraction for a `SocketChannel`: you can read bytes from an `EndPoint` via `EndPoint.fill(ByteBuffer)`, you can write bytes to an `EndPoint` via `EndPoint.flush(ByteBuffer...)` and `EndPoint.write(Callback, ByteBuffer...)`, you can close an `EndPoint` via `EndPoint.close()`, etc.
|
||||
|
||||
`Connection` is the Jetty abstraction that is responsible to read bytes from
|
||||
the `EndPoint` and to deserialize the read bytes into objects.
|
||||
For example, a HTTP/1.1 server-side `Connection` implementation is responsible
|
||||
to deserialize HTTP/1.1 request bytes into a HTTP request object.
|
||||
Conversely, a HTTP/1.1 client-side `Connection` implementation is responsible
|
||||
to deserialize HTTP/1.1 response bytes into a HTTP response object.
|
||||
`Connection` is the Jetty abstraction that is responsible to read bytes from the `EndPoint` and to deserialize the read bytes into objects.
|
||||
For example, a HTTP/1.1 server-side `Connection` implementation is responsible to deserialize HTTP/1.1 request bytes into a HTTP request object.
|
||||
Conversely, a HTTP/1.1 client-side `Connection` implementation is responsible to deserialize HTTP/1.1 response bytes into a HTTP response object.
|
||||
|
||||
`Connection` is the abstraction that implements the reading side of a specific
|
||||
protocol such as HTTP/1.1, or HTTP/2, or WebSocket: it is able to read incoming
|
||||
communication in that protocol.
|
||||
`Connection` is the abstraction that implements the reading side of a specific protocol such as HTTP/1.1, or HTTP/2, or WebSocket: it is able to read incoming communication in that protocol.
|
||||
|
||||
The writing side for a specific protocol _may_ be implemented in the `Connection`
|
||||
but may also be implemented in other components, although eventually the bytes
|
||||
to be written will be written through the `EndPoint`.
|
||||
The writing side for a specific protocol _may_ be implemented in the `Connection` but may also be implemented in other components, although eventually the bytes to be written will be written through the `EndPoint`.
|
||||
|
||||
While there is primarily just one implementation of `EndPoint`,
|
||||
link:{JDURL}/org/eclipse/jetty/io/SocketChannelEndPoint.html[`SocketChannelEndPoint`]
|
||||
(used both on the client-side and on the server-side), there are many
|
||||
implementations of `Connection`, typically two for each protocol (one for the
|
||||
client-side and one for the server-side).
|
||||
While there is primarily just one implementation of `EndPoint`,link:{JDURL}/org/eclipse/jetty/io/SocketChannelEndPoint.html[`SocketChannelEndPoint`] (used both on the client-side and on the server-side), there are many implementations of `Connection`, typically two for each protocol (one for the client-side and one for the server-side).
|
||||
|
||||
The `EndPoint` and `Connection` pairs can be chained, for example in case of
|
||||
encrypted communication using the TLS protocol.
|
||||
There is an `EndPoint` and `Connection` TLS pair where the `EndPoint` reads the
|
||||
encrypted bytes from the network and the `Connection` decrypts them; next in the
|
||||
chain there is an `EndPoint` and `Connection` pair where the `EndPoint` "reads"
|
||||
decrypted bytes (provided by the previous `Connection`) and the `Connection`
|
||||
deserializes them into specific protocol objects (for example HTTP/2 frame
|
||||
objects).
|
||||
The `EndPoint` and `Connection` pairs can be chained, for example in case of encrypted communication using the TLS protocol.
|
||||
There is an `EndPoint` and `Connection` TLS pair where the `EndPoint` reads the encrypted bytes from the network and the `Connection` decrypts them; next in the chain there is an `EndPoint` and `Connection` pair where the `EndPoint` "reads" decrypted bytes (provided by the previous `Connection`) and the `Connection` deserializes them into specific protocol objects (for example HTTP/2 frame objects).
|
||||
|
||||
Certain protocols, such as WebSocket, start the communication with the server
|
||||
using one protocol (e.g. HTTP/1.1), but then change the communication to use
|
||||
another protocol (e.g. WebSocket).
|
||||
`EndPoint` supports changing the `Connection` object on-the-fly via
|
||||
`EndPoint.upgrade(Connection)`.
|
||||
This allows to use the HTTP/1.1 `Connection` during the initial communication
|
||||
and later to replace it with a WebSocket `Connection`.
|
||||
Certain protocols, such as WebSocket, start the communication with the server using one protocol (e.g. HTTP/1.1), but then change the communication to use another protocol (e.g. WebSocket).
|
||||
`EndPoint` supports changing the `Connection` object on-the-fly via `EndPoint.upgrade(Connection)`.
|
||||
This allows to use the HTTP/1.1 `Connection` during the initial communication and later to replace it with a WebSocket `Connection`.
|
||||
|
||||
NOTE: TODO: add a section on `UpgradeFrom` and `UpgradeTo`?
|
||||
|
||||
`SelectorManager` is an abstract class because while it knows how to create
|
||||
concrete `EndPoint` instances, it does not know how to create protocol
|
||||
specific `Connection` instances.
|
||||
`SelectorManager` is an abstract class because while it knows how to create concrete `EndPoint` instances, it does not know how to create protocol specific `Connection` instances.
|
||||
|
||||
Creating `Connection` instances is performed on the server-side by
|
||||
link:{JDURL}/org/eclipse/jetty/server/ConnectionFactory.html[`ConnectionFactory`]s.
|
||||
and on the client-side by
|
||||
link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`]s
|
||||
Creating `Connection` instances is performed on the server-side by link:{JDURL}/org/eclipse/jetty/server/ConnectionFactory.html[`ConnectionFactory`]s and on the client-side by link:{JDURL}/org/eclipse/jetty/io/ClientConnectionFactory.html[`ClientConnectionFactory`]s
|
||||
|
||||
On the server-side, the component that aggregates a `SelectorManager` with a
|
||||
set of ``ConnectionFactory``s is
|
||||
link:{JDURL}/org/eclipse/jetty/server/ServerConnector.html[`ServerConnector`]s.
|
||||
On the server-side, the component that aggregates a `SelectorManager` with a set of ``ConnectionFactory``s is link:{JDURL}/org/eclipse/jetty/server/ServerConnector.html[`ServerConnector`]s, see xref:eg-server-io-arch[].
|
||||
|
||||
NOTE: TODO: add a link to a server-side specific architecture section
|
||||
On the client-side, the components that aggregates a `SelectorManager` with a set of ``ClientConnectionFactory``s are link:{JDURL}/org/eclipse/jetty/client/HttpClientTransport.html[`HttpClientTransport`] subclasses, see xref:eg-client-io-arch[].
|
||||
|
||||
On the client-side, the components that aggregates a `SelectorManager` with a
|
||||
set of ``ClientConnectionFactory``s are
|
||||
link:{JDURL}/org/eclipse/jetty/client/HttpClientTransport.html[`HttpClientTransport`]
|
||||
subclasses.
|
||||
|
||||
NOTE: TODO: add a link to a client-side specific architecture section
|
||||
|
||||
[[io-arch-endpoint]]
|
||||
[[eg-io-arch-endpoint]]
|
||||
=== Jetty I/O: `EndPoint`
|
||||
|
||||
The Jetty I/O library use Java NIO to handle I/O, so that I/O is non-blocking.
|
||||
|
||||
At the Java NIO level, in order to be notified when a `SocketChannel` has data
|
||||
to be read, the `SelectionKey.OP_READ` flag must be set.
|
||||
At the Java NIO level, in order to be notified when a `SocketChannel` has data to be read, the `SelectionKey.OP_READ` flag must be set.
|
||||
|
||||
In the Jetty I/O library, you can call `EndPoint.fillInterested(Callback)`
|
||||
to declare interest in the "read" (or "fill") event, and the `Callback` parameter
|
||||
is the object that is notified when such event occurs.
|
||||
In the Jetty I/O library, you can call `EndPoint.fillInterested(Callback)` to declare interest in the "read" (or "fill") event, and the `Callback` parameter is the object that is notified when such event occurs.
|
||||
|
||||
At the Java NIO level, a `SocketChannel` is always writable, unless it becomes
|
||||
TCP congested. In order to be notified when a `SocketChannel` uncongests and it
|
||||
is therefore writable again, the `SelectionKey.OP_WRITE` flag must be set.
|
||||
At the Java NIO level, a `SocketChannel` is always writable, unless it becomes TCP congested.
|
||||
In order to be notified when a `SocketChannel` uncongests and it is therefore writable again, the `SelectionKey.OP_WRITE` flag must be set.
|
||||
|
||||
In the Jetty I/O library, you can call `EndPoint.write(Callback, ByteBuffer...)`
|
||||
to write the ``ByteBuffer``s and the `Callback` parameter is the object that is
|
||||
notified when the whole write is finished (i.e. _all_ ``ByteBuffer``s have been
|
||||
fully written, even if they are delayed by TCP congestion/uncongestion).
|
||||
In the Jetty I/O library, you can call `EndPoint.write(Callback, ByteBuffer...)` to write the ``ByteBuffer``s and the `Callback` parameter is the object that is notified when the whole write is finished (i.e. _all_ ``ByteBuffer``s have been fully written, even if they are delayed by TCP congestion/uncongestion).
|
||||
|
||||
The `EndPoint` APIs abstract out the Java NIO details by providing non-blocking
|
||||
APIs based on `Callback` objects for I/O operations.
|
||||
The `EndPoint` APIs are typically called by `Connection` implementations, see
|
||||
link:#io-arch-connection[this section].
|
||||
The `EndPoint` APIs abstract out the Java NIO details by providing non-blocking APIs based on `Callback` objects for I/O operations.
|
||||
The `EndPoint` APIs are typically called by `Connection` implementations, see xref:eg-io-arch-connection[this section].
|
||||
|
||||
[[io-arch-connection]]
|
||||
[[eg-io-arch-connection]]
|
||||
=== Jetty I/O: `Connection`
|
||||
|
||||
`Connection` is the abstraction that deserializes incoming bytes into objects,
|
||||
for example a HTTP request object or a WebSocket frame object, that can be used
|
||||
by more abstract layers.
|
||||
`Connection` is the abstraction that deserializes incoming bytes into objects, for example a HTTP request object or a WebSocket frame object, that can be used by more abstract layers.
|
||||
|
||||
`Connection` instances have two lifecycle methods:
|
||||
|
||||
* `Connection.onOpen()`, invoked when the `Connection` is associated with the
|
||||
`EndPoint`
|
||||
* `Connection.onClose(Throwable)`, invoked when the `Connection` is disassociated
|
||||
from the `EndPoint`, where the `Throwable` parameter indicates whether the
|
||||
disassociation was normal (when the parameter is `null`) or was due to an error
|
||||
(when the parameter is not `null`)
|
||||
* `Connection.onOpen()`, invoked when the `Connection` is associated with the `EndPoint`
|
||||
* `Connection.onClose(Throwable)`, invoked when the `Connection` is disassociated from the `EndPoint`, where the `Throwable` parameter indicates whether the disassociation was normal (when the parameter is `null`) or was due to an error (when the parameter is not `null`)
|
||||
|
||||
When a `Connection` is first created, it is not registered for any Java NIO
|
||||
event.
|
||||
It is therefore typical to implement `onOpen()` to call
|
||||
`EndPoint.fillInterested(Callback)` so that the `Connection` declares interest
|
||||
for read events and it is invoked (via the `Callback`) when the read event
|
||||
happens.
|
||||
When a `Connection` is first created, it is not registered for any Java NIO event.
|
||||
It is therefore typical to implement `onOpen()` to call `EndPoint.fillInterested(Callback)` so that the `Connection` declares interest for read events and it is invoked (via the `Callback`) when the read event happens.
|
||||
|
||||
Abstract class `AbstractConnection` partially implements `Connection` and
|
||||
provides simpler APIs. The example below shows a typical implementation that
|
||||
extends `AbstractConnection`:
|
||||
Abstract class `AbstractConnection` partially implements `Connection` and provides simpler APIs.
|
||||
The example below shows a typical implementation that extends `AbstractConnection`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/embedded/SelectorManagerDocs.java[tags=connection]
|
||||
----
|
||||
|
||||
[[io-arch-echo]]
|
||||
// TODO: Introduce Connection.Listener
|
||||
|
||||
[[eg-io-arch-echo]]
|
||||
=== Jetty I/O: Network Echo
|
||||
|
||||
With the concepts above it is now possible to write a simple, fully non-blocking,
|
||||
`Connection` implementation that simply echoes the bytes that it reads back
|
||||
to the other peer.
|
||||
With the concepts above it is now possible to write a simple, fully non-blocking, `Connection` implementation that simply echoes the bytes that it reads back to the other peer.
|
||||
|
||||
A naive, but wrong, implementation may be the following:
|
||||
|
||||
|
@ -212,9 +139,7 @@ include::{doc_code}/embedded/SelectorManagerDocs.java[tags=echo-wrong]
|
|||
|
||||
WARNING: The implementation above is wrong and leads to `StackOverflowError`.
|
||||
|
||||
The problem with this implementation is that if the writes always complete
|
||||
synchronously (i.e. without being delayed by TCP congestion), you end up with
|
||||
this sequence of calls:
|
||||
The problem with this implementation is that if the writes always complete synchronously (i.e. without being delayed by TCP congestion), you end up with this sequence of calls:
|
||||
|
||||
----
|
||||
Connection.onFillable()
|
||||
|
@ -228,13 +153,10 @@ Connection.onFillable()
|
|||
|
||||
which leads to `StackOverflowError`.
|
||||
|
||||
This is a typical side effect of asynchronous programming using non-blocking
|
||||
APIs, and happens in the Jetty I/O library as well.
|
||||
This is a typical side effect of asynchronous programming using non-blocking APIs, and happens in the Jetty I/O library as well.
|
||||
|
||||
NOTE: The callback is invoked synchronously for efficiency reasons.
|
||||
Submitting the invocation of the callback to an `Executor` to be invoked in
|
||||
a different thread would cause a context switch and make simple writes
|
||||
extremely inefficient.
|
||||
Submitting the invocation of the callback to an `Executor` to be invoked in a different thread would cause a context switch and make simple writes extremely inefficient.
|
||||
|
||||
A correct implementation is the following:
|
||||
|
||||
|
@ -243,23 +165,15 @@ A correct implementation is the following:
|
|||
include::{doc_code}/embedded/SelectorManagerDocs.java[tags=echo-correct]
|
||||
----
|
||||
|
||||
The correct implementation performs consecutive reads in a loop (rather than
|
||||
recursively), but _only_ if the correspondent write is completed successfully.
|
||||
The correct implementation performs consecutive reads in a loop (rather than recursively), but _only_ if the correspondent write is completed successfully.
|
||||
|
||||
In order to detect whether the write is completed, a concurrent state machine
|
||||
is used. This is necessary because the notification of the completion of the
|
||||
write may happen in a different thread, while the original writing thread
|
||||
may still be changing the state.
|
||||
In order to detect whether the write is completed, a concurrent state machine is used.
|
||||
This is necessary because the notification of the completion of the write may happen in a different thread, while the original writing thread may still be changing the state.
|
||||
|
||||
The original writing thread starts moves the state from `IDLE` to `WRITING`,
|
||||
then issues the actual `write()` call.
|
||||
The original writing thread then assumes that the `write()` did not complete
|
||||
and tries to move to the `PENDING` state just after the `write()`.
|
||||
If it fails to move from the `WRITING` state to the `PENDING` state, it means
|
||||
that the write was completed.
|
||||
Otherwise, the write is now `PENDING` and waiting for the callback to be
|
||||
notified of the completion at a later time.
|
||||
When the callback is notified of the `write()` completion, it checks whether
|
||||
the `write()` was `PENDING`, and if it was it resumes reading.
|
||||
The original writing thread starts moves the state from `IDLE` to `WRITING`, then issues the actual `write()` call.
|
||||
The original writing thread then assumes that the `write()` did not complete and tries to move to the `PENDING` state just after the `write()`.
|
||||
If it fails to move from the `WRITING` state to the `PENDING` state, it means that the write was completed.
|
||||
Otherwise, the write is now `PENDING` and waiting for the callback to be notified of the completion at a later time.
|
||||
When the callback is notified of the `write()` completion, it checks whether the `write()` was `PENDING`, and if it was it resumes reading.
|
||||
|
||||
NOTE: TODO: Introduce IteratingCallback?
|
||||
|
|
|
@ -23,5 +23,4 @@ General items related to the architecture of jetty and how it deals with certain
|
|||
|
||||
include::basic-architecture.adoc[]
|
||||
include::jetty-classloading.adoc[]
|
||||
include::1xx-responses.adoc[]
|
||||
include::server-side-architecture.adoc[]
|
|
@ -22,7 +22,7 @@
|
|||
This example shows the bare minimum required for deploying a servlet into Jetty.
|
||||
Note that this is strictly a servlet, not a servlet in the context of a web application, that example comes later.
|
||||
This is purely just a servlet deployed and mounted on a context and able to process requests.
|
||||
This example is excellent for situations where you have a simple servlet that you need to unit test, just mount it on a context and issue requests using your favorite http client library (like our Jetty client found in xref:http-client[]).
|
||||
This example is excellent for situations where you have a simple servlet that you need to unit test, just mount it on a context and issue requests using your favorite http client library (like our Jetty client found in xref:client-http[]).
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
This example shows how to deploy a simple webapp with an embedded instance of Jetty.
|
||||
This is useful when you want to manage the lifecycle of a server programmatically, either within a production application or as a simple way to deploying and debugging a full scale application deployment.
|
||||
In many ways it is easier then traditional deployment since you control the classpath yourself, making this easy to wire up in a test case in Maven and issue requests using your favorite http client library (like our Jetty client found in xref:http-client[]).
|
||||
In many ways it is easier then traditional deployment since you control the classpath yourself, making this easy to wire up in a test case in Maven and issue requests using your favorite http client library (like our Jetty client found in xref:client-http[]).
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
|
@ -25,30 +25,74 @@ It is easily configured with Jetty 9.
|
|||
[[weld-setup-distro]]
|
||||
==== Weld Setup
|
||||
|
||||
The easiest way to configure weld is within the jetty distribution itself:
|
||||
The easiest way to configure weld is within the Jetty distribution itself.
|
||||
This can be accomplished either by enabling one of the startup link:#startup-modules[modules] for Weld, or by creating/editing a `jetty-web.xml` descriptor (see also https://www.eclipse.org/jetty/documentation/current/jetty-web-xml-config.html[Jetty XML Reference]).
|
||||
|
||||
1. Enable the startup link:#startup-modules[module] called "cdi".
|
||||
2. Ensure your `WEB-INF/web.xml` contains the following:
|
||||
+
|
||||
[source, xml, subs="{sub-order}"]
|
||||
----
|
||||
<listener>
|
||||
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
|
||||
</listener>
|
||||
===== Jetty Weld Modules
|
||||
|
||||
<resource-env-ref>
|
||||
<description>Object factory for the CDI Bean Manager</description>
|
||||
<resource-env-ref-name>BeanManager</resource-env-ref-name>
|
||||
<resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
|
||||
</resource-env-ref>
|
||||
----
|
||||
====== Jetty `cdi-decorate` Module
|
||||
|
||||
That should be it so when you start up your jetty distribution with the webapp you should see output similar to the following (providing your logging is the default configuration):
|
||||
Since Jetty 9.4.20 and Weld 3.1.2.Final, the Weld/Jetty integration uses the jetty `cdi-decorate` module.
|
||||
To activate this module in Jetty the `cdi-decorate` module needs activated on the command line, which can be done as follows:
|
||||
|
||||
-------------------------
|
||||
cd $JETTY_BASE
|
||||
java -jar $JETTY_HOME/start.jar --add-to-start=cdi-decorate
|
||||
-------------------------
|
||||
|
||||
====== Jetty `cdi2` Module
|
||||
|
||||
For versions prior to Jetty 9.4.20 and Weld 3.1.2, the Weld/Jetty integration required some internal Jetty APIs to be made visible to the web application.
|
||||
This can be done using the deprecated `cdi2` module either by activating the `cdi2` module:
|
||||
|
||||
-------------------------
|
||||
cd $JETTY_BASE
|
||||
java -jar $JETTY_HOME/start.jar --add-to-start=cdi2
|
||||
-------------------------
|
||||
|
||||
|
||||
===== Jetty-Web XML
|
||||
|
||||
|
||||
[source.XML, xml]
|
||||
-------------------------------------------------------------
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
||||
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
|
||||
<Call name="prependServerClass">
|
||||
<Arg>-org.eclipse.jetty.util.Decorator</Arg>
|
||||
</Call>
|
||||
<Call name="prependServerClass">
|
||||
<Arg>-org.eclipse.jetty.util.DecoratedObjectFactory</Arg>
|
||||
</Call>
|
||||
<Call name="prependServerClass">
|
||||
<Arg>-org.eclipse.jetty.server.handler.ContextHandler.</Arg>
|
||||
</Call>
|
||||
<Call name="prependServerClass">
|
||||
<Arg>-org.eclipse.jetty.server.handler.ContextHandler</Arg>
|
||||
</Call>
|
||||
<Call name="prependServerClass">
|
||||
<Arg>-org.eclipse.jetty.servlet.ServletContextHandler</Arg>
|
||||
</Call>
|
||||
</Configure>
|
||||
-------------------------------------------------------------
|
||||
|
||||
//TODO Fix for 10
|
||||
____
|
||||
[TIP]
|
||||
Directly modifying the web application classpath via `jetty-web.xml` will not work for Jetty 10.0.0 and later.
|
||||
____
|
||||
|
||||
===== Jetty `cdi-spi` Module
|
||||
Since Jetty 9.4.20 the Jetty `cdi-spi` module has been available that integrates any compliant CDI implementation by directly calling the CDI SPI.
|
||||
Since Weld support specific Jetty integration, it is not recommended to use this module with Weld.
|
||||
|
||||
When you start up your Jetty distribution with the webapp you should see output similar to the following (providing your logging is the default configuration):
|
||||
|
||||
[source, screen, subs="{sub-order}"]
|
||||
....
|
||||
2015-06-18 12:13:54.924:INFO::main: Logging initialized @485ms
|
||||
2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-9.3.1-SNAPSHOT
|
||||
2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-{VERSION}
|
||||
2015-06-18 12:13:55.264:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/cdi-demo/webapps/] at interval 1
|
||||
2015-06-18 12:13:55.607:WARN:oeja.AnnotationConfiguration:main: ServletContainerInitializers: detected. Class hierarchy: empty
|
||||
Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.EnhancedListener onStartup
|
||||
|
@ -80,4 +124,43 @@ INFO: WELD-ENV-001009: org.jboss.weld.environment.servlet.Listener used for Serv
|
|||
|
||||
....
|
||||
|
||||
For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet artifact a _plugin_ dependency (__not__ a webapp dependency), then follow step 2 above.
|
||||
For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet artifact a _plugin_ dependency (__not__ a webapp dependency).
|
||||
|
||||
[[weld-embedded]]
|
||||
==== Embedded Jetty
|
||||
|
||||
When starting embedded Jetty programmatically from the `main` method it is necessary to register Weld's listener:
|
||||
|
||||
[source.JAVA, java]
|
||||
----
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Server jetty = new Server(8080);
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setContextPath("/");
|
||||
context.setResourceBase("src/main/resources");
|
||||
jetty.setHandler(context);
|
||||
context.addServlet(HelloWorldServlet.class, "/*");
|
||||
|
||||
context.addEventListener(new DecoratingListener()); # <1>
|
||||
context.addEventListener(new Listener()); # <2>
|
||||
|
||||
jetty.start();
|
||||
jetty.join();
|
||||
}
|
||||
|
||||
public static class HelloWorldServlet extends HttpServlet {
|
||||
|
||||
@Inject BeanManager manager;
|
||||
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.setContentType("text/plain");
|
||||
resp.getWriter().append("Hello from " + manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<1> Jetty's `org.eclipse.jetty.webapp.DecoratingListener` registered programmatically (since Jetty-9.4.20)
|
||||
<2> Weld's `org.jboss.weld.environment.servlet.Listener` registered programmatically
|
||||
----
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 332 KiB After Width: | Height: | Size: 332 KiB |
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 269 KiB |
Before Width: | Height: | Size: 433 KiB After Width: | Height: | Size: 433 KiB |
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 275 KiB |
Before Width: | Height: | Size: 542 KiB After Width: | Height: | Size: 542 KiB |
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 396 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |