001package com.pusher.rest;
002
003import com.pusher.rest.data.Result;
004import org.asynchttpclient.AsyncHttpClient;
005import org.asynchttpclient.DefaultAsyncHttpClientConfig;
006import org.asynchttpclient.Request;
007import org.asynchttpclient.RequestBuilder;
008import org.asynchttpclient.util.HttpConstants;
009
010import java.io.IOException;
011import java.net.URI;
012import java.util.concurrent.CompletableFuture;
013
014import static java.nio.charset.StandardCharsets.UTF_8;
015import static org.asynchttpclient.Dsl.asyncHttpClient;
016import static org.asynchttpclient.Dsl.config;
017
018/**
019 * A library for interacting with the Pusher HTTP API asynchronously.
020 * <p>
021 * See http://github.com/pusher/pusher-http-java for an overview
022 * <p>
023 * Essentially:
024 * <pre>
025 * // Init
026 * PusherAsync pusher = new PusherAsync(APP_ID, KEY, SECRET);
027 *
028 * // Publish
029 * CompletableFuture&lt;Result&gt; futureTriggerResult = pusher.trigger("my-channel", "my-eventname", myPojoForSerialisation);
030 * triggerResult.thenAccept(triggerResult -&gt; {
031 *   if (triggerResult.getStatus() == Status.SUCCESS) {
032 *     // request was successful
033 *   } else {
034 *     // something went wrong with the request
035 *   }
036 * });
037 *
038 * // Query
039 * CompletableFuture&lt;Result&gt; futureChannelListResult = pusher.get("/channels");
040 * futureChannelListResult.thenAccept(triggerResult -&gt; {
041 *   if (triggerResult.getStatus() == Status.SUCCESS) {
042 *     String channelListAsJson = channelListResult.getMessage();
043 *     // etc.
044 *   } else {
045 *     // something went wrong with the request
046 *   }
047 * });
048 * </pre>
049 *
050 * See {@link Pusher} for the synchronous implementation.
051 */
052public class PusherAsync extends PusherAbstract<CompletableFuture<Result>> implements AutoCloseable {
053
054    private AsyncHttpClient 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     *
062     * @param appId  The ID of the App you will to interact with.
063     * @param key    The App Key, the same key you give to websocket clients to identify your app when they connect to Pusher.
064     * @param secret The App Secret. Used to sign requests to the API, this should be treated as sensitive and not distributed.
065     */
066    public PusherAsync(final String appId, final String key, final String secret) {
067        super(appId, key, secret);
068        configureHttpClient(config());
069    }
070
071    /**
072     * Construct an instance of the Pusher object through which you may interact with the Pusher API.
073     * <p>
074     * The parameters to use are found on your dashboard at https://app.pusher.com and are specific per App.
075     * <p>
076     *
077     * @param appId  The ID of the App you will to interact with.
078     * @param key    The App Key, the same key you give to websocket clients to identify your app when they connect to Pusher.
079     * @param secret The App Secret. Used to sign requests to the API, this should be treated as sensitive and not distributed.
080     * @param encryptionMasterKeyBase64 32 byte key, base64 encoded. This key, along with the channel name, are used to derive per-channel encryption keys.
081     */
082    public PusherAsync(final String appId, final String key, final String secret, final String encryptionMasterKeyBase64) {
083        super(appId, key, secret, encryptionMasterKeyBase64);
084        configureHttpClient(config());
085    }
086
087    public PusherAsync(final String url) {
088        super(url);
089        configureHttpClient(config());
090    }
091
092    /*
093     * CONFIG
094     */
095
096    /**
097     * Configure the AsyncHttpClient instance which will be used for making calls to the Pusher API.
098     * <p>
099     * This method allows almost complete control over all aspects of the HTTP client, including
100     * <ul>
101     * <li>proxy host</li>
102     * <li>connection pooling and reuse strategies</li>
103     * <li>automatic retry and backoff strategies</li>
104     * </ul>
105     * <p>
106     * e.g.
107     * <pre>
108     * pusher.configureHttpClient(
109     *     config()
110     *         .setProxyServer(proxyServer("127.0.0.1", 38080))
111     *         .setMaxRequestRetry(5)
112     * );
113     * </pre>
114     *
115     * @param builder an {@link DefaultAsyncHttpClientConfig.Builder} with which to configure
116     *                the internal HTTP client
117     */
118    public void configureHttpClient(final DefaultAsyncHttpClientConfig.Builder builder) {
119        try {
120            close();
121        } catch (final Exception e) {
122            // Not a lot useful we can do here
123        }
124
125        this.client = asyncHttpClient(builder);
126    }
127
128    /*
129     * REST
130     */
131
132    @Override
133    protected CompletableFuture<Result> doGet(final URI uri) {
134        final Request request = new RequestBuilder(HttpConstants.Methods.GET)
135            .setUrl(uri.toString())
136            .build();
137
138        return httpCall(request);
139    }
140
141    @Override
142    protected CompletableFuture<Result> doPost(final URI uri, final String body) {
143        final Request request = new RequestBuilder(HttpConstants.Methods.POST)
144                .setUrl(uri.toString())
145                .setBody(body)
146                .addHeader("Content-Type", "application/json")
147                .build();
148
149        return httpCall(request);
150    }
151
152    CompletableFuture<Result> httpCall(final Request request) {
153        return client
154                .prepareRequest(request)
155                .execute()
156                .toCompletableFuture()
157                .thenApply(response -> Result.fromHttpCode(response.getStatusCode(), response.getResponseBody(UTF_8)))
158                .exceptionally(Result::fromThrowable);
159    }
160
161    @Override
162    public void close() throws Exception {
163        if (client != null && !client.isClosed()) {
164            client.close();
165        }
166    }
167
168}