GWT backend for libGDX enabling Java game development for web browsers through JavaScript compilation
—
The GWT backend provides HTTP-based networking through browser APIs. Due to web security restrictions, only HTTP/HTTPS requests are supported, with no TCP/UDP socket capabilities.
public class GwtNet implements Net {
// HTTP requests
public void sendHttpRequest(HttpRequest httpRequest, HttpResponseListener httpResponseListener);
public void cancelHttpRequest(HttpRequest httpRequest);
// Browser integration
public void openURI(String URI);
// Unsupported operations (throw UnsupportedOperationException)
public ServerSocket newServerSocket(Protocol protocol, String hostname, int port, ServerSocketHints hints);
public ServerSocket newServerSocket(Protocol protocol, int port, ServerSocketHints hints);
public Socket newClientSocket(Protocol protocol, String host, int port, SocketHints hints);
}// GET request example
HttpRequest httpGet = new HttpRequest(HttpMethods.GET);
httpGet.setUrl("https://api.example.com/data");
httpGet.setHeader("User-Agent", "MyGame/1.0");
Gdx.net.sendHttpRequest(httpGet, new HttpResponseListener() {
@Override
public void handleHttpResponse(HttpResponse httpResponse) {
int statusCode = httpResponse.getStatus().getStatusCode();
if (statusCode == 200) {
String responseData = httpResponse.getResultAsString();
// Handle successful response
processApiData(responseData);
} else {
System.err.println("HTTP Error: " + statusCode);
}
}
@Override
public void failed(Throwable t) {
System.err.println("Request failed: " + t.getMessage());
}
@Override
public void cancelled() {
System.out.println("Request was cancelled");
}
});
// POST request example
HttpRequest httpPost = new HttpRequest(HttpMethods.POST);
httpPost.setUrl("https://api.example.com/submit");
httpPost.setHeader("Content-Type", "application/json");
httpPost.setContent("{\"score\": 1000, \"player\": \"Alice\"}");
Gdx.net.sendHttpRequest(httpPost, new HttpResponseListener() {
@Override
public void handleHttpResponse(HttpResponse httpResponse) {
if (httpResponse.getStatus().getStatusCode() == 201) {
System.out.println("Score submitted successfully");
}
}
@Override
public void failed(Throwable t) {
System.err.println("Score submission failed: " + t.getMessage());
}
@Override
public void cancelled() {
System.out.println("Score submission cancelled");
}
});public class GameAPI {
private static final String BASE_URL = "https://api.mygame.com";
private static final String API_KEY = "your-api-key";
public void submitHighScore(int score, String playerName, ApiCallback<Boolean> callback) {
HttpRequest request = new HttpRequest(HttpMethods.POST);
request.setUrl(BASE_URL + "/scores");
request.setHeader("Authorization", "Bearer " + API_KEY);
request.setHeader("Content-Type", "application/json");
String jsonData = String.format(
"{\"player\": \"%s\", \"score\": %d, \"timestamp\": %d}",
playerName, score, System.currentTimeMillis()
);
request.setContent(jsonData);
Gdx.net.sendHttpRequest(request, new HttpResponseListener() {
@Override
public void handleHttpResponse(HttpResponse httpResponse) {
boolean success = httpResponse.getStatus().getStatusCode() == 200;
callback.onResult(success);
}
@Override
public void failed(Throwable t) {
callback.onError(t.getMessage());
}
@Override
public void cancelled() {
callback.onError("Request cancelled");
}
});
}
public void getLeaderboard(ApiCallback<LeaderboardData> callback) {
HttpRequest request = new HttpRequest(HttpMethods.GET);
request.setUrl(BASE_URL + "/leaderboard?limit=10");
request.setHeader("Authorization", "Bearer " + API_KEY);
Gdx.net.sendHttpRequest(request, new HttpResponseListener() {
@Override
public void handleHttpResponse(HttpResponse httpResponse) {
if (httpResponse.getStatus().getStatusCode() == 200) {
String jsonResponse = httpResponse.getResultAsString();
LeaderboardData data = parseLeaderboard(jsonResponse);
callback.onResult(data);
} else {
callback.onError("HTTP " + httpResponse.getStatus().getStatusCode());
}
}
@Override
public void failed(Throwable t) {
callback.onError(t.getMessage());
}
@Override
public void cancelled() {
callback.onError("Request cancelled");
}
});
}
private LeaderboardData parseLeaderboard(String json) {
// Parse JSON response into LeaderboardData object
// Can use libGDX Json class or manual parsing
return new LeaderboardData();
}
public interface ApiCallback<T> {
void onResult(T result);
void onError(String error);
}
public static class LeaderboardData {
public String[] playerNames;
public int[] scores;
public long[] timestamps;
}
}public class AssetDownloader {
private Map<String, HttpRequest> activeRequests = new HashMap<>();
public void downloadAsset(String url, String localPath, DownloadCallback callback) {
HttpRequest request = new HttpRequest(HttpMethods.GET);
request.setUrl(url);
// Store request for potential cancellation
activeRequests.put(localPath, request);
Gdx.net.sendHttpRequest(request, new HttpResponseListener() {
@Override
public void handleHttpResponse(HttpResponse httpResponse) {
activeRequests.remove(localPath);
if (httpResponse.getStatus().getStatusCode() == 200) {
try {
// Save downloaded data to local storage
byte[] data = httpResponse.getResult();
FileHandle localFile = Gdx.files.local(localPath);
localFile.writeBytes(data, false);
callback.onDownloadComplete(localPath);
} catch (Exception e) {
callback.onDownloadFailed(localPath, e.getMessage());
}
} else {
callback.onDownloadFailed(localPath, "HTTP " + httpResponse.getStatus().getStatusCode());
}
}
@Override
public void failed(Throwable t) {
activeRequests.remove(localPath);
callback.onDownloadFailed(localPath, t.getMessage());
}
@Override
public void cancelled() {
activeRequests.remove(localPath);
callback.onDownloadCancelled(localPath);
}
});
}
public void cancelDownload(String localPath) {
HttpRequest request = activeRequests.get(localPath);
if (request != null) {
Gdx.net.cancelHttpRequest(request);
}
}
public void cancelAllDownloads() {
for (HttpRequest request : activeRequests.values()) {
Gdx.net.cancelHttpRequest(request);
}
activeRequests.clear();
}
public interface DownloadCallback {
void onDownloadComplete(String localPath);
void onDownloadFailed(String localPath, String error);
void onDownloadCancelled(String localPath);
}
}public class ConfigManager {
private static final String CONFIG_URL = "https://config.mygame.com/settings.json";
private JsonValue serverConfig;
public void loadServerConfig(Runnable onComplete) {
HttpRequest request = new HttpRequest(HttpMethods.GET);
request.setUrl(CONFIG_URL);
request.setHeader("Accept", "application/json");
Gdx.net.sendHttpRequest(request, new HttpResponseListener() {
@Override
public void handleHttpResponse(HttpResponse httpResponse) {
if (httpResponse.getStatus().getStatusCode() == 200) {
try {
String jsonString = httpResponse.getResultAsString();
Json json = new Json();
serverConfig = json.fromJson(JsonValue.class, jsonString);
System.out.println("Server config loaded successfully");
if (onComplete != null) onComplete.run();
} catch (Exception e) {
System.err.println("Failed to parse server config: " + e.getMessage());
useDefaultConfig();
if (onComplete != null) onComplete.run();
}
} else {
System.err.println("Failed to load server config: HTTP " + httpResponse.getStatus().getStatusCode());
useDefaultConfig();
if (onComplete != null) onComplete.run();
}
}
@Override
public void failed(Throwable t) {
System.err.println("Server config request failed: " + t.getMessage());
useDefaultConfig();
if (onComplete != null) onComplete.run();
}
@Override
public void cancelled() {
System.out.println("Server config request cancelled");
useDefaultConfig();
if (onComplete != null) onComplete.run();
}
});
}
public String getConfigString(String key, String defaultValue) {
if (serverConfig != null && serverConfig.has(key)) {
return serverConfig.getString(key, defaultValue);
}
return defaultValue;
}
public int getConfigInt(String key, int defaultValue) {
if (serverConfig != null && serverConfig.has(key)) {
return serverConfig.getInt(key, defaultValue);
}
return defaultValue;
}
public boolean getConfigBoolean(String key, boolean defaultValue) {
if (serverConfig != null && serverConfig.has(key)) {
return serverConfig.getBoolean(key, defaultValue);
}
return defaultValue;
}
private void useDefaultConfig() {
// Create default configuration
Json json = new Json();
String defaultJson = "{\"maxPlayers\": 4, \"gameMode\": \"classic\", \"enableFeatureX\": true}";
serverConfig = json.fromJson(JsonValue.class, defaultJson);
}
}public class BrowserIntegration {
// Open URLs in browser
public static void openWebsite(String url) {
try {
Gdx.net.openURI(url);
System.out.println("Opened URL: " + url);
} catch (Exception e) {
System.err.println("Failed to open URL: " + e.getMessage());
}
}
// Open social media sharing
public static void shareScore(int score) {
String message = "I just scored " + score + " points in MyGame!";
String twitterUrl = "https://twitter.com/intent/tweet?text=" +
java.net.URLEncoder.encode(message, "UTF-8");
openWebsite(twitterUrl);
}
// Open game store page
public static void openStorePage() {
openWebsite("https://store.example.com/mygame");
}
// Open support/feedback page
public static void openSupport() {
openWebsite("https://support.mygame.com");
}
}public class Analytics {
private static final String ANALYTICS_URL = "https://analytics.mygame.com/events";
private static final String SESSION_ID = generateSessionId();
public static void trackEvent(String eventName, Map<String, Object> properties) {
HttpRequest request = new HttpRequest(HttpMethods.POST);
request.setUrl(ANALYTICS_URL);
request.setHeader("Content-Type", "application/json");
// Build event data
Json json = new Json();
Map<String, Object> eventData = new HashMap<>();
eventData.put("event", eventName);
eventData.put("sessionId", SESSION_ID);
eventData.put("timestamp", System.currentTimeMillis());
eventData.put("properties", properties);
String jsonContent = json.toJson(eventData);
request.setContent(jsonContent);
Gdx.net.sendHttpRequest(request, new HttpResponseListener() {
@Override
public void handleHttpResponse(HttpResponse httpResponse) {
// Analytics requests typically don't need response handling
if (httpResponse.getStatus().getStatusCode() != 200) {
System.err.println("Analytics event failed: " + httpResponse.getStatus().getStatusCode());
}
}
@Override
public void failed(Throwable t) {
System.err.println("Analytics event failed: " + t.getMessage());
}
@Override
public void cancelled() {
// Ignore cancellations for analytics
}
});
}
public static void trackLevelStart(int level) {
Map<String, Object> props = new HashMap<>();
props.put("level", level);
trackEvent("level_start", props);
}
public static void trackLevelComplete(int level, int score, float time) {
Map<String, Object> props = new HashMap<>();
props.put("level", level);
props.put("score", score);
props.put("completionTime", time);
trackEvent("level_complete", props);
}
public static void trackPurchase(String itemId, float price) {
Map<String, Object> props = new HashMap<>();
props.put("itemId", itemId);
props.put("price", price);
trackEvent("purchase", props);
}
private static String generateSessionId() {
return "session_" + System.currentTimeMillis() + "_" + (int)(Math.random() * 10000);
}
}// Browser CORS policy affects HTTP requests
public class CORSHelper {
// Only requests to same origin are always allowed
// Cross-origin requests require server CORS headers
public static void makeCORSRequest(String url) {
// Server must include appropriate CORS headers:
// Access-Control-Allow-Origin: *
// Access-Control-Allow-Methods: GET, POST, PUT, DELETE
// Access-Control-Allow-Headers: Content-Type, Authorization
HttpRequest request = new HttpRequest(HttpMethods.GET);
request.setUrl(url);
Gdx.net.sendHttpRequest(request, new HttpResponseListener() {
@Override
public void handleHttpResponse(HttpResponse httpResponse) {
// Handle response
}
@Override
public void failed(Throwable t) {
// CORS failures often appear as network errors
System.err.println("Request failed (possibly CORS): " + t.getMessage());
}
@Override
public void cancelled() {
// Handle cancellation
}
});
}
}// Web security model limitations:
// ✓ Supported: HTTP/HTTPS requests
HttpRequest httpRequest = new HttpRequest(HttpMethods.GET);
httpRequest.setUrl("https://api.example.com/data");
// ✗ NOT supported: TCP/UDP sockets
try {
Socket socket = Gdx.net.newClientSocket(Net.Protocol.TCP, "example.com", 8080, null);
// Throws UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("TCP sockets not supported on web");
}
// ✗ NOT supported: Server sockets
try {
ServerSocket serverSocket = Gdx.net.newServerSocket(Net.Protocol.TCP, 8080, null);
// Throws UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("Server sockets not supported on web");
}
// ✓ Supported: Opening URLs in browser
Gdx.net.openURI("https://example.com");// Browser request limitations and best practices:
public class RequestManager {
private static final int MAX_CONCURRENT_REQUESTS = 6; // Browser limit
private int activeRequests = 0;
private Queue<Runnable> requestQueue = new LinkedList<>();
public void makeRequest(HttpRequest request, HttpResponseListener listener) {
if (activeRequests < MAX_CONCURRENT_REQUESTS) {
executeRequest(request, listener);
} else {
// Queue request if at browser limit
requestQueue.offer(() -> executeRequest(request, listener));
}
}
private void executeRequest(HttpRequest request, HttpResponseListener listener) {
activeRequests++;
Gdx.net.sendHttpRequest(request, new HttpResponseListener() {
@Override
public void handleHttpResponse(HttpResponse httpResponse) {
activeRequests--;
processQueue();
listener.handleHttpResponse(httpResponse);
}
@Override
public void failed(Throwable t) {
activeRequests--;
processQueue();
listener.failed(t);
}
@Override
public void cancelled() {
activeRequests--;
processQueue();
listener.cancelled();
}
});
}
private void processQueue() {
if (!requestQueue.isEmpty() && activeRequests < MAX_CONCURRENT_REQUESTS) {
Runnable nextRequest = requestQueue.poll();
nextRequest.run();
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-badlogicgames-gdx--gdx-backend-gwt