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

io.jsync.sockjs.impl.Session 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.Handler;
import io.jsync.MultiMap;
import io.jsync.buffer.Buffer;
import io.jsync.impl.AsyncInternal;
import io.jsync.json.DecodeException;
import io.jsync.logging.Logger;
import io.jsync.logging.impl.LoggerFactory;
import io.jsync.shareddata.Shareable;
import io.jsync.sockjs.SockJSSocket;

import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

/**
 * The SockJS session implementation.
 * 

* If multiple instances of the SockJS server are used then instances of this * class can be accessed by different threads (not concurrently), so we store * it in a shared data map * * @author Tim Fox */ class Session extends SockJSSocketBase implements Shareable { private static final Logger log = LoggerFactory.getLogger(Session.class); private final Map sessions; private final Queue pendingWrites = new LinkedList<>(); private final Queue pendingReads = new LinkedList<>(); private final String id; private final long timeout; private final Handler sockHandler; private TransportListener listener; private Handler dataHandler; private boolean closed; private boolean openWritten; private long heartbeatID = -1; private long timeoutTimerID = -1; private boolean paused; private int maxQueueSize = 64 * 1024; // Message queue size is measured in *characters* (not bytes) private int messagesSize; private Handler drainHandler; private Handler endHandler; private Handler exceptionHandler; private boolean handleCalled; private InetSocketAddress localAddress; private InetSocketAddress remoteAddress; private String uri; private MultiMap headers; Session(AsyncInternal async, Map sessions, long heartbeatPeriod, Handler sockHandler) { this(async, sessions, null, -1, heartbeatPeriod, sockHandler); } Session(AsyncInternal async, Map sessions, String id, long timeout, long heartbeatPeriod, Handler sockHandler) { super(async); this.sessions = sessions; this.id = id; this.timeout = timeout; this.sockHandler = sockHandler; // Start a heartbeat heartbeatID = async.setPeriodic(heartbeatPeriod, new Handler() { public void handle(Long id) { if (listener != null) { listener.sendFrame("h"); } } }); } @Override public synchronized SockJSSocket write(Buffer buffer) { String msgStr = buffer.toString(); pendingWrites.add(msgStr); this.messagesSize += msgStr.length(); if (listener != null) { writePendingMessages(); } return this; } @Override public synchronized Session dataHandler(Handler handler) { this.dataHandler = handler; return this; } @Override public synchronized Session pause() { paused = true; return this; } @Override public synchronized Session resume() { paused = false; if (dataHandler != null) { for (String msg : this.pendingReads) { dataHandler.handle(new Buffer(msg)); } } return this; } @Override public synchronized Session setWriteQueueMaxSize(int maxQueueSize) { if (maxQueueSize < 1) { throw new IllegalArgumentException("maxQueueSize must be >= 1"); } this.maxQueueSize = maxQueueSize; return this; } @Override public synchronized boolean writeQueueFull() { return messagesSize >= maxQueueSize; } @Override public synchronized Session drainHandler(Handler handler) { this.drainHandler = handler; return this; } @Override public synchronized Session exceptionHandler(Handler handler) { this.exceptionHandler = handler; return this; } @Override public synchronized Session endHandler(Handler endHandler) { this.endHandler = endHandler; return this; } public synchronized void shutdown() { doClose(); } // When the user calls close() we don't actually close the session - unless it's a websocket one // Yes, SockJS is weird, but it's hard to work out expected server behaviour when there's no spec @Override public synchronized void close() { if (endHandler != null) { endHandler.handle(null); } closed = true; if (listener != null && handleCalled) { listener.sessionClosed(); } } @Override public InetSocketAddress remoteAddress() { return remoteAddress; } @Override public InetSocketAddress localAddress() { return localAddress; } @Override public MultiMap headers() { return headers; } @Override public String uri() { return uri; } synchronized boolean isClosed() { return closed; } synchronized void resetListener() { listener = null; // We set a timer that will kick in and close the session if the client doesn't come back // We MUST ALWAYS do this or we can get a memory leak on the server setTimer(); } private void cancelTimer() { if (timeoutTimerID != -1) { async.cancelTimer(timeoutTimerID); } } private void setTimer() { if (timeout != -1) { cancelTimer(); timeoutTimerID = async.setTimer(timeout, new Handler() { public void handle(Long id) { async.cancelTimer(heartbeatID); if (listener == null) { shutdown(); } if (listener != null) { listener.close(); } } }); } } synchronized void writePendingMessages() { String json = JsonCodec.encode(pendingWrites.toArray()); listener.sendFrame("a" + json); pendingWrites.clear(); messagesSize = 0; if (drainHandler != null && messagesSize <= maxQueueSize / 2) { Handler dh = drainHandler; drainHandler = null; dh.handle(null); } } synchronized void register(final TransportListener lst) { if (closed) { // Closed by the application writeClosed(lst); // And close the listener request lst.close(); } else if (this.listener != null) { writeClosed(lst, 2010, "Another connection still open"); // And close the listener request lst.close(); } else { cancelTimer(); this.listener = lst; if (!openWritten) { writeOpen(lst); sockHandler.handle(this); handleCalled = true; } if (listener != null) { if (closed) { // Could have already been closed by the user writeClosed(lst); listener = null; lst.close(); } else { if (!pendingWrites.isEmpty()) { writePendingMessages(); } } } } } // Actually close the session - when the user calls close() the session actually continues to exist until timeout // Yes, I know it's weird but that's the way SockJS likes it. private void doClose() { super.close(); // We must call this or handlers don't get unregistered and we get a leak if (heartbeatID != -1) { async.cancelTimer(heartbeatID); } if (timeoutTimerID != -1) { async.cancelTimer(timeoutTimerID); } if (id != null) { // Can be null if websocket session sessions.remove(id); } if (endHandler != null) { endHandler.handle(null); } } private String[] parseMessageString(String msgs) { try { String[] parts; if (msgs.startsWith("[")) { //JSON array parts = (String[]) JsonCodec.decodeValue(msgs, String[].class); } else { //JSON string String str = (String) JsonCodec.decodeValue(msgs, String.class); parts = new String[]{str}; } return parts; } catch (DecodeException e) { return null; } } boolean handleMessages(String messages) { String[] msgArr = parseMessageString(messages); if (msgArr == null) { return false; } else { if (dataHandler != null) { for (String msg : msgArr) { if (!paused) { try { dataHandler.handle(new Buffer(msg)); } catch (Throwable t) { log.error("Unhandle exception", t); } } else { pendingReads.add(msg); } } } return true; } } void handleException(Throwable t) { if (exceptionHandler != null) { exceptionHandler.handle(t); } else { log.error("Unhandled exception", t); } } public void writeClosed(TransportListener lst) { writeClosed(lst, 3000, "Go away!"); } private void writeClosed(TransportListener lst, int code, String msg) { StringBuilder sb = new StringBuilder("c["); sb.append(String.valueOf(code)).append(",\""); sb.append(msg).append("\"]"); lst.sendFrame(sb.toString()); } private void writeOpen(TransportListener lst) { StringBuilder sb = new StringBuilder("o"); lst.sendFrame(sb.toString()); openWritten = true; } void setInfo(InetSocketAddress localAddress, InetSocketAddress remoteAddress, String uri, MultiMap headers) { this.localAddress = localAddress; this.remoteAddress = remoteAddress; this.uri = uri; this.headers = BaseTransport.removeCookieHeaders(headers); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy