Java tutorial
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()); } }