001package com.pusher.client; 002 003import java.io.IOException; 004import java.io.InputStream; 005import java.net.Proxy; 006import java.util.Properties; 007 008/** 009 * Configuration for a {@link com.pusher.client.Pusher} instance. 010 */ 011public class PusherOptions { 012 013 private static final String SRC_LIB_DEV_VERSION = "@version@"; 014 private static final String LIB_DEV_VERSION = "0.0.0-dev"; 015 public static final String LIB_VERSION = readVersionFromProperties(); 016 017 private static final String URI_SUFFIX = "?client=java-client&protocol=5&version=" + LIB_VERSION; 018 private static final String WS_SCHEME = "ws"; 019 private static final String WSS_SCHEME = "wss"; 020 021 private static final int WS_PORT = 80; 022 private static final int WSS_PORT = 443; 023 private static final String PUSHER_DOMAIN = "pusher.com"; 024 025 private static final long DEFAULT_ACTIVITY_TIMEOUT = 120000; 026 private static final long DEFAULT_PONG_TIMEOUT = 30000; 027 028 private static final int MAX_RECONNECTION_ATTEMPTS = 6; //Taken from the Swift lib 029 private static final int MAX_RECONNECT_GAP_IN_SECONDS = 30; 030 031 // Note that the primary cluster lives on a different domain 032 // (others are subdomains of pusher.com). This is not an oversight. 033 // Legacy reasons. 034 private String host = "ws.pusherapp.com"; 035 private int wsPort = WS_PORT; 036 private int wssPort = WSS_PORT; 037 private boolean encrypted = true; 038 private long activityTimeout = DEFAULT_ACTIVITY_TIMEOUT; 039 private long pongTimeout = DEFAULT_PONG_TIMEOUT; 040 private Authorizer authorizer; 041 private Proxy proxy = Proxy.NO_PROXY; 042 private int maxReconnectionAttempts = MAX_RECONNECTION_ATTEMPTS; 043 private int maxReconnectGapInSeconds = MAX_RECONNECT_GAP_IN_SECONDS; 044 045 /** 046 * Gets whether an encrypted (SSL) connection should be used when connecting 047 * to Pusher. 048 * 049 * @return true if an encrypted connection should be used; otherwise false. 050 */ 051 public boolean isEncrypted() { 052 return encrypted; 053 } 054 055 /** 056 * Sets whether an encrypted (SSL) connection should be used when connecting to 057 * Pusher. 058 * 059 * @param encrypted Whether to use an SSL connection 060 * @return this, for chaining 061 */ 062 public PusherOptions setEncrypted(final boolean encrypted) { 063 this.encrypted = encrypted; 064 return this; 065 } 066 067 /** 068 * Gets the authorizer to be used when authenticating private and presence 069 * channels. 070 * 071 * @return the authorizer 072 */ 073 public Authorizer getAuthorizer() { 074 return authorizer; 075 } 076 077 /** 078 * Sets the authorizer to be used when authenticating private and presence 079 * channels. 080 * 081 * @param authorizer 082 * The authorizer to be used. 083 * @return this, for chaining 084 */ 085 public PusherOptions setAuthorizer(final Authorizer authorizer) { 086 this.authorizer = authorizer; 087 return this; 088 } 089 090 /** 091 * The host to which connections will be made. 092 * 093 * Note that if you wish to connect to a standard Pusher cluster, the 094 * convenience method setCluster will set the host and ports correctly from 095 * a single argument. 096 * 097 * @param host The host 098 * @return this, for chaining 099 */ 100 public PusherOptions setHost(final String host) { 101 this.host = host; 102 return this; 103 } 104 105 /** 106 * The port to which unencrypted connections will be made. 107 * 108 * Note that if you wish to connect to a standard Pusher cluster, the 109 * convenience method setCluster will set the host and ports correctly from 110 * a single argument. 111 * 112 * @param wsPort port number 113 * @return this, for chaining 114 */ 115 public PusherOptions setWsPort(final int wsPort) { 116 this.wsPort = wsPort; 117 return this; 118 } 119 120 /** 121 * The port to which encrypted connections will be made. 122 * 123 * Note that if you wish to connect to a standard Pusher cluster, the 124 * convenience method setCluster will set the host and ports correctly from 125 * a single argument. 126 * 127 * @param wssPort port number 128 * @return this, for chaining 129 */ 130 public PusherOptions setWssPort(final int wssPort) { 131 this.wssPort = wssPort; 132 return this; 133 } 134 135 public PusherOptions setCluster(final String cluster) { 136 host = "ws-" + cluster + "." + PUSHER_DOMAIN; 137 wsPort = WS_PORT; 138 wssPort = WSS_PORT; 139 return this; 140 } 141 142 /** 143 * The number of milliseconds of inactivity at which a "ping" will be 144 * triggered to check the connection. 145 * 146 * The default value is 120,000 (2 minutes). On some connections, where 147 * intermediate hops between the application and Pusher are aggressively 148 * culling connections they consider to be idle, a lower value may help 149 * preserve the connection. 150 * 151 * @param activityTimeout 152 * time to consider connection idle, in milliseconds 153 * @return this, for chaining 154 */ 155 public PusherOptions setActivityTimeout(final long activityTimeout) { 156 if (activityTimeout < 1000) { 157 throw new IllegalArgumentException( 158 "Activity timeout must be at least 1,000ms (and is recommended to be much higher)"); 159 } 160 161 this.activityTimeout = activityTimeout; 162 return this; 163 } 164 165 public long getActivityTimeout() { 166 return activityTimeout; 167 } 168 169 /** 170 * The number of milliseconds after a "ping" is sent that the client will 171 * wait to receive a "pong" response from the server before considering the 172 * connection broken and triggering a transition to the disconnected state. 173 * 174 * The default value is 30,000. 175 * 176 * @param pongTimeout 177 * time to wait for pong response, in milliseconds 178 * @return this, for chaining 179 */ 180 public PusherOptions setPongTimeout(final long pongTimeout) { 181 if (pongTimeout < 1000) { 182 throw new IllegalArgumentException( 183 "Pong timeout must be at least 1,000ms (and is recommended to be much higher)"); 184 } 185 186 this.pongTimeout = pongTimeout; 187 return this; 188 } 189 190 /** 191 * Number of reconnect attempts when websocket connection failed 192 * @param maxReconnectionAttempts 193 * number of max reconnection attempts, default = {@link #MAX_RECONNECTION_ATTEMPTS} 6 194 * @return this, for chaining 195 */ 196 public PusherOptions setMaxReconnectionAttempts(int maxReconnectionAttempts) { 197 this.maxReconnectionAttempts = maxReconnectionAttempts; 198 return this; 199 } 200 201 /** 202 * The delay in two reconnection extends exponentially (1, 2, 4, .. seconds) This property sets the maximum in between two 203 * reconnection attempts. 204 * @param maxReconnectGapInSeconds 205 * time in seconds of the maximum gab between two reconnection attempts, default = {@link #MAX_RECONNECT_GAP_IN_SECONDS} 30s 206 * @return this, for chaining 207 */ 208 public PusherOptions setMaxReconnectGapInSeconds(int maxReconnectGapInSeconds) { 209 this.maxReconnectGapInSeconds = maxReconnectGapInSeconds; 210 return this; 211 } 212 213 public long getPongTimeout() { 214 return pongTimeout; 215 } 216 217 /** 218 * Construct the URL for the WebSocket connection based on the options 219 * previous set on this object and the provided API key 220 * 221 * @param apiKey The API key 222 * @return the WebSocket URL 223 */ 224 public String buildUrl(final String apiKey) { 225 return String.format("%s://%s:%s/app/%s%s", encrypted ? WSS_SCHEME : WS_SCHEME, host, encrypted ? wssPort 226 : wsPort, apiKey, URI_SUFFIX); 227 } 228 229 /** 230 * 231 * The default value is Proxy.NO_PROXY. 232 * 233 * @param proxy 234 * Specify a proxy, e.g. <code>options.setProxy( new Proxy( Proxy.Type.HTTP, new InetSocketAddress( "proxyaddress", 80 ) ) )</code>; 235 * @return this, for chaining 236 */ 237 public PusherOptions setProxy(Proxy proxy){ 238 if (proxy == null) { 239 throw new IllegalArgumentException("proxy must not be null (instead use Proxy.NO_PROXY)"); 240 } 241 this.proxy = proxy; 242 return this; 243 } 244 245 /** 246 * @return The proxy to be used when opening a websocket connection to Pusher. 247 */ 248 public Proxy getProxy() { 249 return this.proxy; 250 } 251 252 /** 253 * @return the maximum reconnection attempts 254 */ 255 public int getMaxReconnectionAttempts() { 256 return maxReconnectionAttempts; 257 } 258 259 /** 260 * @return the maximum reconnection gap in seconds 261 */ 262 public int getMaxReconnectGapInSeconds() { 263 return maxReconnectGapInSeconds; 264 } 265 266 private static String readVersionFromProperties() { 267 InputStream inStream = null; 268 try { 269 final Properties p = new Properties(); 270 inStream = PusherOptions.class.getResourceAsStream("/pusher.properties"); 271 p.load(inStream); 272 String version = (String)p.get("version"); 273 274 // If the properties file contents indicates the version is being run 275 // from source then replace with a dev indicator. Otherwise the Pusher 276 // Socket API will reject the connection. 277 if(version.equals(SRC_LIB_DEV_VERSION)) { 278 version = LIB_DEV_VERSION; 279 } 280 281 if (version != null && version.length() > 0) { 282 return version; 283 } 284 } 285 catch (final Exception e) { 286 // Fall back to fixed value 287 } 288 finally { 289 try { 290 if (inStream != null) { 291 inStream.close(); 292 } 293 } 294 catch (final IOException e) { 295 // Ignore problem closing stream 296 } 297 } 298 return "0.0.0"; 299 } 300}