com.conwet.silbops.connectors.comet.CometAPIHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.conwet.silbops.connectors.comet.CometAPIHandler.java

Source

package com.conwet.silbops.connectors.comet;

/*
 * #%L
 * SilboPS Service
 * %%
 * Copyright (C) 2011 - 2014 CoNWeT Lab., Universidad Politcnica de Madrid
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.atmosphere.cpr.AtmosphereHandler;
import org.atmosphere.cpr.AtmosphereResource;
import org.atmosphere.cpr.AtmosphereResourceEvent;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.conwet.silbops.apibroker.EndPoint;
import com.conwet.silbops.broker.Broker;
import com.conwet.silbops.connectors.comet.handlers.AdvertiseResponse;
import com.conwet.silbops.connectors.comet.handlers.ContextResponse;
import com.conwet.silbops.connectors.comet.handlers.CreateConnectionResponse;
import com.conwet.silbops.connectors.comet.handlers.DisconnectResponse;
import com.conwet.silbops.connectors.comet.handlers.KeepAliveResponse;
import com.conwet.silbops.connectors.comet.handlers.PublishResponse;
import com.conwet.silbops.connectors.comet.handlers.SubscribeResponse;
import com.conwet.silbops.connectors.comet.handlers.UnadvertiseResponse;
import com.conwet.silbops.connectors.comet.handlers.UnsubscribeResponse;

/**
 *
 * @author jvelasco
 * @author sergio
 */
public class CometAPIHandler implements AtmosphereHandler<HttpServletRequest, HttpServletResponse> {

    public static final String ATTRIBUTE_CONNECTION_ID = CometAPIHandler.class.getName() + ".connection";
    private static final Logger logger = LoggerFactory.getLogger(CometAPIHandler.class);

    private final Broker broker;
    private AtomicLong nextStreamID;
    private final ConnectionStatus connStatus;
    private int keepAlivePeriod;
    private ScheduledExecutorService connectionMonitor;

    private Map<String, HandlerRequest> dispatcher;
    private static final String RECV_SYS_MESSAGE = "SilboPS.recvSystemMessage";

    public CometAPIHandler(Broker broker, int keelAlivePeriod) {

        this.broker = broker;
        this.keepAlivePeriod = keelAlivePeriod * 1000; // from sec to millis
        nextStreamID = new AtomicLong();
        connStatus = new ConnectionStatus();
        dispatcher = new HashMap<>();
        connectionMonitor = Executors.newScheduledThreadPool(1);

        this.initialize();
        this.createMonitorService();
    }

    private void initialize() {

        dispatcher.put("connect", new CreateConnectionResponse(broker, RECV_SYS_MESSAGE, connStatus));
        dispatcher.put("disconnect", new DisconnectResponse(broker, RECV_SYS_MESSAGE, connStatus));
        dispatcher.put("context", new ContextResponse(broker, RECV_SYS_MESSAGE, connStatus));
        dispatcher.put("subscribe", new SubscribeResponse(broker, RECV_SYS_MESSAGE, connStatus));
        dispatcher.put("unsubscribe", new UnsubscribeResponse(broker, RECV_SYS_MESSAGE, connStatus));
        dispatcher.put("publish", new PublishResponse(broker, RECV_SYS_MESSAGE, connStatus));
        dispatcher.put("advertise", new AdvertiseResponse(broker, RECV_SYS_MESSAGE, connStatus));
        dispatcher.put("unadvertise", new UnadvertiseResponse(broker, RECV_SYS_MESSAGE, connStatus));
        dispatcher.put("keepAlive", new KeepAliveResponse(broker, RECV_SYS_MESSAGE, connStatus, 5));
    }

    private void createMonitorService() {

        connectionMonitor.scheduleWithFixedDelay(new Runnable() {

            @Override
            public void run() {

                Set<String> streamToRemove = new HashSet<>();

                for (String stream : connStatus.keySet()) {

                    int ttl = connStatus.decrementAndGetTTL(stream);
                    logger.debug("streamID=" + stream + " read ttl=" + ttl);

                    if (ttl <= 0) {

                        streamToRemove.add(stream);
                    }
                }

                for (String streams : streamToRemove) {

                    disconnectStreamID(streams);
                }
            }
        }, 10 * 1000, keepAlivePeriod, TimeUnit.MILLISECONDS);
    }

    @Override
    public void onRequest(AtmosphereResource<HttpServletRequest, HttpServletResponse> event) throws IOException {

        HttpServletRequest req = event.getRequest();
        HttpServletResponse res = event.getResponse();

        res.addHeader("Cache-Control", "max-age=0, no-cache, must-revalidate");
        res.addHeader("Expires", "Sat, 26 Jul 1997 05:00:00 GMT");

        String method = req.getMethod();

        if (method.equals("GET")) {
            onRequestGet(event);
        } else if (method.equals("POST")) {
            onRequestPost(event);
        } else if (method.equals("OPTIONS")) {
            onRequestOption(event);
        }
    }

    public void onRequestOption(AtmosphereResource<HttpServletRequest, HttpServletResponse> event) {

        HttpServletResponse res = event.getResponse();
        HttpServletRequest req = event.getRequest();
        String accessCRH = sanitize(req.getHeader("Access-Control-Request-Headers"));

        res.setContentType("text/plain;charset=UTF-8");
        res.setContentLength(0);
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
        res.setHeader("Access-Control-Allow-Headers", "X-JSON, " + accessCRH);
        res.setHeader("Access-Control-Expose-Headers", "X-JSON, " + accessCRH);
        res.setHeader("Access-Control-Max-Age", "1728000");
        res.setHeader("Access-Control-Allow-Credentials", "true");
    }

    /**
     * Sanitize the input header to prevent HTTP response splitting attack
     * 
     * @param header the header to validate
     * @return the sanitized header.
     */
    private String sanitize(String header) {

        String sanitized = header;

        if (header.contains("%0d%0a%0d%0a")) {

            sanitized = "";
            logger.warn("Possible HTTP Response attack, header={}", header);
        }

        return sanitized;
    }

    @SuppressWarnings("unchecked")
    public void onRequestGet(AtmosphereResource<HttpServletRequest, HttpServletResponse> event) throws IOException {

        HttpServletResponse res = event.getResponse();
        res.setContentType("text/event-stream;charset=UTF-8");
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader("Access-Control-Allow-Credentials", "true");

        String stream = getNewStreamId();
        connStatus.addStream(stream, 5, event);

        JSONObject json = new JSONObject();
        json.put("streamID", stream);
        json.put("keepAlivePeriod", keepAlivePeriod);
        PrintWriter writer = res.getWriter();
        writer.println(JSONUtils.sendData("SilboPS.openStream", null, json.toJSONString()));

        event.suspend(Long.MAX_VALUE, true);
    }

    public void onRequestPost(AtmosphereResource<HttpServletRequest, HttpServletResponse> event)
            throws IOException {

        HttpServletRequest req = event.getRequest();
        HttpServletResponse res = event.getResponse();

        res.setContentType("text/plain");
        res.setHeader("Access-Control-Allow-Origin", "*");

        HandlerRequest handler = dispatcher.get(req.getParameter("action"));

        if (handler != null) {

            handler.handle(req, res);

        } else {
            String msg = "No handler found for action parameter: " + req.getParameter("action");
            res.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }

    @Override
    public void onStateChange(AtmosphereResourceEvent<HttpServletRequest, HttpServletResponse> ev)
            throws IOException {

        AtmosphereResource<HttpServletRequest, HttpServletResponse> resource = ev.getResource();
        HttpServletRequest req = resource.getRequest();
        HttpServletResponse res = resource.getResponse();
        String streamID = (String) req.getAttribute(ATTRIBUTE_CONNECTION_ID);

        PrintWriter writer = res.getWriter();

        if (ev.isSuspended()) {
            writer.println("<!-- Suspended -->");
        }
        if (ev.isResuming()) {
            writer.println("<!-- Resuming -->");
        }
        if (ev.isCancelled()) {
            writer.println("<!-- Cancelled -->");
        }
        if (ev.isResumedOnTimeout()) {
            writer.println("<!-- Timeout -->");
        }
        if (ev.isResumedOnTimeout() || ev.isCancelled()) {

            disconnectStreamID(streamID);
        }
        writer.flush();
    }

    @Override
    public void destroy() {

        connectionMonitor.shutdown();
        connStatus.dispose();
    }

    private void disconnectStreamID(String streamID) {

        // removing all the clients with the same streamID
        logger.debug("removing streamID=" + streamID);
        StreamInfo info = connStatus.remove(streamID);

        for (EndPoint conn : info.getEndPointMap().values()) {

            broker.disconnectEndPoint(conn);
            conn.close();
        }
    }

    private String getNewStreamId() {

        return Long.toHexString(nextStreamID.incrementAndGet());
    }
}