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    public PusherAsync(final String url) {
072        super(url);
073        configureHttpClient(config());
074    }
075
076    /*
077     * CONFIG
078     */
079
080    /**
081     * Configure the AsyncHttpClient instance which will be used for making calls to the Pusher API.
082     * <p>
083     * This method allows almost complete control over all aspects of the HTTP client, including
084     * <ul>
085     * <li>proxy host</li>
086     * <li>connection pooling and reuse strategies</li>
087     * <li>automatic retry and backoff strategies</li>
088     * </ul>
089     * <p>
090     * e.g.
091     * <pre>
092     * pusher.configureHttpClient(
093     *     config()
094     *         .setProxyServer(proxyServer("127.0.0.1", 38080))
095     *         .setMaxRequestRetry(5)
096     * );
097     * </pre>
098     *
099     * @param builder an {@link DefaultAsyncHttpClientConfig.Builder} with which to configure
100     *                the internal HTTP client
101     */
102    public void configureHttpClient(final DefaultAsyncHttpClientConfig.Builder builder) {
103        try {
104            close();
105        } catch (final Exception e) {
106            // Not a lot useful we can do here
107        }
108
109        this.client = asyncHttpClient(builder);
110    }
111
112    /*
113     * REST
114     */
115
116    @Override
117    protected CompletableFuture<Result> doGet(final URI uri) {
118        final Request request = new RequestBuilder(HttpConstants.Methods.GET)
119            .setUrl(uri.toString())
120            .build();
121
122        return httpCall(request);
123    }
124
125    @Override
126    protected CompletableFuture<Result> doPost(final URI uri, final String body) {
127        final Request request = new RequestBuilder(HttpConstants.Methods.POST)
128                .setUrl(uri.toString())
129                .setBody(body)
130                .addHeader("Content-Type", "application/json")
131                .build();
132
133        return httpCall(request);
134    }
135
136    CompletableFuture<Result> httpCall(final Request request) {
137        return client
138                .prepareRequest(request)
139                .execute()
140                .toCompletableFuture()
141                .thenApply(response -> Result.fromHttpCode(response.getStatusCode(), response.getResponseBody(UTF_8)))
142                .exceptionally(Result::fromThrowable);
143    }
144
145    @Override
146    public void close() throws Exception {
147        if (client != null && !client.isClosed()) {
148            client.close();
149        }
150    }
151
152}