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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
023import org.slf4j.LoggerFactory;
024
025/**
026 * A generic HTTP connector. It creates {@link HttpClient.Builder} and
027 * {@link HttpRequest.Builder} that can be individually customized according to the needs
028 * of the CA.
029 *
030 * @since 3.0.0
031 */
032public class HttpConnector {
033    private static final String USER_AGENT;
034
035    private final NetworkSettings networkSettings;
036
037    static {
038        var agent = new StringBuilder("acme4j");
039
040        try (var in = HttpConnector.class.getResourceAsStream("/org/shredzone/acme4j/version.properties")) {
041            var prop = new Properties();
042            prop.load(in);
043            agent.append('/').append(prop.getProperty("version"));
044        } catch (Exception ex) {
045            // Ignore, just don't use a version
046            LoggerFactory.getLogger(HttpConnector.class).warn("Could not read library version", ex);
047        }
048
049        agent.append(" Java/").append(System.getProperty("java.version"));
050        USER_AGENT = agent.toString();
051    }
052
053    /**
054     * Returns the default User-Agent to be used.
055     *
056     * @return User-Agent
057     */
058    public static String defaultUserAgent() {
059        return USER_AGENT;
060    }
061
062    /**
063     * Creates a new {@link HttpConnector} that is using the given
064     * {@link NetworkSettings}.
065     */
066    @SuppressFBWarnings("EI_EXPOSE_REP2")   // behavior is intended
067    public HttpConnector(NetworkSettings networkSettings) {
068        this.networkSettings = networkSettings;
069    }
070
071    /**
072     * Creates a new {@link HttpRequest.Builder} that is preconfigured and bound to the
073     * given URL. Subclasses can override this method to extend the configuration, or
074     * create a different builder.
075     *
076     * @param url
077     *            {@link URL} to connect to
078     * @return {@link HttpRequest.Builder} connected to the {@link URL}
079     */
080    public HttpRequest.Builder createRequestBuilder(URL url) {
081        try {
082            return HttpRequest.newBuilder(url.toURI())
083                    .header("User-Agent", USER_AGENT)
084                    .timeout(networkSettings.getTimeout());
085        } catch (URISyntaxException ex) {
086            throw new IllegalArgumentException("Invalid URL", ex);
087        }
088    }
089
090    /**
091     * Creates a new {@link HttpClient.Builder}.
092     * <p>
093     * The {@link HttpClient.Builder} is already preconfigured with a reasonable timeout,
094     * the proxy settings, authenticator, and that it follows normal redirects.
095     * Subclasses can override this method to extend the configuration, or to create a
096     * different builder.
097     */
098    public HttpClient.Builder createClientBuilder() {
099        var builder = HttpClient.newBuilder()
100                .followRedirects(HttpClient.Redirect.NORMAL)
101                .connectTimeout(networkSettings.getTimeout())
102                .proxy(networkSettings.getProxySelector());
103
104        if (networkSettings.getAuthenticator() != null) {
105            builder.authenticator(networkSettings.getAuthenticator());
106        }
107
108        return builder;
109    }
110
111}