/**
* Copyright (C) 2014 Red Hat, Inc, and individual contributors.
* Copyright (C) 2011-2012 VMware, Inc.
*/
package org.projectodd.sockjs;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* The main entry point for handling SockJS connections.
*
* Typically a SockJsServer is created, its {@link #options} set, an
* {@link #onConnection} handler added, and the server is then passed to a
* SockJsServlet to handle the routing of requests.
*/
public class SockJsServer {
public SockJsServer() {
}
public void init() {
// Set everything up here instead of the constructor so that we have a chance to set options
appHandler = new AppHandler(this);
webHandler = new WebHandler(this);
iframeHandler = new IframeHandler(this);
chunkingHandler = new ChunkingHandler(this);
websocketHandler = new WebsocketHandler(this);
jsonpHandler = new JsonpHandler(this);
xhrHandler = new XhrHandler(this);
eventsourceHandler = new EventsourceHandler(this);
htmlfileHandler = new HtmlfileHandler(this);
dispatcher = new Dispatcher(appHandler.handle404, webHandler.handle405, webHandler.handleError);
dispatcher.push("GET", p(""), appHandler.welcomeScreen);
dispatcher.push("GET", p("/iframe[0-9-.a-z_]*.html"), iframeHandler.iframe,
webHandler.cacheFor, webHandler.expose);
dispatcher.push("OPTIONS", p("/info"), optsFilters(chunkingHandler.infoOptions));
dispatcher.push("GET", p("/info"), xhrHandler.xhrCors, webHandler.hNoCache,
chunkingHandler.info, webHandler.expose);
dispatcher.push("GET", p("/websocket"), websocketHandler.rawWebsocket);
dispatcher.push("GET", t("/jsonp"), appHandler.hSid, webHandler.hNoCache, jsonpHandler.jsonp);
dispatcher.push("POST", t("/jsonp_send"), appHandler.hSid, webHandler.hNoCache, webHandler.expectForm, jsonpHandler.jsonpSend);
dispatcher.push("POST", t("/xhr"), appHandler.hSid, webHandler.hNoCache, xhrHandler.xhrCors, xhrHandler.xhrPoll);
dispatcher.push("OPTIONS", t("/xhr"), optsFilters());
dispatcher.push("POST", t("/xhr_send"), appHandler.hSid, webHandler.hNoCache, xhrHandler.xhrCors, webHandler.expectXhr, xhrHandler.xhrSend);
dispatcher.push("OPTIONS", t("/xhr_send"), optsFilters());
dispatcher.push("POST", t("/xhr_streaming"), appHandler.hSid, webHandler.hNoCache, xhrHandler.xhrCors, xhrHandler.xhrStreaming);
dispatcher.push("OPTIONS", t("/xhr_streaming"), optsFilters());
dispatcher.push("GET", t("/eventsource"), appHandler.hSid, webHandler.hNoCache, eventsourceHandler.eventsource);
dispatcher.push("GET", t("/htmlfile"), appHandler.hSid, webHandler.hNoCache, htmlfileHandler.htmlfile);
if (options.websocket) {
dispatcher.push("GET", t("/websocket"), websocketHandler.sockjsWebsocket);
} else {
dispatcher.push("GET", t("/websocket"), webHandler.cacheFor, appHandler.disabledTransport);
}
scheduledExecutor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = Executors.defaultThreadFactory().newThread(r);
// Mark as a daemon thread so we never prevent shutdown
thread.setDaemon(true);
return thread;
}
});
}
public void destroy() {
scheduledExecutor.shutdownNow();
}
public void dispatch(SockJsRequest req, SockJsResponse res) throws SockJsException {
dispatcher.dispatch(req, res);
}
protected String p(String match) {
return "^" + match + "[/]?$";
}
protected String[] t(String match) {
String pattern = p("/([^/.]+)/([^/.]+)" + match);
return new String[] { pattern, "server", "session" };
}
protected DispatchFunction[] optsFilters() {
return optsFilters(xhrHandler.xhrOptions);
}
protected DispatchFunction[] optsFilters(DispatchFunction optionsFilter) {
return new DispatchFunction[] { appHandler.hSid, xhrHandler.xhrCors, webHandler.cacheFor, optionsFilter, webHandler.expose };
}
/**
* Handle incoming connections - a SockJsServer isn't very useful
* unless you set an OnConnectionHandler here.
*
* @param handler The handler to call when a new connection is established
*/
public void onConnection(OnConnectionHandler handler) {
onConnectionHandler = handler;
}
public void emitConnection(SockJsConnection connection) {
if (onConnectionHandler != null) {
onConnectionHandler.handle(connection);
}
}
public ScheduledFuture setTimeout(Runnable callback, long delay) {
return scheduledExecutor.schedule(callback, delay, TimeUnit.MILLISECONDS);
}
public void clearTimeout(ScheduledFuture future) {
future.cancel(false);
}
private Dispatcher dispatcher;
private AppHandler appHandler;
private WebHandler webHandler;
private IframeHandler iframeHandler;
private ChunkingHandler chunkingHandler;
private WebsocketHandler websocketHandler;
private JsonpHandler jsonpHandler;
private XhrHandler xhrHandler;
private EventsourceHandler eventsourceHandler;
private HtmlfileHandler htmlfileHandler;
private ScheduledExecutorService scheduledExecutor;
private OnConnectionHandler onConnectionHandler;
public Options options = new Options();
public static class Options {
/**
* Transports which don't support cross-domain communication natively
* ('eventsource' to name one) use an iframe trick. A simple page is
* served from the SockJS server (using its foreign domain) and is
* placed in an invisible iframe. Code run from this iframe doesn't
* need to worry about cross-domain issues, as it's being run from a
* domain local to the SockJS server. This iframe also needs to load
* the SockJS javascript client library, and this option lets you
* specify its url (if you're unsure, point it to the latest minified
* SockJS client release, this is the default). You must explicitly
* specify this url on the server side for security reasons - we don't
* want the possibility of running any foreign javascript within the
* SockJS domain (aka cross site scripting attack). Also, the sockjs
* javascript library is probably already cached by the browser - it
* makes sense to reuse the sockjs url you're normally using.
*/
public String sockjsUrl = "http://cdn.sockjs.org/sockjs-0.3.min.js";
/**
* Most streaming transports save responses on the client side and
* don't free memory used by delivered messages. Such transports need
* to be garbage-collected once in a while. `responseLimit` sets a
* maximum number of bytes that can be send over a single http
* streaming request before it will be closed. After that client
* needs to open new request. Setting this value to one effectively
* disables streaming and will make streaming transports to behave
* like polling transports. The default value is 128K.
*/
// TODO externalize
public int responseLimit = 128 * 1024;
/**
* Some load balancers don't support websockets. This option can be
* used to disable websockets support by the server. By default
* websockets are enabled.
*/
public boolean websocket = true;
/**
* Some hosting providers enable sticky sessions only to requests
* that have a JSESSIONID cookie set. This setting controls if the
* server should set this cookie to a dummy value. By default setting
* of a JSESSIONID cookie is disabled.
*/
public boolean jsessionid = false;
/**
* In order to keep proxies and load balancers from closing long
* running http requests we need to pretend that the connection is
* active and send a heartbeat packet once in a while. This setting
* controls how often this is done. By default a heartbeat packet
* is sent every 25 seconds.
*/
public int heartbeatDelay = 25000;
/**
* The server sends a `close` event when a client receiving
* connection has not been seen for a while. This delay is
* configured by this setting. By default the `close` event will
* be emitted when a receiving connection wasn't seen for 5 seconds.
*/
public int disconnectDelay = 5000;
/**
* Users can specify a base URL which all client requests after an
* initial info request will be made against. This probably is more
* useful as a method and not a static string, but that's not
* implemented yet.
*/
public String baseUrl = null;
// sockjs-node exposes some options as callback functions
// TODO: Add jsessionidCallback
// TODO: Add baseUrlCallback
}
public static interface OnConnectionHandler {
public void handle(SockJsConnection connection);
}
}