Frequently asked questions
- How to deal with cookies
- How to use with AWS Load Balancing
- How to force TLS v1.2 and above
- How to create a lot of clients
- How to properly close a client
- How to map the event arguments to POJO
How to deal with cookies
In order to store the cookies sent by the server and include them in all subsequent requests, you need to create an OkHttpClient with a custom cookie jar.
You can either implement your own cookie jar:
public class MyApp { public static void main(String[] argz) throws Exception { IO.Options options = new IO.Options(); OkHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(new MyCookieJar()) .build(); options.callFactory = okHttpClient; options.webSocketFactory = okHttpClient; Socket socket = IO.socket(URI.create("https://example.com"), options); socket.connect(); } private static class MyCookieJar implements CookieJar { private Set<WrappedCookie> cache = new HashSet<>(); @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { for (Cookie cookie : cookies) { this.cache.add(new WrappedCookie(cookie)); } } @Override public List<Cookie> loadForRequest(HttpUrl url) { List<Cookie> cookies = new ArrayList<>(); Iterator<WrappedCookie> iterator = this.cache.iterator(); while (iterator.hasNext()) { Cookie cookie = iterator.next().cookie; if (isCookieExpired(cookie)) { iterator.remove(); } else if (cookie.matches(url)) { cookies.add(cookie); } } return cookies; } private static boolean isCookieExpired(Cookie cookie) { return cookie.expiresAt() < System.currentTimeMillis(); } } private static class WrappedCookie { private final Cookie cookie; public WrappedCookie(Cookie cookie) { this.cookie = cookie; } @Override public boolean equals(Object o) { if (!(o instanceof WrappedCookie)) return false; WrappedCookie that = (WrappedCookie) o; return that.cookie.name().equals(this.cookie.name()) && that.cookie.domain().equals(this.cookie.domain()) && that.cookie.path().equals(this.cookie.path()) && that.cookie.secure() == this.cookie.secure() && that.cookie.hostOnly() == this.cookie.hostOnly(); } @Override public int hashCode() { int hash = 17; hash = 31 * hash + cookie.name().hashCode(); hash = 31 * hash + cookie.domain().hashCode(); hash = 31 * hash + cookie.path().hashCode(); hash = 31 * hash + (cookie.secure() ? 0 : 1); hash = 31 * hash + (cookie.hostOnly() ? 0 : 1); return hash; } } }
Or use a package like PersistentCookieJar:
public class MyApp { public static void main(String[] argz) throws Exception { IO.Options options = new IO.Options(); ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context)); OkHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(cookieJar) .build(); options.callFactory = okHttpClient; options.webSocketFactory = okHttpClient; Socket socket = IO.socket(URI.create("https://example.com"), options); socket.connect(); } }
How to use with AWS Load Balancing
When scaling to multiple Socket.IO servers, you must ensure that all the HTTP requests of a given session reach the same server (explanation here).
Sticky sessions can be enabled on AWS Application Load Balancers, which works by sending a cookie (AWSALB
) to the client.
Please see above for how to deal with cookies.
Reference: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html
How to force TLS v1.2 and above
This library relies on the OkHttp library to create HTTP requests and WebSocket connections.
Reference: https://square.github.io/okhttp/
We currently depend on version 3.12.12
, which is the last version that supports Java 7+ and Android 2.3+ (API level 9+). With this version, the OkHttpClient allows TLSv1
and TLSv1.1
by default (MODERN_TLS configuration).
You can overwrite it by providing your own OkHttp client:
OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectionSpecs(Arrays.asList( ConnectionSpec.RESTRICTED_TLS )) .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling .build(); IO.Options options = new IO.Options(); options.callFactory = okHttpClient; options.webSocketFactory = okHttpClient; Socket socket = IO.socket(URI.create("https://example.com"), options);
Note: we will upgrade to OkHttp 4 in the next major version.
How to create a lot of clients
By default, you won’t be able to create more than 5 Socket.IO clients (any additional client will be disconnected with “transport error” or “ping timeout” reason). That is due to the default OkHttp dispatcher, whose maxRequestsPerHost
is set to 5 by default.
You can overwrite it by providing your own OkHttp client:
int MAX_CLIENTS = 100; Dispatcher dispatcher = new Dispatcher(); dispatcher.setMaxRequests(MAX_CLIENTS * 2); dispatcher.setMaxRequestsPerHost(MAX_CLIENTS * 2); OkHttpClient okHttpClient = new OkHttpClient.Builder() .dispatcher(dispatcher) .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling .build(); IO.Options options = new IO.Options(); options.callFactory = okHttpClient; options.webSocketFactory = okHttpClient; for (int i = 0; i < MAX_CLIENTS; i++) { Socket socket = IO.socket(URI.create("https://example.com"), options); }
Note: we use MAX_CLIENTS * 2
because a client in HTTP long-polling mode will have one long-running GET request for receiving data from the server, and will create a POST request for sending data to the server.
How to properly close a client
Calling socket.disconnect()
may not be sufficient, because the underlying OkHttp client creates a ThreadPoolExecutor that will prevent your Java program from quitting for 60 seconds.
As a workaround, you can manually shut down this ThreadPoolExecutor:
Dispatcher dispatcher = new Dispatcher(); OkHttpClient okHttpClient = new OkHttpClient.Builder() .dispatcher(dispatcher) .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling .build(); IO.Options options = new IO.Options(); options.callFactory = okHttpClient; options.webSocketFactory = okHttpClient; Socket socket = IO.socket(URI.create("https://example.com"), options); socket.connect(); // then later socket.disconnect(); dispatcher.executorService().shutdown();
How to map the event arguments to POJO
This library uses the JSONTokener class from the org.json
package in order to parse the packets that are sent by the server, which means you will receive JSONObjects in your listeners.
Here’s how you can convert these JSONObjects to Plain Old Java Objects (POJO):
With Jackson
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project> <dependencies> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.13.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-json-org</artifactId> <version>2.13.3</version> </dependency> ... </dependencies> ... </project>
Maven repository:
- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-json-org
src/main/java/MyApp.java
public class MyApp { private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new JsonOrgModule()); public static void main(String[] argz) throws Exception { Socket socket = IO.socket(URI.create("https://example.com")); socket.on("my-event", (args) -> { MyObject object = MAPPER.convertValue(args[0], MyObject.class); // ... }); socket.connect(); } public static class MyObject { public int id; public String label; } }
With Gson
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project> <dependencies> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.9</version> </dependency> ... </dependencies> ... </project>
Maven repository:
src/main/java/MyApp.java
You can either call toString()
on the JSONObject
:
public class MyApp { private static final Gson GSON = new Gson(); public static void main(String[] argz) throws Exception { Socket socket = IO.socket(URI.create("https://example.com")); socket.on("my-event", (args) -> { MyObject object = GSON.fromJson(args[0].toString(), MyObject.class); // ... }); socket.connect(); } public static class MyObject { public int id; public String label; } }
Or manually convert the JSONObject
to a JsonObject
(for performance purposes):
public class MyApp { private static final Gson GSON = new Gson(); public static void main(String[] argz) throws Exception { Socket socket = IO.socket(URI.create("https://example.com")); socket.on("my-event", (args) -> { MyObject object = GSON.fromJson(map(args[0]), MyObject.class); // ... }); socket.connect(); } public static class MyObject { public int id; public String label; } public static JsonObject map(JSONObject source) throws JSONException { JsonObject output = new JsonObject(); Iterator<String> iterator = source.keys(); while (iterator.hasNext()) { String key = iterator.next(); Object value = source.get(key); if (value instanceof JSONObject) { output.add(key, map((JSONObject) value)); } else if (value instanceof JSONArray) { output.add(key, map((JSONArray) value)); } else if (value instanceof Number) { output.addProperty(key, (Number) value); } else if (value instanceof String) { output.addProperty(key, (String) value); } else if (value instanceof Boolean) { output.addProperty(key, (Boolean) value); } else if (value instanceof Character) { output.addProperty(key, (Character) value); } } return output; } public static JsonArray map(JSONArray source) throws JSONException { JsonArray output = new JsonArray(); for (int i = 0; i < source.length(); i++) { Object value = source.get(i); if (value instanceof JSONObject) { output.add(map((JSONObject) value)); } else if (value instanceof JSONArray) { output.add(map((JSONArray) value)); } else if (value instanceof Number) { output.add((Number) value); } else if (value instanceof String) { output.add((String) value); } else if (value instanceof Boolean) { output.add((Boolean) value); } else if (value instanceof Character) { output.add((Character) value); } } return output; } }