/** * Copyright 2015 StreamSets Inc. * * Licensed under the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.streamsets.datacollector.websockets; import com.streamsets.datacollector.alerts.AlertEventListener; import com.streamsets.datacollector.execution.EventListenerManager; import com.streamsets.datacollector.execution.StateEventListener; import com.streamsets.datacollector.main.RuntimeInfo; import com.streamsets.datacollector.metrics.MetricsEventListener; import com.streamsets.datacollector.util.AuthzRole; import com.streamsets.datacollector.util.Configuration; import com.streamsets.pipeline.lib.executor.SafeScheduledExecutorService; import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class SDCWebSocketServlet extends WebSocketServlet implements WebSocketCreator { private final static Logger LOG = LoggerFactory.getLogger(SDCWebSocketServlet.class); private final Configuration config; private final RuntimeInfo runtimeInfo; private final EventListenerManager eventListenerManager; private BlockingQueue<WebSocketMessage> queue; private ScheduledExecutorService executorService; private static final String MAX_WEB_SOCKETS_CONCURRENT_REQUESTS_KEY = "max.webSockets.concurrent.requests"; private static final int MAX_WEB_SOCKETS_CONCURRENT_REQUESTS_DEFAULT = 50; protected static volatile int webSocketClients; public SDCWebSocketServlet(Configuration configuration, RuntimeInfo runtimeInfo, EventListenerManager eventListenerManager) { this.config = configuration; this.runtimeInfo = runtimeInfo; this.eventListenerManager = eventListenerManager; } @Override public void init() throws ServletException { super.init(); queue = new ArrayBlockingQueue<>(10000); executorService = new SafeScheduledExecutorService(1, "WebSocket"); executorService.submit(new Runnable() { @Override public void run() { while (!executorService.isShutdown()) { try { WebSocketMessage message = queue.poll(100, TimeUnit.MILLISECONDS); if (message != null) { message.send(); } } catch (InterruptedException ex) { //NOP } catch (IOException | WebSocketException ex) { LOG.warn("Failed to send WebSocket message: {}", ex.toString(), ex); } } } }); } @Override public void destroy() { executorService.shutdownNow(); super.destroy(); } @Override public void configure(WebSocketServletFactory factory) { factory.getPolicy().setIdleTimeout(7200000); factory.setCreator(this); } @Override public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) { HttpServletRequest httpRequest = req.getHttpServletRequest(); String webSocketType = httpRequest.getParameter("type"); final String pipelineName = httpRequest.getParameter("pipelineName"); if(webSocketType != null) { switch (webSocketType) { case LogMessageWebSocket.TYPE: return new LogMessageWebSocket(config, runtimeInfo); case StatusWebSocket.TYPE: return new StatusWebSocket(new ListenerManager<StateEventListener>() { @Override public void register(StateEventListener listener) { eventListenerManager.addStateEventListener(listener); } @Override public void unregister(StateEventListener listener) { eventListenerManager.removeStateEventListener(listener); } }, queue); case MetricsWebSocket.TYPE: return new MetricsWebSocket(new ListenerManager<MetricsEventListener>() { @Override public void register(MetricsEventListener listener) { eventListenerManager.addMetricsEventListener(pipelineName, listener); } @Override public void unregister(MetricsEventListener listener) { eventListenerManager.removeMetricsEventListener(pipelineName, listener); } }, queue); case AlertsWebSocket.TYPE: return new AlertsWebSocket(new ListenerManager<AlertEventListener>() { @Override public void register(AlertEventListener listener) { eventListenerManager.addAlertEventListener(listener); } @Override public void unregister(AlertEventListener listener) { eventListenerManager.removeAlertEventListener(listener); } }, queue); } } return null; } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { synchronized (SDCWebSocketServlet.class) { int maxClients = config.get(MAX_WEB_SOCKETS_CONCURRENT_REQUESTS_KEY, MAX_WEB_SOCKETS_CONCURRENT_REQUESTS_DEFAULT); if (webSocketClients < maxClients) { webSocketClients++; } else { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Maximum WebSockets concurrent connections reached - " + webSocketClients); return; } } String webSocketType = request.getParameter("type"); if(webSocketType != null) { switch (webSocketType) { case LogMessageWebSocket.TYPE: if (request.isUserInRole(AuthzRole.ADMIN) || request.isUserInRole(AuthzRole.MANAGER) || request.isUserInRole(AuthzRole.CREATOR) || request.isUserInRole(AuthzRole.ADMIN_REMOTE) || request.isUserInRole(AuthzRole.MANAGER_REMOTE) || request.isUserInRole(AuthzRole.CREATOR_REMOTE)) { super.service(request, response); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN); } break; case StatusWebSocket.TYPE: case MetricsWebSocket.TYPE: case AlertsWebSocket.TYPE: //All roles are supported super.service(request, response); break; default: response.sendError(HttpServletResponse.SC_FORBIDDEN); } } } }