All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.jsync.sockjs.impl.DefaultSockJSServer Maven / Gradle / Ivy

There is a newer version: 1.10.13
Show newest version
/*
 * 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