/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.http.localserver;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpResponseFactory;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpServerConnection;
import org.apache.http.impl.DefaultBHttpServerConnection;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpExpectationVerifier;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.protocol.UriHttpRequestHandlerMapper;
import org.apache.http.util.Asserts;
/**
* Local HTTP server for tests that require one.
* Based on the <code>ElementalHttpServer</code> example in HttpCore.
*/
public class LocalTestServer {
public final static String ORIGIN = "LocalTestServer/1.1";
/**
* The local address to bind to.
* The host is an IP number rather than "localhost" to avoid surprises
* on hosts that map "localhost" to an IPv6 address or something else.
* The port is 0 to let the system pick one.
*/
public final static InetSocketAddress TEST_SERVER_ADDR =
new InetSocketAddress("127.0.0.1", 0);
/** The request handler registry. */
private final UriHttpRequestHandlerMapper handlerRegistry;
private final HttpService httpservice;
/** Optional SSL context */
private final SSLContext sslcontext;
/** Optional flag whether to force SSL context */
private final boolean forceSSLAuth;
/** The server socket, while being served. */
private volatile ServerSocket servicedSocket;
/** The request listening thread, while listening. */
private volatile ListenerThread listenerThread;
/** Set of active worker threads */
private final Set<Worker> workers;
/** The number of connections this accepted. */
private final AtomicInteger acceptedConnections = new AtomicInteger(0);
private volatile int timeout;
/**
* Creates a new test server.
*
* @param proc the HTTP processors to be used by the server, or
* <code>null</code> to use a
* {@link #newProcessor default} processor
* @param reuseStrat the connection reuse strategy to be used by the
* server, or <code>null</code> to use
* {@link #newConnectionReuseStrategy() default}
* strategy.
* @param responseFactory the response factory to be used by the
* server, or <code>null</code> to use
* {@link #newHttpResponseFactory() default} factory.
* @param expectationVerifier the expectation verifier. May be
* <code>null</code>.
* @param sslcontext optional SSL context if the server is to leverage
* SSL/TLS transport security
* @param forceSSLAuth whether or not the server needs to enforce client auth
*/
public LocalTestServer(
final HttpProcessor proc,
final ConnectionReuseStrategy reuseStrat,
final HttpResponseFactory responseFactory,
final HttpExpectationVerifier expectationVerifier,
final SSLContext sslcontext,
final boolean forceSSLAuth) {
super();
this.handlerRegistry = new UriHttpRequestHandlerMapper();
this.workers = Collections.synchronizedSet(new HashSet<Worker>());
this.httpservice = new HttpService(
proc != null ? proc : newProcessor(),
reuseStrat != null ? reuseStrat: newConnectionReuseStrategy(),
responseFactory != null ? responseFactory: newHttpResponseFactory(),
handlerRegistry,
expectationVerifier);
this.sslcontext = sslcontext;
this.forceSSLAuth = forceSSLAuth;
}
public LocalTestServer(
final HttpProcessor proc,
final ConnectionReuseStrategy reuseStrat) {
this(proc, reuseStrat, null, null, null, false);
}
/**
* Creates a new test server with SSL/TLS encryption.
*
* @param sslcontext SSL context
* @param forceSSLAuth whether or not the server needs to enforce client auth
*/
public LocalTestServer(final SSLContext sslcontext, final boolean forceSSLAuth) {
this(null, null, null, null, sslcontext, forceSSLAuth);
}
/**
* Creates a new test server with SSL/TLS encryption.
*
* @param sslcontext SSL context
*/
public LocalTestServer(final SSLContext sslcontext) {
this(null, null, null, null, sslcontext, false);
}
/**
* Obtains an HTTP protocol processor with default interceptors.
*
* @return a protocol processor for server-side use
*/
protected HttpProcessor newProcessor() {
return new ImmutableHttpProcessor(
new HttpResponseInterceptor[] {
new ResponseDate(),
new ResponseServer(ORIGIN),
new ResponseContent(),
new ResponseConnControl()
});
}
protected ConnectionReuseStrategy newConnectionReuseStrategy() {
return DefaultConnectionReuseStrategy.INSTANCE;
}
protected HttpResponseFactory newHttpResponseFactory() {
return DefaultHttpResponseFactory.INSTANCE;
}
/**
* Returns the number of connections this test server has accepted.
*/
public int getAcceptedConnectionCount() {
return acceptedConnections.get();
}
/**
* {@link #register Registers} a set of default request handlers.
* <pre>
* URI pattern Handler
* ----------- -------
* /echo/* {@link EchoHandler EchoHandler}
* /random/* {@link RandomHandler RandomHandler}
* </pre>
*/
public void registerDefaultHandlers() {
handlerRegistry.register("/echo/*", new EchoHandler());
handlerRegistry.register("/random/*", new RandomHandler());
}
/**
* Registers a handler with the local registry.
*
* @param pattern the URL pattern to match
* @param handler the handler to appl