001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2014-2015 ForgeRock AS.
015 */
016
017package org.forgerock.http.handler;
018
019import static org.forgerock.util.time.Duration.duration;
020
021import java.io.Closeable;
022import java.io.IOException;
023import java.util.Collections;
024import java.util.List;
025
026import javax.net.ssl.KeyManager;
027import javax.net.ssl.TrustManager;
028
029import org.forgerock.services.context.Context;
030import org.forgerock.http.Handler;
031import org.forgerock.http.HttpApplicationException;
032import org.forgerock.http.io.Buffer;
033import org.forgerock.http.io.IO;
034import org.forgerock.http.protocol.Request;
035import org.forgerock.http.protocol.Response;
036import org.forgerock.http.spi.HttpClient;
037import org.forgerock.http.spi.HttpClientProvider;
038import org.forgerock.http.spi.Loader;
039import org.forgerock.util.Factory;
040import org.forgerock.util.Option;
041import org.forgerock.util.Options;
042import org.forgerock.util.Reject;
043import org.forgerock.util.promise.NeverThrowsException;
044import org.forgerock.util.promise.Promise;
045import org.forgerock.util.time.Duration;
046
047/**
048 * An HTTP client for sending requests to remote servers.
049 */
050public final class HttpClientHandler implements Handler, Closeable {
051
052    /**
053     * The TCP connect timeout for new HTTP connections. The default timeout is
054     * 10 seconds.
055     */
056    public static final Option<Duration> OPTION_CONNECT_TIMEOUT = Option.withDefault(duration("10 seconds"));
057
058    /**
059     * The TCP socket timeout when waiting for HTTP responses. The default
060     * timeout is 10 seconds.
061     */
062    public static final Option<Duration> OPTION_SO_TIMEOUT = Option.withDefault(duration("10 seconds"));
063
064    /**
065     * Specifies whether HTTP connections should be kept alive an reused for
066     * additional requests. By default, connections will be reused if possible.
067     */
068    public static final Option<Boolean> OPTION_REUSE_CONNECTIONS = Option.withDefault(true);
069
070    /**
071     * Specifies whether requests should be retried if a failure is detected. By
072     * default requests will be retried.
073     */
074    public static final Option<Boolean> OPTION_RETRY_REQUESTS = Option.withDefault(true);
075
076    /**
077     * Specifies the list of key managers that should be used when configuring
078     * SSL/TLS connections. By default the system key manager(s) will be used.
079     */
080    public static final Option<KeyManager[]> OPTION_KEY_MANAGERS = Option.of(KeyManager[].class, null);
081
082    /**
083     * The strategy which should be used for loading the
084     * {@link HttpClientProvider}. By default, the provider will be loaded using
085     * a {@code ServiceLoader}.
086     *
087     * @see Loader#SERVICE_LOADER
088     */
089    public static final Option<Loader> OPTION_LOADER = Option.of(Loader.class, Loader.SERVICE_LOADER);
090
091    /**
092     * Specifies the maximum number of connections that should be pooled by the
093     * HTTP client. At most 64 connections will be cached by default.
094     */
095    public static final Option<Integer> OPTION_MAX_CONNECTIONS = Option.withDefault(64);
096
097    /**
098     * Specifies the temporary storage that should be used for storing HTTP
099     * responses. By default {@link IO#newTemporaryStorage()} is used.
100     */
101    @SuppressWarnings({ "unchecked", "rawtypes" })
102    public static final Option<Factory<Buffer>> OPTION_TEMPORARY_STORAGE = (Option) Option.of(
103            Factory.class, IO.newTemporaryStorage());
104
105    /**
106     * Specifies the list of trust managers that should be used when configuring
107     * SSL/TLS connections. By default the system trust manager(s) will be used.
108     */
109    public static final Option<TrustManager[]> OPTION_TRUST_MANAGERS = Option.of(TrustManager[].class, null);
110
111    /** The client implementation. */
112    private final HttpClient httpClient;
113
114    /**
115     * SSL host name verification policies.
116     */
117    public enum HostnameVerifier {
118        /**
119         * Accepts any host name (disables host name verification).
120         */
121        ALLOW_ALL,
122
123        /**
124         * Requires that the host name matches the host name presented in the
125         * certificate. Wild-cards only match a single domain.
126         */
127        STRICT;
128    }
129
130    /**
131     * Specifies the SSL host name verification policy. The default is to allow
132     * all host names.
133     */
134    public static final Option<HostnameVerifier> OPTION_HOSTNAME_VERIFIER = Option.of(
135            HostnameVerifier.class, HostnameVerifier.ALLOW_ALL);
136
137    /**
138     * SSLContext algorithm to be used when making SSL/TLS connections.
139     */
140    public static final Option<String> OPTION_SSLCONTEXT_ALGORITHM = Option.withDefault("TLS");
141
142    /**
143     * List of SSL protocols to be enabled on the HttpClient.
144     * Defaults to the list of SSL protocols supported by the Java runtime.
145     *
146     * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#jssenames">
147     *     JDK 7 Supported Protocols</a>
148     */
149    @SuppressWarnings({ "rawtypes", "unchecked" })
150    public static final Option<List<String>> OPTION_SSL_ENABLED_PROTOCOLS =
151            (Option) Option.of(List.class, Collections.<String>emptyList());
152
153    /**
154     * List of JSSE ciphers to be enabled on the HttpClient.
155     * Defaults to the list of ciphers supported by the Java runtime.
156     *
157     * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#ciphersuites">
158     *     JDK 7 Cipher Suite Names</a>
159     */
160    @SuppressWarnings({ "rawtypes", "unchecked" })
161    public static final Option<List<String>> OPTION_SSL_CIPHER_SUITES =
162            (Option) Option.of(List.class, Collections.<String>emptyList());
163
164    /**
165     * Creates a new HTTP client using default client options. The returned
166     * client must be closed when it is no longer needed by the application.
167     *
168     * @throws HttpApplicationException
169     *             If no client provider could be found.
170     */
171    public HttpClientHandler() throws HttpApplicationException {
172        this(Options.unmodifiableDefaultOptions());
173    }
174
175    /**
176     * Creates a new HTTP client using the provided client options. The returned
177     * client must be closed when it is no longer needed by the application.
178     *
179     * @param options
180     *            The options which will be used to configure the client.
181     * @throws HttpApplicationException
182     *             If no client provider could be found, or if the client could
183     *             not be configured using the provided set of options.
184     * @throws NullPointerException
185     *             If {@code options} was {@code null}.
186     */
187    public HttpClientHandler(final Options options) throws HttpApplicationException {
188        Reject.ifNull(options);
189        final Loader loader = options.get(OPTION_LOADER);
190        final HttpClientProvider factory = loader.load(HttpClientProvider.class, options);
191        if (factory == null) {
192            throw new HttpApplicationException("No HTTP client provider found");
193        }
194        this.httpClient = factory.newHttpClient(options);
195    }
196
197    /**
198     * Completes all pending requests and release resources associated with
199     * underlying implementation.
200     *
201     * @throws IOException
202     *             if an I/O error occurs
203     */
204    @Override
205    public void close() throws IOException {
206        httpClient.close();
207    }
208
209    /**
210     * Sends an HTTP request to a remote server and returns a {@code Promise}
211     * representing the asynchronous response.
212     * <p>
213     * {@inheritDoc}
214     */
215    @Override
216    public Promise<Response, NeverThrowsException> handle(Context context, Request request) {
217        return httpClient.sendAsync(request);
218    }
219}