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}