001package com.pusher.rest;
002
003import com.pusher.rest.data.Result;
004import org.apache.http.HttpResponse;
005import org.apache.http.client.config.RequestConfig;
006import org.apache.http.client.methods.HttpGet;
007import org.apache.http.client.methods.HttpPost;
008import org.apache.http.client.methods.HttpRequestBase;
009import org.apache.http.entity.StringEntity;
010import org.apache.http.impl.DefaultConnectionReuseStrategy;
011import org.apache.http.impl.client.CloseableHttpClient;
012import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
013import org.apache.http.impl.client.HttpClientBuilder;
014import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
015
016import java.io.ByteArrayOutputStream;
017import java.io.IOException;
018import java.net.URI;
019
020/**
021 * A library for interacting with the Pusher HTTP API.
022 * <p>
023 * See http://github.com/pusher/pusher-http-java for an overview
024 * <p>
025 * Essentially:
026 * <pre>
027 * // Init
028 * Pusher pusher = new Pusher(APP_ID, KEY, SECRET);
029 * // Publish
030 * Result triggerResult = pusher.trigger("my-channel", "my-eventname", myPojoForSerialisation);
031 * if (triggerResult.getStatus() != Status.SUCCESS) {
032 *   if (triggerResult.getStatus().shouldRetry()) {
033 *     // Temporary, let's schedule a retry
034 *   }
035 *   else {
036 *     // Something is wrong with our request
037 *   }
038 * }
039 *
040 * // Query
041 * Result channelListResult = pusher.get("/channels");
042 * if (channelListResult.getStatus() == Status.SUCCESS) {
043 *   String channelListAsJson = channelListResult.getMessage();
044 *   // etc
045 * }
046 * </pre>
047 *
048 * See {@link PusherAsync} for the asynchronous implementation.
049 */
050public class Pusher extends PusherAbstract<Result> implements AutoCloseable {
051
052    private int requestTimeout = 4000; // milliseconds
053
054    private CloseableHttpClient client;
055
056    /**
057     * Construct an instance of the Pusher object through which you may interact with the Pusher API.
058     * <p>
059     * The parameters to use are found on your dashboard at https://app.pusher.com and are specific per App.
060     * <p>
061     * @param appId The ID of the App you will to interact with.
062     * @param key The App Key, the same key you give to websocket clients to identify your app when they connect to Pusher.
063     * @param secret The App Secret. Used to sign requests to the API, this should be treated as sensitive and not distributed.
064     */
065    public Pusher(final String appId, final String key, final String secret) {
066        super(appId, key, secret);
067        configureHttpClient(defaultHttpClientBuilder());
068    }
069
070    public Pusher(final String url) {
071        super(url);
072        configureHttpClient(defaultHttpClientBuilder());
073    }
074
075    /*
076     * CONFIG
077     */
078
079    /**
080     * Default: 4000
081     *
082     * @param requestTimeout the request timeout in milliseconds
083     */
084    public void setRequestTimeout(final int requestTimeout) {
085        this.requestTimeout = requestTimeout;
086    }
087
088    /**
089     * Returns an HttpClientBuilder with the settings used by default applied. You may apply
090     * further configuration (for example an HTTP proxy), override existing configuration
091     * (for example, the connection manager which handles connection pooling for reuse) and
092     * then call {@link #configureHttpClient(HttpClientBuilder)} to have this configuration
093     * applied to all subsequent calls.
094     *
095     * @see #configureHttpClient(HttpClientBuilder)
096     *
097     * @return an {@link org.apache.http.impl.client.HttpClientBuilder} with the default settings applied
098     */
099    public static HttpClientBuilder defaultHttpClientBuilder() {
100        return HttpClientBuilder.create()
101                .setConnectionManager(new PoolingHttpClientConnectionManager())
102                .setConnectionReuseStrategy(new DefaultConnectionReuseStrategy())
103                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
104                .disableRedirectHandling();
105    }
106
107    /**
108     * Configure the HttpClient instance which will be used for making calls to the Pusher API.
109     * <p>
110     * This method allows almost complete control over all aspects of the HTTP client, including
111     * <ul>
112     * <li>proxy host</li>
113     * <li>connection pooling and reuse strategies</li>
114     * <li>automatic retry and backoff strategies</li>
115     * </ul>
116     * It is <strong>strongly</strong> recommended that you take the value of {@link #defaultHttpClientBuilder()}
117     * as a base, apply your custom config to that and then pass the builder in here, to ensure
118     * that sensible defaults for configuration areas you are not setting explicitly are retained.
119     * <p>
120     * e.g.
121     * <pre>
122     * pusher.configureHttpClient(
123     *     Pusher.defaultHttpClientBuilder()
124     *           .setProxy(new HttpHost("proxy.example.com"))
125     *           .disableAutomaticRetries()
126     * );
127     * </pre>
128     *
129     * @see #defaultHttpClientBuilder()
130     *
131     * @param builder an {@link org.apache.http.impl.client.HttpClientBuilder} with which to configure
132     * the internal HTTP client
133     */
134    public void configureHttpClient(final HttpClientBuilder builder) {
135        try {
136            close();
137        } catch (final Exception e) {
138            // Not a lot useful we can do here
139        }
140
141        this.client = builder.build();
142    }
143
144    /*
145     * REST
146     */
147
148    @Override
149    protected Result doGet(final URI uri) {
150        return httpCall(new HttpGet(uri));
151    }
152
153    @Override
154    protected Result doPost(final URI uri, final String body) {
155        final StringEntity bodyEntity = new StringEntity(body, "UTF-8");
156        bodyEntity.setContentType("application/json");
157
158        final HttpPost request = new HttpPost(uri);
159        request.setEntity(bodyEntity);
160
161        return httpCall(request);
162    }
163
164    Result httpCall(final HttpRequestBase request) {
165        final RequestConfig config = RequestConfig.custom()
166                .setSocketTimeout(requestTimeout)
167                .setConnectionRequestTimeout(requestTimeout)
168                .setConnectTimeout(requestTimeout)
169                .build();
170        request.setConfig(config);
171
172        try {
173            final HttpResponse response = client.execute(request);
174
175            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
176            response.getEntity().writeTo(baos);
177            final String responseBody = new String(baos.toByteArray(), "UTF-8");
178
179            return Result.fromHttpCode(response.getStatusLine().getStatusCode(), responseBody);
180        }
181        catch (final IOException e) {
182            return Result.fromException(e);
183        }
184    }
185
186    @Override
187    public void close() throws Exception {
188        if (client != null) {
189            client.close();
190        }
191    }
192
193}