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    /**
071     * Construct an instance of the Pusher object through which you may interact with the Pusher API.
072     * <p>
073     * The parameters to use are found on your dashboard at https://app.pusher.com and are specific per App.
074     * <p>
075     *
076     * @param appId  The ID of the App you will to interact with.
077     * @param key    The App Key, the same key you give to websocket clients to identify your app when they connect to Pusher.
078     * @param secret The App Secret. Used to sign requests to the API, this should be treated as sensitive and not distributed.
079     * @param encryptionMasterKeyBase64 32 byte key, base64 encoded. This key, along with the channel name, are used to derive per-channel encryption keys.
080     */
081    public Pusher(final String appId, final String key, final String secret, final String encryptionMasterKeyBase64) {
082        super(appId, key, secret, encryptionMasterKeyBase64);
083
084        configureHttpClient(defaultHttpClientBuilder());
085    }
086
087    public Pusher(final String url) {
088        super(url);
089        configureHttpClient(defaultHttpClientBuilder());
090    }
091
092    /*
093     * CONFIG
094     */
095
096    /**
097     * Default: 4000
098     *
099     * @param requestTimeout the request timeout in milliseconds
100     */
101    public void setRequestTimeout(final int requestTimeout) {
102        this.requestTimeout = requestTimeout;
103    }
104
105    /**
106     * Returns an HttpClientBuilder with the settings used by default applied. You may apply
107     * further configuration (for example an HTTP proxy), override existing configuration
108     * (for example, the connection manager which handles connection pooling for reuse) and
109     * then call {@link #configureHttpClient(HttpClientBuilder)} to have this configuration
110     * applied to all subsequent calls.
111     *
112     * @see #configureHttpClient(HttpClientBuilder)
113     *
114     * @return an {@link org.apache.http.impl.client.HttpClientBuilder} with the default settings applied
115     */
116    public static HttpClientBuilder defaultHttpClientBuilder() {
117        return HttpClientBuilder.create()
118                .setConnectionManager(new PoolingHttpClientConnectionManager())
119                .setConnectionReuseStrategy(new DefaultConnectionReuseStrategy())
120                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
121                .disableRedirectHandling();
122    }
123
124    /**
125     * Configure the HttpClient instance which will be used for making calls to the Pusher API.
126     * <p>
127     * This method allows almost complete control over all aspects of the HTTP client, including
128     * <ul>
129     * <li>proxy host</li>
130     * <li>connection pooling and reuse strategies</li>
131     * <li>automatic retry and backoff strategies</li>
132     * </ul>
133     * It is <strong>strongly</strong> recommended that you take the value of {@link #defaultHttpClientBuilder()}
134     * as a base, apply your custom config to that and then pass the builder in here, to ensure
135     * that sensible defaults for configuration areas you are not setting explicitly are retained.
136     * <p>
137     * e.g.
138     * <pre>
139     * pusher.configureHttpClient(
140     *     Pusher.defaultHttpClientBuilder()
141     *           .setProxy(new HttpHost("proxy.example.com"))
142     *           .disableAutomaticRetries()
143     * );
144     * </pre>
145     *
146     * @see #defaultHttpClientBuilder()
147     *
148     * @param builder an {@link org.apache.http.impl.client.HttpClientBuilder} with which to configure
149     * the internal HTTP client
150     */
151    public void configureHttpClient(final HttpClientBuilder builder) {
152        try {
153            close();
154        } catch (final Exception e) {
155            // Not a lot useful we can do here
156        }
157
158        this.client = builder.build();
159    }
160
161    /*
162     * REST
163     */
164
165    @Override
166    protected Result doGet(final URI uri) {
167        return httpCall(new HttpGet(uri));
168    }
169
170    @Override
171    protected Result doPost(final URI uri, final String body) {
172        final StringEntity bodyEntity = new StringEntity(body, "UTF-8");
173        bodyEntity.setContentType("application/json");
174
175        final HttpPost request = new HttpPost(uri);
176        request.setEntity(bodyEntity);
177
178        return httpCall(request);
179    }
180
181    Result httpCall(final HttpRequestBase request) {
182        final RequestConfig config = RequestConfig.custom()
183                .setSocketTimeout(requestTimeout)
184                .setConnectionRequestTimeout(requestTimeout)
185                .setConnectTimeout(requestTimeout)
186                .build();
187        request.setConfig(config);
188
189        try {
190            final HttpResponse response = client.execute(request);
191
192            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
193            response.getEntity().writeTo(baos);
194            final String responseBody = new String(baos.toByteArray(), "UTF-8");
195
196            return Result.fromHttpCode(response.getStatusLine().getStatusCode(), responseBody);
197        }
198        catch (final IOException e) {
199            return Result.fromException(e);
200        }
201    }
202
203    @Override
204    public void close() throws Exception {
205        if (client != null) {
206            client.close();
207        }
208    }
209
210}