/* * Copyright 2017 Async-IO.org * * Licensed 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 org.atmosphere.nettosphere; import io.netty.channel.ChannelInboundHandler; import io.netty.handler.ssl.SslContext; import org.atmosphere.cpr.AtmosphereFramework; import org.atmosphere.cpr.AtmosphereHandler; import org.atmosphere.cpr.AtmosphereInterceptor; import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.Broadcaster; import org.atmosphere.cpr.BroadcasterCache; import org.atmosphere.cpr.BroadcasterFactory; import org.atmosphere.handler.AbstractReflectorAtmosphereHandler; import org.atmosphere.handler.ReflectorServletProcessor; import org.atmosphere.nettosphere.util.SSLContextListener; import org.atmosphere.websocket.WebSocketHandler; import org.atmosphere.websocket.WebSocketProtocol; import org.atmosphere.websocket.protocol.SimpleHttpProtocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.servlet.Servlet; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; /** * A Configuration class used to configure Atmosphere. */ public class Config { private static final Logger logger = LoggerFactory.getLogger(Config.class); private final Builder b; public Config(Builder b) { this.b = b; } public ExecutorService bossExecutor() { return b.bossExecutor; } public ExecutorService workerExecutor() { return b.workerExecutor; } public String host() { return b.host; } public int port() { return b.port; } public Map<String, String> initParams() { return b.initParams; } public List<String> path() { return b.paths; } public String configFile() { return b.atmosphereDotXmlPath; } public Class<? extends Broadcaster> broadcaster() { return b.broadcasterClass; } public Map<String, AtmosphereHandler> handlersMap() { return b.handlers; } public Map<String, WebSocketHandler> webSocketHandlersMap() { return b.webSocketHandlers; } public BroadcasterFactory broadcasterFactory() { return b.broadcasterFactory; } public Class<? extends BroadcasterCache> broadcasterCache() { return b.broadcasterCache; } public Class<? extends WebSocketProtocol> webSocketProtocol() { return b.webSocketProtocol; } public List<AtmosphereInterceptor> interceptors() { return b.interceptors; } public String scanLib() { return b.librariesPath; } public List<Class<?>> scanPackages() { return b.packages; } public String mappingPath() { return b.mappingPath; } public SSLContext sslContext() { return b.context; } public String[] enabledCipherSuites() { return b.enabledCipherSuites; } public SslContext nettySslContext() { return b.sslContext; } public SSLContextListener sslContextListener() { return b.contextListener; } public LinkedList<ChannelInboundHandler> channelUpstreamHandlers() { return b.nettyHandlers; } public boolean supportChunking() { return b.supportChunking; } public boolean aggregateRequestBodyInMemory() { return b.aggregateRequestBodyInMemory; } public boolean socketKeepAlive() { return b.socketKeepAlive; } public boolean socketNoTcpDelay() { return b.socketNoTcpDelay; } public int maxChunkContentLength() { return b.maxContentLength; } public List<String> excludedInterceptors() { return b.excludedInterceptors; } public boolean enablePong() { return b.enablePong; } public int maxWebSocketFrameSize() { return b.maxWebSocketFrameSize; } public boolean textFrameAsBinary() { return b.textFrameAsBinary; } public Map<String, Object> servletContextAttributes() { return b.servletContextAttributes; } public String subProtocols() { return b.subProtocols; } public boolean noInternalAlloc() { return b.noInternalAlloc; } public boolean binaryWrite() { return b.binaryWrite; } public boolean epoll() { return b.epoll; } public final static class Builder { private final List<String> paths = new ArrayList<String>(); private String atmosphereDotXmlPath = AtmosphereFramework.DEFAULT_ATMOSPHERE_CONFIG_PATH; private ExecutorService bossExecutor; private ExecutorService workerExecutor; private String host = "0.0.0.0"; private int port = 8080; private final Map<String, String> initParams = new HashMap<String, String>(); private final Map<String, Object> servletContextAttributes = new HashMap<String, Object>(); private final Map<String, AtmosphereHandler> handlers = new HashMap<String, AtmosphereHandler>(); private final Map<String, WebSocketHandler> webSocketHandlers = new HashMap<String, WebSocketHandler>(); private Class<? extends WebSocketProtocol> webSocketProtocol = SimpleHttpProtocol.class; private Class<? extends Broadcaster> broadcasterClass; private BroadcasterFactory broadcasterFactory; private Class<? extends BroadcasterCache> broadcasterCache; private final List<AtmosphereInterceptor> interceptors = new ArrayList<AtmosphereInterceptor>(); private final List<String> excludedInterceptors = new ArrayList<String>(); private String librariesPath = "." + File.separator + "lib"; private String mappingPath = ""; private final List<Class<?>> packages = new ArrayList<Class<?>>(); private SSLContext context; private String[] enabledCipherSuites; private SslContext sslContext; private SSLContextListener contextListener; private final LinkedList<ChannelInboundHandler> nettyHandlers = new LinkedList<ChannelInboundHandler>(); private boolean supportChunking = true; private boolean aggregateRequestBodyInMemory = true; private boolean socketNoTcpDelay = true; private boolean socketKeepAlive = true; private int maxContentLength = 65536; private boolean enablePong = false; private int maxWebSocketFrameSize = 65536; private boolean textFrameAsBinary = false; public String subProtocols = ""; private boolean noInternalAlloc = false; private boolean binaryWrite = false; public boolean epoll = false; /** * Set an SSLContext in order enable SSL * * @param context * @return this */ public Builder sslContext(SSLContext context) { this.context = context; return this; } /** * Set a String[] of cipher suites to be enabled. * * @param String-array of cipher suites to be enabled. * @return this */ public Builder enabledCipherSuites(String[] cipherSuitesToEnable) { this.enabledCipherSuites = cipherSuitesToEnable; return this; } /** * Set the {@link SslContext} * * @param sslContext * @return this */ public Builder sslContext(SslContext sslContext) { this.sslContext = sslContext; return this; } /** * Set the maximum WebSocket Frame Size. Default is 65536 * * @param maxWebSocketFrameSize the maximum WebSocket Frame Size. * @return this */ public Builder maxWebSocketFrameSize(int maxWebSocketFrameSize) { this.maxWebSocketFrameSize = maxWebSocketFrameSize; return this; } /** * Add a {@link SSLContextListener} * * @param listener * @return this */ public Builder sslContextListener(SSLContextListener listener) { this.contextListener = listener; return this; } /** * Set the mapping path. If you have worked with Servlet, the mapping path is equivalent to the servlet path. * * @param mappingPath the path under which the application will be mapped. * @return this */ public Builder mappingPath(String mappingPath) { this.mappingPath = mappingPath; return this; } /** * Enable WebSokcet Pong message. Disabled by default. * * @param enablePong Enable WebSokcet Pong message * @return this */ public Builder enablePong(boolean enablePong) { this.enablePong = enablePong; return this; } /** * When {@link #aggregateRequestBodyInMemory} is true,the maximum length of the aggregated content. * If the length of the aggregated content exceeds this value, * a {@link org.jboss.netty.handler.codec.frame.TooLongFrameException} will be raised. * * @return this */ public Builder maxChunkContentLength(int maxChunkContentLength) { this.maxContentLength = maxChunkContentLength; return this; } /** * Set the path to the library when annotation scanning is enabled. Default is "./". Use this method * when your annotated resource is packaged inside the jar/zip. * * @param librariesPath the path to the library when annotation scanning is enabled. * @return this */ public Builder scanLibrary(String librariesPath) { this.librariesPath = librariesPath; return this; } /** * The path location of the atmosphere.xml file. * * @param atmosphereDotXmlPath path location of the atmosphere.xml file. * @return this */ public Builder configFile(String atmosphereDotXmlPath) { this.atmosphereDotXmlPath = atmosphereDotXmlPath; return this; } /** * The Executor to be used in providing (the) I/O boss-thread(s). * * @param bossExecutor ExecutorService to be used for boss threads. * @return this */ public Builder bossExecutor(ExecutorService bossExecutor) { this.bossExecutor = bossExecutor; return this; } /** * The Executor to be used in providing (the) I/O worker-thread(s). * * @param workerExecutor ExecutorService to be used for worker threads. * @return this */ public Builder workerExecutor(ExecutorService workerExecutor) { this.workerExecutor = workerExecutor; return this; } /** * The server's host * * @param host server's host * @return this */ public Builder host(String host) { this.host = host; return this; } /** * The server's port * * @param port server's port * @return this */ public Builder port(int port) { this.port = port; return this; } /** * Add some init param * * @param name the name * @param value the value * @return this */ public Builder initParam(String name, String value) { initParams.put(name, value); return this; } /** * Add a path to scan when looking for static resources like javascript file, html, etc. * * @param path * @return this */ public Builder resource(String path) { paths.add(path); return this; } /** * Add an {@link AtmosphereHandler} that will be mapped to the specified path * * @param path a mapping path * @param c an {@link AtmosphereHandler} * @return this */ public Builder resource(String path, AtmosphereHandler c) { handlers.put(path, c); return this; } /** * Add an {@link WebSocketHandler} that will be mapped to the specified path * * @param path a mapping path * @param c an {@link AtmosphereHandler} * @return this */ public Builder resource(String path, WebSocketHandler c) { webSocketHandlers.put(path, c); return this; } /** * Add an {@link Servlet} that will be mapped to the specified path * * @param path a mapping path * @param c an {@link Servlet} * @return this */ public Builder resource(String path, Servlet c) { handlers.put(path, new ReflectorServletProcessor(c)); return this; } /** * Add an {@link Handler} mapped to the default, which is '/*' * * @param handler {@link Handler} * @return this */ public Builder resource(Handler handler) { return resource("/*", handler); } /** * Add an {@link Handler} that will be mapped to the specified path * * @param handler {@link Handler} * @return this */ public Builder resource(String path, final Handler handler) { handlers.put(path, new AbstractReflectorAtmosphereHandler() { @Override public void onRequest(AtmosphereResource resource) throws IOException { handler.handle(resource); } @Override public void destroy() { } }); return this; } /** * Add an annotated class. The annotation can be from Atmosphere or Jersey. * * @param c an annotated class. The annotation can be from Atmosphere or Jersey. * @return this */ public Builder resource(Class<?> c) { packages.add(c); return this; } /** * Add an {@link AtmosphereHandler} or {@link Servlet} class * * @param path a mapping path * @param c an {@link AtmosphereHandler} or {@link Servlet} class * @return this */ public Builder resource(String path, Class<?> c) { try { if (AtmosphereHandler.class.isAssignableFrom(c)) { handlers.put(path, AtmosphereHandler.class.cast(c.newInstance())); } else if (WebSocketHandler.class.isAssignableFrom(c)) { webSocketHandlers.put(path, WebSocketHandler.class.cast(c.newInstance())); } else if (Servlet.class.isAssignableFrom(c)) { handlers.put(path, new ReflectorServletProcessor(Servlet.class.cast(c.newInstance()))); } else { throw new IllegalStateException("You class must implements AtmosphereHandler or be a Servlet"); } } catch (Exception ex) { logger.error("Invalid resource {}", c); } return this; } /** * Configure the default {@link Broadcaster} * * @param broadcasterClass a Broadcaster * @return this */ public Builder broadcaster(Class<? extends Broadcaster> broadcasterClass) { this.broadcasterClass = broadcasterClass; return this; } /** * Configure the default {@link BroadcasterFactory} * * @param broadcasterFactory a BroadcasterFactory's class * @return this */ public Builder broadcasterFactory(BroadcasterFactory broadcasterFactory) { this.broadcasterFactory = broadcasterFactory; return this; } /** * Configure the default {@link BroadcasterCache} * * @param broadcasterCache a BroadcasterCache's class * @return this */ public Builder broadcasterCache(Class<? extends BroadcasterCache> broadcasterCache) { this.broadcasterCache = broadcasterCache; return this; } /** * Configure the default {@link WebSocketProtocol} * * @param webSocketProtocol a WebSocketProtocol's class * @return this */ public Builder webSocketProtocol(Class<? extends WebSocketProtocol> webSocketProtocol) { this.webSocketProtocol = webSocketProtocol; return this; } /** * Add an {@link AtmosphereInterceptor} * * @param interceptor an {@link AtmosphereInterceptor} * @return this */ public Builder interceptor(AtmosphereInterceptor interceptor) { interceptors.add(interceptor); return this; } /** * Exclude an {@link AtmosphereInterceptor} from being added, at startup, by Atmosphere. The default's {@link AtmosphereInterceptor} * are candidates for being excluded * * @param interceptor an {@link AtmosphereInterceptor} * @return this */ public Builder excludeInterceptor(String interceptor) { excludedInterceptors.add(interceptor); return this; } /** * Add a {@link ChannelUpstreamHandler}. All will be executed before {@link BridgeRuntime} * * @param h {@link ChannelUpstreamHandler} * @return this; */ public Builder channelUpstreamHandler(ChannelInboundHandler h) { nettyHandlers.addLast(h); return this; } /** * Set to false to override the default behavior when writing bytes, which is use chunking. When set to false * the {@link org.jboss.netty.handler.stream.ChunkedWriteHandler} will not be added to the Netty's {@link org.jboss.netty.channel.ChannelPipeline} * <p/> * This is strongly recommended to turn chunking to false if you are using websocket to get better performance. * * @param supportChunking false to disable. * @return this */ public Builder supportChunking(boolean supportChunking) { this.supportChunking = supportChunking; return this; } /** * By default, Nettosphere aggregate the HTTP request's body in memory an invoke an Atmosphere's components with * a single {@link AtmosphereResource}. Setting supportChunkAggregator to false will instead invoke Atmosphere's component * with a new {@link AtmosphereResource} each time the request's body is read in memory. Setting to false * may significantly increase the performance and reduce memory footprint. Note that setting this value to false * may deliver to your Atmosphere's component partial body, so your application must make sure to aggregate the * body before parsing the data if needed. For example, if you are using JSON as format, make sure you parse the * data incrementally. * * @param aggregateRequestBodyInMemory false to disable. * @return this */ public Builder aggregateRequestBodyInMemory(boolean aggregateRequestBodyInMemory) { this.aggregateRequestBodyInMemory = aggregateRequestBodyInMemory; return this; } /** * Set Netty's Bootstrap 'child.tcpDelay' * * @param socketNoTcpDelay * @return this */ public Builder socketNoTcpDelay(boolean socketNoTcpDelay) { this.socketNoTcpDelay = socketNoTcpDelay; return this; } /** * Set Netty's Bootstrap 'child.keepAlive' * * @param socketKeepAlive * @return this */ public Builder socketKeepAlive(boolean socketKeepAlive) { this.socketKeepAlive = socketKeepAlive; return this; } /** * Do not decode {@link org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame} into a String and instead pass * it to the {@link org.atmosphere.websocket.WebSocketProcessor} as binary. * * @param textFrameAsBinary * @return this */ public Builder textFrameAsBinary(boolean textFrameAsBinary) { this.textFrameAsBinary = textFrameAsBinary; return this; } /** * A coma delimited of allowed WebSocket Sub Protocol (Sec-WebSocket-Protocol) * * @param subProtocols A coma delimited of allowed WebSocket Sub Protocol * @return this */ public Builder subProtocols(String subProtocols) { this.subProtocols = subProtocols; return this; } /** * Proxy {@link org.atmosphere.cpr.AtmosphereRequest}, {@link AtmosphereResource} and {@link org.atmosphere.cpr.AtmosphereRequest} * with no ops implementations. * <p/> * Set it to true only if you are using WebSocket with a your own implementation of {@link org.atmosphere.websocket.WebSocketProcessor}. The WebSocketProcessor MUST not invoked those objects and only use the {@link org.atmosphere.websocket.WebSocket} API. * <p/> * Default is false * * @param noInternalAlloc * @return this */ public Builder noInternalAlloc(boolean noInternalAlloc) { this.noInternalAlloc = noInternalAlloc; return this; } /** * Write binary frame when websocket transport is used. * * @param binaryWrite true or false * @return this */ public Builder binaryWrite(boolean binaryWrite) { this.binaryWrite = binaryWrite; return this; } /** * Build an instance of this class. * * @return this; */ public Config build() { if (paths.isEmpty()) { paths.add("/"); } if (context != null) { if (enabledCipherSuites == null) { throw new IllegalStateException("Incomplete Config: SSLContext requires cipherSuites to be specified."); } if (contextListener == null) { contextListener = new SSLContextListener() { @Override public void onPostCreate(SSLEngine e) { e.setEnabledCipherSuites(enabledCipherSuites); e.setUseClientMode(false); } }; } } return new Config(this); } /** * Set ServletContext Attribute * * @param name * @param value * @return this */ public Builder servletContextAttribute(String name, Object value) { servletContextAttributes.put(name, value); return this; } /** * Use {@link io.netty.channel.epoll.EpollEventLoopGroup} * @See http://netty.io/wiki/native-transports.html * * @return this */ public Builder epoll(boolean epoll) { this.epoll = epoll; return this; } } }