Merge branch 'jetty-9.1' into release-9.1
This commit is contained in:
commit
9e8d07337b
56
VERSION.txt
56
VERSION.txt
|
@ -191,6 +191,62 @@ jetty-9.1.0.M0 - 16 September 2013
|
|||
+ 417225 added Container.addEventListener method
|
||||
+ 417260 Protected targets matched as true URI path segments
|
||||
|
||||
jetty-9.0.6.v20130930 - 30 September 2013
|
||||
+ 411069 better set compiler defaults to 1.7, including webdefault.xml for jsp
|
||||
+ 411934 War overlay configuration assumes src/main/webapp exists
|
||||
+ 413484 setAttribute in nosql session management better handles _dirty status
|
||||
+ 413684 deprecated unsafe alias checkers
|
||||
+ 413737 hide stacktrace in ReferrerPushStrategyTest
|
||||
+ 414431 Avoid debug NPE race
|
||||
+ 414898 Only upgrade v0 to v1 cookies on dquote , ; backslash space and tab
|
||||
in the value
|
||||
+ 415192 <jsp-file> maps to JspPropertyGroupServlet instead of JspServlet
|
||||
+ 415194 Deployer gives management of context to context collection
|
||||
+ 415302
|
||||
+ 415330 Avoid multiple callbacks at EOF
|
||||
+ 415401 Add initalizeDefaults call to SpringConfigurationProcessor
|
||||
+ 415548 migrate ProxyHTTPToSPDYTest to use HttpClient to avoid intermittent
|
||||
NPE part 2
|
||||
+ 415605 fix status code logging for async requests
|
||||
+ 415999 Fix some of FindBugs warnings
|
||||
+ 416015 Handle null Accept-Language and other headers
|
||||
+ 416096 DefaultServlet leaves open file descriptors with file sizes greater
|
||||
than response buffer
|
||||
+ 416102 Clean up of async sendContent process
|
||||
+ 416103 Added AllowSymLinkAliasChecker.java
|
||||
+ 416251 ProxyHTTPToSPDYConnection now sends a 502 to the client if it
|
||||
receives a rst frame from the upstream spdy server
|
||||
+ 416266 HttpServletResponse.encodeURL() encodes on first request when only
|
||||
SessionTrackingMode.COOKIE is used
|
||||
+ 416314 jetty async client wrong behaviour for HEAD Method + Redirect.
|
||||
+ 416321 handle failure during blocked committing write
|
||||
+ 416453 Add comments to embedded SplitFileServer example
|
||||
+ 416477 Improved consumeAll error handling
|
||||
+ 416568 Simplified servlet exception logging
|
||||
+ 416577 enhanced shutdown handler to send shutdown at startup
|
||||
+ 416585 WebInfConfiguration examines webapp classloader first instead of its
|
||||
parent when looking for container jars
|
||||
+ 416597 Allow classes and jars on the webappcontext extraclasspath to be
|
||||
scanned for annotations
|
||||
+ 416663 Content-length set by resourcehandler
|
||||
+ 416674 run all jetty-ant tests on random ports
|
||||
+ 416679 Change warning to debug if no transaction manager present
|
||||
+ 416787 StringIndexOutOfBounds with a pathMap of ""
|
||||
+ 416940 avoid download of spring-beans.dtd
|
||||
+ 416990 JMX names statically unique
|
||||
+ 417110 Demo / html body end tag missing in authfail.html
|
||||
+ 417225 added Container.addEventListener method
|
||||
+ 417260 Protected targets matched as true URI path segments
|
||||
+ 417289 SPDY replace use of direct buffers with indirect buffers or make it
|
||||
configurable
|
||||
+ 417475 Do not null context Trie during dynamic deploy
|
||||
+ 417574 Setting options with _JAVA_OPTIONS breaks run-forked with
|
||||
<waitForChild>true</waitForChild>
|
||||
+ 417831 Remove jetty-logging.properties from distro/resources
|
||||
+ 418014 Handle NTFS canonical exceptions during alias check
|
||||
+ 418212 org.eclipse.jetty.spdy.server.http.SSLExternalServerTest hangs
|
||||
+ 418227 Null cookie value test
|
||||
|
||||
jetty-9.0.5.v20130815 - 15 August 2013
|
||||
+ 414898 Only upgrade v0 to v1 cookies on dquote , ; backslash space and tab
|
||||
in the value
|
||||
|
|
|
@ -18,11 +18,17 @@
|
|||
|
||||
package org.eclipse.jetty.annotations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
|
@ -41,7 +47,6 @@ import org.eclipse.jetty.util.MultiException;
|
|||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.webapp.AbstractConfiguration;
|
||||
import org.eclipse.jetty.webapp.FragmentDescriptor;
|
||||
import org.eclipse.jetty.webapp.MetaDataComplete;
|
||||
|
@ -57,6 +62,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(AnnotationConfiguration.class);
|
||||
|
||||
public static final String SERVLET_CONTAINER_INITIALIZER_ORDER = "org.eclipse.jetty.containerInitializerOrder";
|
||||
public static final String CLASS_INHERITANCE_MAP = "org.eclipse.jetty.classInheritanceMap";
|
||||
public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers";
|
||||
public static final String CONTAINER_INITIALIZER_STARTER = "org.eclipse.jetty.containerInitializerStarter";
|
||||
|
@ -76,6 +82,12 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* ParserTask
|
||||
*
|
||||
* Task to executing scanning of a resource for annotations.
|
||||
*
|
||||
*/
|
||||
public class ParserTask implements Callable<Void>
|
||||
{
|
||||
protected Exception _exception;
|
||||
|
@ -176,6 +188,143 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ServletContainerInitializerOrdering
|
||||
*
|
||||
* A list of classnames of ServletContainerInitializers in the order in which
|
||||
* they are to be called back. One name only in the list can be "*", which is a
|
||||
* wildcard which matches any other ServletContainerInitializer name not already
|
||||
* matched.
|
||||
*/
|
||||
public class ServletContainerInitializerOrdering
|
||||
{
|
||||
private Map<String, Integer> _indexMap = new HashMap<String, Integer>();
|
||||
private Integer _star = null;
|
||||
private String _ordering = null;
|
||||
|
||||
public ServletContainerInitializerOrdering (String ordering)
|
||||
{
|
||||
if (ordering != null)
|
||||
{
|
||||
_ordering = ordering;
|
||||
|
||||
String[] tmp = ordering.split(",");
|
||||
|
||||
for (int i=0; i<tmp.length; i++)
|
||||
{
|
||||
String s = tmp[i].trim();
|
||||
_indexMap.put(s, Integer.valueOf(i));
|
||||
if ("*".equals(s))
|
||||
{
|
||||
if (_star != null)
|
||||
throw new IllegalArgumentException("Duplicate wildcards in ServletContainerInitializer ordering "+ordering);
|
||||
_star = Integer.valueOf(i);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if "*" is one of the values.
|
||||
* @return
|
||||
*/
|
||||
public boolean hasWildcard()
|
||||
{
|
||||
return _star != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the "*" element, if it is specified. -1 otherwise.
|
||||
* @return
|
||||
*/
|
||||
public int getWildcardIndex()
|
||||
{
|
||||
if (!hasWildcard())
|
||||
return -1;
|
||||
return _star.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the ordering contains a single value of "*"
|
||||
* @return
|
||||
*/
|
||||
public boolean isDefaultOrder ()
|
||||
{
|
||||
return (getSize() == 1 && hasWildcard());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the order index of the given classname
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public int getIndexOf (String name)
|
||||
{
|
||||
Integer i = _indexMap.get(name);
|
||||
if (i == null)
|
||||
return -1;
|
||||
return i.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of elements of the ordering
|
||||
* @return
|
||||
*/
|
||||
public int getSize()
|
||||
{
|
||||
return _indexMap.size();
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
if (_ordering == null)
|
||||
return "";
|
||||
return _ordering;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* ServletContainerInitializerComparator
|
||||
*
|
||||
* Comparator impl that orders a set of ServletContainerInitializers according to the
|
||||
* list of classnames (optionally containing a "*" wildcard character) established in a
|
||||
* ServletContainerInitializerOrdering.
|
||||
* @see ServletContainerInitializerOrdering
|
||||
*/
|
||||
public class ServletContainerInitializerComparator implements Comparator<ServletContainerInitializer>
|
||||
{
|
||||
private ServletContainerInitializerOrdering _ordering;
|
||||
|
||||
|
||||
public ServletContainerInitializerComparator (ServletContainerInitializerOrdering ordering)
|
||||
{
|
||||
_ordering = ordering;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(ServletContainerInitializer sci1, ServletContainerInitializer sci2)
|
||||
{
|
||||
String c1 = (sci1 != null? sci1.getClass().getName() : null);
|
||||
String c2 = (sci2 != null? sci2.getClass().getName() : null);
|
||||
|
||||
if (c1 == null && c2 == null)
|
||||
return 0;
|
||||
|
||||
int i1 = _ordering.getIndexOf(c1);
|
||||
if (i1 < 0 && _ordering.hasWildcard())
|
||||
i1 = _ordering.getWildcardIndex();
|
||||
int i2 = _ordering.getIndexOf(c2);
|
||||
if (i2 < 0 && _ordering.hasWildcard())
|
||||
i2 = _ordering.getWildcardIndex();
|
||||
|
||||
return Integer.compare(i1, i2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConfigure(final WebAppContext context) throws Exception
|
||||
{
|
||||
|
@ -278,24 +427,18 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
throws Exception
|
||||
{
|
||||
AnnotationParser parser = createAnnotationParser();
|
||||
boolean multiThreadedScan = isUseMultiThreading(context);
|
||||
int maxScanWait = 0;
|
||||
if (multiThreadedScan)
|
||||
{
|
||||
_parserTasks = new ArrayList<ParserTask>();
|
||||
maxScanWait = getMaxScanWait(context);
|
||||
}
|
||||
_parserTasks = new ArrayList<ParserTask>();
|
||||
|
||||
long start = 0;
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
start = System.nanoTime();
|
||||
LOG.debug("Scanning for annotations: webxml={}, metadatacomplete={}, configurationDiscovered={}, multiThreaded={}",
|
||||
LOG.debug("Scanning for annotations: webxml={}, metadatacomplete={}, configurationDiscovered={}, multiThreaded={}, maxScanWait={}",
|
||||
context.getServletContext().getEffectiveMajorVersion(),
|
||||
context.getMetaData().isMetaDataComplete(),
|
||||
context.isConfigurationDiscovered(),
|
||||
multiThreadedScan);
|
||||
isUseMultiThreading(context),
|
||||
getMaxScanWait(context));
|
||||
}
|
||||
|
||||
parseContainerPath(context, parser);
|
||||
|
@ -307,23 +450,15 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
parseWebInfClasses(context, parser);
|
||||
parseWebInfLib (context, parser);
|
||||
|
||||
if (!multiThreadedScan)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
long end = System.nanoTime();
|
||||
LOG.debug("Annotation parsing millisec={}",(TimeUnit.MILLISECONDS.convert(end-start, TimeUnit.NANOSECONDS)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
start = System.nanoTime();
|
||||
|
||||
//execute scan asynchronously using jetty's thread pool
|
||||
//execute scan, either effectively synchronously (1 thread only), or asychronously (limited by number of processors available)
|
||||
final Semaphore task_limit = (isUseMultiThreading(context)? new Semaphore(Runtime.getRuntime().availableProcessors()):new Semaphore(1));
|
||||
final CountDownLatch latch = new CountDownLatch(_parserTasks.size());
|
||||
final MultiException me = new MultiException();
|
||||
final Semaphore task_limit=new Semaphore(Runtime.getRuntime().availableProcessors());
|
||||
|
||||
for (final ParserTask p:_parserTasks)
|
||||
{
|
||||
task_limit.acquire();
|
||||
|
@ -349,13 +484,10 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
});
|
||||
}
|
||||
|
||||
boolean timeout = !latch.await(maxScanWait, TimeUnit.SECONDS);
|
||||
boolean timeout = !latch.await(getMaxScanWait(context), TimeUnit.SECONDS);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
long end = System.nanoTime();
|
||||
LOG.debug("Annotation parsing millisec={}",(TimeUnit.MILLISECONDS.convert(end-start, TimeUnit.NANOSECONDS)));
|
||||
}
|
||||
LOG.debug("Annotation parsing millisec={}",(TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS)));
|
||||
|
||||
if (timeout)
|
||||
me.add(new Exception("Timeout scanning annotations"));
|
||||
|
@ -507,18 +639,42 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
context.addBean(starter, true);
|
||||
}
|
||||
|
||||
|
||||
public Resource getJarFor (ServletContainerInitializer service)
|
||||
throws MalformedURLException, IOException
|
||||
{
|
||||
String loadingJarName = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class").toString();
|
||||
|
||||
int i = loadingJarName.indexOf(".jar");
|
||||
if (i < 0)
|
||||
return null; //not from a jar
|
||||
|
||||
loadingJarName = loadingJarName.substring(0,i+4);
|
||||
loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
|
||||
return Resource.newResource(loadingJarName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
|
||||
* from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
|
||||
* @param context
|
||||
* @param service
|
||||
* @param sci
|
||||
* @return true if excluded
|
||||
*/
|
||||
public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer service)
|
||||
public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer sci, Resource sciResource)
|
||||
throws Exception
|
||||
{
|
||||
if (sci == null)
|
||||
throw new IllegalArgumentException("ServletContainerInitializer null");
|
||||
if (context == null)
|
||||
throw new IllegalArgumentException("WebAppContext null");
|
||||
|
||||
//A ServletContainerInitializer that came from the container's classpath cannot be excluded by an ordering
|
||||
//of WEB-INF/lib jars
|
||||
if (sci.getClass().getClassLoader()==context.getClassLoader().getParent())
|
||||
return false;
|
||||
|
||||
List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
|
||||
|
||||
//If no ordering, nothing is excluded
|
||||
|
@ -529,15 +685,10 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
if (orderedJars.isEmpty())
|
||||
return true;
|
||||
|
||||
String loadingJarName = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class").toString();
|
||||
|
||||
int i = loadingJarName.indexOf(".jar");
|
||||
if (i < 0)
|
||||
if (sciResource == null)
|
||||
return false; //not from a jar therefore not from WEB-INF so not excludable
|
||||
|
||||
loadingJarName = loadingJarName.substring(0,i+4);
|
||||
loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
|
||||
URI loadingJarURI = Resource.newResource(loadingJarName).getURI();
|
||||
|
||||
URI loadingJarURI = sciResource.getURI();
|
||||
boolean found = false;
|
||||
Iterator<Resource> itor = orderedJars.iterator();
|
||||
while (!found && itor.hasNext())
|
||||
|
@ -559,8 +710,9 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
|
||||
throws Exception
|
||||
{
|
||||
List<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
|
||||
ArrayList<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
|
||||
|
||||
|
||||
//We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
|
||||
long start = 0;
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -569,18 +721,104 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Service loaders found in {}ms", (TimeUnit.MILLISECONDS.convert((System.nanoTime()-start), TimeUnit.NANOSECONDS)));
|
||||
|
||||
if (loadedInitializers != null)
|
||||
//no ServletContainerInitializers found
|
||||
if (loadedInitializers == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
|
||||
|
||||
if (initializerOrdering != null && !initializerOrdering.isDefaultOrder())
|
||||
{
|
||||
for (ServletContainerInitializer service : loadedInitializers)
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Ordering ServletContainerInitializers with "+initializerOrdering);
|
||||
|
||||
//There is an ordering that is not just "*".
|
||||
//Arrange ServletContainerInitializers according to the ordering of classnames given, irrespective of coming from container or webapp classpaths
|
||||
for (ServletContainerInitializer sci:loadedInitializers)
|
||||
{
|
||||
if (!isFromExcludedJar(context, service))
|
||||
nonExcludedInitializers.add(service);
|
||||
Resource sciResource = getJarFor(sci);
|
||||
if (!isFromExcludedJar(context, sci, sciResource))
|
||||
{
|
||||
String name = sci.getClass().getName();
|
||||
if (initializerOrdering.getIndexOf(name) >= 0 || initializerOrdering.hasWildcard())
|
||||
nonExcludedInitializers.add(sci);
|
||||
}
|
||||
}
|
||||
|
||||
//apply the ordering
|
||||
Collections.sort(nonExcludedInitializers, new ServletContainerInitializerComparator(initializerOrdering));
|
||||
}
|
||||
else
|
||||
{
|
||||
//No ordering specified, or just the wildcard value "*" specified.
|
||||
//Fallback to ordering the ServletContainerInitializers according to:
|
||||
//container classpath first, WEB-INF/clases then WEB-INF/lib (obeying any web.xml jar ordering)
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Ordering ServletContainerInitializers as container path, webapp path");
|
||||
|
||||
Map<ServletContainerInitializer,Resource> webappPathInitializerResourceMap = new HashMap<ServletContainerInitializer,Resource>();
|
||||
for (ServletContainerInitializer sci : loadedInitializers)
|
||||
{
|
||||
//if its on the container's classpath then add it
|
||||
if (sci.getClass().getClassLoader() == context.getClassLoader().getParent())
|
||||
{
|
||||
nonExcludedInitializers.add(sci);
|
||||
}
|
||||
else
|
||||
{
|
||||
//if on the webapp's classpath then check the containing jar is not excluded from consideration
|
||||
Resource sciResource = getJarFor(sci);
|
||||
if (!isFromExcludedJar(context, sci, sciResource))
|
||||
{
|
||||
webappPathInitializerResourceMap.put(sci, sciResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//add the webapp classpath ones according to any web.xml ordering
|
||||
if (context.getMetaData().getOrdering() == null)
|
||||
nonExcludedInitializers.addAll(webappPathInitializerResourceMap.keySet()); //no ordering, just add them
|
||||
else
|
||||
{
|
||||
//add in any ServletContainerInitializers which are not in a jar, as they must be from WEB-INF/classes
|
||||
for (Map.Entry<ServletContainerInitializer, Resource> entry:webappPathInitializerResourceMap.entrySet())
|
||||
{
|
||||
if (entry.getValue() == null)
|
||||
nonExcludedInitializers.add(entry.getKey());
|
||||
}
|
||||
|
||||
//add ServletContainerInitializers according to the ordering of its containing jar
|
||||
for (Resource webInfJar:context.getMetaData().getOrderedWebInfJars())
|
||||
{
|
||||
for (Map.Entry<ServletContainerInitializer, Resource> entry:webappPathInitializerResourceMap.entrySet())
|
||||
{
|
||||
if (webInfJar.equals(entry.getValue()))
|
||||
nonExcludedInitializers.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nonExcludedInitializers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Jetty-specific extension that allows an ordering to be applied across ALL ServletContainerInitializers.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ServletContainerInitializerOrdering getInitializerOrdering (WebAppContext context)
|
||||
{
|
||||
if (context == null)
|
||||
return null;
|
||||
|
||||
String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_ORDER);
|
||||
if (tmp == null || "".equals(tmp.trim()))
|
||||
return null;
|
||||
|
||||
return new ServletContainerInitializerOrdering(tmp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -607,9 +845,6 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
//queue it up for scanning if using multithreaded mode
|
||||
if (_parserTasks != null)
|
||||
_parserTasks.add(new ParserTask(parser, handlers, r, _containerClassNameResolver));
|
||||
else
|
||||
//just scan it now
|
||||
parser.parse(handlers, r, _containerClassNameResolver);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -664,8 +899,6 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
|
||||
if (_parserTasks != null)
|
||||
_parserTasks.add (new ParserTask(parser, handlers,r, _webAppClassNameResolver));
|
||||
else
|
||||
parser.parse(handlers, r, _webAppClassNameResolver);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -694,8 +927,6 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
{
|
||||
if (_parserTasks != null)
|
||||
_parserTasks.add(new ParserTask(parser, handlers, dir, _webAppClassNameResolver));
|
||||
else
|
||||
parser.parse(handlers, dir, _webAppClassNameResolver);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import java.util.jar.JarInputStream;
|
|||
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
import org.eclipse.jetty.util.Loader;
|
||||
import org.eclipse.jetty.util.MultiException;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
@ -370,15 +371,6 @@ public class AnnotationParser
|
|||
final MethodInfo _mi;
|
||||
final Set<? extends Handler> _handlers;
|
||||
|
||||
|
||||
/**
|
||||
* @param classname
|
||||
* @param access
|
||||
* @param name
|
||||
* @param methodDesc
|
||||
* @param signature
|
||||
* @param exceptions
|
||||
*/
|
||||
public MyMethodVisitor(final Set<? extends Handler> handlers,
|
||||
final ClassInfo classInfo,
|
||||
final int access,
|
||||
|
@ -423,9 +415,6 @@ public class AnnotationParser
|
|||
final Set<? extends Handler> _handlers;
|
||||
|
||||
|
||||
/**
|
||||
* @param classname
|
||||
*/
|
||||
public MyFieldVisitor(final Set<? extends Handler> handlers,
|
||||
final ClassInfo classInfo,
|
||||
final int access,
|
||||
|
@ -614,6 +603,7 @@ public class AnnotationParser
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visitSuperClasses)
|
||||
cz = cz.getSuperclass();
|
||||
else
|
||||
|
@ -650,19 +640,29 @@ public class AnnotationParser
|
|||
public void parse (Set<? extends Handler> handlers, List<String> classNames, ClassNameResolver resolver)
|
||||
throws Exception
|
||||
{
|
||||
MultiException me = new MultiException();
|
||||
|
||||
for (String s:classNames)
|
||||
{
|
||||
if ((resolver == null) || (!resolver.isExcluded(s) && (!isParsed(s) || resolver.shouldOverride(s))))
|
||||
try
|
||||
{
|
||||
s = s.replace('.', '/')+".class";
|
||||
URL resource = Loader.getResource(this.getClass(), s);
|
||||
if (resource!= null)
|
||||
if ((resolver == null) || (!resolver.isExcluded(s) && (!isParsed(s) || resolver.shouldOverride(s))))
|
||||
{
|
||||
Resource r = Resource.newResource(resource);
|
||||
scanClass(handlers, null, r.getInputStream());
|
||||
s = s.replace('.', '/')+".class";
|
||||
URL resource = Loader.getResource(this.getClass(), s);
|
||||
if (resource!= null)
|
||||
{
|
||||
Resource r = Resource.newResource(resource);
|
||||
scanClass(handlers, null, r.getInputStream());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
me.add(new RuntimeException("Error scanning class "+s, e));
|
||||
}
|
||||
}
|
||||
me.ifExceptionThrow();
|
||||
}
|
||||
|
||||
|
||||
|
@ -682,25 +682,22 @@ public class AnnotationParser
|
|||
|
||||
if (LOG.isDebugEnabled()) {LOG.debug("Scanning dir {}", dir);};
|
||||
|
||||
MultiException me = new MultiException();
|
||||
|
||||
String[] files=dir.list();
|
||||
for (int f=0;files!=null && f<files.length;f++)
|
||||
{
|
||||
try
|
||||
Resource res = dir.addPath(files[f]);
|
||||
if (res.isDirectory())
|
||||
parseDir(handlers, res, resolver);
|
||||
else
|
||||
{
|
||||
Resource res = dir.addPath(files[f]);
|
||||
if (res.isDirectory())
|
||||
parseDir(handlers, res, resolver);
|
||||
else
|
||||
System.err.println("TRYING TO SCAN "+res);
|
||||
//we've already verified the directories, so just verify the class file name
|
||||
File file = res.getFile();
|
||||
if (isValidClassFileName((file==null?null:file.getName())))
|
||||
{
|
||||
//we've already verified the directories, so just verify the class file name
|
||||
boolean valid = true;
|
||||
File file = res.getFile();
|
||||
if (file == null)
|
||||
LOG.warn("Unable to validate class file name for {}", res);
|
||||
else
|
||||
valid = isValidClassFileName(file.getName());
|
||||
|
||||
if (valid)
|
||||
try
|
||||
{
|
||||
String name = res.getName();
|
||||
if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
|
||||
|
@ -709,15 +706,20 @@ public class AnnotationParser
|
|||
if (LOG.isDebugEnabled()) {LOG.debug("Scanning class {}", r);};
|
||||
scanClass(handlers, dir, r.getInputStream());
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
me.add(new RuntimeException("Error scanning file "+files[f],ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION,ex);
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled()) LOG.debug("Skipping scan on invalid file {}", res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
me.ifExceptionThrow();
|
||||
}
|
||||
|
||||
|
||||
|
@ -740,6 +742,8 @@ public class AnnotationParser
|
|||
if (!(loader instanceof URLClassLoader))
|
||||
return; //can't extract classes?
|
||||
|
||||
final MultiException me = new MultiException();
|
||||
|
||||
JarScanner scanner = new JarScanner()
|
||||
{
|
||||
@Override
|
||||
|
@ -751,13 +755,14 @@ public class AnnotationParser
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Problem parsing jar entry: {}", entry.getName());
|
||||
me.add(new RuntimeException("Error parsing entry "+entry.getName()+" from jar "+ jarUri, e));
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
scanner.scan(null, loader, nullInclusive, visitParents);
|
||||
me.ifExceptionThrow();
|
||||
}
|
||||
|
||||
|
||||
|
@ -774,6 +779,8 @@ public class AnnotationParser
|
|||
if (uris==null)
|
||||
return;
|
||||
|
||||
MultiException me = new MultiException();
|
||||
|
||||
for (URI uri:uris)
|
||||
{
|
||||
try
|
||||
|
@ -782,10 +789,10 @@ public class AnnotationParser
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Problem parsing classes from {}", uri);
|
||||
me.add(new RuntimeException("Problem parsing classes from "+ uri, e));
|
||||
}
|
||||
}
|
||||
|
||||
me.ifExceptionThrow();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -801,8 +808,6 @@ public class AnnotationParser
|
|||
return;
|
||||
|
||||
parse (handlers, Resource.newResource(uri), resolver);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -895,13 +900,22 @@ public class AnnotationParser
|
|||
if (in==null)
|
||||
return;
|
||||
|
||||
MultiException me = new MultiException();
|
||||
|
||||
JarInputStream jar_in = new JarInputStream(in);
|
||||
try
|
||||
{
|
||||
JarEntry entry = jar_in.getNextJarEntry();
|
||||
while (entry!=null)
|
||||
{
|
||||
parseJarEntry(handlers, jarResource, entry, resolver);
|
||||
try
|
||||
{
|
||||
parseJarEntry(handlers, jarResource, entry, resolver);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
me.add(new RuntimeException("Error scanning entry "+entry.getName()+" from jar "+jarResource, e));
|
||||
}
|
||||
entry = jar_in.getNextJarEntry();
|
||||
}
|
||||
}
|
||||
|
@ -909,7 +923,8 @@ public class AnnotationParser
|
|||
{
|
||||
jar_in.close();
|
||||
}
|
||||
}
|
||||
me.ifExceptionThrow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -50,8 +50,9 @@ public class ContainerInitializerAnnotationHandler extends AbstractHandler
|
|||
|
||||
/**
|
||||
* Handle finding a class that is annotated with the annotation we were constructed with.
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handle(ClassInfo)
|
||||
* */
|
||||
*
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(ClassInfo, String)
|
||||
*/
|
||||
public void handle(ClassInfo info, String annotationName)
|
||||
{
|
||||
if (annotationName == null || !_annotation.getName().equals(annotationName))
|
||||
|
@ -63,7 +64,7 @@ public class ContainerInitializerAnnotationHandler extends AbstractHandler
|
|||
/**
|
||||
* Handle finding a field that is annotated with the annotation we were constructed with.
|
||||
*
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handle(org.eclipse.jetty.annotations.AnnotationParser.FieldAnnotationInfo)
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(FieldInfo, String)
|
||||
*/
|
||||
public void handle(FieldInfo info, String annotationName)
|
||||
{
|
||||
|
@ -75,7 +76,7 @@ public class ContainerInitializerAnnotationHandler extends AbstractHandler
|
|||
/**
|
||||
* Handle finding a method that is annotated with the annotation we were constructed with.
|
||||
*
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handle(org.eclipse.jetty.annotations.AnnotationParser.MethodAnnotationInfo)
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(MethodInfo, String)
|
||||
*/
|
||||
public void handle(MethodInfo info, String annotationName)
|
||||
{
|
||||
|
|
|
@ -18,14 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.annotations;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -71,6 +69,8 @@ public class ServletContainerInitializersStarter extends AbstractLifeCycle
|
|||
//instantiate ServletContainerInitializers, call doStart
|
||||
try
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Calling ServletContainerInitializer "+i.getTarget().getClass().getName());
|
||||
i.callStartup(_context);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -36,7 +36,7 @@ public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotation
|
|||
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handle(ClassAnnotationInfo)
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(ClassInfo, String)
|
||||
*/
|
||||
public void handle(ClassInfo info, String annotationName)
|
||||
{
|
||||
|
|
|
@ -18,14 +18,11 @@
|
|||
|
||||
package org.eclipse.jetty.annotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.annotations.AnnotationParser.ClassInfo;
|
||||
import org.eclipse.jetty.annotations.AnnotationParser.FieldInfo;
|
||||
import org.eclipse.jetty.annotations.AnnotationParser.MethodInfo;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
|
||||
/**
|
||||
|
@ -47,8 +44,7 @@ public class WebServletAnnotationHandler extends AbstractDiscoverableAnnotationH
|
|||
/**
|
||||
* Handle discovering a WebServlet annotation.
|
||||
*
|
||||
*
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List)
|
||||
* @see org.eclipse.jetty.annotations.AnnotationParser.Handler#handle(ClassInfo, String)
|
||||
*/
|
||||
@Override
|
||||
public void handle(ClassInfo info, String annotationName)
|
||||
|
|
|
@ -124,18 +124,5 @@
|
|||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ning</groupId>
|
||||
<artifactId>async-http-client</artifactId>
|
||||
<version>1.7.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.2.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -3,5 +3,4 @@
|
|||
#
|
||||
|
||||
[lib]
|
||||
# Client jars
|
||||
lib/jetty-client-${jetty.version}.jar
|
||||
|
|
|
@ -19,24 +19,20 @@
|
|||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
public abstract class AbstractHttpClientTransport extends ContainerLifeCycle implements HttpClientTransport
|
||||
{
|
||||
|
@ -72,12 +68,20 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
|||
}
|
||||
|
||||
@Override
|
||||
public void connect(HttpDestination destination, SocketAddress address, Promise<org.eclipse.jetty.client.api.Connection> promise)
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
super.doStop();
|
||||
removeBean(selectorManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(SocketAddress address, Map<String, Object> context)
|
||||
{
|
||||
SocketChannel channel = null;
|
||||
try
|
||||
{
|
||||
channel = SocketChannel.open();
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpClient client = destination.getHttpClient();
|
||||
SocketAddress bindAddress = client.getBindAddress();
|
||||
if (bindAddress != null)
|
||||
|
@ -86,8 +90,9 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
|||
channel.configureBlocking(false);
|
||||
channel.connect(address);
|
||||
|
||||
ConnectionCallback callback = new ConnectionCallback(destination, promise);
|
||||
selectorManager.connect(channel, callback);
|
||||
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, destination.getHost());
|
||||
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, destination.getPort());
|
||||
selectorManager.connect(channel, context);
|
||||
}
|
||||
// Must catch all exceptions, since some like
|
||||
// UnresolvedAddressException are not IOExceptions.
|
||||
|
@ -104,12 +109,14 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
|||
}
|
||||
finally
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void configure(HttpClient client, SocketChannel channel) throws SocketException
|
||||
protected void configure(HttpClient client, SocketChannel channel) throws IOException
|
||||
{
|
||||
channel.socket().setTcpNoDelay(client.isTCPNoDelay());
|
||||
}
|
||||
|
@ -119,40 +126,6 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
|||
return new ClientSelectorManager(client, selectors);
|
||||
}
|
||||
|
||||
protected SslConnection createSslConnection(EndPoint endPoint, HttpDestination destination)
|
||||
{
|
||||
HttpClient httpClient = destination.getHttpClient();
|
||||
SslContextFactory sslContextFactory = httpClient.getSslContextFactory();
|
||||
SSLEngine engine = sslContextFactory.newSSLEngine(destination.getHost(), destination.getPort());
|
||||
engine.setUseClientMode(true);
|
||||
|
||||
SslConnection sslConnection = newSslConnection(httpClient, endPoint, engine);
|
||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
||||
endPoint.setConnection(sslConnection);
|
||||
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
|
||||
Connection connection = newConnection(appEndPoint, destination);
|
||||
appEndPoint.setConnection(connection);
|
||||
|
||||
return sslConnection;
|
||||
}
|
||||
|
||||
protected SslConnection newSslConnection(HttpClient httpClient, EndPoint endPoint, SSLEngine engine)
|
||||
{
|
||||
return new SslConnection(httpClient.getByteBufferPool(), httpClient.getExecutor(), endPoint, engine);
|
||||
}
|
||||
|
||||
protected abstract Connection newConnection(EndPoint endPoint, HttpDestination destination);
|
||||
|
||||
protected org.eclipse.jetty.client.api.Connection tunnel(EndPoint endPoint, HttpDestination destination, org.eclipse.jetty.client.api.Connection connection)
|
||||
{
|
||||
SslConnection sslConnection = createSslConnection(endPoint, destination);
|
||||
Connection result = sslConnection.getDecryptedEndPoint().getConnection();
|
||||
selectorManager.connectionClosed((Connection)connection);
|
||||
selectorManager.connectionOpened(sslConnection);
|
||||
LOG.debug("Tunnelled {} over {}", connection, result);
|
||||
return (org.eclipse.jetty.client.api.Connection)result;
|
||||
}
|
||||
|
||||
protected class ClientSelectorManager extends SelectorManager
|
||||
{
|
||||
private final HttpClient client;
|
||||
|
@ -170,63 +143,21 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
|||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
|
||||
public org.eclipse.jetty.io.Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
|
||||
{
|
||||
ConnectionCallback callback = (ConnectionCallback)attachment;
|
||||
HttpDestination destination = callback.destination;
|
||||
|
||||
SslContextFactory sslContextFactory = client.getSslContextFactory();
|
||||
if (!destination.isProxied() && HttpScheme.HTTPS.is(destination.getScheme()))
|
||||
{
|
||||
if (sslContextFactory == null)
|
||||
{
|
||||
IOException failure = new ConnectException("Missing " + SslContextFactory.class.getSimpleName() + " for " + destination.getScheme() + " requests");
|
||||
callback.failed(failure);
|
||||
throw failure;
|
||||
}
|
||||
else
|
||||
{
|
||||
SslConnection sslConnection = createSslConnection(endPoint, destination);
|
||||
callback.succeeded((org.eclipse.jetty.client.api.Connection)sslConnection.getDecryptedEndPoint().getConnection());
|
||||
return sslConnection;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Connection connection = AbstractHttpClientTransport.this.newConnection(endPoint, destination);
|
||||
callback.succeeded((org.eclipse.jetty.client.api.Connection)connection);
|
||||
return connection;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
return destination.getClientConnectionFactory().newConnection(endPoint, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
|
||||
{
|
||||
ConnectionCallback callback = (ConnectionCallback)attachment;
|
||||
callback.failed(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectionCallback implements Promise<org.eclipse.jetty.client.api.Connection>
|
||||
{
|
||||
private final HttpDestination destination;
|
||||
private final Promise<org.eclipse.jetty.client.api.Connection> promise;
|
||||
|
||||
private ConnectionCallback(HttpDestination destination, Promise<org.eclipse.jetty.client.api.Connection> promise)
|
||||
{
|
||||
this.destination = destination;
|
||||
this.promise = promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded(org.eclipse.jetty.client.api.Connection result)
|
||||
{
|
||||
promise.succeeded(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
protected void connectionFailed(SocketChannel channel, Throwable x, Object attachment)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(x);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,16 +109,19 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
|||
return;
|
||||
}
|
||||
|
||||
URI uri = getAuthenticationURI(request);
|
||||
Authentication authentication = null;
|
||||
Authentication.HeaderInfo headerInfo = null;
|
||||
for (Authentication.HeaderInfo element : headerInfos)
|
||||
URI uri = getAuthenticationURI(request);
|
||||
if (uri != null)
|
||||
{
|
||||
authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
|
||||
if (authentication != null)
|
||||
for (Authentication.HeaderInfo element : headerInfos)
|
||||
{
|
||||
headerInfo = element;
|
||||
break;
|
||||
authentication = client.getAuthenticationStore().findAuthentication(element.getType(), uri, element.getRealm());
|
||||
if (authentication != null)
|
||||
{
|
||||
headerInfo = element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (authentication == null)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client.http;
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.BlockingDeque;
|
||||
|
@ -33,9 +33,9 @@ import org.eclipse.jetty.util.component.Dumpable;
|
|||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class HttpConnectionPool implements Dumpable
|
||||
public class ConnectionPool implements Dumpable
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpConnectionPool.class);
|
||||
private static final Logger LOG = Log.getLogger(ConnectionPool.class);
|
||||
|
||||
private final AtomicInteger connectionCount = new AtomicInteger();
|
||||
private final Destination destination;
|
||||
|
@ -44,7 +44,7 @@ public class HttpConnectionPool implements Dumpable
|
|||
private final BlockingDeque<Connection> idleConnections;
|
||||
private final BlockingQueue<Connection> activeConnections;
|
||||
|
||||
public HttpConnectionPool(Destination destination, int maxConnections, Promise<Connection> connectionPromise)
|
||||
public ConnectionPool(Destination destination, int maxConnections, Promise<Connection> connectionPromise)
|
||||
{
|
||||
this.destination = destination;
|
||||
this.maxConnections = maxConnections;
|
|
@ -73,4 +73,10 @@ public abstract class HttpChannel
|
|||
{
|
||||
disassociate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%h", getClass().getSimpleName(), this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,10 +27,12 @@ import java.net.URI;
|
|||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
@ -44,7 +46,6 @@ import org.eclipse.jetty.client.api.AuthenticationStore;
|
|||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Destination;
|
||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||
|
@ -57,7 +58,6 @@ import org.eclipse.jetty.io.MappedByteBufferPool;
|
|||
import org.eclipse.jetty.util.Jetty;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.SocketAddressResolver;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -104,12 +104,13 @@ public class HttpClient extends ContainerLifeCycle
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpClient.class);
|
||||
|
||||
private final ConcurrentMap<String, HttpDestination> destinations = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Origin, HttpDestination> destinations = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Long, HttpConversation> conversations = new ConcurrentHashMap<>();
|
||||
private final List<ProtocolHandler> handlers = new ArrayList<>();
|
||||
private final List<Request.Listener> requestListeners = new ArrayList<>();
|
||||
private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
|
||||
private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
|
||||
private final ProxyConfiguration proxyConfig = new ProxyConfiguration();
|
||||
private final HttpClientTransport transport;
|
||||
private final SslContextFactory sslContextFactory;
|
||||
private volatile CookieManager cookieManager;
|
||||
|
@ -132,7 +133,6 @@ public class HttpClient extends ContainerLifeCycle
|
|||
private volatile boolean tcpNoDelay = true;
|
||||
private volatile boolean dispatchIO = true;
|
||||
private volatile boolean strictEventOrdering = false;
|
||||
private volatile ProxyConfiguration proxyConfig;
|
||||
private volatile HttpField encodingField;
|
||||
|
||||
/**
|
||||
|
@ -359,7 +359,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
*/
|
||||
public Request newRequest(String host, int port)
|
||||
{
|
||||
return newRequest(address("http", host, port));
|
||||
return newRequest(new Origin("http", host, port).asString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -417,13 +417,6 @@ public class HttpClient extends ContainerLifeCycle
|
|||
return newRequest;
|
||||
}
|
||||
|
||||
public String address(String scheme, String host, int port)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
URIUtil.appendSchemeHostPort(result, scheme, host, port);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Destination} for the given scheme, host and port.
|
||||
* Applications may use {@link Destination}s to create {@link Connection}s
|
||||
|
@ -446,20 +439,20 @@ public class HttpClient extends ContainerLifeCycle
|
|||
{
|
||||
port = normalizePort(scheme, port);
|
||||
|
||||
String address = address(scheme, host, port);
|
||||
HttpDestination destination = destinations.get(address);
|
||||
Origin origin = new Origin(scheme, host, port);
|
||||
HttpDestination destination = destinations.get(origin);
|
||||
if (destination == null)
|
||||
{
|
||||
destination = transport.newHttpDestination(scheme, host, port);
|
||||
destination = transport.newHttpDestination(origin);
|
||||
if (isRunning())
|
||||
{
|
||||
HttpDestination existing = destinations.putIfAbsent(address, destination);
|
||||
HttpDestination existing = destinations.putIfAbsent(origin, destination);
|
||||
if (existing != null)
|
||||
destination = existing;
|
||||
else
|
||||
LOG.debug("Created {}", destination);
|
||||
if (!isRunning())
|
||||
destinations.remove(address);
|
||||
destinations.remove(origin);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -486,13 +479,16 @@ public class HttpClient extends ContainerLifeCycle
|
|||
|
||||
protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
|
||||
{
|
||||
Destination.Address address = destination.getConnectAddress();
|
||||
Origin.Address address = destination.getConnectAddress();
|
||||
resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
|
||||
{
|
||||
@Override
|
||||
public void succeeded(SocketAddress socketAddress)
|
||||
{
|
||||
transport.connect(destination, socketAddress, promise);
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
|
||||
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
|
||||
transport.connect(socketAddress, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -559,7 +555,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the max time a connection can take to connect to destinations
|
||||
* @return the max time, in milliseconds, a connection can take to connect to destinations
|
||||
*/
|
||||
public long getConnectTimeout()
|
||||
{
|
||||
|
@ -567,7 +563,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @param connectTimeout the max time a connection can take to connect to destinations
|
||||
* @param connectTimeout the max time, in milliseconds, a connection can take to connect to destinations
|
||||
* @see java.net.Socket#connect(SocketAddress, int)
|
||||
*/
|
||||
public void setConnectTimeout(long connectTimeout)
|
||||
|
@ -592,7 +588,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the max time a connection can be idle (that is, without traffic of bytes in either direction)
|
||||
* @return the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
|
||||
*/
|
||||
public long getIdleTimeout()
|
||||
{
|
||||
|
@ -600,7 +596,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* @param idleTimeout the max time a connection can be idle (that is, without traffic of bytes in either direction)
|
||||
* @param idleTimeout the max time, in milliseconds, a connection can be idle (that is, without traffic of bytes in either direction)
|
||||
*/
|
||||
public void setIdleTimeout(long idleTimeout)
|
||||
{
|
||||
|
@ -881,14 +877,6 @@ public class HttpClient extends ContainerLifeCycle
|
|||
return proxyConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param proxyConfig the forward proxy configuration
|
||||
*/
|
||||
public void setProxyConfiguration(ProxyConfiguration proxyConfig)
|
||||
{
|
||||
this.proxyConfig = proxyConfig;
|
||||
}
|
||||
|
||||
protected HttpField getAcceptEncodingField()
|
||||
{
|
||||
return encodingField;
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
|
||||
/**
|
||||
* {@link HttpClientTransport} represents what transport implementations should provide
|
||||
|
@ -34,8 +34,11 @@ import org.eclipse.jetty.util.Promise;
|
|||
* but the HTTP exchange may also be carried using the SPDY protocol or the FCGI protocol or, in future,
|
||||
* other protocols.
|
||||
*/
|
||||
public interface HttpClientTransport
|
||||
public interface HttpClientTransport extends ClientConnectionFactory
|
||||
{
|
||||
public static final String HTTP_DESTINATION_CONTEXT_KEY = "http.destination";
|
||||
public static final String HTTP_CONNECTION_PROMISE_CONTEXT_KEY = "http.connection.promise";
|
||||
|
||||
/**
|
||||
* Sets the {@link HttpClient} instance on this transport.
|
||||
* <p />
|
||||
|
@ -53,27 +56,16 @@ public interface HttpClientTransport
|
|||
* {@link HttpDestination} controls the destination-connection cardinality: protocols like
|
||||
* HTTP have 1-N cardinality, while multiplexed protocols like SPDY have a 1-1 cardinality.
|
||||
*
|
||||
* @param scheme the destination scheme
|
||||
* @param host the destination host
|
||||
* @param port the destination port
|
||||
* @param origin the destination origin
|
||||
* @return a new, transport-specific, {@link HttpDestination} object
|
||||
*/
|
||||
public HttpDestination newHttpDestination(String scheme, String host, int port);
|
||||
public HttpDestination newHttpDestination(Origin origin);
|
||||
|
||||
/**
|
||||
* Establishes a physical connection to the given {@code address}.
|
||||
*
|
||||
* @param destination the destination
|
||||
* @param address the address to connect to
|
||||
* @param promise the promise to notify when the connection succeeds or fails
|
||||
* @param context the context information to establish the connection
|
||||
*/
|
||||
public void connect(HttpDestination destination, SocketAddress address, Promise<Connection> promise);
|
||||
|
||||
/**
|
||||
* Establishes an encrypted tunnel over the given {@code connection}
|
||||
*
|
||||
* @param connection the connection to tunnel
|
||||
* @return the tunnelled connection
|
||||
*/
|
||||
public Connection tunnel(Connection connection);
|
||||
public void connect(SocketAddress address, Map<String, Object> context);
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ public abstract class HttpConnection implements Connection
|
|||
HttpVersion version = request.getVersion();
|
||||
HttpFields headers = request.getHeaders();
|
||||
ContentProvider content = request.getContent();
|
||||
ProxyConfiguration.Proxy proxy = destination.getProxy();
|
||||
|
||||
// Make sure the path is there
|
||||
String path = request.getPath();
|
||||
|
@ -91,7 +92,7 @@ public abstract class HttpConnection implements Connection
|
|||
path = "/";
|
||||
request.path(path);
|
||||
}
|
||||
if (destination.isProxied() && !HttpMethod.CONNECT.is(method))
|
||||
if (proxy != null && !HttpMethod.CONNECT.is(method))
|
||||
{
|
||||
path = request.getURI().toString();
|
||||
request.path(path);
|
||||
|
@ -104,6 +105,9 @@ public abstract class HttpConnection implements Connection
|
|||
headers.put(getHttpDestination().getHostField());
|
||||
}
|
||||
|
||||
if (request.getAgent() == null)
|
||||
headers.put(getHttpClient().getUserAgentField());
|
||||
|
||||
// Add content headers
|
||||
if (content != null)
|
||||
{
|
||||
|
@ -136,9 +140,18 @@ public abstract class HttpConnection implements Connection
|
|||
request.header(HttpHeader.COOKIE.asString(), cookieString.toString());
|
||||
|
||||
// Authorization
|
||||
URI authenticationURI = destination.isProxied() ? destination.getProxyURI() : request.getURI();
|
||||
Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
|
||||
if (authnResult != null)
|
||||
authnResult.apply(request);
|
||||
URI authenticationURI = proxy != null ? proxy.getURI() : request.getURI();
|
||||
if (authenticationURI != null)
|
||||
{
|
||||
Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
|
||||
if (authnResult != null)
|
||||
authnResult.apply(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%h", getClass().getSimpleName(), this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,51 +20,45 @@ package org.eclipse.jetty.client;
|
|||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.channels.AsynchronousCloseException;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.client.api.Destination;
|
||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
public abstract class HttpDestination implements Destination, Closeable, Dumpable
|
||||
{
|
||||
protected static final Logger LOG = Log.getLogger(HttpDestination.class);
|
||||
|
||||
private final HttpClient client;
|
||||
private final String scheme;
|
||||
private final String host;
|
||||
private final Address address;
|
||||
private final Origin origin;
|
||||
private final Queue<HttpExchange> exchanges;
|
||||
private final RequestNotifier requestNotifier;
|
||||
private final ResponseNotifier responseNotifier;
|
||||
private final Address proxyAddress;
|
||||
private final ProxyConfiguration.Proxy proxy;
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
private final HttpField hostField;
|
||||
|
||||
public HttpDestination(HttpClient client, String scheme, String host, int port)
|
||||
public HttpDestination(HttpClient client, Origin origin)
|
||||
{
|
||||
this.client = client;
|
||||
this.scheme = scheme;
|
||||
this.host = host;
|
||||
this.address = new Address(host, port);
|
||||
this.origin = origin;
|
||||
|
||||
this.exchanges = new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination());
|
||||
|
||||
|
@ -72,19 +66,40 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
this.responseNotifier = new ResponseNotifier(client);
|
||||
|
||||
ProxyConfiguration proxyConfig = client.getProxyConfiguration();
|
||||
proxyAddress = proxyConfig != null && proxyConfig.matches(host, port) ?
|
||||
new Address(proxyConfig.getHost(), proxyConfig.getPort()) : null;
|
||||
proxy = proxyConfig.match(origin);
|
||||
ClientConnectionFactory connectionFactory = client.getTransport();
|
||||
if (proxy != null)
|
||||
{
|
||||
connectionFactory = proxy.newClientConnectionFactory(connectionFactory);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HttpScheme.HTTPS.is(getScheme()))
|
||||
connectionFactory = newSslClientConnectionFactory(connectionFactory);
|
||||
}
|
||||
this.connectionFactory = connectionFactory;
|
||||
|
||||
if (!client.isDefaultPort(scheme, port))
|
||||
host += ":" + port;
|
||||
String host = getHost();
|
||||
if (!client.isDefaultPort(getScheme(), getPort()))
|
||||
host += ":" + getPort();
|
||||
hostField = new HttpField(HttpHeader.HOST, host);
|
||||
}
|
||||
|
||||
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||
}
|
||||
|
||||
public HttpClient getHttpClient()
|
||||
{
|
||||
return client;
|
||||
}
|
||||
|
||||
public Origin getOrigin()
|
||||
{
|
||||
return origin;
|
||||
}
|
||||
|
||||
public Queue<HttpExchange> getHttpExchanges()
|
||||
{
|
||||
return exchanges;
|
||||
|
@ -100,10 +115,20 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
return responseNotifier;
|
||||
}
|
||||
|
||||
public ProxyConfiguration.Proxy getProxy()
|
||||
{
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public ClientConnectionFactory getClientConnectionFactory()
|
||||
{
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScheme()
|
||||
{
|
||||
return scheme;
|
||||
return origin.getScheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -111,32 +136,18 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
{
|
||||
// InetSocketAddress.getHostString() transforms the host string
|
||||
// in case of IPv6 addresses, so we return the original host string
|
||||
return host;
|
||||
return origin.getAddress().getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort()
|
||||
{
|
||||
return address.getPort();
|
||||
return origin.getAddress().getPort();
|
||||
}
|
||||
|
||||
public Address getConnectAddress()
|
||||
public Origin.Address getConnectAddress()
|
||||
{
|
||||
return isProxied() ? proxyAddress : address;
|
||||
}
|
||||
|
||||
public boolean isProxied()
|
||||
{
|
||||
return proxyAddress != null;
|
||||
}
|
||||
|
||||
public URI getProxyURI()
|
||||
{
|
||||
ProxyConfiguration proxyConfiguration = client.getProxyConfiguration();
|
||||
String uri = getScheme() + "://" + proxyConfiguration.getHost();
|
||||
if (!client.isDefaultPort(getScheme(), proxyConfiguration.getPort()))
|
||||
uri += ":" + proxyConfiguration.getPort();
|
||||
return URI.create(uri);
|
||||
return proxy == null ? origin.getAddress() : proxy.getAddress();
|
||||
}
|
||||
|
||||
public HttpField getHostField()
|
||||
|
@ -146,7 +157,7 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
|
||||
protected void send(Request request, List<Response.ResponseListener> listeners)
|
||||
{
|
||||
if (!scheme.equals(request.getScheme()))
|
||||
if (!getScheme().equals(request.getScheme()))
|
||||
throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this);
|
||||
if (!getHost().equals(request.getHost()))
|
||||
throw new IllegalArgumentException("Invalid request host " + request.getHost() + " for destination " + this);
|
||||
|
@ -188,7 +199,7 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
|
||||
public void newConnection(Promise<Connection> promise)
|
||||
{
|
||||
createConnection(new ProxyPromise(promise));
|
||||
createConnection(promise);
|
||||
}
|
||||
|
||||
protected void createConnection(Promise<Connection> promise)
|
||||
|
@ -207,6 +218,10 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
LOG.debug("Closed {}", this);
|
||||
}
|
||||
|
||||
public void close(Connection connection)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts all the {@link HttpExchange}s queued in this destination.
|
||||
*
|
||||
|
@ -236,18 +251,6 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
getResponseNotifier().notifyComplete(listeners, new Result(request, cause, response, cause));
|
||||
}
|
||||
|
||||
protected void tunnelSucceeded(Connection connection, Promise<Connection> promise)
|
||||
{
|
||||
// Wrap the connection with TLS
|
||||
promise.succeeded(client.getTransport().tunnel(connection));
|
||||
}
|
||||
|
||||
protected void tunnelFailed(Connection connection, Promise<Connection> promise, Throwable failure)
|
||||
{
|
||||
promise.failed(failure);
|
||||
connection.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dump()
|
||||
{
|
||||
|
@ -262,7 +265,7 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
|
||||
public String asString()
|
||||
{
|
||||
return client.address(getScheme(), getHost(), getPort());
|
||||
return origin.asString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -271,84 +274,6 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
return String.format("%s(%s)%s",
|
||||
HttpDestination.class.getSimpleName(),
|
||||
asString(),
|
||||
proxyAddress == null ? "" : " via " + proxyAddress.getHost() + ":" + proxyAddress.getPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether to establish a proxy tunnel using HTTP CONNECT.
|
||||
* It is implemented as a promise because it needs to establish the tunnel
|
||||
* when the TCP connection is succeeded, and needs to notify another
|
||||
* promise when the tunnel is established (or failed).
|
||||
*/
|
||||
private class ProxyPromise implements Promise<Connection>
|
||||
{
|
||||
private final Promise<Connection> delegate;
|
||||
|
||||
private ProxyPromise(Promise<Connection> delegate)
|
||||
{
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded(Connection connection)
|
||||
{
|
||||
if (isProxied() && HttpScheme.HTTPS.is(getScheme()))
|
||||
{
|
||||
if (client.getSslContextFactory() != null)
|
||||
{
|
||||
tunnel(connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
String message = String.format("Cannot perform requests over SSL, no %s in %s",
|
||||
SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
|
||||
delegate.failed(new IllegalStateException(message));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
delegate.succeeded(connection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
delegate.failed(x);
|
||||
}
|
||||
|
||||
private void tunnel(final Connection connection)
|
||||
{
|
||||
String target = address.getHost() + ":" + address.getPort();
|
||||
Request connect = client.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
|
||||
.scheme(HttpScheme.HTTP.asString())
|
||||
.method(HttpMethod.CONNECT)
|
||||
.path(target)
|
||||
.header(HttpHeader.HOST, target)
|
||||
.timeout(client.getConnectTimeout(), TimeUnit.MILLISECONDS);
|
||||
connection.send(connect, new Response.CompleteListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (result.isFailed())
|
||||
{
|
||||
tunnelFailed(connection, delegate, result.getFailure());
|
||||
}
|
||||
else
|
||||
{
|
||||
Response response = result.getResponse();
|
||||
if (response.getStatus() == 200)
|
||||
{
|
||||
tunnelSucceeded(connection, delegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
tunnelFailed(connection, delegate, new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
proxy == null ? "" : " via " + proxy);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
public class HttpProxy extends ProxyConfiguration.Proxy
|
||||
{
|
||||
public HttpProxy(String host, int port)
|
||||
{
|
||||
this(new Origin.Address(host, port), false);
|
||||
}
|
||||
|
||||
public HttpProxy(Origin.Address address, boolean secure)
|
||||
{
|
||||
super(address, secure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
return new HttpProxyClientConnectionFactory(connectionFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI()
|
||||
{
|
||||
String scheme = isSecure() ? HttpScheme.HTTPS.asString() : HttpScheme.HTTP.asString();
|
||||
return URI.create(new Origin(scheme, getAddress()).asString());
|
||||
}
|
||||
|
||||
public static class HttpProxyClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpProxyClientConnectionFactory.class);
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
|
||||
public HttpProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
final ProxyPromise proxyPromise = new ProxyPromise(endPoint, promise, context);
|
||||
// Replace the promise with the proxy one
|
||||
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, proxyPromise);
|
||||
return connectionFactory.newConnection(endPoint, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether to establish a proxy tunnel using HTTP CONNECT.
|
||||
* It is implemented as a promise because it needs to establish the
|
||||
* tunnel after the TCP connection is succeeded, and needs to notify
|
||||
* the nested promise when the tunnel is established (or failed).
|
||||
*/
|
||||
private class ProxyPromise implements Promise<Connection>
|
||||
{
|
||||
private final EndPoint endPoint;
|
||||
private final Promise<Connection> promise;
|
||||
private final Map<String, Object> context;
|
||||
|
||||
private ProxyPromise(EndPoint endPoint, Promise<Connection> promise, Map<String, Object> context)
|
||||
{
|
||||
this.endPoint = endPoint;
|
||||
this.promise = promise;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded(Connection connection)
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
if (HttpScheme.HTTPS.is(destination.getScheme()))
|
||||
{
|
||||
SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory();
|
||||
if (sslContextFactory != null)
|
||||
{
|
||||
tunnel(destination, connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
String message = String.format("Cannot perform requests over SSL, no %s in %s",
|
||||
SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
|
||||
promise.failed(new IllegalStateException(message));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
promise.succeeded(connection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
promise.failed(x);
|
||||
}
|
||||
|
||||
private void tunnel(HttpDestination destination, final Connection connection)
|
||||
{
|
||||
String target = destination.getOrigin().getAddress().asString();
|
||||
Origin.Address proxyAddress = destination.getConnectAddress();
|
||||
HttpClient httpClient = destination.getHttpClient();
|
||||
Request connect = httpClient.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
|
||||
.scheme(HttpScheme.HTTP.asString())
|
||||
.method(HttpMethod.CONNECT)
|
||||
.path(target)
|
||||
.header(HttpHeader.HOST, target)
|
||||
.timeout(httpClient.getConnectTimeout(), TimeUnit.MILLISECONDS);
|
||||
|
||||
connection.send(connect, new Response.CompleteListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (result.isFailed())
|
||||
{
|
||||
tunnelFailed(result.getFailure());
|
||||
}
|
||||
else
|
||||
{
|
||||
Response response = result.getResponse();
|
||||
if (response.getStatus() == 200)
|
||||
{
|
||||
tunnelSucceeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
tunnelFailed(new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void tunnelSucceeded()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Replace the promise back with the original
|
||||
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpClient client = destination.getHttpClient();
|
||||
ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||
org.eclipse.jetty.io.Connection oldConnection = endPoint.getConnection();
|
||||
org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
|
||||
Helper.replaceConnection(oldConnection, newConnection);
|
||||
LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
tunnelFailed(x);
|
||||
}
|
||||
}
|
||||
|
||||
private void tunnelFailed(Throwable failure)
|
||||
{
|
||||
endPoint.close();
|
||||
failed(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.client.api.ContentProvider;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
|
@ -55,10 +56,11 @@ public class HttpRequest implements Request
|
|||
private static final AtomicLong ids = new AtomicLong();
|
||||
|
||||
private final HttpFields headers = new HttpFields();
|
||||
private final Fields params = new Fields();
|
||||
private final Fields params = new Fields(true);
|
||||
private final Map<String, Object> attributes = new HashMap<>();
|
||||
private final List<RequestListener> requestListeners = new ArrayList<>();
|
||||
private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
|
||||
private final AtomicReference<Throwable> aborted = new AtomicReference<>();
|
||||
private final HttpClient client;
|
||||
private final long conversation;
|
||||
private final String host;
|
||||
|
@ -73,7 +75,6 @@ public class HttpRequest implements Request
|
|||
private long timeout;
|
||||
private ContentProvider content;
|
||||
private boolean followRedirects;
|
||||
private volatile Throwable aborted;
|
||||
|
||||
public HttpRequest(HttpClient client, URI uri)
|
||||
{
|
||||
|
@ -95,7 +96,6 @@ public class HttpRequest implements Request
|
|||
HttpField acceptEncodingField = client.getAcceptEncodingField();
|
||||
if (acceptEncodingField != null)
|
||||
headers.put(acceptEncodingField);
|
||||
headers.put(client.getUserAgentField());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -505,16 +505,19 @@ public class HttpRequest implements Request
|
|||
@Override
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
aborted = Objects.requireNonNull(cause);
|
||||
// The conversation may be null if it is already completed
|
||||
HttpConversation conversation = client.getConversation(getConversationID(), false);
|
||||
return conversation != null && conversation.abort(cause);
|
||||
if (aborted.compareAndSet(null, Objects.requireNonNull(cause)))
|
||||
{
|
||||
// The conversation may be null if it is already completed
|
||||
HttpConversation conversation = client.getConversation(getConversationID(), false);
|
||||
return conversation != null && conversation.abort(cause);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable getAbortCause()
|
||||
{
|
||||
return aborted;
|
||||
return aborted.get();
|
||||
}
|
||||
|
||||
private String buildQuery()
|
||||
|
@ -523,13 +526,13 @@ public class HttpRequest implements Request
|
|||
for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext();)
|
||||
{
|
||||
Fields.Field field = iterator.next();
|
||||
String[] values = field.values();
|
||||
for (int i = 0; i < values.length; ++i)
|
||||
List<String> values = field.getValues();
|
||||
for (int i = 0; i < values.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
result.append("&");
|
||||
result.append(field.name()).append("=");
|
||||
result.append(urlEncode(values[i]));
|
||||
result.append(field.getName()).append("=");
|
||||
result.append(urlEncode(values.get(i)));
|
||||
}
|
||||
if (iterator.hasNext())
|
||||
result.append("&");
|
||||
|
@ -589,7 +592,7 @@ public class HttpRequest implements Request
|
|||
path += "?" + query;
|
||||
URI result = URI.create(path);
|
||||
if (!result.isAbsolute() && !result.isOpaque())
|
||||
result = URI.create(client.address(getScheme(), getHost(), getPort()) + path);
|
||||
result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,9 +29,9 @@ public abstract class MultiplexHttpDestination<C extends Connection> extends Htt
|
|||
private final AtomicReference<ConnectState> connect = new AtomicReference<>(ConnectState.DISCONNECTED);
|
||||
private C connection;
|
||||
|
||||
protected MultiplexHttpDestination(HttpClient client, String scheme, String host, int port)
|
||||
protected MultiplexHttpDestination(HttpClient client, Origin origin)
|
||||
{
|
||||
super(client, scheme, host, port);
|
||||
super(client, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -94,7 +94,7 @@ public abstract class MultiplexHttpDestination<C extends Connection> extends Htt
|
|||
{
|
||||
HttpClient client = getHttpClient();
|
||||
final HttpExchange exchange = getHttpExchanges().poll();
|
||||
LOG.debug("Processing exchange {} on connection {}", exchange, connection);
|
||||
LOG.debug("Processing {} on {}", exchange, connection);
|
||||
if (exchange == null)
|
||||
return false;
|
||||
|
||||
|
@ -126,6 +126,18 @@ public abstract class MultiplexHttpDestination<C extends Connection> extends Htt
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(Connection connection)
|
||||
{
|
||||
super.close(connection);
|
||||
while (true)
|
||||
{
|
||||
ConnectState current = connect.get();
|
||||
if (connect.compareAndSet(current, ConnectState.DISCONNECTED))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void send(C connection, HttpExchange exchange);
|
||||
|
||||
private enum ConnectState
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
||||
public class Origin
|
||||
{
|
||||
private final String scheme;
|
||||
private final Address address;
|
||||
|
||||
public Origin(String scheme, String host, int port)
|
||||
{
|
||||
this(scheme, new Address(host, port));
|
||||
}
|
||||
|
||||
public Origin(String scheme, Address address)
|
||||
{
|
||||
this.scheme = Objects.requireNonNull(scheme);
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getScheme()
|
||||
{
|
||||
return scheme;
|
||||
}
|
||||
|
||||
public Address getAddress()
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
public String asString()
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
URIUtil.appendSchemeHostPort(result, scheme, address.host, address.port);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
Origin that = (Origin)obj;
|
||||
return scheme.equals(that.scheme) && address.equals(that.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = scheme.hashCode();
|
||||
result = 31 * result + address.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class Address
|
||||
{
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
public Address(String host, int port)
|
||||
{
|
||||
this.host = Objects.requireNonNull(host);
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getHost()
|
||||
{
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort()
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
Address that = (Address)obj;
|
||||
return host.equals(that.host) && port == that.port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = host.hashCode();
|
||||
result = 31 * result + port;
|
||||
return result;
|
||||
}
|
||||
|
||||
public String asString()
|
||||
{
|
||||
return String.format("%s:%d", host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return asString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@ public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHa
|
|||
protected URI getAuthenticationURI(Request request)
|
||||
{
|
||||
HttpDestination destination = getHttpClient().destinationFor(request.getScheme(), request.getHost(), request.getPort());
|
||||
return destination.isProxied() ? destination.getProxyURI() : request.getURI();
|
||||
ProxyConfiguration.Proxy proxy = destination.getProxy();
|
||||
return proxy != null ? proxy.getURI() : request.getURI();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
|
||||
/**
|
||||
* The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}.
|
||||
* <p />
|
||||
* Applications add subclasses of {@link Proxy} to this configuration via:
|
||||
* <pre>
|
||||
* ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
|
||||
* proxyConfig.getProxies().add(new HttpProxy(proxyHost, 8080));
|
||||
* </pre>
|
||||
*
|
||||
* @see HttpClient#getProxyConfiguration()
|
||||
*/
|
||||
public class ProxyConfiguration
|
||||
{
|
||||
private final List<Proxy> proxies = new ArrayList<>();
|
||||
|
||||
public List<Proxy> getProxies()
|
||||
{
|
||||
return proxies;
|
||||
}
|
||||
|
||||
public Proxy match(Origin origin)
|
||||
{
|
||||
for (Proxy proxy : getProxies())
|
||||
{
|
||||
if (proxy.matches(origin))
|
||||
return proxy;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static abstract class Proxy
|
||||
{
|
||||
private final Set<String> included = new HashSet<>();
|
||||
private final Set<String> excluded = new HashSet<>();
|
||||
private final Origin.Address address;
|
||||
private final boolean secure;
|
||||
|
||||
protected Proxy(Origin.Address address, boolean secure)
|
||||
{
|
||||
this.address = address;
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the address of this proxy
|
||||
*/
|
||||
public Origin.Address getAddress()
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the connection to the proxy must be secured via TLS
|
||||
*/
|
||||
public boolean isSecure()
|
||||
{
|
||||
return secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of origins that must be proxied
|
||||
* @see #matches(Origin)
|
||||
* @see #getExcludedAddresses()
|
||||
*/
|
||||
public Set<String> getIncludedAddresses()
|
||||
{
|
||||
return included;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of origins that must not be proxied.
|
||||
* @see #matches(Origin)
|
||||
* @see #getIncludedAddresses()
|
||||
*/
|
||||
public Set<String> getExcludedAddresses()
|
||||
{
|
||||
return excluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an URI representing this proxy, or null if no URI can represent this proxy
|
||||
*/
|
||||
public URI getURI()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the given {@code origin} with the included and excluded addresses,
|
||||
* returning true if the given {@code origin} is to be proxied.
|
||||
*
|
||||
* @param origin the origin to test for proxying
|
||||
* @return true if the origin must be proxied, false otherwise
|
||||
*/
|
||||
public boolean matches(Origin origin)
|
||||
{
|
||||
boolean result = included.isEmpty();
|
||||
Origin.Address address = origin.getAddress();
|
||||
for (String included : this.included)
|
||||
{
|
||||
if (matches(address, included))
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (String excluded : this.excluded)
|
||||
{
|
||||
if (matches(address, excluded))
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean matches(Origin.Address address, String pattern)
|
||||
{
|
||||
// TODO: add support for CIDR notation like 192.168.0.0/24, see DoSFilter
|
||||
int colon = pattern.indexOf(':');
|
||||
if (colon < 0)
|
||||
return pattern.equals(address.getHost());
|
||||
String host = pattern.substring(0, colon);
|
||||
String port = pattern.substring(colon + 1);
|
||||
return host.equals(address.getHost()) && port.equals(String.valueOf(address.getPort()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param connectionFactory the nested {@link ClientConnectionFactory}
|
||||
* @return a new {@link ClientConnectionFactory} for this {@link Proxy}
|
||||
*/
|
||||
public abstract ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory);
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return address.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class Socks4Proxy extends ProxyConfiguration.Proxy
|
||||
{
|
||||
public Socks4Proxy(String host, int port)
|
||||
{
|
||||
this(new Origin.Address(host, port), false);
|
||||
}
|
||||
|
||||
public Socks4Proxy(Origin.Address address, boolean secure)
|
||||
{
|
||||
super(address, secure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
return new Socks4ProxyClientConnectionFactory(connectionFactory);
|
||||
}
|
||||
|
||||
public static class Socks4ProxyClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
|
||||
public Socks4ProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Executor executor = destination.getHttpClient().getExecutor();
|
||||
return new Socks4ProxyConnection(endPoint, executor, connectionFactory, context);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Socks4ProxyConnection extends AbstractConnection implements Callback
|
||||
{
|
||||
private static final Pattern IPv4_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})");
|
||||
private static final Logger LOG = Log.getLogger(Socks4ProxyConnection.class);
|
||||
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
private final Map<String, Object> context;
|
||||
|
||||
public Socks4ProxyConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
|
||||
{
|
||||
super(endPoint, executor);
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
writeSocks4Connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the SOCKS "connect" bytes, differentiating between SOCKS 4 and 4A;
|
||||
* the former sends an IPv4 address, the latter the full domain name.
|
||||
*/
|
||||
private void writeSocks4Connect()
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
String host = destination.getHost();
|
||||
short port = (short)destination.getPort();
|
||||
Matcher matcher = IPv4_PATTERN.matcher(host);
|
||||
if (matcher.matches())
|
||||
{
|
||||
// SOCKS 4
|
||||
ByteBuffer buffer = ByteBuffer.allocate(9);
|
||||
buffer.put((byte)4).put((byte)1).putShort(port);
|
||||
for (int i = 1; i <= 4; ++i)
|
||||
buffer.put((byte)Integer.parseInt(matcher.group(i)));
|
||||
buffer.put((byte)0);
|
||||
buffer.flip();
|
||||
getEndPoint().write(this, buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
// SOCKS 4A
|
||||
byte[] hostBytes = host.getBytes(StandardCharsets.UTF_8);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(9 + hostBytes.length + 1);
|
||||
buffer.put((byte)4).put((byte)1).putShort(port);
|
||||
buffer.put((byte)0).put((byte)0).put((byte)0).put((byte)1).put((byte)0);
|
||||
buffer.put(hostBytes).put((byte)0);
|
||||
buffer.flip();
|
||||
getEndPoint().write(this, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
LOG.debug("Written SOCKS4 connect request");
|
||||
fillInterested();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
close();
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFillable()
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.allocate(8);
|
||||
int filled = getEndPoint().fill(buffer);
|
||||
LOG.debug("Read SOCKS4 connect response, {} bytes", filled);
|
||||
if (filled != 8)
|
||||
throw new IOException("Invalid response from SOCKS4 proxy");
|
||||
int result = buffer.get(1);
|
||||
if (result == 0x5A)
|
||||
tunnel();
|
||||
else
|
||||
throw new IOException("SOCKS4 tunnel failed with code " + result);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
private void tunnel()
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpClient client = destination.getHttpClient();
|
||||
ClientConnectionFactory connectionFactory = this.connectionFactory;
|
||||
if (HttpScheme.HTTPS.is(destination.getScheme()))
|
||||
connectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||
org.eclipse.jetty.io.Connection connection = connectionFactory.newConnection(getEndPoint(), context);
|
||||
ClientConnectionFactory.Helper.replaceConnection(this, connection);
|
||||
LOG.debug("SOCKS4 tunnel established: {} over {}", this, connection);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
failed(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,43 +63,4 @@ public interface Destination
|
|||
* @param promise the promise of a new, unpooled, {@link Connection}
|
||||
*/
|
||||
void newConnection(Promise<Connection> promise);
|
||||
|
||||
public static class Address
|
||||
{
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
public Address(String host, int port)
|
||||
{
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getHost()
|
||||
{
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort()
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
Address that = (Address)obj;
|
||||
return host.equals(that.host) && port == that.port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = host.hashCode();
|
||||
result = 31 * result + port;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client.api;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The configuration of the forward proxy to use with {@link org.eclipse.jetty.client.HttpClient}.
|
||||
* <p />
|
||||
* Configuration parameters include the host and port of the forward proxy, and a list of
|
||||
* {@link #getExcludedOrigins() origins} that are excluded from being proxied.
|
||||
*
|
||||
* @see org.eclipse.jetty.client.HttpClient#setProxyConfiguration(ProxyConfiguration)
|
||||
*/
|
||||
public class ProxyConfiguration
|
||||
{
|
||||
private final Set<String> excluded = new HashSet<>();
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
public ProxyConfiguration(String host, int port)
|
||||
{
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the host name of the forward proxy
|
||||
*/
|
||||
public String getHost()
|
||||
{
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the port of the forward proxy
|
||||
*/
|
||||
public int getPort()
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the given {@code host} and {@code port} with the list of excluded origins,
|
||||
* returning true if the origin is to be proxied, false if it is excluded from proxying.
|
||||
* @param host the host to match
|
||||
* @param port the port to match
|
||||
* @return true if the origin made of {@code host} and {@code port} is to be proxied,
|
||||
* false if it is excluded from proxying.
|
||||
*/
|
||||
public boolean matches(String host, int port)
|
||||
{
|
||||
String hostPort = host + ":" + port;
|
||||
return !getExcludedOrigins().contains(hostPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of origins to exclude from proxying, in the form "host:port".
|
||||
*/
|
||||
public Set<String> getExcludedOrigins()
|
||||
{
|
||||
return excluded;
|
||||
}
|
||||
}
|
|
@ -18,16 +18,21 @@
|
|||
|
||||
package org.eclipse.jetty.client.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.client.AbstractHttpClientTransport;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
|
||||
public class HttpClientTransportOverHTTP extends AbstractHttpClientTransport
|
||||
{
|
||||
public HttpClientTransportOverHTTP()
|
||||
{
|
||||
this(Runtime.getRuntime().availableProcessors());
|
||||
this(Math.max(1, Runtime.getRuntime().availableProcessors() / 2));
|
||||
}
|
||||
|
||||
public HttpClientTransportOverHTTP(int selectors)
|
||||
|
@ -36,21 +41,20 @@ public class HttpClientTransportOverHTTP extends AbstractHttpClientTransport
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(String scheme, String host, int port)
|
||||
public HttpDestination newHttpDestination(Origin origin)
|
||||
{
|
||||
return new HttpDestinationOverHTTP(getHttpClient(), scheme, host, port);
|
||||
return new HttpDestinationOverHTTP(getHttpClient(), origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection newConnection(EndPoint endPoint, HttpDestination destination)
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
return new HttpConnectionOverHTTP(endPoint, destination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.client.api.Connection tunnel(org.eclipse.jetty.client.api.Connection connection)
|
||||
{
|
||||
HttpConnectionOverHTTP httpConnection = (HttpConnectionOverHTTP)connection;
|
||||
return tunnel(httpConnection.getEndPoint(), httpConnection.getHttpDestination(), connection);
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination);
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.succeeded(connection);
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
|||
if (exchange != null)
|
||||
return exchange.getRequest().abort(new TimeoutException());
|
||||
|
||||
getHttpDestination().remove(this);
|
||||
getHttpDestination().close(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
|||
@Override
|
||||
public void close()
|
||||
{
|
||||
getHttpDestination().remove(this);
|
||||
getHttpDestination().close(this);
|
||||
getEndPoint().shutdownOutput();
|
||||
LOG.debug("{} oshut", this);
|
||||
getEndPoint().close();
|
||||
|
@ -136,9 +136,9 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x(l:%s <-> r:%s)",
|
||||
HttpConnection.class.getSimpleName(),
|
||||
hashCode(),
|
||||
return String.format("%s@%h(l:%s <-> r:%s)",
|
||||
getClass().getSimpleName(),
|
||||
this,
|
||||
getEndPoint().getLocalAddress(),
|
||||
getEndPoint().getRemoteAddress());
|
||||
}
|
||||
|
@ -171,5 +171,11 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
|||
{
|
||||
HttpConnectionOverHTTP.this.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return HttpConnectionOverHTTP.this.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,30 +21,32 @@ package org.eclipse.jetty.client.http;
|
|||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jetty.client.ConnectionPool;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
|
||||
public class HttpDestinationOverHTTP extends HttpDestination implements Promise<Connection>
|
||||
public class HttpDestinationOverHTTP extends HttpDestination implements Promise<Connection>
|
||||
{
|
||||
private final HttpConnectionPool connectionPool;
|
||||
private final ConnectionPool connectionPool;
|
||||
|
||||
public HttpDestinationOverHTTP(HttpClient client, String scheme, String host, int port)
|
||||
public HttpDestinationOverHTTP(HttpClient client, Origin origin)
|
||||
{
|
||||
super(client, scheme, host, port);
|
||||
this.connectionPool = newHttpConnectionPool(client);
|
||||
super(client, origin);
|
||||
this.connectionPool = newConnectionPool(client);
|
||||
}
|
||||
|
||||
protected HttpConnectionPool newHttpConnectionPool(HttpClient client)
|
||||
protected ConnectionPool newConnectionPool(HttpClient client)
|
||||
{
|
||||
return new HttpConnectionPool(this, client.getMaxConnectionsPerDestination(), this);
|
||||
return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this);
|
||||
}
|
||||
|
||||
public HttpConnectionPool getHttpConnectionPool()
|
||||
public ConnectionPool getConnectionPool()
|
||||
{
|
||||
return connectionPool;
|
||||
}
|
||||
|
@ -153,23 +155,25 @@ public class HttpDestinationOverHTTP extends HttpDestination implements Promise
|
|||
else
|
||||
{
|
||||
LOG.debug("{} is stopped", client);
|
||||
remove(connection);
|
||||
close(connection);
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected void remove(HttpConnectionOverHTTP connection)
|
||||
@Override
|
||||
public void close(Connection oldConnection)
|
||||
{
|
||||
connectionPool.remove(connection);
|
||||
super.close(oldConnection);
|
||||
connectionPool.remove(oldConnection);
|
||||
|
||||
// We need to execute queued requests even if this connection failed.
|
||||
// We may create a connection that is not needed, but it will eventually
|
||||
// idle timeout, so no worries
|
||||
if (!getHttpExchanges().isEmpty())
|
||||
{
|
||||
connection = acquire();
|
||||
if (connection != null)
|
||||
process(connection, false);
|
||||
HttpConnectionOverHTTP newConnection = acquire();
|
||||
if (newConnection != null)
|
||||
process(newConnection, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,21 +65,25 @@ public abstract class BufferingResponseListener extends Listener.Adapter
|
|||
HttpFields headers = response.getHeaders();
|
||||
long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||
if (length > maxLength)
|
||||
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
|
||||
|
||||
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
|
||||
if (contentType != null)
|
||||
{
|
||||
String charset = "charset=";
|
||||
int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
|
||||
if (index > 0)
|
||||
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
|
||||
}
|
||||
else
|
||||
{
|
||||
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
|
||||
if (contentType != null)
|
||||
{
|
||||
String encoding = contentType.substring(index + charset.length());
|
||||
// Sometimes charsets arrive with an ending semicolon
|
||||
index = encoding.indexOf(';');
|
||||
String charset = "charset=";
|
||||
int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
|
||||
if (index > 0)
|
||||
encoding = encoding.substring(0, index);
|
||||
this.encoding = encoding;
|
||||
{
|
||||
String encoding = contentType.substring(index + charset.length());
|
||||
// Sometimes charsets arrive with an ending semicolon
|
||||
index = encoding.indexOf(';');
|
||||
if (index > 0)
|
||||
encoding = encoding.substring(0, index);
|
||||
this.encoding = encoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,12 +93,16 @@ public abstract class BufferingResponseListener extends Listener.Adapter
|
|||
{
|
||||
long newLength = buffer.length + content.remaining();
|
||||
if (newLength > maxLength)
|
||||
{
|
||||
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
|
||||
|
||||
byte[] newBuffer = new byte[(int)newLength];
|
||||
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
|
||||
content.get(newBuffer, buffer.length, content.remaining());
|
||||
buffer = newBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] newBuffer = new byte[(int)newLength];
|
||||
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
|
||||
content.get(newBuffer, buffer.length, content.remaining());
|
||||
buffer = newBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.eclipse.jetty.client.util;
|
|||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -217,7 +218,7 @@ public class DigestAuthentication implements Authentication
|
|||
if (digester == null)
|
||||
return;
|
||||
|
||||
Charset charset = Charset.forName("ISO-8859-1");
|
||||
Charset charset = StandardCharsets.ISO_8859_1;
|
||||
String A1 = user + ":" + realm + ":" + password;
|
||||
String hashA1 = toHexString(digester.digest(A1.getBytes(charset)));
|
||||
|
||||
|
|
|
@ -112,8 +112,7 @@ public class InputStreamContentProvider implements ContentProvider
|
|||
*/
|
||||
private class InputStreamIterator implements Iterator<ByteBuffer>
|
||||
{
|
||||
private final byte[] bytes = new byte[bufferSize];
|
||||
private Exception failure;
|
||||
private Throwable failure;
|
||||
private ByteBuffer buffer;
|
||||
private Boolean hasNext;
|
||||
|
||||
|
@ -125,6 +124,7 @@ public class InputStreamContentProvider implements ContentProvider
|
|||
if (hasNext != null)
|
||||
return hasNext;
|
||||
|
||||
byte[] bytes = new byte[bufferSize];
|
||||
int read = stream.read(bytes);
|
||||
LOG.debug("Read {} bytes from {}", read, stream);
|
||||
if (read > 0)
|
||||
|
@ -145,7 +145,7 @@ public class InputStreamContentProvider implements ContentProvider
|
|||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.debug(x);
|
||||
if (failure == null)
|
||||
|
|
|
@ -37,6 +37,11 @@ public class StringContentProvider extends BytesContentProvider
|
|||
|
||||
public StringContentProvider(String content, String encoding)
|
||||
{
|
||||
super(content.getBytes(Charset.forName(encoding)));
|
||||
this(content, Charset.forName(encoding));
|
||||
}
|
||||
|
||||
public StringContentProvider(String content, Charset charset)
|
||||
{
|
||||
super(content.getBytes(charset));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,8 +116,8 @@ public class HostnameVerificationTest
|
|||
// Therefore this test may catch a SSLHandshakeException, or a ClosedChannelException.
|
||||
// If it is the former, we verify that its cause is a CertificateException.
|
||||
|
||||
// ExecutionException wraps an EofException that wraps the SSLHandshakeException
|
||||
Throwable cause = x.getCause().getCause();
|
||||
// ExecutionException wraps an SSLHandshakeException
|
||||
Throwable cause = x.getCause();
|
||||
if (cause instanceof SSLHandshakeException)
|
||||
assertThat(cause.getCause().getCause(), instanceOf(CertificateException.class));
|
||||
else
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HttpClientCustomProxyTest
|
||||
{
|
||||
public static final byte[] CAFE_BABE = new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
|
||||
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
private HttpClient client;
|
||||
|
||||
public void prepare(Handler handler) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new ServerConnector(server, new CAFEBABEServerConnectionFactory(new HttpConnectionFactory()));
|
||||
server.addConnector(connector);
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
|
||||
QueuedThreadPool executor = new QueuedThreadPool();
|
||||
executor.setName(executor.getName() + "-client");
|
||||
client = new HttpClient();
|
||||
client.setExecutor(executor);
|
||||
client.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
if (client != null)
|
||||
client.stop();
|
||||
if (server != null)
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomProxy() throws Exception
|
||||
{
|
||||
final String serverHost = "server";
|
||||
final int status = HttpStatus.NO_CONTENT_204;
|
||||
prepare(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
|
||||
response.setStatus(HttpServletResponse.SC_USE_PROXY);
|
||||
else if (serverHost.equals(request.getServerName()))
|
||||
response.setStatus(status);
|
||||
else
|
||||
response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
|
||||
}
|
||||
});
|
||||
|
||||
// Setup the custom proxy
|
||||
int proxyPort = connector.getLocalPort();
|
||||
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
|
||||
client.getProxyConfiguration().getProxies().add(new CAFEBABEProxy(new Origin.Address("localhost", proxyPort), false));
|
||||
|
||||
ContentResponse response = client.newRequest(serverHost, serverPort)
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
Assert.assertEquals(status, response.getStatus());
|
||||
}
|
||||
|
||||
private class CAFEBABEProxy extends ProxyConfiguration.Proxy
|
||||
{
|
||||
private CAFEBABEProxy(Origin.Address address, boolean secure)
|
||||
{
|
||||
super(address, secure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
return new CAFEBABEClientConnectionFactory(connectionFactory);
|
||||
}
|
||||
}
|
||||
|
||||
private class CAFEBABEClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
|
||||
private CAFEBABEClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Executor executor = destination.getHttpClient().getExecutor();
|
||||
return new CAFEBABEConnection(endPoint, executor, connectionFactory, context);
|
||||
}
|
||||
}
|
||||
|
||||
private class CAFEBABEConnection extends AbstractConnection
|
||||
{
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
private final Map<String, Object> context;
|
||||
|
||||
public CAFEBABEConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory connectionFactory, Map<String, Object> context)
|
||||
{
|
||||
super(endPoint, executor);
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
fillInterested();
|
||||
getEndPoint().write(new Callback.Adapter(), ByteBuffer.wrap(CAFE_BABE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFillable()
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.allocate(4);
|
||||
int filled = getEndPoint().fill(buffer);
|
||||
Assert.assertEquals(4, filled);
|
||||
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
||||
|
||||
// We are good, upgrade the connection
|
||||
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(getEndPoint(), context));
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
close();
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CAFEBABEServerConnectionFactory extends AbstractConnectionFactory
|
||||
{
|
||||
private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
|
||||
|
||||
private CAFEBABEServerConnectionFactory(org.eclipse.jetty.server.ConnectionFactory connectionFactory)
|
||||
{
|
||||
super("cafebabe");
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
{
|
||||
return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
|
||||
}
|
||||
}
|
||||
|
||||
private class CAFEBABEServerConnection extends AbstractConnection
|
||||
{
|
||||
private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
|
||||
|
||||
public CAFEBABEServerConnection(Connector connector, EndPoint endPoint, org.eclipse.jetty.server.ConnectionFactory connectionFactory)
|
||||
{
|
||||
super(endPoint, connector.getExecutor());
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
fillInterested();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFillable()
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.allocate(4);
|
||||
int filled = getEndPoint().fill(buffer);
|
||||
Assert.assertEquals(4, filled);
|
||||
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
||||
getEndPoint().write(new Callback.Adapter(), buffer);
|
||||
|
||||
// We are good, upgrade the connection
|
||||
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,6 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
|||
import org.eclipse.jetty.client.api.Destination;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionPool;
|
||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||
|
@ -60,7 +59,7 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
|||
Assert.assertEquals(200, response.getStatus());
|
||||
|
||||
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
||||
HttpConnectionPool connectionPool = httpDestination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = httpDestination.getConnectionPool();
|
||||
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
||||
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
||||
}
|
||||
|
@ -92,7 +91,7 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
|||
Assert.assertFalse(httpConnection.getEndPoint().isOpen());
|
||||
|
||||
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
||||
HttpConnectionPool connectionPool = httpDestination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = httpDestination.getConnectionPool();
|
||||
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
||||
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.eclipse.jetty.client.api.Request;
|
|||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionPool;
|
||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
@ -85,7 +84,7 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
|
||||
// Re-run after warmup
|
||||
iterations = 50_000;
|
||||
iterations = 5_000;
|
||||
for (int i = 0; i < runs; ++i)
|
||||
{
|
||||
run(random, iterations);
|
||||
|
@ -111,7 +110,7 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
|
|||
for (String host : Arrays.asList("localhost", "127.0.0.1"))
|
||||
{
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, connector.getLocalPort());
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
for (Connection connection : new ArrayList<>(connectionPool.getActiveConnections()))
|
||||
{
|
||||
HttpConnectionOverHTTP active = (HttpConnectionOverHTTP)connection;
|
||||
|
|
|
@ -27,7 +27,6 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.BasicAuthentication;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
@ -68,7 +67,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
|
|||
|
||||
int proxyPort = connector.getLocalPort();
|
||||
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
|
||||
client.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort));
|
||||
client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
|
||||
|
||||
ContentResponse response = client.newRequest(serverHost, serverPort)
|
||||
.scheme(scheme)
|
||||
|
@ -115,7 +114,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
|
|||
String proxyHost = "localhost";
|
||||
int proxyPort = connector.getLocalPort();
|
||||
int serverPort = proxyPort + 1; // Any port will do for these tests - just not the same as the proxy
|
||||
client.setProxyConfiguration(new ProxyConfiguration(proxyHost, proxyPort));
|
||||
client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
|
||||
|
||||
ContentResponse response1 = client.newRequest(serverHost, serverPort)
|
||||
.scheme(scheme)
|
||||
|
|
|
@ -28,7 +28,9 @@ import java.nio.channels.UnresolvedAddressException;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
@ -51,10 +53,10 @@ import org.eclipse.jetty.client.api.Request;
|
|||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionPool;
|
||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
|
@ -85,7 +87,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
Assert.assertEquals(200, response.getStatus());
|
||||
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
|
||||
long start = System.nanoTime();
|
||||
HttpConnectionOverHTTP connection = null;
|
||||
|
@ -637,7 +639,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
public void onBegin(Request request)
|
||||
{
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
destination.getHttpConnectionPool().getActiveConnections().peek().close();
|
||||
destination.getConnectionPool().getActiveConnections().peek().close();
|
||||
}
|
||||
})
|
||||
.send(new Response.Listener.Adapter()
|
||||
|
@ -828,4 +830,37 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
});
|
||||
Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomUserAgent() throws Exception
|
||||
{
|
||||
final String userAgent = "Test/1.0";
|
||||
start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
ArrayList<String> userAgents = Collections.list(request.getHeaders("User-Agent"));
|
||||
Assert.assertEquals(1, userAgents.size());
|
||||
Assert.assertEquals(userAgent, userAgents.get(0));
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scheme)
|
||||
.agent(userAgent)
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
|
||||
response = client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scheme)
|
||||
.header(HttpHeader.USER_AGENT, userAgent)
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
|
|||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
@ -35,9 +36,13 @@ import org.eclipse.jetty.client.api.Request;
|
|||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.client.util.InputStreamContentProvider;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||
|
@ -244,15 +249,30 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
|
|||
client = new HttpClient(new HttpClientTransportOverHTTP()
|
||||
{
|
||||
@Override
|
||||
protected SslConnection newSslConnection(HttpClient httpClient, EndPoint endPoint, SSLEngine engine)
|
||||
public HttpDestination newHttpDestination(Origin origin)
|
||||
{
|
||||
return new SslConnection(httpClient.getByteBufferPool(), httpClient.getExecutor(), endPoint, engine)
|
||||
return new HttpDestinationOverHTTP(getHttpClient(), origin)
|
||||
{
|
||||
@Override
|
||||
protected boolean onReadTimeout()
|
||||
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
sslIdle.set(true);
|
||||
return super.onReadTimeout();
|
||||
HttpClient client = getHttpClient();
|
||||
return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory)
|
||||
{
|
||||
@Override
|
||||
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
|
||||
{
|
||||
return new SslConnection(byteBufferPool, executor, endPoint, engine)
|
||||
{
|
||||
@Override
|
||||
protected boolean onReadTimeout()
|
||||
{
|
||||
sslIdle.set(true);
|
||||
return super.onReadTimeout();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
|
|||
Assert.assertEquals(path, request.getPath());
|
||||
Assert.assertNull(request.getQuery());
|
||||
Fields params = request.getParams();
|
||||
Assert.assertEquals(0, params.size());
|
||||
Assert.assertEquals(0, params.getSize());
|
||||
Assert.assertTrue(request.getURI().toString().endsWith(path));
|
||||
|
||||
ContentResponse response = request.send();
|
||||
|
@ -118,8 +118,8 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
|
|||
Assert.assertEquals(query, request.getQuery());
|
||||
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
|
||||
Fields params = request.getParams();
|
||||
Assert.assertEquals(1, params.size());
|
||||
Assert.assertEquals(value, params.get(name).value());
|
||||
Assert.assertEquals(1, params.getSize());
|
||||
Assert.assertEquals(value, params.get(name).getValue());
|
||||
|
||||
ContentResponse response = request.send();
|
||||
|
||||
|
@ -155,8 +155,8 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
|
|||
Assert.assertEquals(query, request.getQuery());
|
||||
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
|
||||
Fields params = request.getParams();
|
||||
Assert.assertEquals(1, params.size());
|
||||
Assert.assertEquals(value, params.get(name).value());
|
||||
Assert.assertEquals(1, params.getSize());
|
||||
Assert.assertEquals(value, params.get(name).getValue());
|
||||
|
||||
ContentResponse response = request.send();
|
||||
|
||||
|
@ -194,9 +194,9 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
|
|||
Assert.assertEquals(query, request.getQuery());
|
||||
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
|
||||
Fields params = request.getParams();
|
||||
Assert.assertEquals(2, params.size());
|
||||
Assert.assertEquals(value1, params.get(name1).value());
|
||||
Assert.assertEquals(value2, params.get(name2).value());
|
||||
Assert.assertEquals(2, params.getSize());
|
||||
Assert.assertEquals(value1, params.get(name1).getValue());
|
||||
Assert.assertEquals(value2, params.get(name2).getValue());
|
||||
|
||||
ContentResponse response = request.send();
|
||||
|
||||
|
@ -238,9 +238,9 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
|
|||
Assert.assertEquals(query, request.getQuery());
|
||||
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
|
||||
Fields params = request.getParams();
|
||||
Assert.assertEquals(2, params.size());
|
||||
Assert.assertEquals(value1, params.get(name1).value());
|
||||
Assert.assertEquals(value2, params.get(name2).value());
|
||||
Assert.assertEquals(2, params.getSize());
|
||||
Assert.assertEquals(value1, params.get(name1).getValue());
|
||||
Assert.assertEquals(value2, params.get(name2).getValue());
|
||||
|
||||
ContentResponse response = request.send();
|
||||
|
||||
|
@ -273,7 +273,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
|
|||
Assert.assertEquals(query, request.getQuery());
|
||||
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
|
||||
Fields params = request.getParams();
|
||||
Assert.assertEquals(0, params.size());
|
||||
Assert.assertEquals(0, params.getSize());
|
||||
|
||||
ContentResponse response = request.send();
|
||||
|
||||
|
@ -306,10 +306,36 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
|
|||
Assert.assertEquals(query, request.getQuery());
|
||||
Assert.assertTrue(request.getURI().toString().endsWith(pathQuery));
|
||||
Fields params = request.getParams();
|
||||
Assert.assertEquals(0, params.size());
|
||||
Assert.assertEquals(0, params.getSize());
|
||||
|
||||
ContentResponse response = request.send();
|
||||
|
||||
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaseSensitiveParameterName() throws Exception
|
||||
{
|
||||
final String name1 = "a";
|
||||
final String name2 = "A";
|
||||
start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
Assert.assertEquals(name1, request.getParameter(name1));
|
||||
Assert.assertEquals(name2, request.getParameter(name2));
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scheme)
|
||||
.path("/path?" + name1 + "=" + name1)
|
||||
.param(name2, name2)
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
|||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.http.HttpConnectionPool;
|
||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
@ -68,7 +67,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
|
||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Assert.assertEquals(0, idleConnections.size());
|
||||
|
@ -129,7 +128,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
|
||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Assert.assertEquals(0, idleConnections.size());
|
||||
|
@ -180,7 +179,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
|
||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Assert.assertEquals(0, idleConnections.size());
|
||||
|
@ -240,7 +239,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
|
||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Assert.assertEquals(0, idleConnections.size());
|
||||
|
@ -313,7 +312,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
|
||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Assert.assertEquals(0, idleConnections.size());
|
||||
|
@ -366,7 +365,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
|
||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Assert.assertEquals(0, idleConnections.size());
|
||||
|
@ -416,7 +415,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
|
||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Assert.assertEquals(0, idleConnections.size());
|
||||
|
@ -466,7 +465,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||
HttpConnectionPool connectionPool = destination.getHttpConnectionPool();
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
|
||||
final BlockingQueue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Assert.assertEquals(0, idleConnections.size());
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ProxyConfigurationTest
|
||||
{
|
||||
@Test
|
||||
public void testProxyMatchesWithoutIncludesWithoutExcludes() throws Exception
|
||||
{
|
||||
HttpProxy proxy = new HttpProxy("host", 0);
|
||||
Assert.assertTrue(proxy.matches(new Origin("http", "any", 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyMatchesWithOnlyExcludes() throws Exception
|
||||
{
|
||||
HttpProxy proxy = new HttpProxy("host", 0);
|
||||
proxy.getExcludedAddresses().add("1.2.3.4:5");
|
||||
|
||||
Assert.assertTrue(proxy.matches(new Origin("http", "any", 0)));
|
||||
Assert.assertTrue(proxy.matches(new Origin("http", "1.2.3.4", 0)));
|
||||
Assert.assertFalse(proxy.matches(new Origin("http", "1.2.3.4", 5)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyMatchesWithOnlyIncludes() throws Exception
|
||||
{
|
||||
HttpProxy proxy = new HttpProxy("host", 0);
|
||||
proxy.getIncludedAddresses().add("1.2.3.4:5");
|
||||
|
||||
Assert.assertFalse(proxy.matches(new Origin("http", "any", 0)));
|
||||
Assert.assertFalse(proxy.matches(new Origin("http", "1.2.3.4", 0)));
|
||||
Assert.assertTrue(proxy.matches(new Origin("http", "1.2.3.4", 5)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyMatchesWithIncludesAndExcludes() throws Exception
|
||||
{
|
||||
HttpProxy proxy = new HttpProxy("host", 0);
|
||||
proxy.getIncludedAddresses().add("1.2.3.4");
|
||||
proxy.getExcludedAddresses().add("1.2.3.4:5");
|
||||
|
||||
Assert.assertFalse(proxy.matches(new Origin("http", "any", 0)));
|
||||
Assert.assertTrue(proxy.matches(new Origin("http", "1.2.3.4", 0)));
|
||||
Assert.assertFalse(proxy.matches(new Origin("http", "1.2.3.4", 5)));
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client.api;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.ning.http.client.AsyncHttpClient;
|
||||
import com.ning.http.client.BodyDeferringAsyncHandler;
|
||||
import com.ning.http.client.Cookie;
|
||||
import com.ning.http.client.Realm;
|
||||
import com.ning.http.client.Request;
|
||||
import com.ning.http.client.Response;
|
||||
|
||||
@Ignore
|
||||
public class NingUsage
|
||||
{
|
||||
@Test
|
||||
public void testFileUpload() throws Exception
|
||||
{
|
||||
AsyncHttpClient client = new AsyncHttpClient();
|
||||
Request request = client.prepareGet("http://localhost:8080/foo").setBody(new FileInputStream("")).build();
|
||||
client.executeRequest(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthentication() throws Exception
|
||||
{
|
||||
AsyncHttpClient client = new AsyncHttpClient();
|
||||
Response response = client.prepareGet("http://localhost:8080/foo")
|
||||
// Not sure what a builder buys me in this case...
|
||||
.setRealm(new Realm.RealmBuilder().build()).execute().get();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCookies() throws Exception
|
||||
{
|
||||
AsyncHttpClient client = new AsyncHttpClient();
|
||||
// Cookie class too complex
|
||||
client.prepareGet("").addCookie(new Cookie("domain", "name", "value", "path", 3600, false)).execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseStream() throws Exception
|
||||
{
|
||||
AsyncHttpClient client = new AsyncHttpClient();
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
BodyDeferringAsyncHandler handler = new BodyDeferringAsyncHandler(output);
|
||||
client.prepareGet("").execute(handler);
|
||||
// What I would really like is an InputStream, not an OutputStream
|
||||
// so I can read the response content
|
||||
|
||||
Response response = handler.getResponse(); // No timeout
|
||||
|
||||
// Not sure how I can read the body ONLY if status == 200 ?
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import org.eclipse.jetty.client.AbstractHttpClientServerTest;
|
||||
import org.eclipse.jetty.client.EmptyServerHandler;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
|
@ -52,12 +53,12 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
|||
@Test
|
||||
public void test_FirstAcquire_WithEmptyQueue() throws Exception
|
||||
{
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort());
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
||||
Connection connection = destination.acquire();
|
||||
if (connection == null)
|
||||
{
|
||||
// There are no queued requests, so the newly created connection will be idle
|
||||
connection = destination.getHttpConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
||||
connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
||||
}
|
||||
Assert.assertNotNull(connection);
|
||||
}
|
||||
|
@ -65,7 +66,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
|||
@Test
|
||||
public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception
|
||||
{
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort());
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
||||
Connection connection1 = destination.acquire();
|
||||
if (connection1 == null)
|
||||
{
|
||||
|
@ -74,7 +75,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
|||
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
||||
{
|
||||
TimeUnit.MILLISECONDS.sleep(50);
|
||||
connection1 = destination.getHttpConnectionPool().getIdleConnections().peek();
|
||||
connection1 = destination.getConnectionPool().getIdleConnections().peek();
|
||||
}
|
||||
Assert.assertNotNull(connection1);
|
||||
|
||||
|
@ -87,7 +88,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
|||
public void test_SecondAcquire_ConcurrentWithFirstAcquire_WithEmptyQueue_CreatesTwoConnections() throws Exception
|
||||
{
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort())
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()))
|
||||
{
|
||||
@Override
|
||||
protected void process(HttpConnectionOverHTTP connection, boolean dispatch)
|
||||
|
@ -115,23 +116,23 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
|||
latch.countDown();
|
||||
|
||||
// There must be 2 idle connections
|
||||
Connection connection = destination.getHttpConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
||||
Connection connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
||||
Assert.assertNotNull(connection);
|
||||
connection = destination.getHttpConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
||||
connection = destination.getConnectionPool().getIdleConnections().poll(5, TimeUnit.SECONDS);
|
||||
Assert.assertNotNull(connection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Acquire_Process_Release_Acquire_ReturnsSameConnection() throws Exception
|
||||
{
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort());
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
||||
HttpConnectionOverHTTP connection1 = destination.acquire();
|
||||
|
||||
long start = System.nanoTime();
|
||||
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
||||
{
|
||||
TimeUnit.MILLISECONDS.sleep(50);
|
||||
connection1 = (HttpConnectionOverHTTP)destination.getHttpConnectionPool().getIdleConnections().peek();
|
||||
connection1 = (HttpConnectionOverHTTP)destination.getConnectionPool().getIdleConnections().peek();
|
||||
}
|
||||
Assert.assertNotNull(connection1);
|
||||
|
||||
|
@ -152,7 +153,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
|||
long idleTimeout = 1000;
|
||||
client.setIdleTimeout(idleTimeout);
|
||||
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, "http", "localhost", connector.getLocalPort());
|
||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
||||
Connection connection1 = destination.acquire();
|
||||
if (connection1 == null)
|
||||
{
|
||||
|
@ -161,13 +162,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
|||
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
||||
{
|
||||
TimeUnit.MILLISECONDS.sleep(50);
|
||||
connection1 = destination.getHttpConnectionPool().getIdleConnections().peek();
|
||||
connection1 = destination.getConnectionPool().getIdleConnections().peek();
|
||||
}
|
||||
Assert.assertNotNull(connection1);
|
||||
|
||||
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
|
||||
|
||||
connection1 = destination.getHttpConnectionPool().getIdleConnections().poll();
|
||||
connection1 = destination.getConnectionPool().getIdleConnections().poll();
|
||||
Assert.assertNull(connection1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
private ExecutorService threadPool;
|
||||
private Server server;
|
||||
private SslContextFactory sslContextFactory;
|
||||
private int serverPort;
|
||||
private SSLContext sslContext;
|
||||
private SimpleProxy proxy;
|
||||
private Runnable idleHook;
|
||||
|
@ -215,7 +216,7 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
}
|
||||
});
|
||||
server.start();
|
||||
int serverPort = connector.getLocalPort();
|
||||
serverPort = connector.getLocalPort();
|
||||
|
||||
sslContext = sslContextFactory.getSslContext();
|
||||
|
||||
|
@ -296,6 +297,88 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
closeClient(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeWithResumedSessionThenClose() throws Exception
|
||||
{
|
||||
// First socket will establish the SSL session
|
||||
SSLSocket client1 = newClient();
|
||||
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
|
||||
client1.startHandshake();
|
||||
client1.close();
|
||||
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
|
||||
int proxyPort = proxy.getPort();
|
||||
proxy.stop();
|
||||
|
||||
proxy = new SimpleProxy(threadPool, proxyPort, "localhost", serverPort);
|
||||
proxy.start();
|
||||
logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort);
|
||||
|
||||
final SSLSocket client2 = newClient(proxy);
|
||||
|
||||
Future<Object> handshake = threadPool.submit(new Callable<Object>()
|
||||
{
|
||||
@Override
|
||||
public Object call() throws Exception
|
||||
{
|
||||
client2.startHandshake();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Client Hello with SessionID
|
||||
TLSRecord record = proxy.readFromClient();
|
||||
Assert.assertNotNull(record);
|
||||
proxy.flushToServer(record);
|
||||
|
||||
// Server Hello
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertNotNull(record);
|
||||
proxy.flushToClient(record);
|
||||
|
||||
// Change Cipher Spec
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertNotNull(record);
|
||||
proxy.flushToClient(record);
|
||||
|
||||
// Server Done
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertNotNull(record);
|
||||
proxy.flushToClient(record);
|
||||
|
||||
// Client Key Exchange
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertNotNull(record);
|
||||
// Client Done
|
||||
TLSRecord doneRecord = proxy.readFromClient();
|
||||
Assert.assertNotNull(doneRecord);
|
||||
// Close
|
||||
client2.close();
|
||||
TLSRecord closeRecord = proxy.readFromClient();
|
||||
Assert.assertNotNull(closeRecord);
|
||||
Assert.assertEquals(TLSRecord.Type.ALERT, closeRecord.getType());
|
||||
// Flush to server Client Key Exchange + Client Done + Close in one chunk
|
||||
byte[] recordBytes = record.getBytes();
|
||||
byte[] doneBytes = doneRecord.getBytes();
|
||||
byte[] closeRecordBytes = closeRecord.getBytes();
|
||||
byte[] chunk = new byte[recordBytes.length + doneBytes.length + closeRecordBytes.length];
|
||||
System.arraycopy(recordBytes, 0, chunk, 0, recordBytes.length);
|
||||
System.arraycopy(doneBytes, 0, chunk, recordBytes.length, doneBytes.length);
|
||||
System.arraycopy(closeRecordBytes, 0, chunk, recordBytes.length + doneBytes.length, closeRecordBytes.length);
|
||||
proxy.flushToServer(0, chunk);
|
||||
// Close the raw socket
|
||||
proxy.flushToServer(null);
|
||||
|
||||
// Expect the server to send a FIN as well
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertNull(record);
|
||||
|
||||
// Check that we did not spin
|
||||
TimeUnit.MILLISECONDS.sleep(500);
|
||||
Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
|
||||
Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
|
||||
Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeWithSplitBoundary() throws Exception
|
||||
{
|
||||
|
@ -1849,6 +1932,11 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
}
|
||||
|
||||
private SSLSocket newClient() throws IOException, InterruptedException
|
||||
{
|
||||
return newClient(proxy);
|
||||
}
|
||||
|
||||
private SSLSocket newClient(SimpleProxy proxy) throws IOException, InterruptedException
|
||||
{
|
||||
SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", proxy.getPort());
|
||||
client.setUseClientMode(true);
|
||||
|
|
|
@ -105,6 +105,7 @@ public abstract class SslBytesTest
|
|||
{
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private final ExecutorService threadPool;
|
||||
private final int proxyPort;
|
||||
private final String serverHost;
|
||||
private final int serverPort;
|
||||
private volatile ServerSocket serverSocket;
|
||||
|
@ -112,15 +113,21 @@ public abstract class SslBytesTest
|
|||
private volatile Socket client;
|
||||
|
||||
public SimpleProxy(ExecutorService threadPool, String serverHost, int serverPort)
|
||||
{
|
||||
this(threadPool, 0, serverHost, serverPort);
|
||||
}
|
||||
|
||||
public SimpleProxy(ExecutorService threadPool, int proxyPort, String serverHost, int serverPort)
|
||||
{
|
||||
this.threadPool = threadPool;
|
||||
this.proxyPort = proxyPort;
|
||||
this.serverHost = serverHost;
|
||||
this.serverPort = serverPort;
|
||||
}
|
||||
|
||||
public void start() throws Exception
|
||||
{
|
||||
serverSocket = new ServerSocket(0);
|
||||
serverSocket = new ServerSocket(proxyPort);
|
||||
Thread acceptor = new Thread(this);
|
||||
acceptor.start();
|
||||
server = new Socket(serverHost, serverPort);
|
||||
|
|
|
@ -6,12 +6,10 @@
|
|||
webapp
|
||||
|
||||
[lib]
|
||||
# Deploy jars
|
||||
lib/jetty-deploy-${jetty.version}.jar
|
||||
|
||||
[files]
|
||||
webapps/
|
||||
|
||||
[xml]
|
||||
# Deploy configuration
|
||||
etc/jetty-deploy.xml
|
||||
|
|
|
@ -502,7 +502,7 @@
|
|||
<arguments>
|
||||
<argument>jetty.home=${assembly-directory}</argument>
|
||||
<argument>jetty.base=${assembly-directory}/demo-base</argument>
|
||||
<argument>--add-to-start=server,deploy,jsp,ext,resources,client,annotations,jndi</argument>
|
||||
<argument>--add-to-start=server,continuation,deploy,jsp,ext,resources,client,annotations,jndi</argument>
|
||||
<argument>--add-to-startd-ini=http,https</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#
|
||||
# Set UID Feature
|
||||
#
|
||||
|
||||
[depend]
|
||||
server
|
||||
|
||||
|
@ -8,6 +12,7 @@ lib/setuid/jetty-setuid-java-1.0.1.jar
|
|||
etc/jetty-setuid.xml
|
||||
|
||||
[ini-template]
|
||||
## SetUID Configuration
|
||||
# jetty.startServerAsPrivileged=false
|
||||
# jetty.username=jetty
|
||||
# jetty.groupname=jetty
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/**
|
||||
* ThreadLocal Date formatters for HTTP style dates.
|
||||
*
|
||||
*/
|
||||
public class DateGenerator
|
||||
{
|
||||
private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
|
||||
static
|
||||
{
|
||||
__GMT.setID("GMT");
|
||||
}
|
||||
|
||||
static final String[] DAYS =
|
||||
{ "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
||||
static final String[] MONTHS =
|
||||
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
|
||||
|
||||
|
||||
private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
|
||||
{
|
||||
@Override
|
||||
protected DateGenerator initialValue()
|
||||
{
|
||||
return new DateGenerator();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public final static String __01Jan1970=DateGenerator.formatDate(0);
|
||||
|
||||
/**
|
||||
* Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
|
||||
*/
|
||||
public static String formatDate(long date)
|
||||
{
|
||||
return __dateGenerator.get().doFormatDate(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
|
||||
*/
|
||||
public static void formatCookieDate(StringBuilder buf, long date)
|
||||
{
|
||||
__dateGenerator.get().doFormatCookieDate(buf,date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
|
||||
*/
|
||||
public static String formatCookieDate(long date)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(28);
|
||||
formatCookieDate(buf, date);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private final StringBuilder buf = new StringBuilder(32);
|
||||
private final GregorianCalendar gc = new GregorianCalendar(__GMT);
|
||||
|
||||
/**
|
||||
* Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
|
||||
*/
|
||||
public String doFormatDate(long date)
|
||||
{
|
||||
buf.setLength(0);
|
||||
gc.setTimeInMillis(date);
|
||||
|
||||
int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
|
||||
int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
|
||||
int month = gc.get(Calendar.MONTH);
|
||||
int year = gc.get(Calendar.YEAR);
|
||||
int century = year / 100;
|
||||
year = year % 100;
|
||||
|
||||
int hours = gc.get(Calendar.HOUR_OF_DAY);
|
||||
int minutes = gc.get(Calendar.MINUTE);
|
||||
int seconds = gc.get(Calendar.SECOND);
|
||||
|
||||
buf.append(DAYS[day_of_week]);
|
||||
buf.append(',');
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, day_of_month);
|
||||
|
||||
buf.append(' ');
|
||||
buf.append(MONTHS[month]);
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, century);
|
||||
StringUtil.append2digits(buf, year);
|
||||
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, hours);
|
||||
buf.append(':');
|
||||
StringUtil.append2digits(buf, minutes);
|
||||
buf.append(':');
|
||||
StringUtil.append2digits(buf, seconds);
|
||||
buf.append(" GMT");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
|
||||
*/
|
||||
public void doFormatCookieDate(StringBuilder buf, long date)
|
||||
{
|
||||
gc.setTimeInMillis(date);
|
||||
|
||||
int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
|
||||
int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
|
||||
int month = gc.get(Calendar.MONTH);
|
||||
int year = gc.get(Calendar.YEAR);
|
||||
year = year % 10000;
|
||||
|
||||
int epoch = (int) ((date / 1000) % (60 * 60 * 24));
|
||||
int seconds = epoch % 60;
|
||||
epoch = epoch / 60;
|
||||
int minutes = epoch % 60;
|
||||
int hours = epoch / 60;
|
||||
|
||||
buf.append(DAYS[day_of_week]);
|
||||
buf.append(',');
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, day_of_month);
|
||||
|
||||
buf.append('-');
|
||||
buf.append(MONTHS[month]);
|
||||
buf.append('-');
|
||||
StringUtil.append2digits(buf, year/100);
|
||||
StringUtil.append2digits(buf, year%100);
|
||||
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, hours);
|
||||
buf.append(':');
|
||||
StringUtil.append2digits(buf, minutes);
|
||||
buf.append(':');
|
||||
StringUtil.append2digits(buf, seconds);
|
||||
buf.append(" GMT");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* ThreadLocal data parsers for HTTP style dates
|
||||
*
|
||||
*/
|
||||
class DateParser
|
||||
{
|
||||
private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
|
||||
static
|
||||
{
|
||||
__GMT.setID("GMT");
|
||||
}
|
||||
|
||||
final static String __dateReceiveFmt[] =
|
||||
{
|
||||
"EEE, dd MMM yyyy HH:mm:ss zzz",
|
||||
"EEE, dd-MMM-yy HH:mm:ss",
|
||||
"EEE MMM dd HH:mm:ss yyyy",
|
||||
|
||||
"EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
|
||||
"EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
|
||||
"EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
|
||||
"dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
|
||||
"MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
|
||||
"EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
|
||||
"EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
|
||||
};
|
||||
|
||||
public static long parseDate(String date)
|
||||
{
|
||||
return __dateParser.get().parse(date);
|
||||
}
|
||||
|
||||
private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
|
||||
{
|
||||
@Override
|
||||
protected DateParser initialValue()
|
||||
{
|
||||
return new DateParser();
|
||||
}
|
||||
};
|
||||
|
||||
final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
|
||||
|
||||
private long parse(final String dateVal)
|
||||
{
|
||||
for (int i = 0; i < _dateReceive.length; i++)
|
||||
{
|
||||
if (_dateReceive[i] == null)
|
||||
{
|
||||
_dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
|
||||
_dateReceive[i].setTimeZone(__GMT);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Date date = (Date) _dateReceive[i].parseObject(dateVal);
|
||||
return date.getTime();
|
||||
}
|
||||
catch (java.lang.Exception e)
|
||||
{
|
||||
// LOG.ignore(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (dateVal.endsWith(" GMT"))
|
||||
{
|
||||
final String val = dateVal.substring(0, dateVal.length() - 4);
|
||||
|
||||
for (SimpleDateFormat element : _dateReceive)
|
||||
{
|
||||
try
|
||||
{
|
||||
Date date = (Date) element.parseObject(val);
|
||||
return date.getTime();
|
||||
}
|
||||
catch (java.lang.Exception e)
|
||||
{
|
||||
// LOG.ignore(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -18,93 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jetty.util.ArrayTrie;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.Trie;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** A HTTP Field
|
||||
*/
|
||||
public class HttpField
|
||||
{
|
||||
/**
|
||||
* Cache of common {@link HttpField}s including: <UL>
|
||||
* <LI>Common static combinations such as:<UL>
|
||||
* <li>Connection: close
|
||||
* <li>Accept-Encoding: gzip
|
||||
* <li>Content-Length: 0
|
||||
* </ul>
|
||||
* <li>Combinations of Content-Type header for common mime types by common charsets
|
||||
* <li>Most common headers with null values so that a lookup will at least
|
||||
* determine the header name even if the name:value combination is not cached
|
||||
* </ul>
|
||||
*/
|
||||
public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
|
||||
public final static Trie<HttpField> CONTENT_TYPE = new ArrayTrie<>(512);
|
||||
|
||||
static
|
||||
{
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.ACCEPT,"*/*"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.PRAGMA,"no-cache"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_LENGTH,"0"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
|
||||
|
||||
// Content types
|
||||
for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/x-www-form-urlencoded"})
|
||||
{
|
||||
HttpField field=new CachedHttpField(HttpHeader.CONTENT_TYPE,type);
|
||||
CACHE.put(field);
|
||||
CONTENT_TYPE.put(type,field);
|
||||
|
||||
for (String charset : new String[]{"UTF-8","ISO-8859-1"})
|
||||
{
|
||||
String type_charset=type+"; charset="+charset;
|
||||
field=new CachedHttpField(HttpHeader.CONTENT_TYPE,type_charset);
|
||||
CACHE.put(field);
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
|
||||
CONTENT_TYPE.put(type_charset,field);
|
||||
CONTENT_TYPE.put(type+";charset="+charset,field);
|
||||
}
|
||||
}
|
||||
|
||||
// Add headers with null values so HttpParser can avoid looking up name again for unknown values
|
||||
for (HttpHeader h:HttpHeader.values())
|
||||
if (!CACHE.put(new HttpField(h,(String)null)))
|
||||
throw new IllegalStateException("CACHE FULL");
|
||||
// Add some more common headers
|
||||
CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
|
||||
}
|
||||
|
||||
private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");
|
||||
private final static byte[] __colon_space = new byte[] {':',' '};
|
||||
|
||||
private final HttpHeader _header;
|
||||
private final String _name;
|
||||
private final String _value;
|
||||
|
@ -120,7 +39,6 @@ public class HttpField
|
|||
{
|
||||
this(header,header.asString(),value);
|
||||
}
|
||||
|
||||
|
||||
public HttpField(HttpHeader header, HttpHeaderValue value)
|
||||
{
|
||||
|
@ -146,89 +64,7 @@ public class HttpField
|
|||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
public boolean contains(String value)
|
||||
{
|
||||
if (_value==null)
|
||||
return false;
|
||||
|
||||
if (value.equalsIgnoreCase(_value))
|
||||
return true;
|
||||
|
||||
String[] split = __splitter.split(_value);
|
||||
for (int i = 0; split!=null && i < split.length; i++)
|
||||
{
|
||||
if (value.equalsIgnoreCase(split[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public int getIntValue()
|
||||
{
|
||||
return StringUtil.toInt(_value);
|
||||
}
|
||||
|
||||
public long getLongValue()
|
||||
{
|
||||
return StringUtil.toLong(_value);
|
||||
}
|
||||
|
||||
private static byte[] toSanitisedName(String s)
|
||||
{
|
||||
byte[] bytes = s.getBytes(StringUtil.__ISO_8859_1_CHARSET);
|
||||
for (int i=bytes.length;i-->0;)
|
||||
{
|
||||
switch(bytes[i])
|
||||
{
|
||||
case '\r':
|
||||
case '\n':
|
||||
case ':' :
|
||||
bytes[i]=(byte)'?';
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static byte[] toSanitisedValue(String s)
|
||||
{
|
||||
byte[] bytes = s.getBytes(StringUtil.__ISO_8859_1_CHARSET);
|
||||
for (int i=bytes.length;i-->0;)
|
||||
{
|
||||
switch(bytes[i])
|
||||
{
|
||||
case '\r':
|
||||
case '\n':
|
||||
bytes[i]=(byte)'?';
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void putTo(ByteBuffer bufferInFillMode)
|
||||
{
|
||||
if (_header!=null)
|
||||
{
|
||||
bufferInFillMode.put(_header.getBytesColonSpace());
|
||||
bufferInFillMode.put(toSanitisedValue(_value));
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferInFillMode.put(toSanitisedName(_name));
|
||||
bufferInFillMode.put(__colon_space);
|
||||
bufferInFillMode.put(toSanitisedValue(_value));
|
||||
}
|
||||
|
||||
BufferUtil.putCRLF(bufferInFillMode);
|
||||
}
|
||||
|
||||
public void putValueTo(ByteBuffer buffer)
|
||||
{
|
||||
buffer.put(toSanitisedValue(_value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
@ -250,31 +86,4 @@ public class HttpField
|
|||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** A HTTP Field optimised to be reused.
|
||||
*/
|
||||
public static class CachedHttpField extends HttpField
|
||||
{
|
||||
final byte[] _bytes;
|
||||
public CachedHttpField(HttpHeader header, String value)
|
||||
{
|
||||
super(header,value);
|
||||
_bytes=new byte[header.asString().length()+2+value.length()+2];
|
||||
System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,header.asString().length()+2);
|
||||
System.arraycopy(toSanitisedValue(value),0,_bytes,header.asString().length()+2,value.length());
|
||||
_bytes[_bytes.length-2]='\r';
|
||||
_bytes[_bytes.length-1]='\n';
|
||||
}
|
||||
|
||||
CachedHttpField(HttpHeader header, HttpHeaderValue value)
|
||||
{
|
||||
this(header,value.asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putTo(ByteBuffer bufferInFillMode)
|
||||
{
|
||||
bufferInFillMode.put(_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,29 +18,21 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jetty.util.ArrayTernaryTrie;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.DateCache;
|
||||
import org.eclipse.jetty.util.LazyList;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
@ -48,13 +40,11 @@ import org.eclipse.jetty.util.Trie;
|
|||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
|
||||
|
||||
|
||||
/**
|
||||
* HTTP Fields. A collection of HTTP header and or Trailer fields.
|
||||
*
|
||||
* <p>This class is not synchronised as it is expected that modifications will only be performed by a
|
||||
* <p>This class is not synchronized as it is expected that modifications will only be performed by a
|
||||
* single thread.
|
||||
*
|
||||
* <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
|
||||
|
@ -63,222 +53,9 @@ import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
|
|||
public class HttpFields implements Iterable<HttpField>
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpFields.class);
|
||||
public static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
|
||||
public static final DateCache __dateCache = new DateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
|
||||
|
||||
public static final String __COOKIE_DELIM="\",;\\ \t";
|
||||
|
||||
static
|
||||
{
|
||||
__GMT.setID("GMT");
|
||||
__dateCache.setTimeZone(__GMT);
|
||||
}
|
||||
|
||||
private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");
|
||||
public final static String __separators = ", \t";
|
||||
|
||||
private static final String[] DAYS =
|
||||
{ "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
||||
private static final String[] MONTHS =
|
||||
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
|
||||
|
||||
public static class DateGenerator
|
||||
{
|
||||
private final StringBuilder buf = new StringBuilder(32);
|
||||
private final GregorianCalendar gc = new GregorianCalendar(__GMT);
|
||||
|
||||
/**
|
||||
* Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
|
||||
*/
|
||||
public String formatDate(long date)
|
||||
{
|
||||
buf.setLength(0);
|
||||
gc.setTimeInMillis(date);
|
||||
|
||||
int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
|
||||
int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
|
||||
int month = gc.get(Calendar.MONTH);
|
||||
int year = gc.get(Calendar.YEAR);
|
||||
int century = year / 100;
|
||||
year = year % 100;
|
||||
|
||||
int hours = gc.get(Calendar.HOUR_OF_DAY);
|
||||
int minutes = gc.get(Calendar.MINUTE);
|
||||
int seconds = gc.get(Calendar.SECOND);
|
||||
|
||||
buf.append(DAYS[day_of_week]);
|
||||
buf.append(',');
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, day_of_month);
|
||||
|
||||
buf.append(' ');
|
||||
buf.append(MONTHS[month]);
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, century);
|
||||
StringUtil.append2digits(buf, year);
|
||||
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, hours);
|
||||
buf.append(':');
|
||||
StringUtil.append2digits(buf, minutes);
|
||||
buf.append(':');
|
||||
StringUtil.append2digits(buf, seconds);
|
||||
buf.append(" GMT");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
|
||||
*/
|
||||
public void formatCookieDate(StringBuilder buf, long date)
|
||||
{
|
||||
gc.setTimeInMillis(date);
|
||||
|
||||
int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
|
||||
int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
|
||||
int month = gc.get(Calendar.MONTH);
|
||||
int year = gc.get(Calendar.YEAR);
|
||||
year = year % 10000;
|
||||
|
||||
int epoch = (int) ((date / 1000) % (60 * 60 * 24));
|
||||
int seconds = epoch % 60;
|
||||
epoch = epoch / 60;
|
||||
int minutes = epoch % 60;
|
||||
int hours = epoch / 60;
|
||||
|
||||
buf.append(DAYS[day_of_week]);
|
||||
buf.append(',');
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, day_of_month);
|
||||
|
||||
buf.append('-');
|
||||
buf.append(MONTHS[month]);
|
||||
buf.append('-');
|
||||
StringUtil.append2digits(buf, year/100);
|
||||
StringUtil.append2digits(buf, year%100);
|
||||
|
||||
buf.append(' ');
|
||||
StringUtil.append2digits(buf, hours);
|
||||
buf.append(':');
|
||||
StringUtil.append2digits(buf, minutes);
|
||||
buf.append(':');
|
||||
StringUtil.append2digits(buf, seconds);
|
||||
buf.append(" GMT");
|
||||
}
|
||||
}
|
||||
|
||||
private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
|
||||
{
|
||||
@Override
|
||||
protected DateGenerator initialValue()
|
||||
{
|
||||
return new DateGenerator();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
|
||||
*/
|
||||
public static String formatDate(long date)
|
||||
{
|
||||
return __dateGenerator.get().formatDate(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
|
||||
*/
|
||||
public static void formatCookieDate(StringBuilder buf, long date)
|
||||
{
|
||||
__dateGenerator.get().formatCookieDate(buf,date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
|
||||
*/
|
||||
public static String formatCookieDate(long date)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(28);
|
||||
formatCookieDate(buf, date);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private final static String __dateReceiveFmt[] =
|
||||
{
|
||||
"EEE, dd MMM yyyy HH:mm:ss zzz",
|
||||
"EEE, dd-MMM-yy HH:mm:ss",
|
||||
"EEE MMM dd HH:mm:ss yyyy",
|
||||
|
||||
"EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
|
||||
"EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
|
||||
"EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
|
||||
"dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
|
||||
"MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
|
||||
"EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
|
||||
"EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
|
||||
};
|
||||
|
||||
private static class DateParser
|
||||
{
|
||||
final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
|
||||
|
||||
long parse(final String dateVal)
|
||||
{
|
||||
for (int i = 0; i < _dateReceive.length; i++)
|
||||
{
|
||||
if (_dateReceive[i] == null)
|
||||
{
|
||||
_dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
|
||||
_dateReceive[i].setTimeZone(__GMT);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Date date = (Date) _dateReceive[i].parseObject(dateVal);
|
||||
return date.getTime();
|
||||
}
|
||||
catch (java.lang.Exception e)
|
||||
{
|
||||
// LOG.ignore(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (dateVal.endsWith(" GMT"))
|
||||
{
|
||||
final String val = dateVal.substring(0, dateVal.length() - 4);
|
||||
|
||||
for (SimpleDateFormat element : _dateReceive)
|
||||
{
|
||||
try
|
||||
{
|
||||
Date date = (Date) element.parseObject(val);
|
||||
return date.getTime();
|
||||
}
|
||||
catch (java.lang.Exception e)
|
||||
{
|
||||
// LOG.ignore(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static long parseDate(String date)
|
||||
{
|
||||
return __dateParser.get().parse(date);
|
||||
}
|
||||
|
||||
private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
|
||||
{
|
||||
@Override
|
||||
protected DateParser initialValue()
|
||||
{
|
||||
return new DateParser();
|
||||
}
|
||||
};
|
||||
|
||||
public final static String __01Jan1970=formatDate(0);
|
||||
public final static ByteBuffer __01Jan1970_BUFFER=BufferUtil.toBuffer(__01Jan1970);
|
||||
public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim();
|
||||
private final ArrayList<HttpField> _fields = new ArrayList<>(20);
|
||||
|
||||
/**
|
||||
|
@ -288,7 +65,6 @@ public class HttpFields implements Iterable<HttpField>
|
|||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Collection of header names.
|
||||
*/
|
||||
|
@ -354,13 +130,13 @@ public class HttpFields implements Iterable<HttpField>
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public boolean contains(HttpHeader header, String value)
|
||||
{
|
||||
for (int i=0;i<_fields.size();i++)
|
||||
{
|
||||
HttpField f=_fields.get(i);
|
||||
if (f.getHeader()==header && f.contains(value))
|
||||
if (f.getHeader()==header && contains(f,value))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -371,12 +147,31 @@ public class HttpFields implements Iterable<HttpField>
|
|||
for (int i=0;i<_fields.size();i++)
|
||||
{
|
||||
HttpField f=_fields.get(i);
|
||||
if (f.getName().equalsIgnoreCase(name) && f.contains(value))
|
||||
if (f.getName().equalsIgnoreCase(name) && contains(f,value))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean contains(HttpField field,String value)
|
||||
{
|
||||
String v = field.getValue();
|
||||
if (v==null)
|
||||
return false;
|
||||
|
||||
if (value.equalsIgnoreCase(v))
|
||||
return true;
|
||||
|
||||
String[] split = __splitter.split(v);
|
||||
for (int i = 0; split!=null && i < split.length; i++)
|
||||
{
|
||||
if (value.equals(split[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey(String name)
|
||||
{
|
||||
for (int i=0;i<_fields.size();i++)
|
||||
|
@ -414,14 +209,13 @@ public class HttpFields implements Iterable<HttpField>
|
|||
return field==null?null:field.getValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get multi headers
|
||||
*
|
||||
* @return Enumeration of the values, or null if no such header.
|
||||
* @return List the values
|
||||
* @param name the case-insensitive field name
|
||||
*/
|
||||
public Collection<String> getValuesCollection(String name)
|
||||
public List<String> getValuesList(String name)
|
||||
{
|
||||
final List<String> list = new ArrayList<>();
|
||||
for (HttpField f : _fields)
|
||||
|
@ -485,7 +279,6 @@ public class HttpFields implements Iterable<HttpField>
|
|||
|
||||
List<String> empty=Collections.emptyList();
|
||||
return Collections.enumeration(empty);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -683,7 +476,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
public long getLongField(String name) throws NumberFormatException
|
||||
{
|
||||
HttpField field = getField(name);
|
||||
return field==null?-1L:field.getLongValue();
|
||||
return field==null?-1L:StringUtil.toLong(field.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -702,7 +495,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
if (val == null)
|
||||
return -1;
|
||||
|
||||
final long date = __dateParser.get().parse(val);
|
||||
final long date = DateParser.parseDate(val);
|
||||
if (date==-1)
|
||||
throw new IllegalArgumentException("Cannot convert date: " + val);
|
||||
return date;
|
||||
|
@ -742,7 +535,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
*/
|
||||
public void putDateField(HttpHeader name, long date)
|
||||
{
|
||||
String d=formatDate(date);
|
||||
String d=DateGenerator.formatDate(date);
|
||||
put(name, d);
|
||||
}
|
||||
|
||||
|
@ -754,7 +547,7 @@ public class HttpFields implements Iterable<HttpField>
|
|||
*/
|
||||
public void putDateField(String name, long date)
|
||||
{
|
||||
String d=formatDate(date);
|
||||
String d=DateGenerator.formatDate(date);
|
||||
put(name, d);
|
||||
}
|
||||
|
||||
|
@ -766,170 +559,10 @@ public class HttpFields implements Iterable<HttpField>
|
|||
*/
|
||||
public void addDateField(String name, long date)
|
||||
{
|
||||
String d=formatDate(date);
|
||||
String d=DateGenerator.formatDate(date);
|
||||
add(name,d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a set cookie value
|
||||
*
|
||||
* @param cookie The cookie.
|
||||
*/
|
||||
public void addSetCookie(HttpCookie cookie)
|
||||
{
|
||||
addSetCookie(
|
||||
cookie.getName(),
|
||||
cookie.getValue(),
|
||||
cookie.getDomain(),
|
||||
cookie.getPath(),
|
||||
cookie.getMaxAge(),
|
||||
cookie.getComment(),
|
||||
cookie.isSecure(),
|
||||
cookie.isHttpOnly(),
|
||||
cookie.getVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a set cookie value
|
||||
*
|
||||
* @param name the name
|
||||
* @param value the value
|
||||
* @param domain the domain
|
||||
* @param path the path
|
||||
* @param maxAge the maximum age
|
||||
* @param comment the comment (only present on versions > 0)
|
||||
* @param isSecure true if secure cookie
|
||||
* @param isHttpOnly true if for http only
|
||||
* @param version version of cookie logic to use (0 == default behavior)
|
||||
*/
|
||||
public void addSetCookie(
|
||||
final String name,
|
||||
final String value,
|
||||
final String domain,
|
||||
final String path,
|
||||
final long maxAge,
|
||||
final String comment,
|
||||
final boolean isSecure,
|
||||
final boolean isHttpOnly,
|
||||
int version)
|
||||
{
|
||||
// Check arguments
|
||||
if (name == null || name.length() == 0)
|
||||
throw new IllegalArgumentException("Bad cookie name");
|
||||
|
||||
// Format value and params
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
|
||||
// Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
|
||||
boolean quote_name=isQuoteNeededForCookie(name);
|
||||
quoteOnlyOrAppend(buf,name,quote_name);
|
||||
|
||||
buf.append('=');
|
||||
|
||||
// Remember name= part to look for other matching set-cookie
|
||||
String name_equals=buf.toString();
|
||||
|
||||
// Append the value
|
||||
boolean quote_value=isQuoteNeededForCookie(value);
|
||||
quoteOnlyOrAppend(buf,value,quote_value);
|
||||
|
||||
// Look for domain and path fields and check if they need to be quoted
|
||||
boolean has_domain = domain!=null && domain.length()>0;
|
||||
boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
|
||||
boolean has_path = path!=null && path.length()>0;
|
||||
boolean quote_path = has_path && isQuoteNeededForCookie(path);
|
||||
|
||||
// Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
|
||||
if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
|
||||
version=1;
|
||||
|
||||
// Append version
|
||||
if (version==1)
|
||||
buf.append (";Version=1");
|
||||
else if (version>1)
|
||||
buf.append (";Version=").append(version);
|
||||
|
||||
// Append path
|
||||
if (has_path)
|
||||
{
|
||||
buf.append(";Path=");
|
||||
quoteOnlyOrAppend(buf,path,quote_path);
|
||||
}
|
||||
|
||||
// Append domain
|
||||
if (has_domain)
|
||||
{
|
||||
buf.append(";Domain=");
|
||||
quoteOnlyOrAppend(buf,domain,quote_domain);
|
||||
}
|
||||
|
||||
// Handle max-age and/or expires
|
||||
if (maxAge >= 0)
|
||||
{
|
||||
// Always use expires
|
||||
// This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
|
||||
buf.append(";Expires=");
|
||||
if (maxAge == 0)
|
||||
buf.append(__01Jan1970_COOKIE);
|
||||
else
|
||||
formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
|
||||
|
||||
// for v1 cookies, also send max-age
|
||||
if (version>=1)
|
||||
{
|
||||
buf.append(";Max-Age=");
|
||||
buf.append(maxAge);
|
||||
}
|
||||
}
|
||||
|
||||
// add the other fields
|
||||
if (isSecure)
|
||||
buf.append(";Secure");
|
||||
if (isHttpOnly)
|
||||
buf.append(";HttpOnly");
|
||||
if (comment != null)
|
||||
{
|
||||
buf.append(";Comment=");
|
||||
quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
|
||||
}
|
||||
|
||||
// remove any existing set-cookie fields of same name
|
||||
Iterator<HttpField> i=_fields.iterator();
|
||||
while (i.hasNext())
|
||||
{
|
||||
HttpField field=i.next();
|
||||
if (field.getHeader()==HttpHeader.SET_COOKIE)
|
||||
{
|
||||
String val = field.getValue();
|
||||
if (val!=null && val.startsWith(name_equals))
|
||||
{
|
||||
//existing cookie has same name, does it also match domain and path?
|
||||
if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
|
||||
((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
|
||||
{
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the set cookie
|
||||
add(HttpHeader.SET_COOKIE.toString(), buf.toString());
|
||||
|
||||
// Expire responses with set-cookie headers so they do not get cached.
|
||||
put(HttpHeader.EXPIRES.toString(), __01Jan1970);
|
||||
}
|
||||
|
||||
public void putTo(ByteBuffer bufferInFillMode)
|
||||
{
|
||||
for (HttpField field : _fields)
|
||||
{
|
||||
if (field != null)
|
||||
field.putTo(bufferInFillMode);
|
||||
}
|
||||
BufferUtil.putCRLF(bufferInFillMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String
|
||||
toString()
|
||||
|
@ -1148,39 +781,4 @@ public class HttpFields implements Iterable<HttpField>
|
|||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Does a cookie value need to be quoted?
|
||||
* @param s value string
|
||||
* @return true if quoted;
|
||||
* @throws IllegalArgumentException If there a control characters in the string
|
||||
*/
|
||||
public static boolean isQuoteNeededForCookie(String s)
|
||||
{
|
||||
if (s==null || s.length()==0)
|
||||
return true;
|
||||
|
||||
if (QuotedStringTokenizer.isQuoted(s))
|
||||
return false;
|
||||
|
||||
for (int i=0;i<s.length();i++)
|
||||
{
|
||||
char c = s.charAt(i);
|
||||
if (__COOKIE_DELIM.indexOf(c)>=0)
|
||||
return true;
|
||||
|
||||
if (c<0x20 || c>=0x7f)
|
||||
throw new IllegalArgumentException("Illegal character in cookie value");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
|
||||
{
|
||||
if (quote)
|
||||
QuotedStringTokenizer.quoteOnly(buf,s);
|
||||
else
|
||||
buf.append(s);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ public class HttpGenerator
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpGenerator.class);
|
||||
|
||||
private final static byte[] __colon_space = new byte[] {':',' '};
|
||||
public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
|
||||
public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
|
||||
public final static ResponseInfo RESPONSE_500_INFO =
|
||||
|
@ -495,6 +496,9 @@ public class HttpGenerator
|
|||
case HTTP_1_1:
|
||||
header.put((byte)' ');
|
||||
header.put(request.getHttpVersion().toBytes());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
header.put(HttpTokens.CRLF);
|
||||
}
|
||||
|
@ -585,7 +589,7 @@ public class HttpGenerator
|
|||
|
||||
// write the field to the header
|
||||
content_type=true;
|
||||
field.putTo(header);
|
||||
putTo(field,header);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -600,7 +604,7 @@ public class HttpGenerator
|
|||
case CONNECTION:
|
||||
{
|
||||
if (request!=null)
|
||||
field.putTo(header);
|
||||
putTo(field,header);
|
||||
|
||||
// Lookup and/or split connection value field
|
||||
HttpHeaderValue[] values = new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
|
||||
|
@ -672,12 +676,12 @@ public class HttpGenerator
|
|||
case SERVER:
|
||||
{
|
||||
send=send&~SEND_SERVER;
|
||||
field.putTo(header);
|
||||
putTo(field,header);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
field.putTo(header);
|
||||
putTo(field,header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -776,7 +780,7 @@ public class HttpGenerator
|
|||
{
|
||||
String c = transfer_encoding.getValue();
|
||||
if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
|
||||
transfer_encoding.putTo(header);
|
||||
putTo(transfer_encoding,header);
|
||||
else
|
||||
throw new IllegalArgumentException("BAD TE");
|
||||
}
|
||||
|
@ -1007,5 +1011,88 @@ public class HttpGenerator
|
|||
{
|
||||
return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
|
||||
}
|
||||
}
|
||||
|
||||
private static void putSanitisedName(String s,ByteBuffer buffer)
|
||||
{
|
||||
int l=s.length();
|
||||
for (int i=0;i<l;i++)
|
||||
{
|
||||
char c=s.charAt(i);
|
||||
|
||||
if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
|
||||
buffer.put((byte)'?');
|
||||
else
|
||||
buffer.put((byte)(0xff&c));
|
||||
}
|
||||
}
|
||||
|
||||
private static void putSanitisedValue(String s,ByteBuffer buffer)
|
||||
{
|
||||
int l=s.length();
|
||||
for (int i=0;i<l;i++)
|
||||
{
|
||||
char c=s.charAt(i);
|
||||
|
||||
if (c<0 || c>0xff || c=='\r' || c=='\n')
|
||||
buffer.put((byte)'?');
|
||||
else
|
||||
buffer.put((byte)(0xff&c));
|
||||
}
|
||||
}
|
||||
|
||||
public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
|
||||
{
|
||||
if (field instanceof CachedHttpField)
|
||||
{
|
||||
((CachedHttpField)field).putTo(bufferInFillMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpHeader header=field.getHeader();
|
||||
if (header!=null)
|
||||
{
|
||||
bufferInFillMode.put(header.getBytesColonSpace());
|
||||
putSanitisedValue(field.getValue(),bufferInFillMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
putSanitisedName(field.getName(),bufferInFillMode);
|
||||
bufferInFillMode.put(__colon_space);
|
||||
putSanitisedValue(field.getValue(),bufferInFillMode);
|
||||
}
|
||||
|
||||
BufferUtil.putCRLF(bufferInFillMode);
|
||||
}
|
||||
}
|
||||
|
||||
public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode)
|
||||
{
|
||||
for (HttpField field : fields)
|
||||
{
|
||||
if (field != null)
|
||||
putTo(field,bufferInFillMode);
|
||||
}
|
||||
BufferUtil.putCRLF(bufferInFillMode);
|
||||
}
|
||||
|
||||
public static class CachedHttpField extends HttpField
|
||||
{
|
||||
private final byte[] _bytes;
|
||||
public CachedHttpField(HttpHeader header,String value)
|
||||
{
|
||||
super(header,value);
|
||||
int cbl=header.getBytesColonSpace().length;
|
||||
_bytes=new byte[cbl+value.length()+2];
|
||||
System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,cbl);
|
||||
System.arraycopy(value.getBytes(StringUtil.__ISO_8859_1_CHARSET),0,_bytes,cbl,value.length());
|
||||
_bytes[_bytes.length-2]=(byte)'\r';
|
||||
_bytes[_bytes.length-1]=(byte)'\n';
|
||||
}
|
||||
|
||||
public void putTo(ByteBuffer bufferInFillMode)
|
||||
{
|
||||
bufferInFillMode.put(_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
|
|||
|
||||
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
|
||||
import org.eclipse.jetty.util.ArrayTernaryTrie;
|
||||
import org.eclipse.jetty.util.ArrayTrie;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.Trie;
|
||||
|
@ -76,6 +77,21 @@ public class HttpParser
|
|||
public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT");
|
||||
public final static int INITIAL_URI_LENGTH=256;
|
||||
|
||||
/**
|
||||
* Cache of common {@link HttpField}s including: <UL>
|
||||
* <LI>Common static combinations such as:<UL>
|
||||
* <li>Connection: close
|
||||
* <li>Accept-Encoding: gzip
|
||||
* <li>Content-Length: 0
|
||||
* </ul>
|
||||
* <li>Combinations of Content-Type header for common mime types by common charsets
|
||||
* <li>Most common headers with null values so that a lookup will at least
|
||||
* determine the header name even if the name:value combination is not cached
|
||||
* </ul>
|
||||
*/
|
||||
public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
|
||||
public final static Trie<HttpField> CONTENT_TYPE = new ArrayTrie<>(512);
|
||||
|
||||
// States
|
||||
public enum State
|
||||
{
|
||||
|
@ -138,6 +154,59 @@ public class HttpParser
|
|||
private int _length;
|
||||
private final StringBuilder _string=new StringBuilder();
|
||||
|
||||
static
|
||||
{
|
||||
CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
|
||||
CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
|
||||
CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
|
||||
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
|
||||
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
|
||||
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
|
||||
CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
|
||||
CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
|
||||
CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
|
||||
CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
|
||||
CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
|
||||
CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
|
||||
CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
|
||||
CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
|
||||
CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
|
||||
CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
|
||||
CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
|
||||
CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
|
||||
CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
|
||||
CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
|
||||
|
||||
// Content types
|
||||
for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/x-www-form-urlencoded"})
|
||||
{
|
||||
HttpField field=new HttpField(HttpHeader.CONTENT_TYPE,type);
|
||||
CACHE.put(field);
|
||||
CONTENT_TYPE.put(type,field);
|
||||
|
||||
for (String charset : new String[]{"UTF-8","ISO-8859-1"})
|
||||
{
|
||||
String type_charset=type+"; charset="+charset;
|
||||
field=new HttpField(HttpHeader.CONTENT_TYPE,type_charset);
|
||||
CACHE.put(field);
|
||||
CACHE.put(new HttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
|
||||
CONTENT_TYPE.put(type_charset,field);
|
||||
CONTENT_TYPE.put(type+";charset="+charset,field);
|
||||
}
|
||||
}
|
||||
|
||||
// Add headers with null values so HttpParser can avoid looking up name again for unknown values
|
||||
for (HttpHeader h:HttpHeader.values())
|
||||
if (!CACHE.put(new HttpField(h,(String)null)))
|
||||
throw new IllegalStateException("CACHE FULL");
|
||||
// Add some more common headers
|
||||
CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public HttpParser(RequestHandler<ByteBuffer> handler)
|
||||
{
|
||||
|
@ -801,7 +870,7 @@ public class HttpParser
|
|||
|
||||
if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
|
||||
{
|
||||
_field=new HttpField.CachedHttpField(_header,_valueString);
|
||||
_field=new HttpField(_header,_valueString);
|
||||
_connectionFields.put(_field);
|
||||
}
|
||||
|
||||
|
@ -944,7 +1013,7 @@ public class HttpParser
|
|||
// Try a look ahead for the known header name and value.
|
||||
HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
|
||||
if (field==null)
|
||||
field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining());
|
||||
field=CACHE.getBest(buffer,-1,buffer.remaining());
|
||||
|
||||
if (field!=null)
|
||||
{
|
||||
|
@ -1547,5 +1616,4 @@ public class HttpParser
|
|||
{
|
||||
return _connectionFields;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -136,10 +136,10 @@ public class MimeTypes
|
|||
try
|
||||
{
|
||||
ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
|
||||
Enumeration i = mime.getKeys();
|
||||
Enumeration<String> i = mime.getKeys();
|
||||
while(i.hasMoreElements())
|
||||
{
|
||||
String ext = (String)i.nextElement();
|
||||
String ext = i.nextElement();
|
||||
String m = mime.getString(ext);
|
||||
__dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -39,6 +40,7 @@ import org.eclipse.jetty.util.URIUtil;
|
|||
* /foo/* - a prefix path specification (must end '/*').
|
||||
* *.ext - a suffix path specification.
|
||||
* / - the default path specification.
|
||||
* "" - the / path specification
|
||||
* </PRE>
|
||||
* Matching is performed in the following order <NL>
|
||||
* <LI>Exact match.
|
||||
|
@ -267,20 +269,22 @@ public class PathMap<O> extends HashMap<String,O>
|
|||
/** Get all entries matched by the path.
|
||||
* Best match first.
|
||||
* @param path Path to match
|
||||
* @return LazyList of Map.Entry instances key=pathSpec
|
||||
* @return List of Map.Entry instances key=pathSpec
|
||||
*/
|
||||
public Object getLazyMatches(String path)
|
||||
public List<? extends Map.Entry<String,O>> getMatches(String path)
|
||||
{
|
||||
MappedEntry<O> entry;
|
||||
Object entries=null;
|
||||
List<MappedEntry<O>> entries=new ArrayList<>();
|
||||
|
||||
if (path==null)
|
||||
return LazyList.getList(entries);
|
||||
return entries;
|
||||
if (path.length()==0)
|
||||
return _defaultSingletonList;
|
||||
|
||||
// try exact match
|
||||
entry=_exactMap.get(path);
|
||||
if (entry!=null)
|
||||
entries=LazyList.add(entries,entry);
|
||||
entries.add(entry);
|
||||
|
||||
// prefix search
|
||||
int l=path.length();
|
||||
|
@ -293,14 +297,14 @@ public class PathMap<O> extends HashMap<String,O>
|
|||
break;
|
||||
String key = entry.getKey();
|
||||
if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
|
||||
entries=LazyList.add(entries,entry);
|
||||
entries.add(entry);
|
||||
|
||||
i=key.length()-3;
|
||||
}
|
||||
|
||||
// Prefix Default
|
||||
if (_prefixDefault!=null)
|
||||
entries=LazyList.add(entries,_prefixDefault);
|
||||
entries.add(_prefixDefault);
|
||||
|
||||
// Extension search
|
||||
i=0;
|
||||
|
@ -309,32 +313,24 @@ public class PathMap<O> extends HashMap<String,O>
|
|||
{
|
||||
entry=suffix_map.get(path,i+1,l-i-1);
|
||||
if (entry!=null)
|
||||
entries=LazyList.add(entries,entry);
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
// root match
|
||||
if ("/".equals(path))
|
||||
{
|
||||
entry=_exactMap.get("");
|
||||
if (entry!=null)
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
// Default
|
||||
if (_default!=null)
|
||||
{
|
||||
// Optimization for just the default
|
||||
if (entries==null)
|
||||
return _defaultSingletonList;
|
||||
|
||||
entries=LazyList.add(entries,_default);
|
||||
}
|
||||
entries.add(_default);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/** Get all entries matched by the path.
|
||||
* Best match first.
|
||||
* @param path Path to match
|
||||
* @return List of Map.Entry instances key=pathSpec
|
||||
*/
|
||||
public List<Map.Entry<String,O>> getMatches(String path)
|
||||
{
|
||||
return LazyList.getList(getLazyMatches(path));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/** Return whether the path matches any entries in the PathMap,
|
||||
|
@ -381,6 +377,7 @@ public class PathMap<O> extends HashMap<String,O>
|
|||
_suffixMap=new ArrayTernaryTrie<>(false);
|
||||
_default=null;
|
||||
_defaultSingletonList=null;
|
||||
_prefixDefault=null;
|
||||
super.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ public class HttpFieldsTest
|
|||
|
||||
ByteBuffer buffer=BufferUtil.allocate(1024);
|
||||
BufferUtil.flipToFill(buffer);
|
||||
header.putTo(buffer);
|
||||
HttpGenerator.putTo(header,buffer);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
String result=BufferUtil.toString(buffer);
|
||||
|
||||
|
@ -117,7 +117,7 @@ public class HttpFieldsTest
|
|||
|
||||
ByteBuffer buffer = BufferUtil.allocate(1024);
|
||||
BufferUtil.flipToFill(buffer);
|
||||
header.putTo(buffer);
|
||||
HttpGenerator.putTo(header,buffer);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
String out = BufferUtil.toString(buffer);
|
||||
assertThat(out,containsString("name0: value??0"));
|
||||
|
@ -136,7 +136,7 @@ public class HttpFieldsTest
|
|||
|
||||
ByteBuffer buffer = BufferUtil.allocate(1024);
|
||||
BufferUtil.flipToFill(buffer);
|
||||
header.putTo(buffer);
|
||||
HttpGenerator.putTo(header,buffer);
|
||||
BufferUtil.flipToFlush(buffer,0);
|
||||
String out = BufferUtil.toString(buffer).toLowerCase();
|
||||
|
||||
|
@ -268,123 +268,6 @@ public class HttpFieldsTest
|
|||
|
||||
|
||||
|
||||
@Test
|
||||
public void testSetCookie() throws Exception
|
||||
{
|
||||
HttpFields fields = new HttpFields();
|
||||
|
||||
fields.addSetCookie("null",null,null,null,-1,null,false,false,-1);
|
||||
assertEquals("null=",fields.getStringField("Set-Cookie"));
|
||||
|
||||
fields.clear();
|
||||
|
||||
fields.addSetCookie("minimal","value",null,null,-1,null,false,false,-1);
|
||||
assertEquals("minimal=value",fields.getStringField("Set-Cookie"));
|
||||
|
||||
fields.clear();
|
||||
//test cookies with same name, domain and path, only 1 allowed
|
||||
fields.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0);
|
||||
fields.addSetCookie("everything","value","domain","path",0,"comment",true,true,0);
|
||||
assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie"));
|
||||
Enumeration<String> e =fields.getValues("Set-Cookie");
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
|
||||
assertFalse(e.hasMoreElements());
|
||||
assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires"));
|
||||
assertFalse(e.hasMoreElements());
|
||||
|
||||
//test cookies with same name, different domain
|
||||
fields.clear();
|
||||
fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
|
||||
fields.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0);
|
||||
e =fields.getValues("Set-Cookie");
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=value;Version=1;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
|
||||
assertFalse(e.hasMoreElements());
|
||||
|
||||
//test cookies with same name, same path, one with domain, one without
|
||||
fields.clear();
|
||||
fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
|
||||
fields.addSetCookie("everything","value","","path",0,"comment",true,true,0);
|
||||
e =fields.getValues("Set-Cookie");
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=value;Version=1;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
|
||||
assertFalse(e.hasMoreElements());
|
||||
|
||||
|
||||
//test cookies with same name, different path
|
||||
fields.clear();
|
||||
fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
|
||||
fields.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0);
|
||||
e =fields.getValues("Set-Cookie");
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=value;Version=1;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
|
||||
assertFalse(e.hasMoreElements());
|
||||
|
||||
//test cookies with same name, same domain, one with path, one without
|
||||
fields.clear();
|
||||
fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
|
||||
fields.addSetCookie("everything","value","domain1","",0,"comment",true,true,0);
|
||||
e =fields.getValues("Set-Cookie");
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=value;Version=1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
|
||||
assertFalse(e.hasMoreElements());
|
||||
|
||||
//test cookies same name only, no path, no domain
|
||||
fields.clear();
|
||||
fields.addSetCookie("everything","other","","",0,"blah",true,true,0);
|
||||
fields.addSetCookie("everything","value","","",0,"comment",true,true,0);
|
||||
e =fields.getValues("Set-Cookie");
|
||||
assertTrue(e.hasMoreElements());
|
||||
assertEquals("everything=value;Version=1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
|
||||
assertFalse(e.hasMoreElements());
|
||||
|
||||
fields.clear();
|
||||
fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1);
|
||||
String setCookie=fields.getStringField("Set-Cookie");
|
||||
assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires="));
|
||||
assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\""));
|
||||
|
||||
fields.clear();
|
||||
fields.addSetCookie("name","value",null,null,-1,null,false,false,0);
|
||||
setCookie=fields.getStringField("Set-Cookie");
|
||||
assertEquals(-1,setCookie.indexOf("Version="));
|
||||
fields.clear();
|
||||
fields.addSetCookie("name","v a l u e",null,null,-1,null,false,false,0);
|
||||
setCookie=fields.getStringField("Set-Cookie");
|
||||
|
||||
fields.clear();
|
||||
fields.addSetCookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1);
|
||||
assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"",fields.getStringField("Set-Cookie"));
|
||||
|
||||
fields.clear();
|
||||
fields.addSetCookie("name","value","domain",null,-1,null,false,false,-1);
|
||||
fields.addSetCookie("name","other","domain",null,-1,null,false,false,-1);
|
||||
assertEquals("name=other;Domain=domain",fields.getStringField("Set-Cookie"));
|
||||
fields.addSetCookie("name","more","domain",null,-1,null,false,false,-1);
|
||||
assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
|
||||
fields.addSetCookie("foo","bar","domain",null,-1,null,false,false,-1);
|
||||
fields.addSetCookie("foo","bob","domain",null,-1,null,false,false,-1);
|
||||
assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie"));
|
||||
|
||||
e=fields.getValues("Set-Cookie");
|
||||
assertEquals("name=more;Domain=domain",e.nextElement());
|
||||
assertEquals("foo=bob;Domain=domain",e.nextElement());
|
||||
|
||||
fields=new HttpFields();
|
||||
fields.addSetCookie("name","value%=",null,null,-1,null,false,false,0);
|
||||
setCookie=fields.getStringField("Set-Cookie");
|
||||
assertEquals("name=value%=",setCookie);
|
||||
|
||||
}
|
||||
|
||||
private Set<String> enum2set(Enumeration<String> e)
|
||||
{
|
||||
|
|
|
@ -372,7 +372,7 @@ public class HttpGeneratorServerTest
|
|||
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||
|
||||
ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false);
|
||||
info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970);
|
||||
info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
|
||||
|
||||
result = gen.generateResponse(info, null, null, null, true);
|
||||
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
|
||||
|
@ -441,7 +441,7 @@ public class HttpGeneratorServerTest
|
|||
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||
|
||||
ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false);
|
||||
info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970);
|
||||
info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
|
||||
result = gen.generateResponse(info, null, null, content0, false);
|
||||
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
|
||||
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||
|
@ -503,7 +503,7 @@ public class HttpGeneratorServerTest
|
|||
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||
|
||||
ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 59, 200, null, false);
|
||||
info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970);
|
||||
info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
|
||||
result = gen.generateResponse(info, null, null, content0, false);
|
||||
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
|
||||
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||
|
@ -570,7 +570,7 @@ public class HttpGeneratorServerTest
|
|||
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||
|
||||
ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 59, 200, null, false);
|
||||
info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970);
|
||||
info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970);
|
||||
result = gen.generateResponse(info, null, null, content0, false);
|
||||
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
|
||||
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||
|
|
|
@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
|
@ -30,6 +31,7 @@ import org.junit.Test;
|
|||
public class PathMapTest
|
||||
{
|
||||
@Test
|
||||
@Ignore
|
||||
public void testPathMap() throws Exception
|
||||
{
|
||||
PathMap<String> p = new PathMap<>();
|
||||
|
@ -65,6 +67,7 @@ public class PathMapTest
|
|||
{ "/animal/path.gz", "5"},
|
||||
{ "/Other/path", "8"},
|
||||
{ "/\u20ACuro/path", "11"},
|
||||
{ "/", "10"},
|
||||
};
|
||||
|
||||
for (String[] test : tests)
|
||||
|
@ -78,8 +81,8 @@ public class PathMapTest
|
|||
p.getMatches("/animal/bird/path.tar.gz").toString());
|
||||
assertEquals("Dir matches", "[/animal/fish/*=4, /animal/*=5, /=8]", p.getMatches("/animal/fish/").toString());
|
||||
assertEquals("Dir matches", "[/animal/fish/*=4, /animal/*=5, /=8]", p.getMatches("/animal/fish").toString());
|
||||
assertEquals("Dir matches", "[/=8]", p.getMatches("/").toString());
|
||||
assertEquals("Dir matches", "[=10, /=8]", p.getMatches("").toString());
|
||||
assertEquals("Root matches", "[=10, /=8]",p.getMatches("/").toString());
|
||||
assertEquals("Dir matches", "[/=8]", p.getMatches("").toString());
|
||||
|
||||
assertEquals("pathMatch exact", "/Foo/bar", PathMap.pathMatch("/Foo/bar", "/Foo/bar"));
|
||||
assertEquals("pathMatch prefix", "/Foo", PathMap.pathMatch("/Foo/*", "/Foo/bar"));
|
||||
|
|
|
@ -35,7 +35,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
* Channel End Point.
|
||||
* <p>Holds the channel and socket for an NIO endpoint.
|
||||
*/
|
||||
public class ChannelEndPoint extends AbstractEndPoint implements SocketBased
|
||||
public class ChannelEndPoint extends AbstractEndPoint
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
|
||||
|
||||
|
@ -208,7 +208,6 @@ public class ChannelEndPoint extends AbstractEndPoint implements SocketBased
|
|||
return _channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _socket;
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* Factory for client-side {@link Connection} instances.
|
||||
*/
|
||||
public interface ClientConnectionFactory
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to
|
||||
* @param context the context data to create the connection
|
||||
* @return a new {@link Connection}
|
||||
* @throws IOException if the connection cannot be created
|
||||
*/
|
||||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException;
|
||||
|
||||
public static class Helper
|
||||
{
|
||||
private static Logger LOG = Log.getLogger(Helper.class);
|
||||
|
||||
private Helper()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the given {@code oldConnection} with the given {@code newConnection} on the
|
||||
* {@link EndPoint} associated with {@code oldConnection}, performing connection lifecycle management.
|
||||
* <p />
|
||||
* The {@code oldConnection} will be closed by invoking {@link org.eclipse.jetty.io.Connection#onClose()}
|
||||
* and the {@code newConnection} will be opened by invoking {@link org.eclipse.jetty.io.Connection#onOpen()}.
|
||||
* @param oldConnection the old connection to replace
|
||||
* @param newConnection the new connection replacement
|
||||
*/
|
||||
public static void replaceConnection(Connection oldConnection, Connection newConnection)
|
||||
{
|
||||
close(oldConnection);
|
||||
oldConnection.getEndPoint().setConnection(newConnection);
|
||||
open(newConnection);
|
||||
}
|
||||
|
||||
private static void open(Connection connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.onOpen();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.debug(x);
|
||||
}
|
||||
}
|
||||
|
||||
private static void close(Connection connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.onClose();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.debug(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -133,7 +133,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements SelectorMa
|
|||
if (_interestOps.compareAndSet(oldInterestOps, newInterestOps))
|
||||
{
|
||||
LOG.debug("Local interests updated {} -> {} for {}", oldInterestOps, newInterestOps, this);
|
||||
_selector.submit(_updateTask);
|
||||
_selector.updateKey(_updateTask);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -152,7 +152,6 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements SelectorMa
|
|||
|
||||
private void setKeyInterests(int oldInterestOps, int newInterestOps)
|
||||
{
|
||||
assert _selector.isSelectorThread();
|
||||
LOG.debug("Key interests updated {} -> {}", oldInterestOps, newInterestOps);
|
||||
_key.interestOps(newInterestOps);
|
||||
}
|
||||
|
|
|
@ -59,11 +59,14 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
|
||||
{
|
||||
protected static final Logger LOG = Log.getLogger(SelectorManager.class);
|
||||
public static final String SUBMIT_KEY_UPDATES="org.eclipse.jetty.io.SelectorManager.submitKeyUpdates";
|
||||
/**
|
||||
* The default connect timeout, in milliseconds
|
||||
*/
|
||||
public static final int DEFAULT_CONNECT_TIMEOUT = 15000;
|
||||
|
||||
private final static boolean __submitKeyUpdates=Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES,"FALSE"));
|
||||
|
||||
private final Executor executor;
|
||||
private final Scheduler scheduler;
|
||||
private final ManagedSelector[] _selectors;
|
||||
|
@ -356,6 +359,25 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
|
|||
LOG.debug("Stopped {}", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a task to update a selector key. If the System property {@link SelectorManager#SUBMIT_KEY_UPDATES}
|
||||
* is set true (default is false), the task is passed to {@link #submit(Runnable)}. Otherwise it is run immediately and the selector
|
||||
* woken up if need be.
|
||||
* @param update the update to a key
|
||||
*/
|
||||
public void updateKey(Runnable update)
|
||||
{
|
||||
if (__submitKeyUpdates)
|
||||
submit(update);
|
||||
else
|
||||
{
|
||||
update.run();
|
||||
|
||||
if (_state.compareAndSet(State.SELECT, State.WAKEUP))
|
||||
wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Submits a change to be executed in the selector thread.</p>
|
||||
* <p>Changes may be submitted from any thread, and the selector thread woken up
|
||||
|
@ -748,14 +770,30 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
|
|||
}
|
||||
catch (ClosedSelectorException | ClosedChannelException x)
|
||||
{
|
||||
LOG.debug(x);
|
||||
failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
protected void failed(Throwable failure)
|
||||
{
|
||||
if (failed.compareAndSet(false, true))
|
||||
{
|
||||
timeout.cancel();
|
||||
close();
|
||||
connectionFailed(channel, failure, attachment);
|
||||
}
|
||||
}
|
||||
|
||||
private void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
LOG.ignore(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -775,19 +813,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
|
|||
if (channel.isConnectionPending())
|
||||
{
|
||||
LOG.debug("Channel {} timed out while connecting, closing it", channel);
|
||||
try
|
||||
{
|
||||
// This will unregister the channel from the selector
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
LOG.ignore(x);
|
||||
}
|
||||
finally
|
||||
{
|
||||
connect.failed(new SocketTimeoutException());
|
||||
}
|
||||
connect.failed(new SocketTimeoutException());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.io.ssl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
public class SslClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
|
||||
public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
|
||||
public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
|
||||
|
||||
private final SslContextFactory sslContextFactory;
|
||||
private final ByteBufferPool byteBufferPool;
|
||||
private final Executor executor;
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
|
||||
public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
|
||||
{
|
||||
this.sslContextFactory = sslContextFactory;
|
||||
this.byteBufferPool = byteBufferPool;
|
||||
this.executor = executor;
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
|
||||
int port = (Integer)context.get(SSL_PEER_PORT_CONTEXT_KEY);
|
||||
SSLEngine engine = sslContextFactory.newSSLEngine(host, port);
|
||||
engine.setUseClientMode(true);
|
||||
context.put(SSL_ENGINE_CONTEXT_KEY, engine);
|
||||
|
||||
SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine);
|
||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
||||
endPoint.setConnection(sslConnection);
|
||||
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
|
||||
appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
|
||||
|
||||
return sslConnection;
|
||||
}
|
||||
|
||||
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
|
||||
{
|
||||
return new SslConnection(byteBufferPool, executor, endPoint, engine);
|
||||
}
|
||||
}
|
|
@ -107,21 +107,6 @@ public class SslConnection extends AbstractConnection
|
|||
this._bufferPool = byteBufferPool;
|
||||
this._sslEngine = sslEngine;
|
||||
this._decryptedEndPoint = newDecryptedEndPoint();
|
||||
|
||||
// commented out for now as it might cause native code being stuck in preClose0.
|
||||
// See: https://java.net/jira/browse/GRIZZLY-547
|
||||
|
||||
// if (endPoint instanceof SocketBased)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// ((SocketBased)endPoint).getSocket().setSoLinger(true, 30000);
|
||||
// }
|
||||
// catch (SocketException e)
|
||||
// {
|
||||
// throw new RuntimeIOException(e);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
protected DecryptedEndPoint newDecryptedEndPoint()
|
||||
|
@ -193,8 +178,8 @@ public class SslConnection extends AbstractConnection
|
|||
|
||||
// We have received a close handshake, close the end point to send FIN.
|
||||
if (_decryptedEndPoint.isInputShutdown())
|
||||
getEndPoint().close();
|
||||
|
||||
_decryptedEndPoint.close();
|
||||
|
||||
// wake up whoever is doing the fill or the flush so they can
|
||||
// do all the filling, unwrapping, wrapping and flushing
|
||||
_decryptedEndPoint.getFillInterest().fillable();
|
||||
|
@ -312,7 +297,7 @@ public class SslConnection extends AbstractConnection
|
|||
fail_filler = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final boolean filler_failed=fail_filler;
|
||||
|
||||
getExecutor().execute(new Runnable()
|
||||
|
@ -508,150 +493,145 @@ public class SslConnection extends AbstractConnection
|
|||
int net_filled = getEndPoint().fill(_encryptedInput);
|
||||
if (DEBUG)
|
||||
LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled);
|
||||
if (net_filled > 0)
|
||||
_underFlown = false;
|
||||
|
||||
// Let's try the SSL thang even if we have no net data because in that
|
||||
// case we want to fall through to the handshake handling
|
||||
int pos = BufferUtil.flipToFill(app_in);
|
||||
|
||||
SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
|
||||
|
||||
BufferUtil.flipToFlush(app_in, pos);
|
||||
if (DEBUG)
|
||||
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
|
||||
|
||||
Status unwrapResultStatus = unwrapResult.getStatus();
|
||||
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
|
||||
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
|
||||
|
||||
// and deal with the results
|
||||
switch (unwrapResultStatus)
|
||||
decryption: while (true)
|
||||
{
|
||||
case BUFFER_OVERFLOW:
|
||||
throw new IllegalStateException();
|
||||
// Let's unwrap even if we have no net data because in that
|
||||
// case we want to fall through to the handshake handling
|
||||
int pos = BufferUtil.flipToFill(app_in);
|
||||
SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
|
||||
BufferUtil.flipToFlush(app_in, pos);
|
||||
if (DEBUG)
|
||||
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
|
||||
|
||||
case CLOSED:
|
||||
// Dang! we have to care about the handshake state specially for close
|
||||
switch (handshakeStatus)
|
||||
{
|
||||
case NOT_HANDSHAKING:
|
||||
// We were not handshaking, so just tell the app we are closed
|
||||
return -1;
|
||||
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
|
||||
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
|
||||
Status unwrapResultStatus = unwrapResult.getStatus();
|
||||
|
||||
case NEED_TASK:
|
||||
// run the task
|
||||
_sslEngine.getDelegatedTask().run();
|
||||
continue;
|
||||
_underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW;
|
||||
|
||||
case NEED_WRAP:
|
||||
// we need to send some handshake data (probably to send a close handshake).
|
||||
// but that will not enable any extra data to fill, so we just return -1
|
||||
// The wrapping can be done by any output drivers doing flushing or shutdown output.
|
||||
return -1;
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
|
||||
default:
|
||||
if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
|
||||
{
|
||||
_handshaken = true;
|
||||
if (DEBUG)
|
||||
LOG.debug("{} handshake completed client-side", SslConnection.this);
|
||||
}
|
||||
|
||||
// Check whether renegotiation is allowed
|
||||
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
|
||||
{
|
||||
if (DEBUG)
|
||||
LOG.debug("{} renegotiation denied", SslConnection.this);
|
||||
if (_underFlown)
|
||||
{
|
||||
if (net_filled < 0)
|
||||
closeInbound();
|
||||
return -1;
|
||||
}
|
||||
if (net_filled <= 0)
|
||||
return net_filled;
|
||||
}
|
||||
|
||||
if (unwrapResultStatus == Status.BUFFER_UNDERFLOW)
|
||||
_underFlown = true;
|
||||
|
||||
// If bytes were produced, don't bother with the handshake status;
|
||||
// pass the decrypted data to the application, which will perform
|
||||
// another call to fill() or flush().
|
||||
if (unwrapResult.bytesProduced() > 0)
|
||||
switch (unwrapResultStatus)
|
||||
{
|
||||
case CLOSED:
|
||||
{
|
||||
if (app_in == buffer)
|
||||
return unwrapResult.bytesProduced();
|
||||
return BufferUtil.flipPutFlip(_decryptedInput, buffer);
|
||||
}
|
||||
|
||||
// Dang! we have to care about the handshake state
|
||||
switch (handshakeStatus)
|
||||
{
|
||||
case NOT_HANDSHAKING:
|
||||
// we just didn't read anything.
|
||||
if (net_filled < 0)
|
||||
switch (handshakeStatus)
|
||||
{
|
||||
case NOT_HANDSHAKING:
|
||||
{
|
||||
closeInbound();
|
||||
// We were not handshaking, so just tell the app we are closed
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case NEED_TASK:
|
||||
// run the task
|
||||
_sslEngine.getDelegatedTask().run();
|
||||
continue;
|
||||
|
||||
case NEED_WRAP:
|
||||
// we need to send some handshake data
|
||||
|
||||
// if we are called from flush
|
||||
if (buffer == __FLUSH_CALLED_FILL)
|
||||
return 0; // let it do the wrapping
|
||||
|
||||
_fillRequiresFlushToProgress = true;
|
||||
flush(__FILL_CALLED_FLUSH);
|
||||
if (BufferUtil.isEmpty(_encryptedOutput))
|
||||
case NEED_TASK:
|
||||
{
|
||||
// the flush completed so continue
|
||||
_fillRequiresFlushToProgress = false;
|
||||
_sslEngine.getDelegatedTask().run();
|
||||
continue;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case NEED_UNWRAP:
|
||||
// if we just filled some net data
|
||||
if (net_filled < 0)
|
||||
case NEED_WRAP:
|
||||
{
|
||||
closeInbound();
|
||||
// We need to send some handshake data (probably the close handshake).
|
||||
// We return -1 so that the application can drive the close by flushing
|
||||
// or shutting down the output.
|
||||
return -1;
|
||||
}
|
||||
else if (net_filled > 0)
|
||||
default:
|
||||
{
|
||||
// maybe we will fill some more on a retry
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
case BUFFER_UNDERFLOW:
|
||||
case OK:
|
||||
{
|
||||
if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
|
||||
{
|
||||
_handshaken = true;
|
||||
if (DEBUG)
|
||||
LOG.debug("{} {} handshake completed", SslConnection.this,
|
||||
_sslEngine.getUseClientMode() ? "client-side" : "resumed session server-side");
|
||||
}
|
||||
|
||||
// Check whether renegotiation is allowed
|
||||
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
|
||||
{
|
||||
if (DEBUG)
|
||||
LOG.debug("{} renegotiation denied", SslConnection.this);
|
||||
closeInbound();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If bytes were produced, don't bother with the handshake status;
|
||||
// pass the decrypted data to the application, which will perform
|
||||
// another call to fill() or flush().
|
||||
if (unwrapResult.bytesProduced() > 0)
|
||||
{
|
||||
if (app_in == buffer)
|
||||
return unwrapResult.bytesProduced();
|
||||
return BufferUtil.flipPutFlip(_decryptedInput, buffer);
|
||||
}
|
||||
|
||||
switch (handshakeStatus)
|
||||
{
|
||||
case NOT_HANDSHAKING:
|
||||
{
|
||||
if (_underFlown)
|
||||
break decryption;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
case NEED_TASK:
|
||||
{
|
||||
if (_encryptedInput.hasRemaining())
|
||||
_sslEngine.getDelegatedTask().run();
|
||||
continue;
|
||||
}
|
||||
case NEED_WRAP:
|
||||
{
|
||||
// If we are called from flush()
|
||||
// return to let it do the wrapping.
|
||||
if (buffer == __FLUSH_CALLED_FILL)
|
||||
return 0;
|
||||
|
||||
_fillRequiresFlushToProgress = true;
|
||||
flush(__FILL_CALLED_FLUSH);
|
||||
if (BufferUtil.isEmpty(_encryptedOutput))
|
||||
{
|
||||
// if there are more encrypted bytes,
|
||||
// then we need to unwrap more, we don't
|
||||
// care if net_filled is zero
|
||||
// The flush wrote all the encrypted bytes so continue to fill
|
||||
_fillRequiresFlushToProgress = false;
|
||||
continue;
|
||||
}
|
||||
// we need to wait for more net data
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
// The flush did not complete, return from fill()
|
||||
// and let the write completion mechanism to kick in.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
case FINISHED:
|
||||
throw new IllegalStateException();
|
||||
case NEED_UNWRAP:
|
||||
{
|
||||
if (_underFlown)
|
||||
break decryption;
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SSLException e)
|
||||
{
|
||||
getEndPoint().close();
|
||||
throw new EofException(e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
getEndPoint().close();
|
||||
|
@ -778,7 +758,7 @@ public class SslConnection extends AbstractConnection
|
|||
{
|
||||
_handshaken = true;
|
||||
if (DEBUG)
|
||||
LOG.debug("{} handshake completed server-side", SslConnection.this);
|
||||
LOG.debug("{} {} handshake completed", SslConnection.this, "server-side");
|
||||
}
|
||||
|
||||
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
|
||||
|
@ -824,7 +804,7 @@ public class SslConnection extends AbstractConnection
|
|||
if (handshakeStatus == HandshakeStatus.NEED_WRAP)
|
||||
continue;
|
||||
}
|
||||
return allConsumed&&BufferUtil.isEmpty(_encryptedOutput);
|
||||
return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
|
||||
|
||||
case FINISHED:
|
||||
throw new IllegalStateException();
|
||||
|
@ -860,17 +840,18 @@ public class SslConnection extends AbstractConnection
|
|||
public void shutdownOutput()
|
||||
{
|
||||
boolean ishut = isInputShutdown();
|
||||
boolean oshut = isOutputShutdown();
|
||||
if (DEBUG)
|
||||
LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, isOutputShutdown(), ishut);
|
||||
LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, oshut, ishut);
|
||||
if (ishut)
|
||||
{
|
||||
// Aggressively close, since inbound close alert has already been processed
|
||||
// and the TLS specification allows to close the connection directly, which
|
||||
// is what most other implementations expect: a FIN rather than a TLS close
|
||||
// reply. If a TLS close reply is sent, most implementation send a RST.
|
||||
// reply. If a TLS close reply is sent, most implementations send a RST.
|
||||
getEndPoint().close();
|
||||
}
|
||||
else
|
||||
else if (!oshut)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -895,6 +876,8 @@ public class SslConnection extends AbstractConnection
|
|||
@Override
|
||||
public void close()
|
||||
{
|
||||
// First send the TLS Close Alert, then the FIN
|
||||
shutdownOutput();
|
||||
getEndPoint().close();
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.nio.channels.ServerSocketChannel;
|
|||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
@ -67,6 +68,7 @@ public class SelectorManagerTest
|
|||
client.configureBlocking(false);
|
||||
client.connect(address);
|
||||
|
||||
final AtomicBoolean timeoutConnection = new AtomicBoolean();
|
||||
final long connectTimeout = 1000;
|
||||
SelectorManager selectorManager = new SelectorManager(executor, scheduler)
|
||||
{
|
||||
|
@ -81,7 +83,8 @@ public class SelectorManagerTest
|
|||
{
|
||||
try
|
||||
{
|
||||
TimeUnit.MILLISECONDS.sleep(connectTimeout * 2);
|
||||
if (timeoutConnection.get())
|
||||
TimeUnit.MILLISECONDS.sleep(connectTimeout * 2);
|
||||
return super.finishConnect(channel);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
|
@ -113,17 +116,30 @@ public class SelectorManagerTest
|
|||
|
||||
try
|
||||
{
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
timeoutConnection.set(true);
|
||||
final CountDownLatch latch1 = new CountDownLatch(1);
|
||||
selectorManager.connect(client, new Callback.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
latch.countDown();
|
||||
latch1.countDown();
|
||||
}
|
||||
});
|
||||
Assert.assertTrue(latch1.await(connectTimeout * 3, TimeUnit.MILLISECONDS));
|
||||
|
||||
Assert.assertTrue(latch.await(connectTimeout * 3, TimeUnit.MILLISECONDS));
|
||||
// Verify that after the failure we can connect successfully
|
||||
timeoutConnection.set(false);
|
||||
final CountDownLatch latch2 = new CountDownLatch(1);
|
||||
selectorManager.connect(client, new Callback.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
latch2.countDown();
|
||||
}
|
||||
});
|
||||
Assert.assertTrue(latch2.await(connectTimeout, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
#
|
||||
# JAAS Feature
|
||||
# JAAS Module
|
||||
#
|
||||
|
||||
[depend]
|
||||
server
|
||||
|
||||
[lib]
|
||||
# JAAS jars
|
||||
lib/jetty-jaas-${jetty.version}.jar
|
||||
|
||||
[xml]
|
||||
# JAAS configuration
|
||||
etc/jetty-jaas.xml
|
||||
|
||||
[ini-template]
|
||||
## JAAS Configuration
|
||||
jaas.login.conf=etc/login.conf
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#
|
||||
# Jetty JASPI Module
|
||||
#
|
||||
|
||||
[depend]
|
||||
security
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
|
||||
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
<!-- Add a remote JMX connector. The parameters of the constructor
|
||||
below specify the JMX service URL, and the object name string for the
|
||||
connector server bean. The parameters of the JMXServiceURL constructor
|
||||
specify the protocol that clients will use to connect to the remote JMX
|
||||
connector (RMI), the hostname of the server (local hostname), port number
|
||||
(automatically assigned), and the URL path. Note that URL path contains
|
||||
the RMI registry hostname and port number, that may need to be modified
|
||||
in order to comply with the firewall requirements.
|
||||
-->
|
||||
<New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
|
||||
<Arg>
|
||||
<New class="javax.management.remote.JMXServiceURL">
|
||||
<Arg type="java.lang.String">rmi</Arg>
|
||||
<Arg type="java.lang.String" />
|
||||
<Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
|
||||
<Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
|
||||
</New>
|
||||
</Arg>
|
||||
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
|
||||
<Call name="start" />
|
||||
</New>
|
||||
</Configure>
|
||||
|
|
@ -39,49 +39,5 @@
|
|||
<New class="org.eclipse.jetty.util.log.Log" />
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
<!-- In order to connect to the JMX server remotely from a different
|
||||
process, possibly running on a different host, Jetty JMX module
|
||||
can create a remote JMX connector. It requires RMI registry to
|
||||
be started prior to creating the connector server because the
|
||||
JMX specification uses RMI to facilitate connections.
|
||||
-->
|
||||
|
||||
<!-- Optionally start the RMI registry. Normally RMI registry runs on
|
||||
port 1099. The argument below can be changed in order to comply
|
||||
with the firewall requirements.
|
||||
-->
|
||||
<!--
|
||||
<Call name="createRegistry" class="java.rmi.registry.LocateRegistry">
|
||||
<Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
|
||||
<Call name="sleep" class="java.lang.Thread">
|
||||
<Arg type="java.lang.Integer">1000</Arg>
|
||||
</Call>
|
||||
</Call>
|
||||
-->
|
||||
|
||||
<!-- Optionally add a remote JMX connector. The parameters of the constructor
|
||||
below specify the JMX service URL, and the object name string for the
|
||||
connector server bean. The parameters of the JMXServiceURL constructor
|
||||
specify the protocol that clients will use to connect to the remote JMX
|
||||
connector (RMI), the hostname of the server (local hostname), port number
|
||||
(automatically assigned), and the URL path. Note that URL path contains
|
||||
the RMI registry hostname and port number, that may need to be modified
|
||||
in order to comply with the firewall requirements.
|
||||
-->
|
||||
<!--
|
||||
<New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
|
||||
<Arg>
|
||||
<New class="javax.management.remote.JMXServiceURL">
|
||||
<Arg type="java.lang.String">rmi</Arg>
|
||||
<Arg type="java.lang.String" />
|
||||
<Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
|
||||
<Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
|
||||
</New>
|
||||
</Arg>
|
||||
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
|
||||
<Call name="start" />
|
||||
</New>
|
||||
-->
|
||||
</Configure>
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# JMX Remote Module
|
||||
#
|
||||
|
||||
[depend]
|
||||
jmx
|
||||
|
||||
[xml]
|
||||
etc/jetty-jmx-remote.xml
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
#
|
||||
# JMX Feature
|
||||
# JMX Module
|
||||
#
|
||||
|
||||
[lib]
|
||||
# JMX jars (as defined in start.config)
|
||||
lib/jetty-jmx-${jetty.version}.jar
|
||||
|
||||
[xml]
|
||||
# JMX configuration
|
||||
etc/jetty-jmx.xml
|
||||
|
||||
[ini-template]
|
||||
## JMX Configuration
|
||||
## Enable the "jmx-remote" module for an open port accessible by remote machines
|
||||
# jetty.jmxrmihost=localhost
|
||||
# jetty.jmxrmiport=1099
|
||||
## Strictly speaking you shouldn't need --exec to use this in most environments.
|
||||
## If this isn't working, make sure you enable --exec as well
|
||||
# -Dcom.sun.management.jmxremote
|
||||
|
|
|
@ -9,5 +9,6 @@ servlet
|
|||
lib/jsp/*.jar
|
||||
|
||||
[ini-template]
|
||||
# JSP Configuration
|
||||
# To use an non-jdk compiler for JSP compilation uncomment next line
|
||||
#-Dorg.apache.jasper.compiler.disablejsr199=true
|
||||
# -Dorg.apache.jasper.compiler.disablejsr199=true
|
||||
|
|
|
@ -163,12 +163,6 @@ public class JspcMojo extends AbstractMojo
|
|||
*/
|
||||
private boolean keepSources;
|
||||
|
||||
/**
|
||||
* Default root package for all generated classes
|
||||
*
|
||||
* @parameter default-value="jsp"
|
||||
*/
|
||||
private String packageRoot;
|
||||
|
||||
/**
|
||||
* Root directory for all html/jsp etc files
|
||||
|
@ -209,48 +203,6 @@ public class JspcMojo extends AbstractMojo
|
|||
*/
|
||||
private File classesDirectory;
|
||||
|
||||
/**
|
||||
* Whether or not to output more verbose messages during compilation.
|
||||
*
|
||||
* @parameter default-value="false";
|
||||
*/
|
||||
private boolean verbose;
|
||||
|
||||
/**
|
||||
* If true, validates tlds when parsing.
|
||||
*
|
||||
* @parameter default-value="false";
|
||||
*/
|
||||
private boolean validateXml;
|
||||
|
||||
/**
|
||||
* The encoding scheme to use.
|
||||
*
|
||||
* @parameter default-value="UTF-8"
|
||||
*/
|
||||
private String javaEncoding;
|
||||
|
||||
/**
|
||||
* Whether or not to generate JSR45 compliant debug info
|
||||
*
|
||||
* @parameter default-value="true";
|
||||
*/
|
||||
private boolean suppressSmap;
|
||||
|
||||
/**
|
||||
* Whether or not to ignore precompilation errors caused by jsp fragments.
|
||||
*
|
||||
* @parameter default-value="false"
|
||||
*/
|
||||
private boolean ignoreJspFragmentErrors;
|
||||
|
||||
/**
|
||||
* Allows a prefix to be appended to the standard schema locations so that
|
||||
* they can be loaded from elsewhere.
|
||||
*
|
||||
* @parameter
|
||||
*/
|
||||
private String schemaResourcePrefix;
|
||||
|
||||
/**
|
||||
* Patterns of jars on the system path that contain tlds. Use | to separate each pattern.
|
||||
|
@ -258,35 +210,32 @@ public class JspcMojo extends AbstractMojo
|
|||
* @parameter default-value=".*taglibs[^/]*\.jar|.*jstl-impl[^/]*\.jar$
|
||||
*/
|
||||
private String tldJarNamePatterns;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Should white spaces in template text between actions or directives be trimmed? Defaults to false.
|
||||
*
|
||||
* The JspC instance being used to compile the jsps.
|
||||
*
|
||||
* @parameter
|
||||
*/
|
||||
private boolean trimSpaces = false;
|
||||
private JspC jspc;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void execute() throws MojoExecutionException, MojoFailureException
|
||||
{
|
||||
if (getLog().isDebugEnabled())
|
||||
{
|
||||
getLog().info("verbose=" + verbose);
|
||||
|
||||
getLog().info("webAppSourceDirectory=" + webAppSourceDirectory);
|
||||
getLog().info("generatedClasses=" + generatedClasses);
|
||||
getLog().info("webXmlFragment=" + webXmlFragment);
|
||||
getLog().info("webXml="+webXml);
|
||||
getLog().info("validateXml=" + validateXml);
|
||||
getLog().info("packageRoot=" + packageRoot);
|
||||
getLog().info("javaEncoding=" + javaEncoding);
|
||||
getLog().info("insertionMarker="+ (insertionMarker == null || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker));
|
||||
getLog().info("keepSources=" + keepSources);
|
||||
getLog().info("mergeFragment=" + mergeFragment);
|
||||
getLog().info("suppressSmap=" + suppressSmap);
|
||||
getLog().info("ignoreJspFragmentErrors=" + ignoreJspFragmentErrors);
|
||||
getLog().info("schemaResourcePrefix=" + schemaResourcePrefix);
|
||||
getLog().info("trimSpaces=" + trimSpaces);
|
||||
getLog().info("mergeFragment=" + mergeFragment);
|
||||
}
|
||||
try
|
||||
{
|
||||
|
@ -338,22 +287,14 @@ public class JspcMojo extends AbstractMojo
|
|||
}
|
||||
|
||||
Thread.currentThread().setContextClassLoader(webAppClassLoader);
|
||||
|
||||
JspC jspc = new JspC();
|
||||
|
||||
jspc.setWebXmlFragment(webXmlFragment);
|
||||
jspc.setUriroot(webAppSourceDirectory);
|
||||
jspc.setPackage(packageRoot);
|
||||
jspc.setUriroot(webAppSourceDirectory);
|
||||
jspc.setOutputDir(generatedClasses);
|
||||
jspc.setValidateXml(validateXml);
|
||||
jspc.setClassPath(webAppClassPath.toString());
|
||||
jspc.setCompile(true);
|
||||
jspc.setSmapSuppressed(suppressSmap);
|
||||
jspc.setSmapDumped(!suppressSmap);
|
||||
jspc.setJavaEncoding(javaEncoding);
|
||||
jspc.setTrimSpaces(trimSpaces);
|
||||
jspc.setSystemClassPath(sysClassPath);
|
||||
|
||||
|
||||
|
||||
|
||||
// JspC#setExtensions() does not exist, so
|
||||
// always set concrete list of files that will be processed.
|
||||
|
@ -362,34 +303,8 @@ public class JspcMojo extends AbstractMojo
|
|||
getLog().info("Includes="+includes);
|
||||
getLog().info("Excludes="+excludes);
|
||||
jspc.setJspFiles(jspFiles);
|
||||
if (verbose)
|
||||
{
|
||||
getLog().info("Files selected to precompile: " + jspFiles);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
jspc.setIgnoreJspFragmentErrors(ignoreJspFragmentErrors);
|
||||
}
|
||||
catch (NoSuchMethodError e)
|
||||
{
|
||||
getLog().debug("Tomcat Jasper does not support configuration option 'ignoreJspFragmentErrors': ignored");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (schemaResourcePrefix != null)
|
||||
jspc.setSchemaResourcePrefix(schemaResourcePrefix);
|
||||
}
|
||||
catch (NoSuchMethodError e)
|
||||
{
|
||||
getLog().debug("Tomcat Jasper does not support configuration option 'schemaResourcePrefix': ignored");
|
||||
}
|
||||
if (verbose)
|
||||
jspc.setVerbose(99);
|
||||
else
|
||||
jspc.setVerbose(0);
|
||||
getLog().info("Files selected to precompile: " + jspFiles);
|
||||
|
||||
jspc.execute();
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ import org.eclipse.jetty.util.IO;
|
|||
* </p>
|
||||
*
|
||||
* @goal run-forked
|
||||
* @requiresDependencyResolution compile+runtime
|
||||
* @requiresDependencyResolution test
|
||||
* @execute phase="test-compile"
|
||||
* @description Runs Jetty in forked JVM on an unassembled webapp
|
||||
*
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.eclipse.jetty.annotations.AnnotationConfiguration;
|
||||
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
|
||||
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
|
@ -111,7 +112,7 @@ public class JettyWebAppContext extends WebAppContext
|
|||
new FragmentConfiguration(),
|
||||
_envConfig = new EnvConfiguration(),
|
||||
new PlusConfiguration(),
|
||||
new MavenAnnotationConfiguration(),
|
||||
new AnnotationConfiguration(),
|
||||
new JettyWebXmlConfiguration()
|
||||
});
|
||||
// Turn off copyWebInf option as it is not applicable for plugin.
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.maven.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.annotations.AnnotationConfiguration;
|
||||
import org.eclipse.jetty.annotations.AnnotationParser;
|
||||
import org.eclipse.jetty.annotations.AnnotationParser.Handler;
|
||||
import org.eclipse.jetty.annotations.ClassNameResolver;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.webapp.MetaData;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
|
||||
public class MavenAnnotationConfiguration extends AnnotationConfiguration
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(MavenAnnotationConfiguration.class);
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public void parseWebInfClasses(final WebAppContext context, final AnnotationParser parser) throws Exception
|
||||
{
|
||||
JettyWebAppContext jwac = (JettyWebAppContext)context;
|
||||
if (jwac.getClassPathFiles() == null || jwac.getClassPathFiles().size() == 0)
|
||||
super.parseWebInfClasses (context, parser);
|
||||
else
|
||||
{
|
||||
LOG.debug("Scanning classes ");
|
||||
//Look for directories on the classpath and process each one of those
|
||||
|
||||
MetaData metaData = context.getMetaData();
|
||||
if (metaData == null)
|
||||
throw new IllegalStateException ("No metadata");
|
||||
|
||||
Set<Handler> handlers = new HashSet<Handler>();
|
||||
handlers.addAll(_discoverableAnnotationHandlers);
|
||||
if (_classInheritanceHandler != null)
|
||||
handlers.add(_classInheritanceHandler);
|
||||
handlers.addAll(_containerInitializerAnnotationHandlers);
|
||||
|
||||
|
||||
for (File f:jwac.getClassPathFiles())
|
||||
{
|
||||
//scan the equivalent of the WEB-INF/classes directory that has been synthesised by the plugin
|
||||
if (f.isDirectory() && f.exists())
|
||||
{
|
||||
doParse(handlers, context, parser, Resource.newResource(f.toURI()));
|
||||
}
|
||||
}
|
||||
|
||||
//if an actual WEB-INF/classes directory also exists (eg because of overlayed wars) then scan that
|
||||
//too
|
||||
if (context.getWebInf() != null && context.getWebInf().exists())
|
||||
{
|
||||
Resource classesDir = context.getWebInf().addPath("classes/");
|
||||
if (classesDir.exists())
|
||||
{
|
||||
doParse(handlers, context, parser, classesDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void doParse (final Set<? extends Handler> handlers, final WebAppContext context, final AnnotationParser parser, Resource resource)
|
||||
throws Exception
|
||||
{
|
||||
if (_parserTasks != null)
|
||||
_parserTasks.add(new ParserTask(parser, handlers, resource, _webAppClassNameResolver));
|
||||
else
|
||||
parser.parse(handlers, resource, _webAppClassNameResolver);
|
||||
}
|
||||
}
|
|
@ -238,6 +238,46 @@ public class MavenWebInfConfiguration extends WebInfConfiguration
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add in the classes dirs from test/classes and target/classes
|
||||
* @see org.eclipse.jetty.webapp.WebInfConfiguration#findClassDirs(org.eclipse.jetty.webapp.WebAppContext)
|
||||
*/
|
||||
@Override
|
||||
protected List<Resource> findClassDirs(WebAppContext context) throws Exception
|
||||
{
|
||||
List<Resource> list = new ArrayList<Resource>();
|
||||
|
||||
JettyWebAppContext jwac = (JettyWebAppContext)context;
|
||||
if (jwac.getClassPathFiles() != null)
|
||||
{
|
||||
for (File f: jwac.getClassPathFiles())
|
||||
{
|
||||
if (f.exists() && f.isDirectory())
|
||||
{
|
||||
try
|
||||
{
|
||||
list.add(Resource.newResource(f.toURI()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Bad url ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Resource> classesDirs = super.findClassDirs(context);
|
||||
if (classesDirs != null)
|
||||
list.addAll(classesDirs);
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
protected Resource unpackOverlay (WebAppContext context, Overlay overlay)
|
||||
throws IOException
|
||||
|
|
|
@ -179,8 +179,6 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
|
|||
ClassNameResolver classNameResolver = createClassNameResolver(context);
|
||||
if (_parserTasks != null)
|
||||
_parserTasks.add(new BundleParserTask(parser, handlers, bundleRes, classNameResolver));
|
||||
else
|
||||
parser.parse(handlers, bundle, classNameResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,5 +9,4 @@ deploy
|
|||
lib/jetty-overlay-deployer-${jetty.version}.jar
|
||||
|
||||
[xml]
|
||||
# Plus requires configuration
|
||||
etc/jetty-overlay.xml
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Jetty Proxy module
|
||||
# Jetty Plus module
|
||||
#
|
||||
|
||||
[depend]
|
||||
|
@ -11,5 +11,4 @@ jndi
|
|||
lib/jetty-plus-${jetty.version}.jar
|
||||
|
||||
[xml]
|
||||
# Plus requires configuration
|
||||
etc/jetty-plus.xml
|
||||
|
|
|
@ -10,5 +10,4 @@ client
|
|||
lib/jetty-proxy-${jetty.version}.jar
|
||||
|
||||
[xml]
|
||||
# Proxy requires configuration
|
||||
etc/jetty-proxy.xml
|
||||
|
|
|
@ -31,9 +31,9 @@ import java.util.Set;
|
|||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.UnavailableException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
@ -62,7 +62,7 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
|||
* <p/>
|
||||
* To facilitate JMX monitoring, the {@link HttpClient} instance is set as context attribute,
|
||||
* prefixed with the servlet's name and exposed by the mechanism provided by
|
||||
* {@link ContextHandler#MANAGED_ATTRIBUTES}.
|
||||
* {@link ServletContext#setAttribute(String, Object)}.
|
||||
* <p/>
|
||||
* The following init parameters may be used to configure the servlet:
|
||||
* <ul>
|
||||
|
@ -389,6 +389,7 @@ public class ProxyServlet extends HttpServlet
|
|||
.version(HttpVersion.fromString(request.getProtocol()));
|
||||
|
||||
// Copy headers
|
||||
boolean hasContent = false;
|
||||
for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
|
||||
{
|
||||
String headerName = headerNames.nextElement();
|
||||
|
@ -398,9 +399,13 @@ public class ProxyServlet extends HttpServlet
|
|||
if (HOP_HEADERS.contains(lowerHeaderName))
|
||||
continue;
|
||||
|
||||
if (_hostHeader!=null && lowerHeaderName.equals("host"))
|
||||
if (_hostHeader != null && HttpHeader.HOST.is(headerName))
|
||||
continue;
|
||||
|
||||
if (request.getContentLength() > 0 || request.getContentType() != null ||
|
||||
HttpHeader.TRANSFER_ENCODING.is(headerName))
|
||||
hasContent = true;
|
||||
|
||||
for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
|
||||
{
|
||||
String headerValue = headerValues.nextElement();
|
||||
|
@ -420,21 +425,24 @@ public class ProxyServlet extends HttpServlet
|
|||
proxyRequest.header(HttpHeader.X_FORWARDED_HOST, request.getHeader(HttpHeader.HOST.asString()));
|
||||
proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, request.getLocalName());
|
||||
|
||||
proxyRequest.content(new InputStreamContentProvider(request.getInputStream())
|
||||
if (hasContent)
|
||||
{
|
||||
@Override
|
||||
public long getLength()
|
||||
proxyRequest.content(new InputStreamContentProvider(request.getInputStream())
|
||||
{
|
||||
return request.getContentLength();
|
||||
}
|
||||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
return request.getContentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
|
||||
{
|
||||
_log.debug("{} proxying content to upstream: {} bytes", requestId, length);
|
||||
return super.onRead(buffer, offset, length);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
|
||||
{
|
||||
_log.debug("{} proxying content to upstream: {} bytes", requestId, length);
|
||||
return super.onRead(buffer, offset, length);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final AsyncContext asyncContext = request.startAsync();
|
||||
// We do not timeout the continuation, but the proxy request
|
||||
|
@ -629,7 +637,12 @@ public class ProxyServlet extends HttpServlet
|
|||
return null;
|
||||
|
||||
StringBuilder uri = new StringBuilder(_proxyTo);
|
||||
uri.append(path.substring(_prefix.length()));
|
||||
if (_proxyTo.endsWith("/"))
|
||||
uri.setLength(uri.length() - 1);
|
||||
String rest = path.substring(_prefix.length());
|
||||
if (!rest.startsWith("/"))
|
||||
uri.append("/");
|
||||
uri.append(rest);
|
||||
String query = request.getQueryString();
|
||||
if (query != null)
|
||||
uri.append("?").append(query);
|
||||
|
|
|
@ -28,10 +28,10 @@ import java.net.HttpCookie;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -48,8 +48,8 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpContentResponse;
|
||||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
|
@ -110,7 +110,7 @@ public class ProxyServletTest
|
|||
private HttpClient prepareClient() throws Exception
|
||||
{
|
||||
HttpClient result = new HttpClient();
|
||||
result.setProxyConfiguration(new ProxyConfiguration("localhost", proxyConnector.getLocalPort()));
|
||||
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
|
||||
result.start();
|
||||
return result;
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ public class ProxyServletTest
|
|||
prepareProxy(new ProxyServlet());
|
||||
|
||||
HttpClient result = new HttpClient();
|
||||
result.setProxyConfiguration(new ProxyConfiguration("localhost", proxyConnector.getLocalPort()));
|
||||
result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort()));
|
||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||
threadPool.setName("foo");
|
||||
threadPool.setMaxThreads(20);
|
||||
|
@ -246,7 +246,7 @@ public class ProxyServletTest
|
|||
ContentResponse[] responses = new ContentResponse[10];
|
||||
|
||||
final byte[] content = new byte[1024];
|
||||
Arrays.fill(content, (byte)'A');
|
||||
new Random().nextBytes(content);
|
||||
prepareServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
|
@ -291,7 +291,7 @@ public class ProxyServletTest
|
|||
});
|
||||
|
||||
byte[] content = new byte[1024];
|
||||
Arrays.fill(content, (byte)'A');
|
||||
new Random().nextBytes(content);
|
||||
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.method(HttpMethod.POST)
|
||||
.content(new BytesContentProvider(content))
|
||||
|
@ -331,6 +331,9 @@ public class ProxyServletTest
|
|||
@Test
|
||||
public void testProxyWithBigRequestContentConsumed() throws Exception
|
||||
{
|
||||
final byte[] content = new byte[128 * 1024];
|
||||
new Random().nextBytes(content);
|
||||
|
||||
prepareProxy(new ProxyServlet());
|
||||
prepareServer(new HttpServlet()
|
||||
{
|
||||
|
@ -340,13 +343,18 @@ public class ProxyServletTest
|
|||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
InputStream input = req.getInputStream();
|
||||
int index = 0;
|
||||
while (true)
|
||||
if (input.read() < 0)
|
||||
{
|
||||
int value = input.read();
|
||||
if (value < 0)
|
||||
break;
|
||||
Assert.assertEquals("Content mismatch at index=" + index, content[index] & 0xFF, value);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
byte[] content = new byte[128 * 1024];
|
||||
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.method(HttpMethod.POST)
|
||||
.content(new BytesContentProvider(content))
|
||||
|
@ -369,7 +377,7 @@ public class ProxyServletTest
|
|||
Files.createDirectories(targetTestsDir);
|
||||
final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
|
||||
byte[] kb = new byte[1024];
|
||||
Arrays.fill(kb, (byte)'X');
|
||||
new Random().nextBytes(kb);
|
||||
try (OutputStream output = Files.newOutputStream(temp, CREATE))
|
||||
{
|
||||
for (int i = 0; i < length; ++i)
|
||||
|
@ -631,7 +639,7 @@ public class ProxyServletTest
|
|||
}
|
||||
});
|
||||
int port = serverConnector.getLocalPort();
|
||||
client.getProxyConfiguration().getExcludedOrigins().add("127.0.0.1:" + port);
|
||||
client.getProxyConfiguration().getProxies().get(0).getExcludedAddresses().add("127.0.0.1:" + port);
|
||||
|
||||
// Try with a proxied host
|
||||
ContentResponse response = client.newRequest("localhost", port)
|
||||
|
@ -650,6 +658,17 @@ public class ProxyServletTest
|
|||
|
||||
@Test
|
||||
public void testTransparentProxy() throws Exception
|
||||
{
|
||||
testTransparentProxyWithPrefix("/proxy");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransparentProxyWithRootContext() throws Exception
|
||||
{
|
||||
testTransparentProxyWithPrefix("/");
|
||||
}
|
||||
|
||||
private void testTransparentProxyWithPrefix(String prefix) throws Exception
|
||||
{
|
||||
final String target = "/test";
|
||||
prepareServer(new HttpServlet()
|
||||
|
@ -664,13 +683,12 @@ public class ProxyServletTest
|
|||
});
|
||||
|
||||
String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
|
||||
String prefix = "/proxy";
|
||||
ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix);
|
||||
prepareProxy(proxyServlet);
|
||||
|
||||
// Make the request to the proxy, it should transparently forward to the server
|
||||
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
|
||||
.path(prefix + target)
|
||||
.path((prefix + target).replaceAll("//", "/"))
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
|
@ -865,7 +883,7 @@ public class ProxyServletTest
|
|||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
|
|
|
@ -26,17 +26,16 @@ import java.util.concurrent.CountDownLatch;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Destination;
|
||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
@ -143,7 +142,7 @@ public class ProxyTunnellingTest
|
|||
startProxy();
|
||||
|
||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
||||
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||
httpClient.start();
|
||||
|
||||
try
|
||||
|
@ -172,7 +171,7 @@ public class ProxyTunnellingTest
|
|||
startProxy();
|
||||
|
||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
||||
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||
httpClient.start();
|
||||
|
||||
try
|
||||
|
@ -215,7 +214,7 @@ public class ProxyTunnellingTest
|
|||
startProxy();
|
||||
|
||||
final HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
||||
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||
httpClient.start();
|
||||
|
||||
try
|
||||
|
@ -285,7 +284,7 @@ public class ProxyTunnellingTest
|
|||
stopProxy();
|
||||
|
||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort));
|
||||
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
|
||||
httpClient.start();
|
||||
|
||||
try
|
||||
|
@ -317,7 +316,7 @@ public class ProxyTunnellingTest
|
|||
startProxy();
|
||||
|
||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
||||
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||
httpClient.start();
|
||||
|
||||
try
|
||||
|
@ -354,7 +353,7 @@ public class ProxyTunnellingTest
|
|||
});
|
||||
|
||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||
httpClient.setProxyConfiguration(new ProxyConfiguration("localhost", proxyPort()));
|
||||
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
|
||||
httpClient.start();
|
||||
|
||||
try
|
||||
|
@ -394,7 +393,7 @@ public class ProxyTunnellingTest
|
|||
sslContextFactory.start();
|
||||
|
||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||
httpClient.setProxyConfiguration(new ProxyConfiguration(proxyHost, proxyPort));
|
||||
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
|
||||
httpClient.start();
|
||||
|
||||
try
|
||||
|
|
|
@ -9,5 +9,4 @@ server
|
|||
lib/jetty-rewrite-${jetty.version}.jar
|
||||
|
||||
[xml]
|
||||
# Annotations needs annotations configuration
|
||||
etc/jetty-rewrite.xml
|
||||
|
|
|
@ -261,7 +261,7 @@ public class JDBCLoginService extends MappedLoginService
|
|||
roles.add(rs2.getString(_roleTableRoleField));
|
||||
}
|
||||
}
|
||||
return putUser(username, Credential.getCredential(credentials),roles.toArray(new String[roles.size()]));
|
||||
return putUser(username, credentials, roles.toArray(new String[roles.size()]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -273,6 +273,13 @@ public class JDBCLoginService extends MappedLoginService
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected UserIdentity putUser (String username, String credentials, String[] roles)
|
||||
{
|
||||
return putUser(username, Credential.getCredential(credentials),roles);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close an existing connection
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<Item>127.0.0.2/black.html</Item>
|
||||
</Array>
|
||||
</Set>
|
||||
<Set name="whiteListByPath">false</Set>
|
||||
</New>
|
||||
</Set>
|
||||
</Configure>
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
<Set name="retainDays"><Property name="requestlog.retain" default="90"/></Set>
|
||||
<Set name="append"><Property name="requestlog.append" default="false"/></Set>
|
||||
<Set name="extended"><Property name="requestlog.extended" default="false"/></Set>
|
||||
<Set name="logCookies">false</Set>
|
||||
<Set name="LogTimeZone">GMT</Set>
|
||||
<Set name="logCookies"><Property name="requestlog.cookies" default="false"/></Set>
|
||||
<Set name="LogTimeZone"><Property name="requestlog.timezone" default="GMT"/></Set>
|
||||
</New>
|
||||
</Set>
|
||||
</New>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#
|
||||
# Classic Jetty Continuation Support Module
|
||||
#
|
||||
|
||||
[lib]
|
||||
lib/jetty-continuation-${jetty.version}.jar
|
|
@ -9,5 +9,6 @@ server
|
|||
etc/jetty-http.xml
|
||||
|
||||
[ini-template]
|
||||
## HTTP Connector Configuration
|
||||
jetty.port=8080
|
||||
http.timeout=30000
|
||||
|
|
|
@ -9,5 +9,8 @@ ssl
|
|||
etc/jetty-https.xml
|
||||
|
||||
[ini-template]
|
||||
## HTTPS Configuration
|
||||
# HTTP port to listen on
|
||||
https.port=8443
|
||||
# HTTPS idle timeout in milliseconds
|
||||
https.timeout=30000
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
[ini-template]
|
||||
#===========================================================
|
||||
# Configure JVM arguments.
|
||||
# If JVM args are include in an ini file then --exec is needed
|
||||
# to start a new JVM from start.jar with the extra args.
|
||||
# If you wish to avoid an extra JVM running, place JVM args
|
||||
# on the normal command line and do not use --exec
|
||||
#-----------------------------------------------------------
|
||||
## JVM Configuration
|
||||
## If JVM args are include in an ini file then --exec is needed
|
||||
## to start a new JVM from start.jar with the extra args.
|
||||
##
|
||||
## If you wish to avoid an extra JVM running, place JVM args
|
||||
## on the normal command line and do not use --exec
|
||||
# --exec
|
||||
# -Xmx2000m
|
||||
# -Xmn512m
|
||||
# -XX:+UseConcMarkSweepGC
|
||||
# -XX:ParallelCMSThreads=2
|
||||
# -XX:+CMSClassUnloadingEnabled
|
||||
# -XX:+CMSClassUnloadingEnabled
|
||||
# -XX:+UseCMSCompactAtFullCollection
|
||||
# -XX:CMSInitiatingOccupancyFraction=80
|
||||
# -verbose:gc
|
||||
|
|
|
@ -9,6 +9,7 @@ server
|
|||
etc/jetty-lowresources.xml
|
||||
|
||||
[ini-template]
|
||||
## Low Resources Configuration
|
||||
# lowresources.period=1050
|
||||
# lowresources.lowResourcesIdleTimeout=200
|
||||
# lowresources.monitorThreads=true
|
||||
|
|
|
@ -12,6 +12,15 @@ etc/jetty-requestlog.xml
|
|||
logs/
|
||||
|
||||
[ini-template]
|
||||
## Request Log Configuration
|
||||
# How many days to retain the logs
|
||||
# requestlog.retain=90
|
||||
# If an existing log with the same name is found, just append to it
|
||||
# requestlog.append=true
|
||||
# Use the extended log output
|
||||
# requestlog.extended=true
|
||||
# Log http cookie information as well
|
||||
# requestlog.cookies=true
|
||||
# Set the log output timezone
|
||||
# requestlog.timezone=GMT
|
||||
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
#
|
||||
# Module to add resources directory to classpath
|
||||
#
|
||||
|
||||
[lib]
|
||||
resources
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Base server
|
||||
# Base Server Module
|
||||
#
|
||||
|
||||
[optional]
|
||||
|
@ -12,22 +12,27 @@ resources
|
|||
lib/servlet-api-3.1.jar
|
||||
lib/jetty-schemas-3.1.jar
|
||||
lib/jetty-http-${jetty.version}.jar
|
||||
lib/jetty-continuation-${jetty.version}.jar
|
||||
lib/jetty-server-${jetty.version}.jar
|
||||
lib/jetty-xml-${jetty.version}.jar
|
||||
lib/jetty-util-${jetty.version}.jar
|
||||
lib/jetty-io-${jetty.version}.jar
|
||||
|
||||
[xml]
|
||||
# Annotations needs annotations configuration
|
||||
etc/jetty.xml
|
||||
|
||||
[ini-template]
|
||||
## Server Threading Configuration
|
||||
# minimum number of threads
|
||||
threads.min=10
|
||||
# maximum number of threads
|
||||
threads.max=200
|
||||
# thread idle timeout in milliseconds
|
||||
threads.timeout=60000
|
||||
# What host to listen on (leave commented to listen on all interfaces)
|
||||
#jetty.host=myhost.com
|
||||
# Dump the state of the Jetty server, components, and webapps after startup
|
||||
jetty.dump.start=false
|
||||
# Dump the state of the Jetty server, before stop
|
||||
jetty.dump.stop=false
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
#
|
||||
# SSL Keystore module
|
||||
#
|
||||
|
||||
[depend]
|
||||
server
|
||||
|
@ -11,6 +12,7 @@ etc/jetty-ssl.xml
|
|||
http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-server/src/main/config/etc/keystore:etc/keystore
|
||||
|
||||
[ini-template]
|
||||
## SSL Keystore Configuration
|
||||
# define the port to use for secure redirection
|
||||
jetty.secure.port=8443
|
||||
|
||||
|
@ -20,6 +22,7 @@ jetty.truststore=etc/keystore
|
|||
|
||||
# Set the demonstration passwords.
|
||||
# Note that OBF passwords are not secure, just protected from casual observation
|
||||
# See http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html
|
||||
jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
|
||||
jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
|
||||
jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue