001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2015 Richard "Shred" K├Ârber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j.connector;
015
016import java.net.URISyntaxException;
017import java.net.URL;
018import java.net.http.HttpClient;
019import java.net.http.HttpRequest;
020import java.util.Properties;
021
022import org.slf4j.LoggerFactory;
023
024/**
025 * A generic HTTP connector. It creates {@link HttpClient.Builder} and
026 * {@link HttpRequest.Builder} that can be individually customized according to the needs
027 * of the CA.
028 *
029 * @since 3.0.0
030 */
031public class HttpConnector {
032    private static final String USER_AGENT;
033
034    private final NetworkSettings networkSettings;
035
036    static {
037        var agent = new StringBuilder("acme4j");
038
039        try (var in = HttpConnector.class.getResourceAsStream("/org/shredzone/acme4j/version.properties")) {
040            var prop = new Properties();
041            prop.load(in);
042            agent.append('/').append(prop.getProperty("version"));
043        } catch (Exception ex) {
044            // Ignore, just don't use a version
045            LoggerFactory.getLogger(HttpConnector.class).warn("Could not read library version", ex);
046        }
047
048        agent.append(" Java/").append(System.getProperty("java.version"));
049        USER_AGENT = agent.toString();
050    }
051
052    /**
053     * Returns the default User-Agent to be used.
054     *
055     * @return User-Agent
056     */
057    public static String defaultUserAgent() {
058        return USER_AGENT;
059    }
060
061    /**
062     * Creates a new {@link HttpConnector} that is using the given
063     * {@link NetworkSettings}.
064     */
065    public HttpConnector(NetworkSettings networkSettings) {
066        this.networkSettings = networkSettings;
067    }
068
069    /**
070     * Creates a new {@link HttpRequest.Builder} that is preconfigured and bound to the
071     * given URL. Subclasses can override this method to extend the configuration, or
072     * create a different builder.
073     *
074     * @param url
075     *            {@link URL} to connect to
076     * @return {@link HttpRequest.Builder} connected to the {@link URL}
077     */
078    public HttpRequest.Builder createRequestBuilder(URL url) {
079        try {
080            return HttpRequest.newBuilder(url.toURI())
081                    .header("User-Agent", USER_AGENT)
082                    .timeout(networkSettings.getTimeout());
083        } catch (URISyntaxException ex) {
084            throw new IllegalArgumentException("Invalid URL", ex);
085        }
086    }
087
088    /**
089     * Creates a new {@link HttpClient.Builder}.
090     * <p>
091     * The {@link HttpClient.Builder} is already preconfigured with a reasonable timeout,
092     * the proxy settings, authenticator, and that it follows normal redirects.
093     * Subclasses can override this method to extend the configuration, or to create a
094     * different builder.
095     */
096    public HttpClient.Builder createClientBuilder() {
097        var builder = HttpClient.newBuilder()
098                .followRedirects(HttpClient.Redirect.NORMAL)
099                .connectTimeout(networkSettings.getTimeout())
100                .proxy(networkSettings.getProxySelector());
101
102        if (networkSettings.getAuthenticator() != null) {
103            builder.authenticator(networkSettings.getAuthenticator());
104        }
105
106        return builder;
107    }
108
109}