
io.jsync.sockjs.impl.DefaultSockJSServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jsync.io Show documentation
Show all versions of jsync.io Show documentation
jsync.io is a non-blocking, event-driven networking framework for Java
/*
* Copyright (c) 2011-2013 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.jsync.sockjs.impl;
import io.jsync.Async;
import io.jsync.AsyncFactory;
import io.jsync.Handler;
import io.jsync.VoidHandler;
import io.jsync.buffer.Buffer;
import io.jsync.http.*;
import io.jsync.http.impl.WebSocketMatcher;
import io.jsync.impl.AsyncInternal;
import io.jsync.json.JsonArray;
import io.jsync.json.JsonObject;
import io.jsync.logging.Logger;
import io.jsync.logging.impl.LoggerFactory;
import io.jsync.sockjs.EventBusBridge;
import io.jsync.sockjs.EventBusBridgeHook;
import io.jsync.sockjs.SockJSServer;
import io.jsync.sockjs.SockJSSocket;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @author Tim Fox
*/
public class DefaultSockJSServer implements SockJSServer, Handler {
private static final Logger log = LoggerFactory.getLogger(DefaultSockJSServer.class);
private static final String IFRAME_TEMPLATE =
"\n" +
"\n" +
"\n" +
" \n" +
" \n" +
" \n" +
" \n" +
"\n" +
"\n" +
" Don't panic!
\n" +
" This is a SockJS hidden iframe. It's used for cross domain magic.
\n" +
"\n" +
"";
private final AsyncInternal async;
private final Map sessions;
private RouteMatcher rm = new RouteMatcher();
private WebSocketMatcher wsMatcher = new WebSocketMatcher();
private EventBusBridgeHook hook;
private long timerID;
public DefaultSockJSServer(final AsyncInternal async, final HttpServer httpServer) {
this.async = async;
this.sessions = async.sharedData().getMap("_async.sockjssessions");
// Any previous request and websocket handlers will become default handlers
// if nothing else matches
rm.noMatch(httpServer.requestHandler());
wsMatcher.noMatch(new Handler() {
Handler wsHandler = httpServer.websocketHandler();
public void handle(WebSocketMatcher.Match match) {
if (wsHandler != null) {
wsHandler.handle(match.ws);
}
}
});
httpServer.requestHandler(this);
httpServer.websocketHandler(wsMatcher);
// Sanity check - a common mistake users make is to set the http request handler AFTER they have created this
// which overwrites this one.
timerID = async.setPeriodic(5000, new Handler() {
@Override
public void handle(Long timerID) {
if (httpServer.requestHandler() == null) {
// Implies server is closed - cancel timer id
async.cancelTimer(timerID);
} else if (httpServer.requestHandler() != DefaultSockJSServer.this) {
log.warn("You have overwritten the Http server request handler AFTER the SockJSServer has been created " +
"which will stop the SockJSServer from functioning. Make sure you set http request handler BEFORE " +
"you create the SockJSServer");
}
}
});
}
private static JsonObject setDefaults(JsonObject config) {
config = config.copy();
//Set the defaults
if (config.getNumber("session_timeout") == null) {
config.putNumber("session_timeout", 5l * 1000); // 5 seconds default
}
if (config.getBoolean("insert_JSESSIONID") == null) {
config.putBoolean("insert_JSESSIONID", true);
}
if (config.getNumber("heartbeat_period") == null) {
config.putNumber("heartbeat_period", 25l * 1000);
}
if (config.getNumber("max_bytes_streaming") == null) {
config.putNumber("max_bytes_streaming", 128 * 1024);
}
if (config.getString("prefix") == null) {
config.putString("prefix", "/");
}
if (config.getString("library_url") == null) {
config.putString("library_url", "http://cdn.sockjs.org/sockjs-0.3.4.min.js");
}
if (config.getArray("disabled_transports") == null) {
config.putArray("disabled_transports", new JsonArray());
}
return config;
}
private static String getMD5String(final String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(str.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(Integer.toHexString(b + 127));
}
return sb.toString();
} catch (Exception e) {
log.error("Failed to generate MD5 for iframe, If-None-Match headers will be ignored");
return null;
}
}
// For debug only
public static void main(String[] args) throws Exception {
Async async = AsyncFactory.newAsync();
HttpServer httpServer = async.createHttpServer();
DefaultSockJSServer sjsServer = (DefaultSockJSServer) async.createSockJSServer(httpServer);
sjsServer.installTestApplications();
httpServer.listen(8081);
Thread.sleep(Long.MAX_VALUE);
}
@Override
public void handle(HttpServerRequest req) {
if (log.isTraceEnabled()) {
log.trace("Got request in sockjs server: " + req.uri());
}
rm.handle(req);
}
@Override
public void close() {
async.cancelTimer(timerID);
}
public SockJSServer setHook(EventBusBridgeHook hook) {
this.hook = hook;
return this;
}
public SockJSServer installApp(JsonObject config,
final Handler sockHandler) {
config = setDefaults(config);
String prefix = config.getString("prefix");
if (prefix == null || prefix.equals("") || prefix.endsWith("/")) {
throw new IllegalArgumentException("Invalid prefix: " + prefix);
}
// Base handler for app
rm.getWithRegEx(prefix + "\\/?", new Handler() {
public void handle(HttpServerRequest req) {
if (log.isTraceEnabled()) log.trace("Returning welcome response");
req.response().headers().set("Content-Type", "text/plain; charset=UTF-8");
req.response().end("Welcome to SockJS!\n");
}
});
// Iframe handlers
String iframeHTML = IFRAME_TEMPLATE.replace("{{ sockjs_url }}", config.getString("library_url"));
Handler iframeHandler = createIFrameHandler(iframeHTML);
// Request exactly for iframe.html
rm.getWithRegEx(prefix + "\\/iframe\\.html", iframeHandler);
// Versioned
rm.getWithRegEx(prefix + "\\/iframe-[^\\/]*\\.html", iframeHandler);
// Chunking test
rm.postWithRegEx(prefix + "\\/chunking_test", createChunkingTestHandler());
rm.optionsWithRegEx(prefix + "\\/chunking_test", BaseTransport.createCORSOptionsHandler(config, "OPTIONS, POST"));
// Info
rm.getWithRegEx(prefix + "\\/info", BaseTransport.createInfoHandler(config));
rm.optionsWithRegEx(prefix + "\\/info", BaseTransport.createCORSOptionsHandler(config, "OPTIONS, GET"));
// Transports
Set enabledTransports = new HashSet<>();
enabledTransports.add(Transport.EVENT_SOURCE.toString());
enabledTransports.add(Transport.HTML_FILE.toString());
enabledTransports.add(Transport.JSON_P.toString());
enabledTransports.add(Transport.WEBSOCKET.toString());
enabledTransports.add(Transport.XHR.toString());
for (Object tr : config.getArray("disabled_transports", new JsonArray())) {
enabledTransports.remove(tr);
}
if (enabledTransports.contains(Transport.XHR.toString())) {
new XhrTransport(async, rm, prefix, sessions, config, sockHandler);
}
if (enabledTransports.contains(Transport.EVENT_SOURCE.toString())) {
new EventSourceTransport(async, rm, prefix, sessions, config, sockHandler);
}
if (enabledTransports.contains(Transport.HTML_FILE.toString())) {
new HtmlFileTransport(async, rm, prefix, sessions, config, sockHandler);
}
if (enabledTransports.contains(Transport.JSON_P.toString())) {
new JsonPTransport(async, rm, prefix, sessions, config, sockHandler);
}
if (enabledTransports.contains(Transport.WEBSOCKET.toString())) {
new WebSocketTransport(async, wsMatcher, rm, prefix, sessions, config, sockHandler);
new RawWebSocketTransport(async, wsMatcher, rm, prefix, sockHandler);
}
// Catch all for any other requests on this app
rm.getWithRegEx(prefix + "\\/.+", new Handler() {
public void handle(HttpServerRequest req) {
if (log.isTraceEnabled()) log.trace("Request: " + req.uri() + " does not match, returning 404");
req.response().setStatusCode(404);
req.response().end();
}
});
return this;
}
public SockJSServer bridge(JsonObject sjsConfig, JsonArray inboundPermitted, JsonArray outboundPermitted) {
EventBusBridge busBridge = new EventBusBridge(async, inboundPermitted, outboundPermitted);
if (hook != null) {
busBridge.setHook(hook);
}
installApp(sjsConfig, busBridge);
return this;
}
public SockJSServer bridge(JsonObject sjsConfig, JsonArray inboundPermitted, JsonArray outboundPermitted,
long authTimeout) {
EventBusBridge busBridge = new EventBusBridge(async, inboundPermitted, outboundPermitted, authTimeout);
if (hook != null) {
busBridge.setHook(hook);
}
installApp(sjsConfig, busBridge);
return this;
}
public SockJSServer bridge(JsonObject sjsConfig, JsonArray inboundPermitted, JsonArray outboundPermitted,
long authTimeout, String authAddress) {
EventBusBridge busBridge = new EventBusBridge(async, inboundPermitted, outboundPermitted, authTimeout, authAddress);
if (hook != null) {
busBridge.setHook(hook);
}
installApp(sjsConfig, busBridge);
return this;
}
public SockJSServer bridge(JsonObject sjsConfig, JsonArray inboundPermitted, JsonArray outboundPermitted,
JsonObject bridgeConfig) {
EventBusBridge busBridge = new EventBusBridge(async, inboundPermitted, outboundPermitted, bridgeConfig);
if (hook != null) {
busBridge.setHook(hook);
}
installApp(sjsConfig, busBridge);
return this;
}
private Handler createChunkingTestHandler() {
return new Handler() {
private void setTimeout(List timeouts, long delay, final Buffer buff) {
timeouts.add(new TimeoutInfo(delay, buff));
}
private void runTimeouts(List timeouts, HttpServerResponse response) {
final Iterator iter = timeouts.iterator();
nextTimeout(timeouts, iter, response);
}
private void nextTimeout(final List timeouts, final Iterator iter, final HttpServerResponse response) {
if (iter.hasNext()) {
final TimeoutInfo timeout = iter.next();
async.setTimer(timeout.timeout, new Handler() {
public void handle(Long id) {
response.write(timeout.buff);
nextTimeout(timeouts, iter, response);
}
});
} else {
timeouts.clear();
}
}
public void handle(HttpServerRequest req) {
req.response().headers().set("Content-Type", "application/javascript; charset=UTF-8");
BaseTransport.setCORS(req);
req.response().setChunked(true);
Buffer h = new Buffer(2);
h.appendString("h\n");
Buffer hs = new Buffer(2050);
for (int i = 0; i < 2048; i++) {
hs.appendByte((byte) ' ');
}
hs.appendString("h\n");
List timeouts = new ArrayList<>();
setTimeout(timeouts, 0, h);
setTimeout(timeouts, 1, hs);
setTimeout(timeouts, 5, h);
setTimeout(timeouts, 25, h);
setTimeout(timeouts, 125, h);
setTimeout(timeouts, 625, h);
setTimeout(timeouts, 3125, h);
runTimeouts(timeouts, req.response());
}
class TimeoutInfo {
final long timeout;
final Buffer buff;
TimeoutInfo(long timeout, Buffer buff) {
this.timeout = timeout;
this.buff = buff;
}
}
};
}
private Handler createIFrameHandler(final String iframeHTML) {
final String etag = getMD5String(iframeHTML);
return new Handler() {
public void handle(HttpServerRequest req) {
try {
if (log.isTraceEnabled()) log.trace("In Iframe handler");
if (etag != null && etag.equals(req.headers().get("if-none-match"))) {
req.response().setStatusCode(304);
req.response().end();
} else {
req.response().headers().set("Content-Type", "text/html; charset=UTF-8");
req.response().headers().set("Cache-Control", "public,max-age=31536000");
long oneYear = 365 * 24 * 60 * 60 * 1000;
String expires = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date(System.currentTimeMillis() + oneYear));
req.response().headers().set("Expires", expires);
req.response().headers().set("ETag", etag);
req.response().end(iframeHTML);
}
} catch (Exception e) {
log.error("Failed to server iframe", e);
}
}
};
}
/*
These applications are required by the SockJS protocol and QUnit tests
*/
public void installTestApplications() {
installApp(new JsonObject().putString("prefix", "/echo")
.putNumber("max_bytes_streaming", 4096),
new Handler() {
public void handle(final SockJSSocket sock) {
sock.dataHandler(new Handler() {
public void handle(Buffer buff) {
sock.write(buff);
}
});
}
});
installApp(new JsonObject().putString("prefix", "/close")
.putNumber("max_bytes_streaming", 4096),
new Handler() {
public void handle(final SockJSSocket sock) {
sock.close();
}
});
JsonArray disabled = new JsonArray();
disabled.add(Transport.WEBSOCKET.toString());
installApp(new JsonObject().putString("prefix", "/disabled_websocket_echo")
.putNumber("max_bytes_streaming", 4096)
.putArray("disabled_transports", disabled),
new Handler() {
public void handle(final SockJSSocket sock) {
sock.dataHandler(new Handler() {
public void handle(Buffer buff) {
sock.write(buff);
}
});
}
});
installApp(new JsonObject().putString("prefix", "/ticker")
.putNumber("max_bytes_streaming", 4096),
new Handler() {
public void handle(final SockJSSocket sock) {
final long timerID = async.setPeriodic(1000, new Handler() {
public void handle(Long id) {
sock.write(new Buffer("tick!"));
}
});
sock.endHandler(new VoidHandler() {
public void handle() {
async.cancelTimer(timerID);
}
});
}
});
installApp(new JsonObject().putString("prefix", "/amplify")
.putNumber("max_bytes_streaming", 4096),
new Handler() {
public void handle(final SockJSSocket sock) {
sock.dataHandler(new Handler() {
public void handle(Buffer data) {
String str = data.toString();
int n = Integer.valueOf(str);
if (n < 0 || n > 19) {
n = 1;
}
int num = (int) Math.pow(2, n);
Buffer buff = new Buffer(num);
for (int i = 0; i < num; i++) {
buff.appendByte((byte) 'x');
}
sock.write(buff);
}
});
}
});
installApp(new JsonObject().putString("prefix", "/broadcast")
.putNumber("max_bytes_streaming", 4096),
new Handler() {
final Set connections = async.sharedData().getSet("conns");
public void handle(final SockJSSocket sock) {
connections.add(sock.writeHandlerID());
sock.dataHandler(new Handler() {
public void handle(Buffer buffer) {
for (String actorID : connections) {
async.eventBus().publish(actorID, buffer);
}
}
});
sock.endHandler(new VoidHandler() {
public void handle() {
connections.remove(sock.writeHandlerID());
}
});
}
});
installApp(new JsonObject().putString("prefix", "/cookie_needed_echo")
.putNumber("max_bytes_streaming", 4096).putBoolean("insert_JSESSIONID", true),
new Handler() {
public void handle(final SockJSSocket sock) {
sock.dataHandler(new Handler() {
public void handle(Buffer buff) {
sock.write(buff);
}
});
}
});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy