/** * Client app, which tests deployed ADD-service. * * @author Alexey Stashok */ @SuppressWarnings("unchecked") public class AddClient { private static final Logger LOGGER = Grizzly.logger(PUServer.class); public static void main(String[] args) throws Exception { Connection connection = null; // Construct the client filter chain final FilterChainBuilder puFilterChainBuilder = FilterChainBuilder.stateless() // Add TransportFilter .add(new TransportFilter()) // Add ADD-service message parser/serializer .add(new AddClientMessageFilter()) // Add Result reporter Filter .add(new ResultFilter()); // Construct TCPNIOTransport final TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build(); transport.setProcessor(puFilterChainBuilder.build()); try { // Start transport.start(); // Create the client connection final Future<Connection> connectFuture = transport.connect("localhost", PUServer.PORT); connection = connectFuture.get(10, TimeUnit.SECONDS); LOGGER.info("Enter 2 numbers separated by space (<value1> <value2>) end press <enter>."); LOGGER.info("Type q and enter to exit."); // Read user input and communicate the ADD-service String line; BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, Charsets.ASCII_CHARSET)); while ((line = reader.readLine()) != null) { if ("q".equals(line)) { break; } // Parse user input final int value1; final int value2; try { final String[] values = line.split(" "); value1 = Integer.parseInt(values[0].trim()); value2 = Integer.parseInt(values[1].trim()); } catch (Exception e) { LOGGER.warning("Bad format, repeat pls"); continue; } // send the request to ADD-service final GrizzlyFuture<WriteResult> writeFuture = connection.write(new AddRequestMessage(value1, value2)); final WriteResult result = writeFuture.get(10, TimeUnit.SECONDS); assert result != null; } } finally { // Close the client connection if (connection != null) { connection.closeSilently(); } // Shutdown the transport transport.shutdownNow(); } } // Simple reporting Filter private static final class ResultFilter extends BaseFilter { @Override public NextAction handleRead(FilterChainContext ctx) throws IOException { // Take ADD-service response final AddResponseMessage addResponseMessage = ctx.getMessage(); // do output LOGGER.log(Level.INFO, "Result={0}", addResponseMessage.getResult()); return ctx.getStopAction(); } } }
/** * Generic {@link GrizzlyListener} implementation, which is not HTTP dependent, and can support any * Transport configuration, based on {@link FilterChain}. * * @author Alexey Stashok */ public class GenericGrizzlyListener implements GrizzlyListener { /** The logger to use for logging messages. */ private static final Logger LOGGER = Grizzly.logger(GenericGrizzlyListener.class); protected volatile String name; protected volatile InetAddress address; protected volatile int port; protected NIOTransport transport; protected FilterChain rootFilterChain; private volatile ExecutorService workerExecutorService; private volatile ExecutorService auxExecutorService; private volatile DelayedExecutor delayedExecutor; private volatile long transactionTimeoutMillis = -1; // ajp enabled flag protected volatile boolean isAjpEnabled; // spdy enabled flag protected volatile boolean isSpdyEnabled; // websocket enabled flag protected volatile boolean isWebSocketEnabled; // comet enabled flag protected volatile boolean isCometEnabled; @Override public String getName() { return name; } protected final void setName(String name) { this.name = name; } @Override public InetAddress getAddress() { return address; } protected final void setAddress(InetAddress inetAddress) { address = inetAddress; } @Override public int getPort() { return port; } protected void setPort(int port) { this.port = port; } @Override public void start() throws IOException { startDelayedExecutor(); ((SocketBinder) transport).bind(new InetSocketAddress(address, port)); transport.start(); } @Override public void stop() throws IOException { stopDelayedExecutor(); final NIOTransport localTransport = transport; transport = null; if (localTransport != null) { localTransport.shutdownNow(); } if (workerExecutorService != null) { final ExecutorService localExecutorService = workerExecutorService; workerExecutorService = null; localExecutorService.shutdownNow(); } rootFilterChain = null; } @Override public void destroy() {} @Override public void processDynamicConfigurationChange( ServiceLocator habitat, PropertyChangeEvent[] events) {} @Override public <T> T getAdapter(Class<T> adapterClass) { return null; } public <E> List<E> getFilters(Class<E> clazz) { return getFilters(clazz, rootFilterChain, new ArrayList<E>(2)); } public org.glassfish.grizzly.Transport getTransport() { return transport; } /** * Returns <tt>true</tt> if AJP (or JK) is enabled for this listener, or <tt>false</tt> otherwise. */ public boolean isAjpEnabled() { return isAjpEnabled; } /** Returns <tt>true</tt> if SPDY is enabled for this listener, or <tt>false</tt> otherwise. */ public boolean isSpdyEnabled() { return isSpdyEnabled; } /** * Returns <tt>true</tt> if WebSocket is enabled for this listener, or <tt>false</tt> otherwise. */ public boolean isWebSocketEnabled() { return isWebSocketEnabled; } /** Returns <tt>true</tt> if Comet is enabled for this listener, or <tt>false</tt> otherwise. */ public boolean isCometEnabled() { return isCometEnabled; } @SuppressWarnings({"unchecked"}) public static <E> List<E> getFilters(Class<E> clazz, FilterChain filterChain, List<E> filters) { for (final Filter filter : filterChain) { if (clazz.isAssignableFrom(filter.getClass())) { filters.add((E) filter); } if (PUFilter.class.isAssignableFrom(filter.getClass())) { final Set<PUProtocol> puProtocols = ((PUFilter) filter).getProtocols(); for (PUProtocol puProtocol : puProtocols) { getFilters(clazz, puProtocol.getFilterChain(), filters); } } } return filters; } /* * Configures the given grizzlyListener. * * @param networkListener The NetworkListener to configure */ // TODO: Must get the information from domain.xml Config objects. // TODO: Pending Grizzly issue 54 @Override public void configure(final ServiceLocator habitat, final NetworkListener networkListener) throws IOException { setName(networkListener.getName()); setAddress(InetAddress.getByName(networkListener.getAddress())); setPort(Integer.parseInt(networkListener.getPort())); final FilterChainBuilder filterChainBuilder = FilterChainBuilder.stateless(); configureTransport(networkListener, networkListener.findTransport(), filterChainBuilder); configureProtocol(habitat, networkListener, networkListener.findProtocol(), filterChainBuilder); configureThreadPool(habitat, networkListener, networkListener.findThreadPool()); rootFilterChain = filterChainBuilder.build(); transport.setProcessor(rootFilterChain); } protected void configureTransport( final NetworkListener networkListener, final Transport transportConfig, final FilterChainBuilder filterChainBuilder) { final String transportClassName = transportConfig.getClassname(); if (TCPNIOTransport.class.getName().equals(transportClassName)) { transport = configureTCPTransport(transportConfig); } else if (UDPNIOTransport.class.getName().equals(transportClassName)) { transport = configureUDPTransport(); } else { throw new GrizzlyConfigException("Unsupported transport type " + transportConfig.getName()); } String selectorName = transportConfig.getSelectionKeyHandler(); if (selectorName != null) { if (getSelectionKeyHandlerByName(selectorName, transportConfig) != null) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.warning( "Element, selection-key-handler, has been deprecated and is effectively ignored by the runtime."); } } } if (!Transport.BYTE_BUFFER_TYPE.equalsIgnoreCase(transportConfig.getByteBufferType())) { transport.setMemoryManager( new ByteBufferManager( true, AbstractMemoryManager.DEFAULT_MAX_BUFFER_SIZE, ByteBufferManager.DEFAULT_SMALL_BUFFER_SIZE)); } final int acceptorThreads = Integer.parseInt(transportConfig.getAcceptorThreads()); transport.setSelectorRunnersCount(acceptorThreads); final int readSize = Integer.parseInt(transportConfig.getSocketReadBufferSize()); if (readSize > 0) { transport.setReadBufferSize(readSize); } final int writeSize = Integer.parseInt(transportConfig.getSocketWriteBufferSize()); if (writeSize > 0) { transport.setWriteBufferSize(writeSize); } final ThreadPoolConfig kernelThreadPoolConfig = transport.getKernelThreadPoolConfig(); kernelThreadPoolConfig.setPoolName(networkListener.getName() + "-kernel"); if (acceptorThreads > 0) { kernelThreadPoolConfig.setCorePoolSize(acceptorThreads).setMaxPoolSize(acceptorThreads); } transport.setIOStrategy(loadIOStrategy(transportConfig.getIoStrategy())); transport.setNIOChannelDistributor( new RoundRobinConnectionDistributor( transport, Boolean.parseBoolean(transportConfig.getDedicatedAcceptorEnabled()))); filterChainBuilder.add(new TransportFilter()); } protected NIOTransport configureTCPTransport(final Transport transportConfig) { final TCPNIOTransport tcpTransport = configureDefaultThreadPoolConfigs(TCPNIOTransportBuilder.newInstance().build()); tcpTransport.setTcpNoDelay(Boolean.parseBoolean(transportConfig.getTcpNoDelay())); tcpTransport.setLinger(Integer.parseInt(transportConfig.getLinger())); tcpTransport.setWriteTimeout( Long.parseLong(transportConfig.getWriteTimeoutMillis()), TimeUnit.MILLISECONDS); tcpTransport.setReadTimeout( Long.parseLong(transportConfig.getReadTimeoutMillis()), TimeUnit.MILLISECONDS); tcpTransport.setServerConnectionBackLog( Integer.parseInt(transportConfig.getMaxConnectionsCount())); return tcpTransport; } protected NIOTransport configureUDPTransport() { return configureDefaultThreadPoolConfigs(UDPNIOTransportBuilder.newInstance().build()); } protected <T extends NIOTransport> T configureDefaultThreadPoolConfigs(final T transport) { transport.setKernelThreadPoolConfig(ThreadPoolConfig.defaultConfig()); transport.setWorkerThreadPoolConfig(ThreadPoolConfig.defaultConfig()); return transport; } protected void configureProtocol( final ServiceLocator habitat, final NetworkListener networkListener, final Protocol protocol, final FilterChainBuilder filterChainBuilder) { if (Boolean.valueOf(protocol.getSecurityEnabled())) { configureSsl(habitat, getSsl(protocol), filterChainBuilder); } configureSubProtocol(habitat, networkListener, protocol, filterChainBuilder); } protected void configureSubProtocol( final ServiceLocator habitat, final NetworkListener networkListener, final Protocol protocol, final FilterChainBuilder filterChainBuilder) { if (protocol.getHttp() != null) { final Http http = protocol.getHttp(); configureHttpProtocol( habitat, networkListener, http, filterChainBuilder, Boolean.valueOf(protocol.getSecurityEnabled())); } else if (protocol.getPortUnification() != null) { // Port unification final PortUnification pu = protocol.getPortUnification(); final String puFilterClassname = pu.getClassname(); PUFilter puFilter = null; if (puFilterClassname != null) { try { puFilter = Utils.newInstance(habitat, PUFilter.class, puFilterClassname, puFilterClassname); configureElement(habitat, networkListener, pu, puFilter); } catch (Exception e) { LOGGER.log( Level.WARNING, "Can not initialize port unification filter: " + puFilterClassname + " default filter will be used instead", e); } } if (puFilter == null) { puFilter = new PUFilter(); } List<org.glassfish.grizzly.config.dom.ProtocolFinder> findersConfig = pu.getProtocolFinder(); for (org.glassfish.grizzly.config.dom.ProtocolFinder finderConfig : findersConfig) { final String finderClassname = finderConfig.getClassname(); try { final ProtocolFinder protocolFinder = Utils.newInstance(habitat, ProtocolFinder.class, finderClassname, finderClassname); configureElement(habitat, networkListener, finderConfig, protocolFinder); final Protocol subProtocol = finderConfig.findProtocol(); final FilterChainBuilder subProtocolFilterChainBuilder = puFilter.getPUFilterChainBuilder(); // If subprotocol is secured - we need to wrap it under SSLProtocolFinder if (Boolean.valueOf(subProtocol.getSecurityEnabled())) { final PUFilter extraSslPUFilter = new PUFilter(); final Filter addedSSLFilter = configureSsl(habitat, getSsl(subProtocol), subProtocolFilterChainBuilder); subProtocolFilterChainBuilder.add(extraSslPUFilter); final FilterChainBuilder extraSslPUFilterChainBuilder = extraSslPUFilter.getPUFilterChainBuilder(); try { // temporary add SSL Filter, so subprotocol // will see it extraSslPUFilterChainBuilder.add(addedSSLFilter); configureSubProtocol( habitat, networkListener, subProtocol, extraSslPUFilterChainBuilder); } finally { // remove SSL Filter extraSslPUFilterChainBuilder.remove(addedSSLFilter); } extraSslPUFilter.register(protocolFinder, extraSslPUFilterChainBuilder.build()); puFilter.register( new SSLProtocolFinder(new SSLConfigurator(habitat, subProtocol.getSsl())), subProtocolFilterChainBuilder.build()); } else { configureSubProtocol( habitat, networkListener, subProtocol, subProtocolFilterChainBuilder); puFilter.register(protocolFinder, subProtocolFilterChainBuilder.build()); } } catch (Exception e) { LOGGER.log( Level.WARNING, "Can not initialize sub protocol. Finder: " + finderClassname, e); } } filterChainBuilder.add(puFilter); } else if (protocol.getHttpRedirect() != null) { filterChainBuilder.add(createHttpServerCodecFilter()); final HttpRedirectFilter filter = new HttpRedirectFilter(); filter.configure(habitat, networkListener, protocol.getHttpRedirect()); filterChainBuilder.add(filter); } else { ProtocolChainInstanceHandler pcihConfig = protocol.getProtocolChainInstanceHandler(); if (pcihConfig == null) { LOGGER.log(Level.WARNING, "Empty protocol declaration"); return; } ProtocolChain filterChainConfig = pcihConfig.getProtocolChain(); for (ProtocolFilter filterConfig : filterChainConfig.getProtocolFilter()) { final String filterClassname = filterConfig.getClassname(); try { final Filter filter = loadFilter(habitat, filterConfig.getName(), filterClassname); configureElement(habitat, networkListener, filterConfig, filter); filterChainBuilder.add(filter); } catch (Exception e) { LOGGER.log(Level.WARNING, "Can not initialize protocol filter: " + filterClassname, e); throw new IllegalStateException("Can not initialize protocol filter: " + filterClassname); } } } } protected static Filter configureSsl( final ServiceLocator habitat, final Ssl ssl, final FilterChainBuilder filterChainBuilder) { final SSLEngineConfigurator serverConfig = new SSLConfigurator(habitat, ssl); // final SSLEngineConfigurator clientConfig = new SSLConfigurator(habitat, ssl); // clientConfig.setClientMode(true); final SSLBaseFilter sslFilter = new SSLBaseFilter( serverConfig, // clientConfig, isRenegotiateOnClientAuthWant(ssl)); sslFilter.setHandshakeTimeout( Long.parseLong(ssl.getHandshakeTimeoutMillis()), TimeUnit.MILLISECONDS); filterChainBuilder.add(sslFilter); return sslFilter; } private static boolean isRenegotiateOnClientAuthWant(final Ssl ssl) { return ssl == null || Boolean.parseBoolean(ssl.getRenegotiateOnClientAuthWant()); } @SuppressWarnings({"unchecked"}) private static boolean configureElement( ServiceLocator habitat, NetworkListener networkListener, ConfigBeanProxy configuration, Object instance) { if (instance instanceof ConfigAwareElement) { ((ConfigAwareElement) instance).configure(habitat, networkListener, configuration); return true; } return false; } protected void configureThreadPool( final ServiceLocator habitat, final NetworkListener networkListener, final ThreadPool threadPool) { final String classname = threadPool.getClassname(); if (classname != null && !ThreadPool.DEFAULT_THREAD_POOL_CLASS_NAME.equals(classname)) { // Use custom thread pool try { final ExecutorService customThreadPool = Utils.newInstance(habitat, ExecutorService.class, classname, classname); if (customThreadPool != null) { if (!configureElement(habitat, networkListener, threadPool, customThreadPool)) { LOGGER.log( Level.INFO, "The ThreadPool configuration bean can not be " + "passed to the custom thread-pool: {0}" + " instance, because it's not {1}.", new Object[] {classname, ConfigAwareElement.class.getName()}); } workerExecutorService = customThreadPool; transport.setWorkerThreadPool(customThreadPool); return; } LOGGER.log(Level.WARNING, "Can not initalize custom thread pool: {0}", classname); } catch (Throwable t) { LOGGER.log(Level.WARNING, "Can not initalize custom thread pool: " + classname, t); } } try { // Use standard Grizzly thread pool workerExecutorService = GrizzlyExecutorService.createInstance( configureThreadPoolConfig(networkListener, threadPool)); transport.setWorkerThreadPool(workerExecutorService); } catch (NumberFormatException ex) { LOGGER.log(Level.WARNING, "Invalid thread-pool attribute", ex); } } protected ThreadPoolConfig configureThreadPoolConfig( final NetworkListener networkListener, final ThreadPool threadPool) { final int maxQueueSize = threadPool.getMaxQueueSize() == null ? Integer.MAX_VALUE : Integer.parseInt(threadPool.getMaxQueueSize()); final int minThreads = Integer.parseInt(threadPool.getMinThreadPoolSize()); final int maxThreads = Integer.parseInt(threadPool.getMaxThreadPoolSize()); final int timeout = Integer.parseInt(threadPool.getIdleThreadTimeoutSeconds()); final ThreadPoolConfig poolConfig = ThreadPoolConfig.defaultConfig(); poolConfig.setPoolName(networkListener.getName()); poolConfig.setCorePoolSize(minThreads); poolConfig.setMaxPoolSize(maxThreads); poolConfig.setQueueLimit(maxQueueSize); // we specify the classloader that loaded this class to ensure // we present the same initial classloader no matter what mode // GlassFish is being run in. // See http://java.net/jira/browse/GLASSFISH-19639 poolConfig.setInitialClassLoader(this.getClass().getClassLoader()); poolConfig.setKeepAliveTime(timeout < 0 ? Long.MAX_VALUE : timeout, TimeUnit.SECONDS); if (transactionTimeoutMillis > 0 && !Utils.isDebugVM()) { poolConfig.setTransactionTimeout( obtainDelayedExecutor(), transactionTimeoutMillis, TimeUnit.MILLISECONDS); } return poolConfig; } private DelayedExecutor obtainDelayedExecutor() { if (delayedExecutor != null) { return delayedExecutor; } final AtomicInteger threadCounter = new AtomicInteger(); auxExecutorService = Executors.newCachedThreadPool( new ThreadFactory() { @Override public Thread newThread(Runnable r) { final Thread newThread = new DefaultWorkerThread( transport.getAttributeBuilder(), getName() + "-expirer(" + threadCounter.incrementAndGet() + ")", null, r); newThread.setDaemon(true); return newThread; } }); delayedExecutor = new DelayedExecutor(auxExecutorService); return delayedExecutor; } protected void startDelayedExecutor() { if (delayedExecutor != null) { delayedExecutor.start(); } } protected void stopDelayedExecutor() { if (delayedExecutor != null) { final DelayedExecutor localDelayedExecutor = delayedExecutor; delayedExecutor = null; if (localDelayedExecutor != null) { localDelayedExecutor.stop(); localDelayedExecutor.destroy(); } final ExecutorService localThreadPool = auxExecutorService; auxExecutorService = null; if (localThreadPool != null) { localThreadPool.shutdownNow(); } } } @SuppressWarnings({"deprecation"}) protected void configureHttpProtocol( final ServiceLocator habitat, final NetworkListener networkListener, final Http http, final FilterChainBuilder filterChainBuilder, boolean secure) { transactionTimeoutMillis = Long.parseLong(http.getRequestTimeoutSeconds()) * 1000; filterChainBuilder.add( new IdleTimeoutFilter( obtainDelayedExecutor(), Integer.parseInt(http.getTimeoutSeconds()), TimeUnit.SECONDS)); final org.glassfish.grizzly.http.HttpServerFilter httpServerFilter = createHttpServerCodecFilter(http); final Set<ContentEncoding> contentEncodings = configureContentEncodings(http); for (ContentEncoding contentEncoding : contentEncodings) { httpServerFilter.addContentEncoding(contentEncoding); } // httpServerFilter.getMonitoringConfig().addProbes( // serverConfig.getMonitoringConfig().getHttpConfig().getProbes()); filterChainBuilder.add(httpServerFilter); final FileCache fileCache = configureHttpFileCache(http.getFileCache()); fileCache.initialize(obtainDelayedExecutor()); final FileCacheFilter fileCacheFilter = new FileCacheFilter(fileCache); // fileCache.getMonitoringConfig().addProbes( // serverConfig.getMonitoringConfig().getFileCacheConfig().getProbes()); filterChainBuilder.add(fileCacheFilter); final HttpServerFilter webServerFilter = new HttpServerFilter(getHttpServerFilterConfiguration(http), obtainDelayedExecutor()); final HttpHandler httpHandler = getHttpHandler(); httpHandler.setAllowEncodedSlash(GrizzlyConfig.toBoolean(http.getEncodedSlashEnabled())); webServerFilter.setHttpHandler(httpHandler); // webServerFilter.getMonitoringConfig().addProbes( // serverConfig.getMonitoringConfig().getWebServerConfig().getProbes()); filterChainBuilder.add(webServerFilter); configureSpdySupport(habitat, networkListener, http.getSpdy(), filterChainBuilder, secure); // TODO: evaluate comet/websocket support over SPDY. configureCometSupport(habitat, networkListener, http, filterChainBuilder); configureWebSocketSupport(habitat, networkListener, http, filterChainBuilder); configureAjpSupport(habitat, networkListener, http, filterChainBuilder); } protected void configureSpdySupport( final ServiceLocator locator, final NetworkListener listener, final Spdy spdyElement, final FilterChainBuilder builder, final boolean secure) { if (spdyElement != null && spdyElement.getEnabled()) { boolean isNpnMode = spdyElement.getMode() == null || "npn".equalsIgnoreCase(spdyElement.getMode()); // Spdy without NPN is supported, but warn that there may // be consequences to this configuration. if (!secure && isNpnMode) { LOGGER.log( Level.WARNING, "SSL is not enabled for listener {0}. SPDY support will be enabled, but will not be secured. Some clients may not be able to use SPDY in this configuration.", listener.getName()); } // first try to lookup a service appropriate for the mode // that has been configured. AddOn spdyAddon = locator.getService(AddOn.class, "spdy"); // if no service was found, attempt to load via reflection. if (spdyAddon == null) { Class<?> spdyMode; try { spdyMode = Utils.loadClass("org.glassfish.grizzly.spdy.SpdyMode"); } catch (ClassNotFoundException cnfe) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine( "Unable to load class org.glassfish.grizzly.spdy.SpdyMode. SPDY support cannot be enabled"); } return; } Object[] enumConstants = spdyMode.getEnumConstants(); Object mode = ((isNpnMode) ? enumConstants[1] : enumConstants[0]); spdyAddon = loadAddOn("org.glassfish.grizzly.spdy.SpdyAddOn", new Class[] {spdyMode}, mode); } if (spdyAddon != null) { // Configure SpdyAddOn configureElement(locator, listener, spdyElement, spdyAddon); // Spdy requires access to more information compared to the other addons // that are currently leveraged. As such, we'll need to mock out a // Grizzly NetworkListener to pass to the addon. This mock object will // only provide the information necessary for the addon to operate. // It will be important to keep this mock in sync with the details the // addon requires. spdyAddon.setup(createMockListener(), builder); isSpdyEnabled = true; } } } protected org.glassfish.grizzly.http.server.NetworkListener createMockListener() { final TCPNIOTransport transportLocal = (TCPNIOTransport) transport; return new org.glassfish.grizzly.http.server.NetworkListener("mock") { @Override public TCPNIOTransport getTransport() { return transportLocal; } @Override public boolean isSecure() { return true; } }; } protected void configureCometSupport( final ServiceLocator habitat, final NetworkListener networkListener, final Http http, final FilterChainBuilder filterChainBuilder) { if (GrizzlyConfig.toBoolean(http.getCometSupportEnabled())) { final AddOn cometAddOn = loadAddOn(habitat, "comet", "org.glassfish.grizzly.comet.CometAddOn"); if (cometAddOn != null) { configureElement(habitat, networkListener, http, cometAddOn); cometAddOn.setup(null, filterChainBuilder); isCometEnabled = true; } } } protected void configureWebSocketSupport( final ServiceLocator habitat, final NetworkListener listener, final Http http, final FilterChainBuilder filterChainBuilder) { final boolean websocketsSupportEnabled = Boolean.parseBoolean(http.getWebsocketsSupportEnabled()); if (websocketsSupportEnabled) { AddOn wsAddOn = loadAddOn(habitat, "websocket", "org.glassfish.grizzly.websockets.WebSocketAddOn"); if (wsAddOn != null) { if (!configureElement(habitat, listener, http, wsAddOn)) { // Dealing with a WebSocketAddOn created by reflection vs // an HK2 service. We need to pass the configuration data // manually via reflection. try { Method m = wsAddOn.getClass().getDeclaredMethod("setTimeoutInSeconds", Long.TYPE); m.invoke(wsAddOn, Long.parseLong(http.getWebsocketsTimeoutSeconds())); } catch (Exception e) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, e.toString(), e); } } } wsAddOn.setup(null, filterChainBuilder); isWebSocketEnabled = true; } } } protected void configureAjpSupport( final ServiceLocator habitat, final NetworkListener networkListener, final Http http, final FilterChainBuilder filterChainBuilder) { final boolean jkSupportEnabled = http.getJkEnabled() != null ? Boolean.parseBoolean(http.getJkEnabled()) : Boolean.parseBoolean(networkListener.getJkEnabled()); if (jkSupportEnabled) { final AddOn ajpAddOn = loadAddOn(habitat, "ajp", "org.glassfish.grizzly.http.ajp.AjpAddOn"); if (ajpAddOn != null) { configureElement(habitat, networkListener, http, ajpAddOn); ajpAddOn.setup(null, filterChainBuilder); isAjpEnabled = true; } } } /** Load {@link AddOn} with the specific service name and classname. */ private AddOn loadAddOn(ServiceLocator habitat, String name, String addOnClassName) { return Utils.newInstance(habitat, AddOn.class, name, addOnClassName); } /** Load {@link AddOn} with the specific service name and classname. */ private AddOn loadAddOn(String addOnClassName, Class[] argTypes, Object... args) { return Utils.newInstance(null, AddOn.class, name, addOnClassName, argTypes, args); } /** Load {@link Filter} with the specific service name and classname. */ private Filter loadFilter(ServiceLocator habitat, String name, String filterClassName) { return Utils.newInstance(habitat, Filter.class, name, filterClassName); } // private Filter loadFilter(ServiceLocator habitat, // String name, // String filterClassName, // Class<?>[] ctorArgTypes, // Object[] ctorArgs) { // return Utils.newInstance(habitat, Filter.class, name, filterClassName, ctorArgTypes, // ctorArgs); // } private org.glassfish.grizzly.http.HttpServerFilter createHttpServerCodecFilter() { return createHttpServerCodecFilter(null); } private org.glassfish.grizzly.http.HttpServerFilter createHttpServerCodecFilter(final Http http) { int maxRequestHeaders = MimeHeaders.MAX_NUM_HEADERS_DEFAULT; int maxResponseHeaders = MimeHeaders.MAX_NUM_HEADERS_DEFAULT; boolean isChunkedEnabled = true; int headerBufferLengthBytes = org.glassfish.grizzly.http.HttpServerFilter.DEFAULT_MAX_HTTP_PACKET_HEADER_SIZE; String defaultResponseType = null; if (http != null) { isChunkedEnabled = Boolean.parseBoolean(http.getChunkingEnabled()); headerBufferLengthBytes = Integer.parseInt(http.getHeaderBufferLengthBytes()); defaultResponseType = http.getDefaultResponseType(); maxRequestHeaders = Integer.parseInt(http.getMaxRequestHeaders()); maxResponseHeaders = Integer.parseInt(http.getMaxResponseHeaders()); } return createHttpServerCodecFilter( http, isChunkedEnabled, headerBufferLengthBytes, defaultResponseType, configureKeepAlive(http), obtainDelayedExecutor(), maxRequestHeaders, maxResponseHeaders); } protected org.glassfish.grizzly.http.HttpServerFilter createHttpServerCodecFilter( final Http http, final boolean isChunkedEnabled, final int headerBufferLengthBytes, final String defaultResponseType, final KeepAlive keepAlive, final DelayedExecutor delayedExecutor, final int maxRequestHeaders, final int maxResponseHeaders) { final org.glassfish.grizzly.http.HttpServerFilter httpCodecFilter = new org.glassfish.grizzly.http.HttpServerFilter( isChunkedEnabled, headerBufferLengthBytes, defaultResponseType, keepAlive, delayedExecutor, maxRequestHeaders, maxResponseHeaders); if (http != null) { // could be null for HTTP redirect httpCodecFilter.setMaxPayloadRemainderToSkip( Integer.parseInt(http.getMaxSwallowingInputBytes())); } return httpCodecFilter; } protected ServerFilterConfiguration getHttpServerFilterConfiguration(Http http) { final ServerFilterConfiguration serverFilterConfiguration = new ServerFilterConfiguration(); serverFilterConfiguration.setScheme(http.getScheme()); serverFilterConfiguration.setPassTraceRequest(true); serverFilterConfiguration.setTraceEnabled(Boolean.valueOf(http.getTraceEnabled())); int maxRequestParameters; try { maxRequestParameters = Integer.parseInt(http.getMaxRequestParameters()); } catch (NumberFormatException nfe) { maxRequestParameters = Http.MAX_REQUEST_PARAMETERS; } serverFilterConfiguration.setMaxRequestParameters(maxRequestParameters); serverFilterConfiguration.setMaxPostSize(Integer.parseInt(http.getMaxPostSizeBytes())); serverFilterConfiguration.setMaxFormPostSize(Integer.parseInt(http.getMaxFormPostSizeBytes())); serverFilterConfiguration.setMaxBufferedPostSize( Integer.parseInt(http.getMaxSavePostSizeBytes())); return serverFilterConfiguration; } protected HttpHandler getHttpHandler() { return new StaticHttpHandler("."); } /** Configure the Grizzly HTTP FileCache mechanism */ protected FileCache configureHttpFileCache(org.glassfish.grizzly.config.dom.FileCache cache) { final FileCache fileCache = new FileCache(); if (cache != null) { fileCache.setEnabled(GrizzlyConfig.toBoolean(cache.getEnabled())); fileCache.setSecondsMaxAge(Integer.parseInt(cache.getMaxAgeSeconds())); fileCache.setMaxCacheEntries(Integer.parseInt(cache.getMaxFilesCount())); fileCache.setMaxLargeFileCacheSize(Integer.parseInt(cache.getMaxCacheSizeBytes())); } else { fileCache.setEnabled(false); } return fileCache; } protected KeepAlive configureKeepAlive(final Http http) { int timeoutInSeconds = 60; int maxConnections = 256; if (http != null) { try { timeoutInSeconds = Integer.parseInt(http.getTimeoutSeconds()); } catch (NumberFormatException ex) { // String msg = _rb.getString("pewebcontainer.invalidKeepAliveTimeout"); String msg = "pewebcontainer.invalidKeepAliveTimeout"; msg = MessageFormat.format(msg, http.getTimeoutSeconds(), Integer.toString(timeoutInSeconds)); LOGGER.log(Level.WARNING, msg, ex); } try { maxConnections = Integer.parseInt(http.getMaxConnections()); } catch (NumberFormatException ex) { // String msg = // _rb.getString("pewebcontainer.invalidKeepAliveMaxConnections"); String msg = "pewebcontainer.invalidKeepAliveMaxConnections"; msg = MessageFormat.format(msg, http.getMaxConnections(), Integer.toString(maxConnections)); LOGGER.log(Level.WARNING, msg, ex); } } final KeepAlive keepAlive = new KeepAlive(); keepAlive.setIdleTimeoutInSeconds(timeoutInSeconds); keepAlive.setMaxRequestsCount(maxConnections); return keepAlive; } protected Set<ContentEncoding> configureContentEncodings(final Http http) { return configureCompressionEncodings(http); } protected Set<ContentEncoding> configureCompressionEncodings(Http http) { final String mode = http.getCompression(); int compressionMinSize = Integer.parseInt(http.getCompressionMinSizeBytes()); CompressionLevel compressionLevel; try { compressionLevel = CompressionLevel.getCompressionLevel(mode); } catch (IllegalArgumentException e) { try { // Try to parse compression as an int, which would give the // minimum compression size compressionLevel = CompressionLevel.ON; compressionMinSize = Integer.parseInt(mode); } catch (Exception ignore) { compressionLevel = CompressionLevel.OFF; } } final String compressableMimeTypesString = http.getCompressableMimeType(); final String noCompressionUserAgentsString = http.getNoCompressionUserAgents(); final String[] compressableMimeTypes = ((compressableMimeTypesString != null) ? compressableMimeTypesString.split(",") : new String[0]); final String[] noCompressionUserAgents = ((noCompressionUserAgentsString != null) ? noCompressionUserAgentsString.split(",") : new String[0]); final ContentEncoding gzipContentEncoding = new GZipContentEncoding( GZipContentEncoding.DEFAULT_IN_BUFFER_SIZE, GZipContentEncoding.DEFAULT_OUT_BUFFER_SIZE, new CompressionEncodingFilter( compressionLevel, compressionMinSize, compressableMimeTypes, noCompressionUserAgents, GZipContentEncoding.getGzipAliases())); final ContentEncoding lzmaEncoding = new LZMAContentEncoding( new CompressionEncodingFilter( compressionLevel, compressionMinSize, compressableMimeTypes, noCompressionUserAgents, LZMAContentEncoding.getLzmaAliases())); final Set<ContentEncoding> set = new HashSet<ContentEncoding>(2); set.add(gzipContentEncoding); set.add(lzmaEncoding); return set; } @SuppressWarnings("unchecked") private static IOStrategy loadIOStrategy(final String classname) { Class<? extends IOStrategy> strategy; if (classname == null) { strategy = WorkerThreadIOStrategy.class; } else { try { strategy = Utils.loadClass(classname); } catch (Exception e) { strategy = WorkerThreadIOStrategy.class; } } try { final Method m = strategy.getMethod("getInstance"); return (IOStrategy) m.invoke(null); } catch (Exception e) { throw new IllegalStateException( "Can not initialize IOStrategy: " + strategy + ". Error: " + e); } } private static Ssl getSsl(Protocol protocol) { Ssl ssl = protocol.getSsl(); if (ssl == null) { ssl = (Ssl) DefaultProxy.createDummyProxy(protocol, Ssl.class); } return ssl; } private static SelectionKeyHandler getSelectionKeyHandlerByName( final String name, final Transport transportConfig) { Transports transports = transportConfig.getParent(); List<SelectionKeyHandler> handlers = transports.getSelectionKeyHandler(); if (!handlers.isEmpty()) { for (SelectionKeyHandler handler : handlers) { if (handler.getName().equals(name)) { return handler; } } } return null; } }
/** * This example demonstrates the use of a {@link HttpHandler} to echo <code>HTTP</code> <code>POST * </code> data sent by the client, back to the client using non-blocking streams introduced in * Grizzly 2.0. * * <p>The is composed of two main parts (as nested classes of <code>BlockingHttpHandlerSample</code> * ) * * <ul> * <li>Client: This is a simple <code>HTTP</code> based on the Grizzly {@link * org.glassfish.grizzly.http.HttpClientFilter}. The client uses a custom {@link * org.glassfish.grizzly.filterchain.Filter} on top of the {@link * org.glassfish.grizzly.http.HttpClientFilter} to send the <code>POST</code> and read, and * ultimately display, the response from the server. To better demonstrate the callbacks * defined by {@link ReadHandler}, the client will send each data chunk two seconds apart. * <li>NoneBlockingEchoHandler: This {@link HttpHandler} is installed to the {@link * org.glassfish.grizzly.http.server.HttpServer} instance and associated with the path <code> * /echo</code>. The handler uses the {@link org.glassfish.grizzly.http.io.NIOReader} returned * by {@link org.glassfish.grizzly.http.server.Request#getReader()}. As data is received * asynchronously, the {@link ReadHandler} callbacks are invoked at this time data is then * written to the response. * </ul> */ public class NonBlockingHttpHandlerSample { private static final Logger LOGGER = Grizzly.logger(NonBlockingHttpHandlerSample.class); public static void main(String[] args) { // create a basic server that listens on port 8080. final HttpServer server = HttpServer.createSimpleServer(); final ServerConfiguration config = server.getServerConfiguration(); // Map the path, /echo, to the NonBlockingEchoHandler config.addHttpHandler(new NonBlockingEchoHandler(), "/echo"); try { server.start(); Client client = new Client(); client.run(); } catch (IOException ioe) { LOGGER.log(Level.SEVERE, ioe.toString(), ioe); } finally { server.shutdownNow(); } } // ---------------------------------------------------------- Nested Classes private static final class Client { private static final String HOST = "localhost"; private static final int PORT = 8080; public void run() throws IOException { final FutureImpl<String> completeFuture = SafeFutureImpl.create(); // Build HTTP client filter chain FilterChainBuilder clientFilterChainBuilder = FilterChainBuilder.newInstance(); // Add transport filter clientFilterChainBuilder.add(new TransportFilter()); // Add HttpClientFilter, which transforms Buffer <-> HttpContent clientFilterChainBuilder.add(new HttpClientFilter()); // Add ClientFilter clientFilterChainBuilder.add(new ClientFilter(completeFuture)); // Initialize Transport final TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build(); // Set filterchain as a Transport Processor transport.setFilterChain(clientFilterChainBuilder.build()); try { // start the transport transport.start(); Connection connection = null; // Connecting to a remote Web server Future<Connection> connectFuture = transport.connect(HOST, PORT); try { // Wait until the client connect operation will be completed // Once connection has been established, the POST will // be sent to the server. connection = connectFuture.get(10, TimeUnit.SECONDS); // Wait no longer than 30 seconds for the response from the // server to be complete. String result = completeFuture.get(30, TimeUnit.SECONDS); // Display the echoed content System.out.println("\nEchoed POST Data: " + result + '\n'); } catch (Exception e) { if (connection == null) { LOGGER.log(Level.WARNING, "Connection failed. Server is not listening."); } else { LOGGER.log(Level.WARNING, "Unexpected error communicating with the server."); } } finally { // Close the client connection if (connection != null) { connection.closeSilently(); } } } finally { // shutdownNow the transport transport.shutdownNow(); } } // ------------------------------------------------------ Nested Classes private static final class ClientFilter extends BaseFilter { private static final HeaderValue HOST_HEADER_VALUE = HeaderValue.newHeaderValue(HOST + ':' + PORT).prepare(); private static final String[] CONTENT = {"contentA-", "contentB-", "contentC-", "contentD"}; private FutureImpl<String> future; private StringBuilder sb = new StringBuilder(); // ---------------------------------------------------- Constructors private ClientFilter(FutureImpl<String> future) { this.future = future; } // ----------------------------------------- Methods from BaseFilter @SuppressWarnings({"unchecked"}) @Override public NextAction handleConnect(FilterChainContext ctx) throws IOException { System.out.println("\nClient connected!\n"); HttpRequestPacket request = createRequest(); System.out.println("Writing request:\n"); System.out.println(request.toString()); ctx.write(request); // write the request // for each of the content parts in CONTENT, wrap in a Buffer, // create the HttpContent to wrap the buffer and write the // content. MemoryManager mm = ctx.getMemoryManager(); for (int i = 0, len = CONTENT.length; i < len; i++) { HttpContent.Builder contentBuilder = request.httpContentBuilder(); Buffer b = Buffers.wrap(mm, CONTENT[i]); contentBuilder.content(b); HttpContent content = contentBuilder.build(); System.out.printf("(Client writing: %s)\n", b.toStringContent()); ctx.write(content); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } // since the request created by createRequest() is chunked, // we need to write the trailer to signify the end of the // POST data ctx.write(request.httpTrailerBuilder().build()); System.out.println("\n"); return ctx.getStopAction(); // discontinue filter chain execution } @Override public NextAction handleRead(FilterChainContext ctx) throws IOException { HttpContent c = (HttpContent) ctx.getMessage(); Buffer b = c.getContent(); if (b.hasRemaining()) { sb.append(b.toStringContent()); } // Last content from the server, set the future result so // the client can display the result and gracefully exit. if (c.isLast()) { future.result(sb.toString()); } return ctx.getStopAction(); // discontinue filter chain execution } // ------------------------------------------------- Private Methods private HttpRequestPacket createRequest() { HttpRequestPacket.Builder builder = HttpRequestPacket.builder(); builder.method("POST"); builder.protocol("HTTP/1.1"); builder.uri("/echo"); builder.chunked(true); HttpRequestPacket packet = builder.build(); packet.addHeader(Header.Host, HOST_HEADER_VALUE); return packet; } } } // END Client /** This handler using non-blocking streams to read POST data and echo it back to the client. */ private static class NonBlockingEchoHandler extends HttpHandler { // -------------------------------------------- Methods from HttpHandler @Override public void service(final Request request, final Response response) throws Exception { final char[] buf = new char[128]; final NIOReader in = request.getReader(); // put the stream in non-blocking mode final NIOWriter out = response.getWriter(); response.suspend(); // If we don't have more data to read - onAllDataRead() will be called in.notifyAvailable( new ReadHandler() { @Override public void onDataAvailable() throws Exception { System.out.printf("[onDataAvailable] echoing %d bytes\n", in.readyData()); echoAvailableData(in, out, buf); in.notifyAvailable(this); } @Override public void onError(Throwable t) { System.out.println("[onError]" + t); response.resume(); } @Override public void onAllDataRead() throws Exception { System.out.printf("[onAllDataRead] length: %d\n", in.readyData()); try { echoAvailableData(in, out, buf); } finally { try { in.close(); } catch (IOException ignored) { } try { out.close(); } catch (IOException ignored) { } response.resume(); } } }); } private void echoAvailableData(NIOReader in, NIOWriter out, char[] buf) throws IOException { while (in.isReady()) { int len = in.read(buf); out.write(buf, 0, len); } } } // END NonBlockingEchoHandler }
/** Utils for introspection and reflection */ public final class IntrospectionUtils { private static final Logger LOGGER = Grizzly.logger(IntrospectionUtils.class); /** * Find a method with the right name If found, call the method ( if param is int or boolean we'll * convert value to the right type before) - that means you can have setDebug(1). */ public static boolean setProperty(Object o, String name, String value) { if (dbg > 1) { d("setProperty(" + o.getClass() + " " + name + "=" + value + ")"); } String setter = "set" + capitalize(name); try { Method methods[] = findMethods(o.getClass()); Method setPropertyMethodVoid = null; Method setPropertyMethodBool = null; // First, the ideal case - a setFoo( String ) method for (int i = 0; i < methods.length; i++) { Class<?> paramT[] = methods[i].getParameterTypes(); if (setter.equals(methods[i].getName()) && paramT.length == 1 && "java.lang.String".equals(paramT[0].getName())) { methods[i].invoke(o, value); return true; } } // Try a setFoo ( int ) or ( boolean ) for (int i = 0; i < methods.length; i++) { boolean ok = true; if (setter.equals(methods[i].getName()) && methods[i].getParameterTypes().length == 1) { // match - find the type and invoke it Class<?> paramType = methods[i].getParameterTypes()[0]; Object params[] = new Object[1]; // Try a setFoo ( int ) if ("java.lang.Integer".equals(paramType.getName()) || "int".equals(paramType.getName())) { try { params[0] = new Integer(value); } catch (NumberFormatException ex) { ok = false; } // Try a setFoo ( long ) } else if ("java.lang.Long".equals(paramType.getName()) || "long".equals(paramType.getName())) { try { params[0] = new Long(value); } catch (NumberFormatException ex) { ok = false; } // Try a setFoo ( boolean ) } else if ("java.lang.Boolean".equals(paramType.getName()) || "boolean".equals(paramType.getName())) { params[0] = Boolean.valueOf(value); // Try a setFoo ( InetAddress ) } else if ("java.net.InetAddress".equals(paramType.getName())) { try { params[0] = InetAddress.getByName(value); } catch (UnknownHostException exc) { d("Unable to resolve host name:" + value); ok = false; } // Unknown type } else { d("Unknown type " + paramType.getName()); } if (ok) { methods[i].invoke(o, params); return true; } } // save "setProperty" for later if ("setProperty".equals(methods[i].getName())) { if (methods[i].getReturnType() == Boolean.TYPE) { setPropertyMethodBool = methods[i]; } else { setPropertyMethodVoid = methods[i]; } } } // Ok, no setXXX found, try a setProperty("name", "value") if (setPropertyMethodBool != null || setPropertyMethodVoid != null) { Object params[] = new Object[2]; params[0] = name; params[1] = value; if (setPropertyMethodBool != null) { try { return (Boolean) setPropertyMethodBool.invoke(o, params); } catch (IllegalArgumentException biae) { // the boolean method had the wrong // parameter types. lets try the other if (setPropertyMethodVoid != null) { setPropertyMethodVoid.invoke(o, params); return true; } else { throw biae; } } } else { setPropertyMethodVoid.invoke(o, params); return true; } } } catch (IllegalArgumentException ex2) { LOGGER.log(Level.INFO, "IAE " + o + " " + name + " " + value, ex2); } catch (SecurityException ex1) { if (dbg > 0) { d("SecurityException for " + o.getClass() + " " + name + "=" + value + ")"); } if (dbg > 1) { LOGGER.log(Level.WARNING, "", ex1); } } catch (IllegalAccessException iae) { if (dbg > 0) { d("IllegalAccessException for " + o.getClass() + " " + name + "=" + value + ")"); } if (dbg > 1) { LOGGER.log(Level.WARNING, "", iae); } } catch (InvocationTargetException ie) { if (dbg > 0) { d("InvocationTargetException for " + o.getClass() + " " + name + "=" + value + ")"); } if (dbg > 1) { LOGGER.log(Level.WARNING, "", ie); } } return false; } /** Reverse of Introspector.decapitalize */ public static String capitalize(String name) { if (name == null || name.length() == 0) { return name; } char chars[] = name.toCharArray(); chars[0] = Character.toUpperCase(chars[0]); return new String(chars); } // -------------------- other utils -------------------- @SuppressWarnings("UnusedDeclaration") public static void clear() { objectMethods.clear(); } static Map<Class<?>, Method[]> objectMethods = new HashMap<Class<?>, Method[]>(); @SuppressWarnings("unchecked") public static Method[] findMethods(Class<?> c) { Method methods[] = objectMethods.get(c); if (methods != null) { return methods; } methods = c.getMethods(); objectMethods.put(c, methods); return methods; } // debug -------------------- static final int dbg = 0; static void d(String s) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "IntrospectionUtils: {0}", s); } } }
/** * Test simple Ajp communication usecases. * * @author Alexey Stashok */ public class BasicAjpTest extends AjpTestBase { private static final Logger LOGGER = Grizzly.logger(BasicAjpTest.class); @Test public void test100ContinuePost() throws IOException, InstantiationException, Exception { HttpHandler httpHanlder = new HttpHandler() { @Override public void service(Request request, Response response) throws Exception { if (request.getHeader("Expect") != null) { response.sendAcknowledgement(); final int length = request.getContentLength(); final InputStream is = request.getInputStream(); for (int i = 0; i < length; i++) { final int c = is.read(); final int expected = (i % 'Z' - 'A') + 'A'; if (c != expected) { response.sendError( 400, "Unexpected char[" + i + "]. Expected: " + ((char) expected) + " but was: " + ((char) c) + "(" + c + ")"); return; } } response.setStatus(200, "FINE"); } else { response.sendError(500, "100-continue header has been lost?"); } } }; final int size = 1024; startHttpServer(httpHanlder); final AjpForwardRequestPacket headersPacket = new AjpForwardRequestPacket("POST", "/myresource", 80, PORT); headersPacket.addHeader("Content-Length", String.valueOf(size)); headersPacket.addHeader("Host", "localhost:80"); headersPacket.addHeader("Expect", "100-continue"); send(headersPacket.toByteArray()); byte[] postBody = new byte[size]; for (int i = 0; i < postBody.length; i++) { postBody[i] = (byte) ((i % 'Z' - 'A') + 'A'); } final AjpDataPacket dataPacket = new AjpDataPacket(postBody); send(dataPacket.toByteArray()); AjpResponse ajpResponse = Utils.parseResponse(readAjpMessage()); Assert.assertEquals(ajpResponse.getResponseMessage(), 200, ajpResponse.getResponseCode()); Assert.assertEquals("FINE", ajpResponse.getResponseMessage()); } @Test public void testStabilityAfterWrongMagic() throws Exception { HttpHandler a = new HttpHandler() { @Override public void service(Request request, Response response) throws Exception { response.setStatus(200, "FINE"); } }; startHttpServer(a); // Send junk data final Socket s = new Socket("localhost", PORT); try { s.getOutputStream().write("junkjunkjunk".getBytes()); s.getOutputStream().flush(); while (s.getInputStream().read() != -1) {} } finally { s.close(); } AjpForwardRequestPacket forward = new AjpForwardRequestPacket("GET", "/bob", PORT, 0); for (int i = 0; i < 1024; i++) { send(forward.toByteArray()); AjpResponse ajpResponse = Utils.parseResponse(readAjpMessage()); Assert.assertEquals(200, ajpResponse.getResponseCode()); Assert.assertEquals("FINE", ajpResponse.getResponseMessage()); closeClient(); } } @Test public void testPingPong() throws Exception { startHttpServer( new HttpHandler() { @Override public void service(Request request, Response response) throws Exception { response.setStatus(200, "FINE"); } }, "/"); final MemoryManager mm = httpServer.getListener(LISTENER_NAME).getTransport().getMemoryManager(); final Buffer request = mm.allocate(512); request.put((byte) 0x12); request.put((byte) 0x34); request.putShort((short) 1); request.put(AjpConstants.JK_AJP13_CPING_REQUEST); request.flip(); final Future<Buffer> responseFuture = send("localhost", PORT, request); Buffer response = responseFuture.get(10, TimeUnit.SECONDS); assertEquals('A', response.get()); assertEquals('B', response.get()); assertEquals((short) 1, response.getShort()); assertEquals(AjpConstants.JK_AJP13_CPONG_REPLY, response.get()); final AjpForwardRequestPacket headersPacket = new AjpForwardRequestPacket("GET", "/TestServlet/normal", 80, PORT); headersPacket.addHeader("Host", "localhost:80"); send(headersPacket.toByteArray()); AjpResponse ajpResponse = Utils.parseResponse(readAjpMessage()); Assert.assertEquals("FINE", ajpResponse.getResponseMessage()); } @Test public void testShutdownHandler() throws Exception { final FutureImpl<Boolean> shutdownFuture = SafeFutureImpl.create(); final ShutdownHandler shutDownHandler = new ShutdownHandler() { @Override public void onShutdown(Connection initiator) { shutdownFuture.result(true); } }; AjpAddOn myAjpAddon = new AjpAddOn() { @Override protected AjpHandlerFilter createAjpHandlerFilter() { final AjpHandlerFilter filter = new AjpHandlerFilter(); filter.addShutdownHandler(shutDownHandler); return filter; } }; final NetworkListener listener = httpServer.getListener(LISTENER_NAME); listener.deregisterAddOn(ajpAddon); listener.registerAddOn(myAjpAddon); startHttpServer( new HttpHandler() { @Override public void service(Request request, Response response) throws Exception {} }, "/"); final MemoryManager mm = listener.getTransport().getMemoryManager(); final Buffer request = mm.allocate(512); request.put((byte) 0x12); request.put((byte) 0x34); request.putShort((short) 1); request.put(AjpConstants.JK_AJP13_SHUTDOWN); request.flip(); send("localhost", PORT, request); final Boolean b = shutdownFuture.get(10, TimeUnit.SECONDS); assertTrue(b); } @Test public void testNullAttribute() throws Exception { final NetworkListener listener = httpServer.getListener(LISTENER_NAME); startHttpServer( new HttpHandler() { @Override public void service(Request request, Response response) throws Exception { final Set<String> attributeNames = request.getAttributeNames(); final boolean isOk = attributeNames.contains("JK_LB_ACTIVATION") && request.getAttribute("JK_LB_ACTIVATION") == null && attributeNames.contains("AJP_REMOTE_PORT") && "60955".equals(request.getAttribute("AJP_REMOTE_PORT")); if (isOk) { response.setStatus(200, "FINE"); } else { response.setStatus(500, "Attributes don't match"); } } }, "/SimpleWebApp/SimpleServlet"); final MemoryManager mm = listener.getTransport().getMemoryManager(); final Buffer request = Buffers.wrap(mm, Utils.loadResourceFile("null-attr-payload.dat")); Buffer responseBuffer = send("localhost", PORT, request).get(10, TimeUnit.SECONDS); // Successful response length is 37 bytes. This includes the status // line and a content-length boolean isFailure = responseBuffer.remaining() != 37; if (isFailure) { byte[] response = new byte[responseBuffer.remaining()]; responseBuffer.get(response); String hex = toHexString(response); fail("unexpected response length=" + response.length + " content=[" + hex + "]"); } } @Test public void testFormParameters() throws Exception { final Map<String, String[]> patternMap = new HashMap<String, String[]>(); patternMap.put("title", new String[] {"Developing PaaS Components"}); patternMap.put("authors", new String[] {"Shalini M"}); patternMap.put("price", new String[] {"100$"}); final NetworkListener listener = httpServer.getListener(LISTENER_NAME); startHttpServer( new HttpHandler() { @Override public void service(Request request, Response response) throws Exception { final Map<String, String[]> paramMap = request.getParameterMap(); boolean isOk = paramMap.size() == patternMap.size(); if (isOk) { // if sizes are equal - compare content for (Map.Entry<String, String[]> patternEntry : patternMap.entrySet()) { final String key = patternEntry.getKey(); final String[] value = patternEntry.getValue(); isOk = paramMap.containsKey(key) && Arrays.equals(value, paramMap.get(key)); if (!isOk) break; } } if (isOk) { response.setStatus(200, "FINE"); } else { response.setStatus(500, "Attributes don't match"); } } }, "/bookstore/BookStoreServlet"); final MemoryManager mm = listener.getTransport().getMemoryManager(); final Buffer requestPart1 = Buffers.wrap(mm, Utils.loadResourceFile("form-params-payload1.dat")); final Buffer requestPart2 = Buffers.wrap(mm, Utils.loadResourceFile("form-params-payload2.dat")); Buffer responseBuffer = send("localhost", PORT, Buffers.appendBuffers(mm, requestPart1, requestPart2)) .get(10, TimeUnit.SECONDS); // Successful response length is 37 bytes. This includes the status // line and a content-length boolean isFailure = responseBuffer.remaining() != 37; if (isFailure) { byte[] response = new byte[responseBuffer.remaining()]; responseBuffer.get(response); String hex = toHexString(response); fail("unexpected response length=" + response.length + " content=[" + hex + "]"); } } @Test public void testSslParams() throws Exception { final NetworkListener listener = httpServer.getListener(LISTENER_NAME); startHttpServer( new HttpHandler() { @Override public void service(Request request, Response response) throws Exception { boolean isOk = request.isSecure(); String error = "unknown"; if (isOk) { try { assertEquals( (Integer) 256, (Integer) request.getAttribute(SSLSupport.KEY_SIZE_KEY)); assertNotNull(request.getAttribute(SSLSupport.SESSION_ID_KEY)); assertNotNull(request.getAttribute(SSLSupport.CIPHER_SUITE_KEY)); assertNotNull(request.getAttribute(SSLSupport.CERTIFICATE_KEY)); } catch (Exception e) { error = e.getClass().getName() + ": " + e.getMessage(); isOk = false; } } if (isOk) { response.setStatus(200, "FINE"); } else { response.setStatus(500, error); } } }); final MemoryManager mm = listener.getTransport().getMemoryManager(); final Buffer request = Buffers.wrap(mm, Utils.loadResourceFile("get-secured.dat")); Buffer responseBuffer = send("localhost", PORT, request).get(10, TimeUnit.SECONDS); // Successful response length is 37 bytes. This includes the status // line and a content-length boolean isFailure = responseBuffer.remaining() != 37; if (isFailure) { byte[] response = new byte[responseBuffer.remaining()]; responseBuffer.get(response); String hex = toHexString(response); fail("unexpected response length=" + response.length + " content=[" + hex + "]"); } } @Test public void testAddresses() throws Exception { final String expectedRemoteAddr = "10.163.27.8"; final String expectedLocalAddr = "10.163.25.1"; final NetworkListener listener = httpServer.getListener(LISTENER_NAME); startHttpServer( new HttpHandler() { @Override public void service(Request request, Response response) throws Exception { boolean isOk = false; final StringBuilder errorBuilder = new StringBuilder(); try { String result = request.getRemoteAddr(); isOk = expectedRemoteAddr.equals(result); if (!isOk) { errorBuilder .append("Remote host don't match. Expected ") .append(expectedRemoteAddr) .append(" but was ") .append(result) .append('\n'); } String localName = request.getLocalName(); String localAddr = request.getLocalAddr(); isOk = expectedLocalAddr.equals(localName) && localName.equals(localAddr); if (!isOk) { errorBuilder .append("Local address and host don't match. Expected=") .append(expectedLocalAddr) .append(" Addr=") .append(localAddr) .append(" name=") .append(localName) .append('\n'); } } catch (Exception e) { errorBuilder.append(e.toString()); } if (isOk) { response.setStatus(200, "FINE"); } else { LOGGER.warning(errorBuilder.toString()); response.setStatus(500, "ERROR"); } } }); final MemoryManager mm = listener.getTransport().getMemoryManager(); final Buffer request = Buffers.wrap(mm, Utils.loadResourceFile("peer-addr-check.dat")); Buffer responseBuffer = send("localhost", PORT, request).get(60, TimeUnit.SECONDS); // Successful response length is 37 bytes. This includes the status // line and a content-length boolean isFailure = responseBuffer.remaining() != 37; if (isFailure) { byte[] response = new byte[responseBuffer.remaining()]; responseBuffer.get(response); String hex = toHexString(response); fail("unexpected response length=" + response.length + " content=[" + hex + "]"); } } @SuppressWarnings({"unchecked"}) private Future<Buffer> send(String host, int port, Buffer request) throws Exception { final FutureImpl<Buffer> future = SafeFutureImpl.create(); final FilterChainBuilder builder = FilterChainBuilder.newInstance(); builder.add(new TransportFilter()); builder.add(new AjpClientMessageFilter()); builder.add(new ResultFilter(future)); SocketConnectorHandler connectorHandler = TCPNIOConnectorHandler.builder( (TCPNIOTransport) httpServer.getListener(LISTENER_NAME).getTransport()) .filterChain(builder.build()) .build(); Future<Connection> connectFuture = connectorHandler.connect(host, port); final Connection connection = connectFuture.get(10, TimeUnit.SECONDS); connection.write(request); return future; } private String toHexString(byte[] response) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < response.length; i++) { sb.append(Integer.toHexString(response[i] & 0xFF)); if (i != response.length - 1) { sb.append(' '); } } return sb.toString(); } private static class ResultFilter extends BaseFilter { private final FutureImpl<Buffer> future; public ResultFilter(FutureImpl<Buffer> future) { this.future = future; } @Override public NextAction handleRead(FilterChainContext ctx) throws IOException { final Buffer content = ctx.getMessage(); try { future.result(content); } catch (Exception e) { future.failure(e); e.printStackTrace(); } return ctx.getStopAction(); } } }
/** * This class implements a file caching mechanism used to cache static resources. * * @author Jeanfrancois Arcand * @author Scott Oaks */ public class FileCache implements MonitoringAware<FileCacheProbe> { private static final File TMP_DIR = new File(System.getProperty("java.io.tmpdir")); static final String[] COMPRESSION_ALIASES = {"gzip"}; public enum CacheType { HEAP, MAPPED, FILE, TIMESTAMP } public enum CacheResult { OK_CACHED, OK_CACHED_TIMESTAMP, FAILED_CACHE_FULL, FAILED_ENTRY_EXISTS, FAILED } private static final Logger LOGGER = Grizzly.logger(FileCache.class); /** Cache size. */ private final AtomicInteger cacheSize = new AtomicInteger(); /** A {@link ByteBuffer} cache of static pages. */ private final ConcurrentMap<FileCacheKey, FileCacheEntry> fileCacheMap = DataStructures.<FileCacheKey, FileCacheEntry>getConcurrentMap(); private final FileCacheEntry NULL_CACHE_ENTRY = new FileCacheEntry(this); /** Specifies the maximum time in seconds a resource may be cached. */ private int secondsMaxAge = -1; /** The maximum entries in the {@link FileCache} */ private volatile int maxCacheEntries = 1024; /** The maximum size of a cached resource. */ private long minEntrySize = Long.MIN_VALUE; /** The maximum size of a cached resource. */ private long maxEntrySize = Long.MAX_VALUE; /** The maximum memory mapped bytes. */ private volatile long maxLargeFileCacheSize = Long.MAX_VALUE; /** The maximum cached bytes */ private volatile long maxSmallFileCacheSize = 1048576; /** The current cache size in bytes */ private AtomicLong mappedMemorySize = new AtomicLong(); /** The current cache size in bytes */ private AtomicLong heapSize = new AtomicLong(); /** Is the file cache enabled. */ private boolean enabled = true; private DelayedExecutor.DelayQueue<FileCacheEntry> delayQueue; /** Folder to store compressed cached files */ private volatile File compressedFilesFolder = TMP_DIR; /** Compression configuration, used to decide if cached resource has to be compressed or not */ private final CompressionConfig compressionConfig = new CompressionConfig(); /** <tt>true</tt>, if zero-copy file-send feature could be used, or <tt>false</tt> otherwise. */ private boolean fileSendEnabled; /** File cache probes */ protected final DefaultMonitoringConfig<FileCacheProbe> monitoringConfig = new DefaultMonitoringConfig<FileCacheProbe>(FileCacheProbe.class) { @Override public Object createManagementObject() { return createJmxManagementObject(); } }; // ---------------------------------------------------- Methods ----------// public void initialize(final DelayedExecutor delayedExecutor) { delayQueue = delayedExecutor.createDelayQueue(new EntryWorker(), new EntryResolver()); } /** * Add a resource to the cache. Unlike the {@link * #add(org.glassfish.grizzly.http.HttpRequestPacket, java.io.File)} this method adds a resource * to a cache but is not able to send the resource content to a client if client doesn't have the * latest version of this resource. */ public CacheResult add(final HttpRequestPacket request, final long lastModified) { return add(request, null, lastModified); } /** * Add a {@link File} resource to the cache. If a client comes with not the latest version of this * resource - the {@link FileCache} will return it the latest resource version. */ public CacheResult add(final HttpRequestPacket request, final File cacheFile) { return add(request, cacheFile, cacheFile.lastModified()); } /** Add a resource to the cache. */ protected CacheResult add( final HttpRequestPacket request, final File cacheFile, final long lastModified) { final String requestURI = request.getRequestURI(); if (requestURI == null) { return CacheResult.FAILED; } final String host = request.getHeader(Header.Host); final FileCacheKey key = new FileCacheKey(host, requestURI); if (fileCacheMap.putIfAbsent(key, NULL_CACHE_ENTRY) != null) { key.recycle(); return CacheResult.FAILED_ENTRY_EXISTS; } final int size = cacheSize.incrementAndGet(); // cache is full. if (size > getMaxCacheEntries()) { cacheSize.decrementAndGet(); fileCacheMap.remove(key); key.recycle(); return CacheResult.FAILED_CACHE_FULL; } final HttpResponsePacket response = request.getResponse(); final MimeHeaders headers = response.getHeaders(); final String contentType = response.getContentType(); final FileCacheEntry entry; if (cacheFile != null) { // If we have a file - try to create File-aware cache resource entry = createEntry(cacheFile); entry.setCanBeCompressed(canBeCompressed(cacheFile, contentType)); } else { entry = new FileCacheEntry(this); entry.type = CacheType.TIMESTAMP; } entry.key = key; entry.requestURI = requestURI; entry.lastModified = lastModified; entry.contentType = ContentType.newContentType(contentType); entry.xPoweredBy = headers.getHeader(Header.XPoweredBy); entry.date = headers.getHeader(Header.Date); entry.lastModifiedHeader = headers.getHeader(Header.LastModified); entry.host = host; entry.Etag = headers.getHeader(Header.ETag); entry.server = headers.getHeader(Header.Server); fileCacheMap.put(key, entry); notifyProbesEntryAdded(this, entry); final int secondsMaxAgeLocal = getSecondsMaxAge(); if (secondsMaxAgeLocal > 0) { delayQueue.add(entry, secondsMaxAgeLocal, TimeUnit.SECONDS); } return ((entry.type == CacheType.TIMESTAMP) ? CacheResult.OK_CACHED_TIMESTAMP : CacheResult.OK_CACHED); } /** * Returns {@link FileCacheEntry}. If {@link FileCacheEntry} has been found - this method also * sets correspondent {@link HttpResponsePacket} status code and reason phrase. */ public FileCacheEntry get(final HttpRequestPacket request) { // It should be faster than calculating the key hash code if (cacheSize.get() == 0) return null; final LazyFileCacheKey key = LazyFileCacheKey.create(request); final FileCacheEntry entry = fileCacheMap.get(key); key.recycle(); try { if (entry != null && entry != NULL_CACHE_ENTRY) { // determine if we need to send the cache entry bytes // to the user-agent final HttpStatus httpStatus = checkIfHeaders(entry, request); final boolean flushBody = (httpStatus == null); if (flushBody && entry.type == CacheType.TIMESTAMP) { return null; // this will cause control to be passed to the static handler } request.getResponse().setStatus(httpStatus != null ? httpStatus : HttpStatus.OK_200); notifyProbesEntryHit(this, entry); return entry; } notifyProbesEntryMissed(this, request); } catch (Exception e) { notifyProbesError(this, e); // If an unexpected exception occurs, try to serve the page // as if it wasn't in a cache. LOGGER.log( Level.WARNING, LogMessages.WARNING_GRIZZLY_HTTP_SERVER_FILECACHE_GENERAL_ERROR(), e); } return null; } protected void remove(final FileCacheEntry entry) { if (fileCacheMap.remove(entry.key) != null) { cacheSize.decrementAndGet(); } if (entry.type == FileCache.CacheType.MAPPED) { subMappedMemorySize(entry.bb.remaining()); } else if (entry.type == FileCache.CacheType.HEAP) { subHeapSize(entry.bb.remaining()); } notifyProbesEntryRemoved(this, entry); } protected Object createJmxManagementObject() { return MonitoringUtils.loadJmxObject( "org.glassfish.grizzly.http.server.filecache.jmx.FileCache", this, FileCache.class); } /** Creates {@link FileCacheEntry}. */ private FileCacheEntry createEntry(final File file) { FileCacheEntry entry = tryMapFileToBuffer(file); if (entry == null) { entry = new FileCacheEntry(this); entry.type = CacheType.FILE; } entry.plainFile = file; entry.plainFileSize = file.length(); return entry; } /** * Map the file to a {@link ByteBuffer} * * @return the preinitialized {@link FileCacheEntry} */ private FileCacheEntry tryMapFileToBuffer(final File file) { final long size = file.length(); if (size > getMaxEntrySize()) { return null; } final CacheType type; final ByteBuffer bb; FileChannel fileChannel = null; FileInputStream stream = null; try { if (size > getMinEntrySize()) { if (addMappedMemorySize(size) > getMaxLargeFileCacheSize()) { // Cache full subMappedMemorySize(size); return null; } type = CacheType.MAPPED; } else { if (addHeapSize(size) > getMaxSmallFileCacheSize()) { // Cache full subHeapSize(size); return null; } type = CacheType.HEAP; } stream = new FileInputStream(file); fileChannel = stream.getChannel(); bb = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size); if (type == CacheType.HEAP) { ((MappedByteBuffer) bb).load(); } } catch (Exception e) { notifyProbesError(this, e); return null; } finally { if (stream != null) { try { stream.close(); } catch (IOException ignored) { notifyProbesError(this, ignored); } } if (fileChannel != null) { try { fileChannel.close(); } catch (IOException ignored) { notifyProbesError(this, ignored); } } } final FileCacheEntry entry = new FileCacheEntry(this); entry.type = type; entry.plainFileSize = size; entry.bb = bb; return entry; } /** Checks if the {@link File} with the given content-type could be compressed. */ private boolean canBeCompressed(final File cacheFile, final String contentType) { switch (compressionConfig.getCompressionMode()) { case FORCE: return true; case OFF: return false; case ON: { if (cacheFile.length() < compressionConfig.getCompressionMinSize()) { return false; } return compressionConfig.checkMimeType(contentType); } default: throw new IllegalStateException("Unknown mode"); } } // ------------------------------------------------ Configuration Properties /** @return the maximum time, in seconds, a file may be cached. */ public int getSecondsMaxAge() { return secondsMaxAge; } /** * Sets the maximum time, in seconds, a file may be cached. * * @param secondsMaxAge max age of a cached file, in seconds. */ public void setSecondsMaxAge(int secondsMaxAge) { this.secondsMaxAge = secondsMaxAge; } /** @return the maximum number of files that may be cached. */ public int getMaxCacheEntries() { return maxCacheEntries; } /** * Sets the maximum number of files that may be cached. * * @param maxCacheEntries the maximum number of files that may be cached. */ public void setMaxCacheEntries(int maxCacheEntries) { this.maxCacheEntries = maxCacheEntries; } /** @return the minimum size, in bytes, a file must be in order to be cached in the heap cache. */ public long getMinEntrySize() { return minEntrySize; } /** * The maximum size, in bytes, a file must be in order to be cached in the heap cache. * * @param minEntrySize the maximum size, in bytes, a file must be in order to be cached in the * heap cache. */ public void setMinEntrySize(long minEntrySize) { this.minEntrySize = minEntrySize; } /** * @return the maximum size, in bytes, a resource may be before it can no longer be considered * cacheable. */ public long getMaxEntrySize() { return maxEntrySize; } /** * The maximum size, in bytes, a resource may be before it can no longer be considered cacheable. * * @param maxEntrySize the maximum size, in bytes, a resource may be before it can no longer be * considered cacheable. */ public void setMaxEntrySize(long maxEntrySize) { this.maxEntrySize = maxEntrySize; } /** @return the maximum size of the memory mapped cache for large files. */ public long getMaxLargeFileCacheSize() { return maxLargeFileCacheSize; } /** * Sets the maximum size, in bytes, of the memory mapped cache for large files. * * @param maxLargeFileCacheSize the maximum size, in bytes, of the memory mapped cache for large * files. */ public void setMaxLargeFileCacheSize(long maxLargeFileCacheSize) { this.maxLargeFileCacheSize = maxLargeFileCacheSize; } /** * @return the maximum size, in bytes, of the heap cache for files below the water mark set by * {@link #getMinEntrySize()}. */ public long getMaxSmallFileCacheSize() { return maxSmallFileCacheSize; } /** * The maximum size, in bytes, of the heap cache for files below the water mark set by {@link * #getMinEntrySize()}. * * @param maxSmallFileCacheSize the maximum size, in bytes, of the heap cache for files below the * water mark set by {@link #getMinEntrySize()}. */ public void setMaxSmallFileCacheSize(long maxSmallFileCacheSize) { this.maxSmallFileCacheSize = maxSmallFileCacheSize; } /** @return <code>true</code> if the {@link FileCache} is enabled, otherwise <code>false</code> */ public boolean isEnabled() { return enabled; } /** * Enables/disables the {@link FileCache}. By default, the {@link FileCache} is disabled. * * @param enabled <code>true</code> to enable the {@link FileCache}. */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** Returns the <tt>FileCache</tt> compression configuration settings. */ public CompressionConfig getCompressionConfig() { return compressionConfig; } /** Returns the folder to be used to store temporary compressed files. */ public File getCompressedFilesFolder() { return compressedFilesFolder; } /** Sets the folder to be used to store temporary compressed files. */ public void setCompressedFilesFolder(final File compressedFilesFolder) { this.compressedFilesFolder = compressedFilesFolder != null ? compressedFilesFolder : TMP_DIR; } /** * Returns <code>true</code> if File resources may be be sent using {@link * java.nio.channels.FileChannel#transferTo(long, long, java.nio.channels.WritableByteChannel)}. * * <p> * * <p>By default, this property will be true, except in the following cases: * * <p> * * <ul> * <li>JVM OS is HP-UX * <li>JVM OS is Linux, and the Oracle JVM in use is 1.6.0_17 or older * </ul> * * <p> * * <p> * * <p>Finally, if the connection between endpoints is secure, send file functionality will be * disabled regardless of configuration. * * @return <code>true</code> if resources will be sent using {@link * java.nio.channels.FileChannel#transferTo(long, long, * java.nio.channels.WritableByteChannel)}. * @since 2.3.5 */ public boolean isFileSendEnabled() { return fileSendEnabled; } /** * Configure whether or send-file support will enabled which allows sending {@link java.io.File} * resources via {@link java.nio.channels.FileChannel#transferTo(long, long, * java.nio.channels.WritableByteChannel)}. If disabled, the more traditional byte[] copy will be * used to send content. * * @param fileSendEnabled <code>true</code> to enable {@link * java.nio.channels.FileChannel#transferTo(long, long, * java.nio.channels.WritableByteChannel)} support. * @since 2.3.5 */ public void setFileSendEnabled(boolean fileSendEnabled) { this.fileSendEnabled = fileSendEnabled; } /** Creates a temporary compressed representation of the given cache entry. */ protected void compressFile(final FileCacheEntry entry) { try { final File tmpCompressedFile = File.createTempFile( String.valueOf(entry.plainFile.hashCode()), ".tmpzip", compressedFilesFolder); tmpCompressedFile.deleteOnExit(); InputStream in = null; OutputStream out = null; try { in = new FileInputStream(entry.plainFile); out = new GZIPOutputStream(new FileOutputStream(tmpCompressedFile)); final byte[] tmp = new byte[1024]; do { final int readNow = in.read(tmp); if (readNow == -1) { break; } out.write(tmp, 0, readNow); } while (true); } finally { if (in != null) { try { in.close(); } catch (IOException ignored) { } } if (out != null) { try { out.close(); } catch (IOException ignored) { } } } final long size = tmpCompressedFile.length(); switch (entry.type) { case HEAP: case MAPPED: { final FileInputStream cFis = new FileInputStream(tmpCompressedFile); try { final FileChannel cFileChannel = cFis.getChannel(); final MappedByteBuffer compressedBb = cFileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size); if (entry.type == CacheType.HEAP) { compressedBb.load(); } entry.compressedBb = compressedBb; } finally { cFis.close(); } break; } case FILE: { break; } default: throw new IllegalStateException("The type is not supported: " + entry.type); } entry.compressedFileSize = size; entry.compressedFile = tmpCompressedFile; } catch (IOException e) { LOGGER.log(Level.FINE, "Can not compress file: " + entry.plainFile, e); } } // ---------------------------------------------------- Monitoring --------// protected final long addHeapSize(long size) { return heapSize.addAndGet(size); } protected final long subHeapSize(long size) { return heapSize.addAndGet(-size); } /** * Return the heap space used for cache * * @return heap size */ public long getHeapCacheSize() { return heapSize.get(); } protected final long addMappedMemorySize(long size) { return mappedMemorySize.addAndGet(size); } protected final long subMappedMemorySize(long size) { return mappedMemorySize.addAndGet(-size); } /** * Return the size of Mapped memory used for caching * * @return Mapped memory size */ public long getMappedCacheSize() { return mappedMemorySize.get(); } /** * Check if the conditions specified in the optional If headers are satisfied. * * @return {@link HttpStatus} if the decision has been made and the response status has been * defined, or <tt>null</tt> otherwise */ private HttpStatus checkIfHeaders(final FileCacheEntry entry, final HttpRequestPacket request) throws IOException { HttpStatus httpStatus = checkIfMatch(entry, request); if (httpStatus == null) { httpStatus = checkIfModifiedSince(entry, request); if (httpStatus == null) { httpStatus = checkIfNoneMatch(entry, request); if (httpStatus == null) { httpStatus = checkIfUnmodifiedSince(entry, request); } } } return httpStatus; } /** * Check if the if-modified-since condition is satisfied. * * @return {@link HttpStatus} if the decision has been made and the response status has been * defined, or <tt>null</tt> otherwise */ private HttpStatus checkIfModifiedSince( final FileCacheEntry entry, final HttpRequestPacket request) throws IOException { try { final String reqModified = request.getHeader(Header.IfModifiedSince); if (reqModified != null) { // optimization - assume the String value sent in the // client's If-Modified-Since header is the same as what // was originally sent if (reqModified.equals(entry.lastModifiedHeader)) { return HttpStatus.NOT_MODIFIED_304; } long headerValue = convertToLong(reqModified); if (headerValue != -1) { long lastModified = entry.lastModified; // If an If-None-Match header has been specified, // If-Modified-Since is ignored. if ((request.getHeader(Header.IfNoneMatch) == null) && (lastModified - headerValue <= 1000)) { // The entity has not been modified since the date // specified by the client. This is not an error case. return HttpStatus.NOT_MODIFIED_304; } } } } catch (IllegalArgumentException illegalArgument) { notifyProbesError(this, illegalArgument); } return null; } /** * Check if the if-none-match condition is satisfied. * * @return {@link HttpStatus} if the decision has been made and the response status has been * defined, or <tt>null</tt> otherwise */ private HttpStatus checkIfNoneMatch(final FileCacheEntry entry, final HttpRequestPacket request) throws IOException { String headerValue = request.getHeader(Header.IfNoneMatch); if (headerValue != null) { String eTag = entry.Etag; boolean conditionSatisfied = false; if (!headerValue.equals("*")) { StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ","); while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) { String currentToken = commaTokenizer.nextToken(); if (currentToken.trim().equals(eTag)) { conditionSatisfied = true; } } } else { conditionSatisfied = true; } if (conditionSatisfied) { // For GET and HEAD, we should respond with // 304 Not Modified. // For every other method, 412 Precondition Failed is sent // back. final Method method = request.getMethod(); if (Method.GET.equals(method) || Method.HEAD.equals(method)) { return HttpStatus.NOT_MODIFIED_304; } else { return HttpStatus.PRECONDITION_FAILED_412; } } } return null; } /** * Check if the if-unmodified-since condition is satisfied. * * @return {@link HttpStatus} if the decision has been made and the response status has been * defined, or <tt>null</tt> otherwise */ private HttpStatus checkIfUnmodifiedSince( final FileCacheEntry entry, final HttpRequestPacket request) throws IOException { try { long lastModified = entry.lastModified; String h = request.getHeader(Header.IfUnmodifiedSince); if (h != null) { // optimization - assume the String value sent in the // client's If-Unmodified-Since header is the same as what // was originally sent if (h.equals(entry.lastModifiedHeader)) { // The entity has not been modified since the date // specified by the client. This is not an error case. return HttpStatus.PRECONDITION_FAILED_412; } long headerValue = convertToLong(h); if (headerValue != -1) { if (headerValue - lastModified <= 1000) { // The entity has not been modified since the date // specified by the client. This is not an error case. return HttpStatus.PRECONDITION_FAILED_412; } } } } catch (IllegalArgumentException illegalArgument) { notifyProbesError(this, illegalArgument); } return null; } /** * Check if the if-match condition is satisfied. * * @param request The servlet request we are processing * @param entry the FileCacheEntry to validate * @return {@link HttpStatus} if the decision has been made and the response status has been * defined, or <tt>null</tt> otherwise */ private HttpStatus checkIfMatch(final FileCacheEntry entry, final HttpRequestPacket request) throws IOException { String headerValue = request.getHeader(Header.IfMatch); if (headerValue != null) { if (headerValue.indexOf('*') == -1) { String eTag = entry.Etag; StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ","); boolean conditionSatisfied = false; while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) { String currentToken = commaTokenizer.nextToken(); if (currentToken.trim().equals(eTag)) { conditionSatisfied = true; } } // If none of the given ETags match, 412 Precondition failed is // sent back if (!conditionSatisfied) { return HttpStatus.PRECONDITION_FAILED_412; } } } return null; } /** {@inheritDoc} */ @Override public MonitoringConfig<FileCacheProbe> getMonitoringConfig() { return monitoringConfig; } /** * Notify registered {@link FileCacheProbe}s about the "entry added" event. * * @param fileCache the <tt>FileCache</tt> event occurred on. * @param entry entry been added */ protected static void notifyProbesEntryAdded( final FileCache fileCache, final FileCacheEntry entry) { final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (FileCacheProbe probe : probes) { probe.onEntryAddedEvent(fileCache, entry); } } } /** * Notify registered {@link FileCacheProbe}s about the "entry removed" event. * * @param fileCache the <tt>FileCache</tt> event occurred on. * @param entry entry been removed */ protected static void notifyProbesEntryRemoved( final FileCache fileCache, final FileCacheEntry entry) { final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (FileCacheProbe probe : probes) { probe.onEntryRemovedEvent(fileCache, entry); } } } /** * Notify registered {@link FileCacheProbe}s about the "entry hit event. * * @param fileCache the <tt>FileCache</tt> event occurred on. * @param entry entry been hit. */ protected static void notifyProbesEntryHit( final FileCache fileCache, final FileCacheEntry entry) { final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (FileCacheProbe probe : probes) { probe.onEntryHitEvent(fileCache, entry); } } } /** * Notify registered {@link FileCacheProbe}s about the "entry missed" event. * * @param fileCache the <tt>FileCache</tt> event occurred on. * @param request HTTP request. */ protected static void notifyProbesEntryMissed( final FileCache fileCache, final HttpRequestPacket request) { final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe(); if (probes != null && probes.length > 0) { for (FileCacheProbe probe : probes) { probe.onEntryMissedEvent( fileCache, request.getHeader(Header.Host), request.getRequestURI()); } } } /** * Notify registered {@link FileCacheProbe}s about the error. * * @param fileCache the <tt>FileCache</tt> event occurred on. */ protected static void notifyProbesError(final FileCache fileCache, final Throwable error) { final FileCacheProbe[] probes = fileCache.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (FileCacheProbe probe : probes) { probe.onErrorEvent(fileCache, error); } } } protected static long convertToLong(final String dateHeader) { if (dateHeader == null) return (-1L); final SimpleDateFormats formats = SimpleDateFormats.create(); try { // Attempt to convert the date header in a variety of formats long result = FastHttpDateFormat.parseDate(dateHeader, formats.getFormats()); if (result != (-1L)) { return result; } throw new IllegalArgumentException(dateHeader); } finally { formats.recycle(); } } private static class EntryWorker implements DelayedExecutor.Worker<FileCacheEntry> { @Override public boolean doWork(final FileCacheEntry element) { element.run(); return true; } } private static class EntryResolver implements DelayedExecutor.Resolver<FileCacheEntry> { @Override public boolean removeTimeout(FileCacheEntry element) { if (element.timeoutMillis != -1) { element.timeoutMillis = -1; return true; } return false; } @Override public long getTimeoutMillis(FileCacheEntry element) { return element.timeoutMillis; } @Override public void setTimeoutMillis(FileCacheEntry element, long timeoutMillis) { element.timeoutMillis = timeoutMillis; } } }
/** @since 2.2.11 */ public class JdkVersion implements Comparable<JdkVersion> { private static final Logger LOGGER = Grizzly.logger(JdkVersion.class); // take max 4 parts of the JDK version and cut the rest (usually the build number) private static final Pattern VERSION_PATTERN = Pattern.compile("([0-9]+)(\\.([0-9]+))?(\\.([0-9]+))?([_\\.]([0-9]+))?.*"); private static final boolean IS_UNSAFE_SUPPORTED; static { boolean isUnsafeFound; try { isUnsafeFound = Class.forName("sun.misc.Unsafe") != null; } catch (Throwable t) { isUnsafeFound = false; } IS_UNSAFE_SUPPORTED = isUnsafeFound; } private static final JdkVersion UNKNOWN_VERSION = new JdkVersion(-1, -1, -1, -1); private static final JdkVersion JDK_VERSION = parseVersion(System.getProperty("java.version")); private final int major; private final int minor; private final int maintenance; private final int update; // ------------------------------------------------------------ Constructors private JdkVersion(final int major, final int minor, final int maintenance, final int update) { this.major = major; this.minor = minor; this.maintenance = maintenance; this.update = update; } // ---------------------------------------------------------- Public Methods public static JdkVersion parseVersion(final String versionString) { try { final Matcher matcher = VERSION_PATTERN.matcher(versionString); if (matcher.matches()) { return new JdkVersion( parseInt(matcher.group(1)), parseInt(matcher.group(3)), parseInt(matcher.group(5)), parseInt(matcher.group(7))); } LOGGER.log(Level.FINE, "Can't parse the JDK version {0}", versionString); } catch (Exception e) { LOGGER.log(Level.FINE, "Error parsing the JDK version " + versionString, e); } return UNKNOWN_VERSION; } public static JdkVersion getJdkVersion() { return JDK_VERSION; } @SuppressWarnings("UnusedDeclaration") public int getMajor() { return major; } @SuppressWarnings("UnusedDeclaration") public int getMinor() { return minor; } @SuppressWarnings("UnusedDeclaration") public int getMaintenance() { return maintenance; } @SuppressWarnings("UnusedDeclaration") public int getUpdate() { return update; } /** * @return <tt>true</tt> if <code>sun.misc.Unsafe</code> is present in the current JDK version, or * <tt>false</tt> otherwise. * @since 2.3.6 */ public boolean isUnsafeSupported() { return IS_UNSAFE_SUPPORTED; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("JdkVersion"); sb.append("{major=").append(major); sb.append(", minor=").append(minor); sb.append(", maintenance=").append(maintenance); sb.append(", update=").append(update); sb.append('}'); return sb.toString(); } // ------------------------------------------------- Methods from Comparable public int compareTo(String versionString) { return compareTo(JdkVersion.parseVersion(versionString)); } @Override public int compareTo(JdkVersion otherVersion) { if (major < otherVersion.major) { return -1; } if (major > otherVersion.major) { return 1; } if (minor < otherVersion.minor) { return -1; } if (minor > otherVersion.minor) { return 1; } if (maintenance < otherVersion.maintenance) { return -1; } if (maintenance > otherVersion.maintenance) { return 1; } if (update < otherVersion.update) { return -1; } if (update > otherVersion.update) { return 1; } return 0; } private static int parseInt(final String s) { return s != null ? Integer.parseInt(s) : 0; } }
/** * Abstraction exposing both byte and character methods to read content from the HTTP messaging * system in Grizzly. */ public class InputBuffer { private static final Logger LOGGER = Grizzly.logger(InputBuffer.class); private static final Level LOGGER_LEVEL = Level.FINER; /** * The {@link org.glassfish.grizzly.http.HttpHeader} associated with this <code>InputBuffer</code> */ private HttpHeader httpHeader; /** * The {@link FilterChainContext} associated with this <code>InputBuffer</code>. The {@link * FilterChainContext} will be using to read <code>POST</code> data in blocking fashion. */ private FilterChainContext ctx; /** Flag indicating whether or not this <code>InputBuffer</code> is processing character data. */ private boolean processingChars; /** Flag indicating whether or not this <code>InputBuffer</code> has been closed. */ private boolean closed; /** The {@link Buffer} consisting of the bytes from the HTTP message chunk(s). */ private Buffer inputContentBuffer; /** * The {@link Connection} associated with this {@link * org.glassfish.grizzly.http.HttpRequestPacket}. */ private Connection connection; /** * The mark position within the current binary content. Marking is not supported for character * content. */ private int markPos = -1; /** Represents how many bytes can be read before {@link #markPos} is invalidated. */ private int readAheadLimit = -1; /** * Counter to track how many bytes have been read. This counter is only used with the byte stream * has been marked. */ private int readCount = 0; /** * The default character encoding to use if none is specified by the {@link * org.glassfish.grizzly.http.HttpRequestPacket}. */ private String encoding = DEFAULT_HTTP_CHARACTER_ENCODING; /** The {@link CharsetDecoder} used to convert binary to character data. */ private CharsetDecoder decoder; /** CharsetDecoders cache */ private final Map<String, CharsetDecoder> decoders = new HashMap<String, CharsetDecoder>(); /** Flag indicating all request content has been read. */ private boolean contentRead; /** The {@link ReadHandler} to be notified as content is read. */ private ReadHandler handler; /** The length of the content that must be read before notifying the {@link ReadHandler}. */ private int requestedSize; /** {@link CharBuffer} for converting a single character at a time. */ private final CharBuffer singleCharBuf = (CharBuffer) CharBuffer.allocate(1).position(1); // create CharBuffer w/ 0 chars remaining /** Used to estimate how many characters can be produced from a variable number of bytes. */ private float averageCharsPerByte = 1.0f; /** * Flag shows if we're currently waiting for input data asynchronously (is OP_READ enabled for the * Connection) */ private boolean isWaitingDataAsynchronously; // ------------------------------------------------------------ Constructors /** * Per-request initialization required for the InputBuffer to function properly. * * @param httpHeader the header from which input will be obtained. * @param ctx the FilterChainContext for the chain processing this request */ public void initialize(final HttpHeader httpHeader, final FilterChainContext ctx) { if (ctx == null) { throw new IllegalArgumentException("ctx cannot be null."); } this.httpHeader = httpHeader; this.ctx = ctx; connection = ctx.getConnection(); final Object message = ctx.getMessage(); if (message instanceof HttpContent) { final HttpContent content = (HttpContent) message; // Check if HttpContent is chunked message trailer w/ headers checkHttpTrailer(content); updateInputContentBuffer(content.getContent()); contentRead = content.isLast(); content.recycle(); if (LOGGER.isLoggable(LOGGER_LEVEL)) { log("InputBuffer %s initialize with ready content: %s", this, inputContentBuffer); } } } /** * Set the default character encoding for this <tt>InputBuffer</tt>, which would be applied if no * encoding was explicitly set on HTTP {@link org.glassfish.grizzly.http.HttpRequestPacket} and * character decoding wasn't started yet. */ @SuppressWarnings("UnusedDeclaration") public void setDefaultEncoding(final String encoding) { this.encoding = encoding; } /** Recycle this <code>InputBuffer</code> for reuse. */ public void recycle() { inputContentBuffer.tryDispose(); inputContentBuffer = null; singleCharBuf.position(singleCharBuf.limit()); connection = null; decoder = null; ctx = null; handler = null; processingChars = false; closed = false; contentRead = false; markPos = -1; readAheadLimit = -1; requestedSize = -1; readCount = 0; averageCharsPerByte = 1.0f; isWaitingDataAsynchronously = false; encoding = DEFAULT_HTTP_CHARACTER_ENCODING; } /** * This method should be called if the InputBuffer is being used in conjunction with a {@link * java.io.Reader} implementation. If this method is not called, any character-based methods * called on this <code>InputBuffer</code> will throw a {@link IllegalStateException}. */ public void processingChars() { if (!processingChars) { processingChars = true; final String enc = httpHeader.getCharacterEncoding(); if (enc != null) { encoding = enc; final CharsetDecoder localDecoder = getDecoder(); averageCharsPerByte = localDecoder.averageCharsPerByte(); } } } // --------------------------------------------- InputStream-Related Methods /** * This method always blocks. * * @see java.io.InputStream#read() */ public int readByte() throws IOException { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log("InputBuffer %s readByte. Ready content: %s", this, inputContentBuffer); } if (closed) { throw new IOException(); } if (!inputContentBuffer.hasRemaining()) { if (fill(1) == -1) { return -1; } } checkMarkAfterRead(1); return inputContentBuffer.get() & 0xFF; } /** @see java.io.InputStream#read(byte[], int, int) */ public int read(final byte b[], final int off, final int len) throws IOException { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log( "InputBuffer %s read byte array of len: %s. Ready content: %s", this, len, inputContentBuffer); } if (closed) { throw new IOException(); } if (len == 0) { return 0; } if (!inputContentBuffer.hasRemaining()) { if (fill(1) == -1) { return -1; } } int nlen = Math.min(inputContentBuffer.remaining(), len); inputContentBuffer.get(b, off, nlen); if (!checkMarkAfterRead(nlen)) { inputContentBuffer.shrink(); } return nlen; } /** * Depending on the <tt>InputBuffer</tt> mode, method will return either number of available bytes * or characters ready to be read without blocking. * * @return depending on the <tt>InputBuffer</tt> mode, method will return either number of * available bytes or characters ready to be read without blocking. */ public int readyData() { if (closed) return 0; return ((processingChars) ? availableChar() : available()); } /** @see java.io.InputStream#available() */ public int available() { return ((closed) ? 0 : inputContentBuffer.remaining()); } /** * Returns the duplicate of the underlying {@link Buffer} used to buffer incoming request data. * The content of the returned buffer will be that of the underlying buffer. Changes to returned * buffer's content will be visible in the underlying buffer, and vice versa; the two buffers' * position, limit, and mark values will be independent. * * @return the duplicate of the underlying {@link Buffer} used to buffer incoming request data. */ public Buffer getBuffer() { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log("InputBuffer %s getBuffer. Ready content: %s", this, inputContentBuffer); } return inputContentBuffer.duplicate(); } /** * @return the underlying {@link Buffer} used to buffer incoming request data. Unlike {@link * #getBuffer()}, this method detaches the returned {@link Buffer}, so user code becomes * responsible for handling the {@link Buffer}. */ public Buffer readBuffer() { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log("InputBuffer %s readBuffer. Ready content: %s", this, inputContentBuffer); } return readBuffer(inputContentBuffer.remaining()); } /** * @param size the requested size of the {@link Buffer} to be returned. * @return the {@link Buffer} of a given size, which represents a chunk of the underlying {@link * Buffer} which contains incoming request data. This method detaches the returned {@link * Buffer}, so user code becomes responsible for handling its life-cycle. */ public Buffer readBuffer(final int size) { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log( "InputBuffer %s readBuffer(size), size: %s. Ready content: %s", this, size, inputContentBuffer); } final int remaining = inputContentBuffer.remaining(); if (size > remaining) { throw new IllegalStateException("Can not read more bytes than available"); } final Buffer buffer; if (size == remaining) { buffer = inputContentBuffer; inputContentBuffer = Buffers.EMPTY_BUFFER; } else { final Buffer tmpBuffer = inputContentBuffer.split(inputContentBuffer.position() + size); buffer = inputContentBuffer; inputContentBuffer = tmpBuffer; } return buffer; } /** @return the {@link ReadHandler} current in use, if any. */ public ReadHandler getReadHandler() { return handler; } // -------------------------------------------------- Reader-Related Methods /** @see java.io.Reader#read(java.nio.CharBuffer) */ public int read(final CharBuffer target) throws IOException { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log("InputBuffer %s read(CharBuffer). Ready content: %s", this, inputContentBuffer); } if (closed) { throw new IOException(); } if (!processingChars) { throw new IllegalStateException(); } if (target == null) { throw new IllegalArgumentException("target cannot be null."); } final int read = fillChars(1, target); checkMarkAfterRead(read); return read; } /** @see java.io.Reader#read() */ public int readChar() throws IOException { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log("InputBuffer %s readChar. Ready content: %s", this, inputContentBuffer); } if (closed) { throw new IOException(); } if (!processingChars) { throw new IllegalStateException(); } if (!singleCharBuf.hasRemaining()) { singleCharBuf.clear(); int read = read(singleCharBuf); if (read == -1) { return -1; } } return singleCharBuf.get(); } /** @see java.io.Reader#read(char[], int, int) */ public int read(final char cbuf[], final int off, final int len) throws IOException { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log( "InputBuffer %s read char array, len: %s. Ready content: %s", this, len, inputContentBuffer); } if (closed) { throw new IOException(); } if (!processingChars) { throw new IllegalStateException(); } if (len == 0) { return 0; } final CharBuffer buf = CharBuffer.wrap(cbuf, off, len); return read(buf); } /** @see java.io.Reader#ready() */ public boolean ready() { if (closed) { return false; } if (!processingChars) { throw new IllegalStateException(); } return (inputContentBuffer.hasRemaining() || httpHeader.isExpectContent()); } /** * /** Fill the buffer (blocking) up to the requested length. * * @param length how much content should attempt to buffer, <code>-1</code> means buffer entire * request. * @throws IOException */ public void fillFully(final int length) throws IOException { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log("InputBuffer %s fillFully, len: %s. Ready content: %s", this, length, inputContentBuffer); } if (length == 0) return; if (length > 0) { final int remaining = length - inputContentBuffer.remaining(); if (remaining > 0) { fill(remaining); } } else { fill(-1); } } public int availableChar() { if (!singleCharBuf.hasRemaining()) { // fill the singleCharBuf to make sure we have at least one char singleCharBuf.clear(); if (fillAvailableChars(1, singleCharBuf) == 0) { singleCharBuf.position(singleCharBuf.limit()); return 0; } singleCharBuf.flip(); } // we have 1 char pre-decoded + estimation for the rest byte[]->char[] count. return 1 + ((int) (inputContentBuffer.remaining() * averageCharsPerByte)); } // ---------------------------------------------------- Common Input Methods /** * Supported with binary and character data. * * @see java.io.InputStream#mark(int) * @see java.io.Reader#mark(int) */ public void mark(final int readAheadLimit) { if (readAheadLimit > 0) { markPos = inputContentBuffer.position(); readCount = 0; this.readAheadLimit = readAheadLimit; } } /** * Only supported with binary data. * * @see java.io.InputStream#markSupported() */ public boolean markSupported() { if (processingChars) { throw new IllegalStateException(); } return true; } /** * Only supported with binary data. * * @see java.io.InputStream#reset() */ public void reset() throws IOException { if (closed) { throw new IOException(); } if (readAheadLimit == -1) { throw new IOException("Mark not set"); } readCount = 0; inputContentBuffer.position(markPos); } /** @see java.io.Closeable#close() */ public void close() throws IOException { closed = true; } /** * Skips the specified number of bytes/characters. * * @see java.io.InputStream#skip(long) * @see java.io.Reader#skip(long) * @deprecated pls. use {@link #skip(long)}, the <tt>block</tt> parameter will be ignored */ public long skip(final long n, @SuppressWarnings("UnusedParameters") final boolean block) throws IOException { return skip(n); } /** * Skips the specified number of bytes/characters. * * @see java.io.InputStream#skip(long) * @see java.io.Reader#skip(long) */ public long skip(final long n) throws IOException { if (LOGGER.isLoggable(LOGGER_LEVEL)) { log("InputBuffer %s skip %s bytes. Ready content: %s", this, n, inputContentBuffer); } if (closed) { throw new IOException(); } if (!processingChars) { if (n <= 0) { return 0L; } if (!inputContentBuffer.hasRemaining()) { if (fill((int) n) == -1) { return -1; } } if (inputContentBuffer.remaining() < n) { fill((int) n); } long nlen = Math.min(inputContentBuffer.remaining(), n); inputContentBuffer.position(inputContentBuffer.position() + (int) nlen); if (!checkMarkAfterRead(n)) { inputContentBuffer.shrink(); } return nlen; } else { if (n < 0) { // required by java.io.Reader.skip() throw new IllegalArgumentException(); } if (n == 0) { return 0L; } final CharBuffer skipBuffer = CharBuffer.allocate((int) n); if (fillChars((int) n, skipBuffer) == -1) { return 0; } return Math.min(skipBuffer.remaining(), n); } } /** * When invoked, this method will call {@link org.glassfish.grizzly.ReadHandler#onAllDataRead()} * on the current {@link ReadHandler} (if any). * * <p>This method shouldn't be invoked by developers directly. */ protected void finished() { if (!contentRead) { contentRead = true; final ReadHandler localHandler = handler; if (localHandler != null) { handler = null; invokeHandlerAllRead(localHandler, getThreadPool()); } } } private void finishedInTheCurrentThread(final ReadHandler readHandler) { if (!contentRead) { contentRead = true; if (readHandler != null) { invokeHandlerAllRead(readHandler, null); } } } private void invokeHandlerAllRead(final ReadHandler readHandler, final Executor executor) { if (executor != null) { executor.execute( new Runnable() { @Override public void run() { try { readHandler.onAllDataRead(); } catch (Throwable t) { readHandler.onError(t); } } }); } else { try { readHandler.onAllDataRead(); } catch (Throwable t) { readHandler.onError(t); } } } public void replayPayload(final Buffer buffer) { if (!isFinished()) { throw new IllegalStateException("Can't replay when InputBuffer is not closed"); } if (LOGGER.isLoggable(LOGGER_LEVEL)) { log("InputBuffer %s replayPayload to %s", this, buffer); } closed = false; readCount = 0; readAheadLimit = -1; markPos = -1; inputContentBuffer = buffer; } /** * @return <code>true</code> if all request data has been read, otherwise returns <code>false * </code>. */ public boolean isFinished() { return contentRead; } /** * @return <code>true</code> if this <tt>InputBuffer</tt> is closed, otherwise returns <code>false * </code>. */ public boolean isClosed() { return closed; } /** * Installs a {@link ReadHandler} that will be notified when any data becomes available to read * without blocking. * * @param handler the {@link ReadHandler} to invoke. * @throws IllegalArgumentException if <code>handler</code> is <code>null</code>. * @throws IllegalStateException if an attempt is made to register a handler before an existing * registered handler has been invoked or if all request data has already been read. */ public void notifyAvailable(final ReadHandler handler) { notifyAvailable(handler, 1); } /** * Installs a {@link ReadHandler} that will be notified when the specified amount of data is * available to be read without blocking. * * @param handler the {@link ReadHandler} to invoke. * @param size the minimum number of bytes that must be available before the {@link ReadHandler} * is notified. * @throws IllegalArgumentException if <code>handler</code> is <code>null</code>, or if <code>size * </code> is less or equal to zero. * @throws IllegalStateException if an attempt is made to register a handler before an existing * registered handler has been invoked. */ public void notifyAvailable(final ReadHandler handler, final int size) { if (handler == null) { throw new IllegalArgumentException("handler cannot be null."); } if (size <= 0) { throw new IllegalArgumentException("size should be positive integer"); } if (this.handler != null) { throw new IllegalStateException( "Illegal attempt to register a new handler before the existing handler has been notified"); } // If we don't expect more data - call onAllDataRead() directly if (closed || isFinished()) { try { handler.onAllDataRead(); } catch (Throwable ioe) { handler.onError(ioe); } return; } final int available = readyData(); if (shouldNotifyNow(size, available)) { try { handler.onDataAvailable(); } catch (Throwable ioe) { handler.onError(ioe); } return; } requestedSize = size; this.handler = handler; if (!isWaitingDataAsynchronously) { isWaitingDataAsynchronously = true; initiateAsyncronousDataReceiving(); } } /** * Appends the specified {@link Buffer} to the internal composite {@link Buffer}. * * @param httpContent the {@link HttpContent} to append * @return <code>true</code> if {@link ReadHandler} callback was invoked, otherwise returns <code> * false</code>. * @throws IOException if an error occurs appending the {@link Buffer} */ public boolean append(final HttpContent httpContent) throws IOException { // Stop waiting for data asynchronously and enable it again // only if we have a handler registered, which requirement // (expected size) is not met. isWaitingDataAsynchronously = false; // check if it's broken HTTP content message or not if (!HttpContent.isBroken(httpContent)) { final Buffer buffer = httpContent.getContent(); if (closed) { buffer.dispose(); return false; } final ReadHandler localHandler = handler; final boolean isLast = httpContent.isLast(); // if we have a handler registered - switch the flag to true boolean askForMoreDataInThisThread = !isLast && localHandler != null; boolean invokeDataAvailable = false; if (buffer.hasRemaining()) { updateInputContentBuffer(buffer); if (localHandler != null) { final int available = readyData(); if (available >= requestedSize) { invokeDataAvailable = true; askForMoreDataInThisThread = false; } } } if (askForMoreDataInThisThread) { // There is a ReadHandler registered, but it requested more // data to be available before we can notify it - so wait for // more data to come isWaitingDataAsynchronously = true; return true; } handler = null; if (isLast) { checkHttpTrailer(httpContent); } invokeHandlerOnProperThread(localHandler, invokeDataAvailable, isLast); } else { // broken content final ReadHandler localHandler = handler; handler = null; invokeErrorHandlerOnProperThread( localHandler, ((HttpBrokenContent) httpContent).getException()); } return false; } /** * @return if this buffer is being used to process asynchronous data. * @deprecated will always return true */ public boolean isAsyncEnabled() { // return asyncEnabled; return true; } /** * Sets the asynchronous processing state of this <code>InputBuffer</code>. * * @param asyncEnabled <code>true</code> if this <code>InputBuffer<code> * is to be used to process asynchronous request data. * @deprecated <tt>InputBuffer</tt> always supports async mode */ @SuppressWarnings("UnusedDeclaration") public void setAsyncEnabled(boolean asyncEnabled) { // this.asyncEnabled = asyncEnabled; } /** * Invoke {@link ReadHandler#onError(Throwable)} (assuming a {@link ReadHandler} is available) } * passing a {#link CancellationException} if the current {@link Connection} is open, or a {#link * EOFException} if the connection was unexpectedly closed. * * @since 2.0.1 */ public void terminate() { final ReadHandler localHandler = handler; if (localHandler != null) { handler = null; // call in the current thread, because otherwise handler executed // in the different thread may deal with recycled Request/Response objects localHandler.onError(connection.isOpen() ? new CancellationException() : new EOFException()); } } /** * Initiates asynchronous data receiving. * * <p>This is service method, usually users don't have to call it explicitly. */ public void initiateAsyncronousDataReceiving() { // fork the FilterChainContext execution // keep the current FilterChainContext suspended, but make a copy and resume it ctx.fork(ctx.getStopAction()); } // --------------------------------------------------------- Private Methods /** * @return {@link Executor}, which will be used for notifying user registered {@link ReadHandler}. */ protected Executor getThreadPool() { if (!Threads.isService()) { return null; } final ExecutorService es = connection.getTransport().getWorkerThreadPool(); return es != null && !es.isShutdown() ? es : null; } private void invokeErrorHandlerOnProperThread( final ReadHandler localHandler, final Throwable error) { if (!closed && localHandler != null) { final Executor executor = getThreadPool(); if (executor != null) { executor.execute( new Runnable() { @Override public void run() { localHandler.onError(error); } }); } else { localHandler.onError(error); } } } private void invokeHandlerOnProperThread( final ReadHandler localHandler, final boolean invokeDataAvailable, final boolean isLast) throws IOException { final Executor executor = getThreadPool(); if (executor != null) { executor.execute( new Runnable() { @Override public void run() { invokeHandler(localHandler, invokeDataAvailable, isLast); } }); } else { invokeHandler(localHandler, invokeDataAvailable, isLast); } } private void invokeHandler( final ReadHandler localHandler, final boolean invokeDataAvailable, final boolean isLast) { try { if (invokeDataAvailable) { localHandler.onDataAvailable(); } if (isLast) { finishedInTheCurrentThread(localHandler); } } catch (Throwable t) { localHandler.onError(t); } } /** * Read next chunk of data in this thread, block if needed. * * @return {@link HttpContent} * @throws IOException */ protected HttpContent blockingRead() throws IOException { final ReadResult rr = ctx.read(); final HttpContent c = (HttpContent) rr.getMessage(); rr.recycle(); return c; } /** * Used to add additional HTTP message chunk content to {@link #inputContentBuffer}. * * @param requestedLen how much content should attempt to be read, <code>-1</code> means read till * the end of the message. * @return the number of bytes actually read * @throws IOException if an I/O error occurs while reading content */ private int fill(final int requestedLen) throws IOException { int read = 0; while ((requestedLen == -1 || read < requestedLen) && httpHeader.isExpectContent()) { final HttpContent c = blockingRead(); final boolean isLast = c.isLast(); // Check if HttpContent is chunked message trailer w/ headers checkHttpTrailer(c); final Buffer b; try { b = c.getContent(); } catch (HttpBrokenContentException e) { final Throwable cause = e.getCause(); throw Exceptions.makeIOException(cause != null ? cause : e); } read += b.remaining(); updateInputContentBuffer(b); c.recycle(); if (isLast) { finished(); break; } } if (read > 0 || requestedLen == 0) { return read; } return -1; } /** * Used to convert bytes to chars. * * @param requestedLen how much content should attempt to be read * @return the number of chars actually read * @throws IOException if an I/O error occurs while reading content */ private int fillChars(final int requestedLen, final CharBuffer dst) throws IOException { int read = 0; // 1) Check pre-decoded singleCharBuf if (dst != singleCharBuf && singleCharBuf.hasRemaining()) { dst.put(singleCharBuf.get()); read = 1; } // 2) Decode available byte[] -> char[] if (inputContentBuffer.hasRemaining()) { read += fillAvailableChars(requestedLen - read, dst); } if (read >= requestedLen) { dst.flip(); return read; } // 3) If we don't expect more data - return what we've read so far if (!httpHeader.isExpectContent()) { dst.flip(); return read > 0 ? read : -1; } // 4) Try to read more data (we may block) CharsetDecoder decoderLocal = getDecoder(); boolean isNeedMoreInput = false; // true, if content in composite buffer is not enough to produce even 1 char boolean last = false; while (read < requestedLen && httpHeader.isExpectContent()) { if (isNeedMoreInput || !inputContentBuffer.hasRemaining()) { final HttpContent c = blockingRead(); updateInputContentBuffer(c.getContent()); last = c.isLast(); c.recycle(); isNeedMoreInput = false; } final ByteBuffer bytes = inputContentBuffer.toByteBuffer(); final int bytesPos = bytes.position(); final int dstPos = dst.position(); final CoderResult result = decoderLocal.decode(bytes, dst, false); final int producedChars = dst.position() - dstPos; final int consumedBytes = bytes.position() - bytesPos; read += producedChars; if (consumedBytes > 0) { bytes.position(bytesPos); inputContentBuffer.position(inputContentBuffer.position() + consumedBytes); if (readAheadLimit == -1) { inputContentBuffer.shrink(); } } else { isNeedMoreInput = true; } if (last || result == CoderResult.OVERFLOW) { break; } } dst.flip(); if (last && read == 0) { read = -1; } return read; } /** * Used to convert pre-read (buffered) bytes to chars. * * @param requestedLen how much content should attempt to be read * @return the number of chars actually read */ private int fillAvailableChars(final int requestedLen, final CharBuffer dst) { final CharsetDecoder decoderLocal = getDecoder(); final ByteBuffer bb = inputContentBuffer.toByteBuffer(); final int oldBBPos = bb.position(); int producedChars = 0; int consumedBytes = 0; int producedCharsNow; int consumedBytesNow; CoderResult result; int remaining = requestedLen; do { final int charPos = dst.position(); final int bbPos = bb.position(); result = decoderLocal.decode(bb, dst, false); producedCharsNow = dst.position() - charPos; consumedBytesNow = bb.position() - bbPos; producedChars += producedCharsNow; consumedBytes += consumedBytesNow; remaining -= producedCharsNow; } while (remaining > 0 && (producedCharsNow > 0 || consumedBytesNow > 0) && bb.hasRemaining() && result == CoderResult.UNDERFLOW); bb.position(oldBBPos); inputContentBuffer.position(inputContentBuffer.position() + consumedBytes); if (readAheadLimit == -1) { inputContentBuffer.shrink(); } return producedChars; } protected void updateInputContentBuffer(final Buffer buffer) { buffer.allowBufferDispose(true); if (inputContentBuffer == null) { inputContentBuffer = buffer; } else if (inputContentBuffer.hasRemaining() || readAheadLimit > 0) { // if the stream is marked - we can't dispose the inputContentBuffer, even if // it's been read off toCompositeInputContentBuffer().append(buffer); } else { inputContentBuffer.tryDispose(); inputContentBuffer = buffer; } } /** * @param size the amount of data that must be available for a {@link ReadHandler} to be notified. * @param available the amount of data currently available. * @return <code>true</code> if the handler should be notified during a call to {@link * #notifyAvailable(ReadHandler)} or {@link #notifyAvailable(ReadHandler, int)}, otherwise * <code>false</code> */ private static boolean shouldNotifyNow(final int size, final int available) { return (available != 0 && available >= size); } /** * @return the {@link CharsetDecoder} that should be used when converting content from binary to * character */ private CharsetDecoder getDecoder() { if (decoder == null) { decoder = decoders.get(encoding); if (decoder == null) { final Charset cs = Charsets.lookupCharset(encoding); decoder = cs.newDecoder(); decoder.onMalformedInput(CodingErrorAction.REPLACE); decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); decoders.put(encoding, decoder); } else { decoder.reset(); } } return decoder; } private CompositeBuffer toCompositeInputContentBuffer() { if (!inputContentBuffer.isComposite()) { final CompositeBuffer compositeBuffer = CompositeBuffer.newBuffer(connection.getMemoryManager()); compositeBuffer.allowBufferDispose(true); compositeBuffer.allowInternalBuffersDispose(true); int posAlign = 0; if (readAheadLimit > 0) { // the simple inputContentBuffer is marked // make the marked data still available inputContentBuffer.position(inputContentBuffer.position() - readCount); posAlign = readCount; markPos = 0; // for the CompositeBuffer markPos is 0 } compositeBuffer.append(inputContentBuffer); compositeBuffer.position(posAlign); inputContentBuffer = compositeBuffer; } return (CompositeBuffer) inputContentBuffer; } /** * @param n read bytes count * @return <tt>true</tt> if mark is still active, or <tt>false</tt> if the mark hasn't been set or * has been invalidated */ private boolean checkMarkAfterRead(final long n) { if (n > 0 && readAheadLimit != -1) { if ((long) readCount + n <= readAheadLimit) { readCount += n; return true; } // invalidate the mark readAheadLimit = -1; markPos = -1; readCount = 0; } return false; } /** * Check if passed {@link HttpContent} is {@link HttpTrailer}, which represents trailer chunk * (when chunked Transfer-Encoding is used), if it is a trailer chunk - then copy all the * available trailer headers to request headers map. * * @param httpContent */ private static void checkHttpTrailer(final HttpContent httpContent) { if (HttpTrailer.isTrailer(httpContent)) { final HttpTrailer httpTrailer = (HttpTrailer) httpContent; final HttpHeader httpHeader = httpContent.getHttpHeader(); final MimeHeaders trailerHeaders = httpTrailer.getHeaders(); final int size = trailerHeaders.size(); for (int i = 0; i < size; i++) { httpHeader.addHeader( trailerHeaders.getName(i).toString(), trailerHeaders.getValue(i).toString()); } } } private static void log(final String message, Object... params) { final String preparedMsg = String.format(message, params); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, preparedMsg, new Exception("Logged at")); } else { LOGGER.log(LOGGER_LEVEL, preparedMsg); } } }
/** * Test {@link HttpHandlerChain} use of the {@link MapperTest} * * @author Jeanfrancois Arcand */ public class MapperTest extends HttpServerAbstractTest { public static final int PORT = 18080; private static final Logger LOGGER = Grizzly.logger(MapperTest.class); public void testOverlappingMapping() throws IOException { LOGGER.fine("testOverlappingMapping"); try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test"); String[] aliases = new String[] {"/aaa/bbb", "/aaa/ccc"}; for (String alias : aliases) { addServlet(ctx, alias); } ctx.deploy(httpServer); for (String alias : aliases) { HttpURLConnection conn = getConnection(alias, PORT); assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn)); assertEquals(alias, readResponse(conn)); assertEquals(alias, conn.getHeaderField("servlet-path")); assertNull(alias, conn.getHeaderField("path-info")); } } finally { stopHttpServer(); } } public void testOverlappingMapping2() throws IOException { LOGGER.fine("testOverlappingMapping2"); try { startHttpServer(PORT); String[] alias = new String[] {"*.jsp", "/jsp/*"}; WebappContext ctx = new WebappContext("Test"); addServlet(ctx, "*.jsp"); addServlet(ctx, "/jsp/*"); ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/jsp/index.jsp", PORT); assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn)); assertEquals(alias[1], readResponse(conn)); assertEquals("/jsp", conn.getHeaderField("servlet-path")); assertEquals("/index.jsp", conn.getHeaderField("path-info")); } finally { stopHttpServer(); } } public void testRootStarMapping() throws IOException { LOGGER.fine("testRootMapping"); try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test"); String alias = "/*"; addServlet(ctx, alias); // overrides the static resource handler ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/index.html", PORT); assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn)); assertEquals(alias, readResponse(conn)); assertEquals("", conn.getHeaderField("servlet-path")); assertEquals("/index.html", conn.getHeaderField("path-info")); } finally { stopHttpServer(); } } public void testRootStarMapping2() throws IOException { LOGGER.fine("testRootMapping"); try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test"); String alias = "/*"; addServlet(ctx, alias); // overrides the static resource handler ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/foo/index.html", PORT); assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn)); assertEquals(alias, readResponse(conn)); assertEquals("", conn.getHeaderField("servlet-path")); assertEquals("/foo/index.html", conn.getHeaderField("path-info")); } finally { stopHttpServer(); } } public void testRootMapping() throws IOException { LOGGER.fine("testRootMapping"); try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test"); String alias = "/"; addServlet(ctx, alias); // overrides the static resource handler ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/", PORT); assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn)); assertEquals(alias, readResponse(conn)); assertEquals("/", conn.getHeaderField("servlet-path")); assertEquals(null, conn.getHeaderField("path-info")); } finally { stopHttpServer(); } } public void testRootMapping2() throws IOException { LOGGER.fine("testRootMapping"); try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test"); String alias = "/"; addServlet(ctx, alias); // overrides the static resource handler ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/foo/index.html", PORT); assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn)); assertEquals(alias, readResponse(conn)); assertEquals("/foo/index.html", conn.getHeaderField("servlet-path")); assertEquals(null, conn.getHeaderField("path-info")); } finally { stopHttpServer(); } } public void testWrongMapping() throws IOException { LOGGER.fine("testWrongMapping"); try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test"); String alias = "/a/b/c"; addServlet(ctx, alias); ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/aaa.html", PORT); assertEquals(HttpServletResponse.SC_NOT_FOUND, getResponseCodeFromAlias(conn)); } finally { stopHttpServer(); } } // // public void testComplexMapping() throws IOException { // LOGGER.fine("testComplexMapping"); // try { // startHttpServer(PORT); // String alias = "/a/b/c/*.html"; // addHttpHandler(alias); // HttpURLConnection conn = getConnection("/a/b/c/index.html", PORT); // assertEquals(HttpServletResponse.SC_OK, // getResponseCodeFromAlias(conn)); // assertEquals(alias, readResponse(conn)); // } finally { // stopHttpServer(); // } // } // public void testWildcardMapping() throws IOException { LOGGER.fine("testWildcardMapping"); try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test"); String alias = "*.html"; addServlet(ctx, alias); ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/index.html", PORT); assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn)); assertEquals(alias, readResponse(conn)); assertEquals("/index.html", conn.getHeaderField("servlet-path")); assertNull(conn.getHeaderField("path-info")); } finally { stopHttpServer(); } } public void testWrongMappingRootContext() throws IOException { LOGGER.fine("testWrongMappingRootContext"); try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test"); String alias = "*.a"; addServlet(ctx, alias); ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/aaa.html", PORT); assertEquals(HttpServletResponse.SC_NOT_FOUND, getResponseCodeFromAlias(conn)); } finally { stopHttpServer(); } } public void testDefaultServletOverride() throws Exception { try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test"); String alias = "/"; addServlet(ctx, alias); ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/", PORT); assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn)); assertEquals("/", conn.getHeaderField("servlet-path")); } finally { stopHttpServer(); } } public void testDefaultContext() throws Exception { try { startHttpServer(PORT); WebappContext ctx = new WebappContext("Test", "/"); String alias = "/foo/*"; addServlet(ctx, alias); ctx.deploy(httpServer); HttpURLConnection conn = getConnection("/foo/bar/baz", PORT); assertEquals(HttpServletResponse.SC_OK, getResponseCodeFromAlias(conn)); assertEquals("/foo/bar/baz", conn.getHeaderField("request-uri")); assertEquals("", conn.getHeaderField("context-path")); assertEquals("/foo", conn.getHeaderField("servlet-path")); assertEquals("/bar/baz", conn.getHeaderField("path-info")); } finally { stopHttpServer(); } } // --------------------------------------------------------- Private Methods private static ServletRegistration addServlet(final WebappContext ctx, final String alias) { final ServletRegistration reg = ctx.addServlet( alias, new HttpServlet() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { LOGGER.log( Level.INFO, "{0} received request {1}", new Object[] {alias, req.getRequestURI()}); resp.setStatus(HttpServletResponse.SC_OK); resp.setHeader("Path-Info", req.getPathInfo()); resp.setHeader("Servlet-Path", req.getServletPath()); resp.setHeader("Request-Was", req.getRequestURI()); resp.setHeader("Servlet-Name", getServletName()); resp.setHeader("Request-Uri", req.getRequestURI()); resp.setHeader("Context-Path", req.getContextPath()); resp.getWriter().write(alias); } }); reg.addMapping(alias); return reg; } }
/** * Common {@link Connection} implementation for Java NIO <tt>Connection</tt>s. * * @author Alexey Stashok */ public abstract class AIOConnection implements Connection<SocketAddress> { private static final Logger logger = Grizzly.logger(AIOConnection.class); protected final AIOTransport transport; protected volatile int readBufferSize; protected volatile int writeBufferSize; protected volatile long readTimeoutMillis = 30000; protected volatile long writeTimeoutMillis = 30000; protected volatile AsynchronousChannel channel; protected volatile Processor processor; protected volatile ProcessorSelector processorSelector; protected final AttributeHolder attributes; protected final TaskQueue<AsyncReadQueueRecord> asyncReadQueue; protected final TaskQueue<AsyncWriteQueueRecord> asyncWriteQueue; protected final AtomicBoolean isClosed = new AtomicBoolean(false); protected volatile boolean isBlocking; protected volatile boolean isStandalone; private final Queue<CloseListener> closeListeners = DataStructures.getLTQinstance(CloseListener.class); /** Connection probes */ protected final MonitoringConfigImpl<ConnectionProbe> monitoringConfig = new MonitoringConfigImpl<>(ConnectionProbe.class); public AIOConnection(AIOTransport transport) { this.transport = transport; asyncReadQueue = TaskQueue.createSafeTaskQueue(); asyncWriteQueue = TaskQueue.createSafeTaskQueue(); attributes = new IndexedAttributeHolder(transport.getAttributeBuilder()); } @Override public void configureBlocking(boolean isBlocking) { this.isBlocking = isBlocking; } @Override public boolean isBlocking() { return isBlocking; } @Override public synchronized void configureStandalone(boolean isStandalone) { if (this.isStandalone != isStandalone) { this.isStandalone = isStandalone; if (isStandalone) { processor = StandaloneProcessor.INSTANCE; processorSelector = StandaloneProcessorSelector.INSTANCE; } else { processor = transport.getProcessor(); processorSelector = transport.getProcessorSelector(); } } } @Override public boolean isStandalone() { return isStandalone; } @Override public Transport getTransport() { return transport; } @Override public int getReadBufferSize() { return readBufferSize; } @Override public void setReadBufferSize(int readBufferSize) { this.readBufferSize = readBufferSize; } @Override public int getWriteBufferSize() { return writeBufferSize; } @Override public void setWriteBufferSize(int writeBufferSize) { this.writeBufferSize = writeBufferSize; } @Override public long getReadTimeout(TimeUnit timeUnit) { return timeUnit.convert(readTimeoutMillis, TimeUnit.MILLISECONDS); } @Override public void setReadTimeout(long timeout, TimeUnit timeUnit) { readTimeoutMillis = TimeUnit.MILLISECONDS.convert(timeout, timeUnit); } @Override public long getWriteTimeout(TimeUnit timeUnit) { return timeUnit.convert(writeTimeoutMillis, TimeUnit.MILLISECONDS); } @Override public void setWriteTimeout(long timeout, TimeUnit timeUnit) { writeTimeoutMillis = TimeUnit.MILLISECONDS.convert(timeout, timeUnit); } public AsynchronousChannel getChannel() { return channel; } protected void setChannel(AsynchronousChannel channel) { this.channel = channel; } @Override public Processor obtainProcessor(IOEvent ioEvent) { if (processor == null && processorSelector == null) { return transport.obtainProcessor(ioEvent, this); } if (processor != null && processor.isInterested(ioEvent)) { return processor; } else if (processorSelector != null) { final Processor selectedProcessor = processorSelector.select(ioEvent, this); if (selectedProcessor != null) { return selectedProcessor; } } return null; } @Override public Processor getProcessor() { return processor; } @Override public void setProcessor(Processor preferableProcessor) { this.processor = preferableProcessor; } @Override public ProcessorSelector getProcessorSelector() { return processorSelector; } @Override public void setProcessorSelector(ProcessorSelector preferableProcessorSelector) { this.processorSelector = preferableProcessorSelector; } public TaskQueue<AsyncReadQueueRecord> getAsyncReadQueue() { return asyncReadQueue; } public TaskQueue<AsyncWriteQueueRecord> getAsyncWriteQueue() { return asyncWriteQueue; } @Override public AttributeHolder getAttributes() { return attributes; } @Override public <M> GrizzlyFuture<ReadResult<M, SocketAddress>> read() throws IOException { return read(null); } @Override public <M> GrizzlyFuture<ReadResult<M, SocketAddress>> read( CompletionHandler<ReadResult<M, SocketAddress>> completionHandler) throws IOException { final Processor obtainedProcessor = obtainProcessor(IOEvent.READ); return obtainedProcessor.read(this, completionHandler); } @Override public <M> GrizzlyFuture<WriteResult<M, SocketAddress>> write(M message) throws IOException { return write(null, message, null); } @Override public <M> GrizzlyFuture<WriteResult<M, SocketAddress>> write( M message, CompletionHandler<WriteResult<M, SocketAddress>> completionHandler) throws IOException { return write(null, message, completionHandler); } @Override public <M> GrizzlyFuture<WriteResult<M, SocketAddress>> write( SocketAddress dstAddress, M message, CompletionHandler<WriteResult<M, SocketAddress>> completionHandler) throws IOException { final Processor obtainedProcessor = obtainProcessor(IOEvent.WRITE); return obtainedProcessor.write(this, dstAddress, message, completionHandler); } @Override public boolean isOpen() { return channel != null && channel.isOpen() && !isClosed.get(); } @Override public GrizzlyFuture close() throws IOException { if (!isClosed.getAndSet(true)) { preClose(); notifyCloseListeners(); notifyProbesClose(this); channel.close(); // return transport.getSelectorHandler().executeInSelectorThread( // selectorRunner, new Runnable() { // // @Override // public void run() { // try { // ((AIOTransport) transport).closeConnection(AIOConnection.this); // } catch (IOException e) { // logger.log(Level.FINE, "Error during connection close", e); // } // } // }, null); } return ReadyFutureImpl.create(this); } /** {@inheritDoc} */ @Override public void addCloseListener(CloseListener closeListener) { // check if connection is still open if (!isClosed.get()) { // add close listener closeListeners.add(closeListener); // check the connection state again if (isClosed.get() && closeListeners.remove(closeListener)) { // if connection was closed during the method call - notify the listener try { closeListener.onClosed(this); } catch (IOException ignored) { } } } else { // if connection is closed - notify the listener try { closeListener.onClosed(this); } catch (IOException ignored) { } } } /** {@inheritDoc} */ @Override public boolean removeCloseListener(CloseListener closeListener) { return closeListeners.remove(closeListener); } /** {@inheritDoc} */ @Override public void notifyConnectionError(Throwable error) { notifyProbesError(this, error); } /** {@inheritDoc} */ @Override public final MonitoringConfig<ConnectionProbe> getMonitoringConfig() { return monitoringConfig; } /** * Notify registered {@link ConnectionProbe}s about the bind event. * * @param connection the <tt>Connection</tt> event occurred on. */ protected static void notifyProbesBind(AIOConnection connection) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onBindEvent(connection); } } } /** * Notify registered {@link ConnectionProbe}s about the accept event. * * @param connection the <tt>Connection</tt> event occurred on. */ protected static void notifyProbesAccept(AIOConnection connection) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onAcceptEvent(connection); } } } /** * Notify registered {@link ConnectionProbe}s about the connect event. * * @param connection the <tt>Connection</tt> event occurred on. */ protected static void notifyProbesConnect(final AIOConnection connection) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onConnectEvent(connection); } } } /** Notify registered {@link ConnectionProbe}s about the read event. */ protected static void notifyProbesRead(AIOConnection connection, Buffer data, int size) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onReadEvent(connection, data, size); } } } /** Notify registered {@link ConnectionProbe}s about the write event. */ protected static void notifyProbesWrite(AIOConnection connection, Buffer data, int size) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onWriteEvent(connection, data, size); } } } /** * Notify registered {@link ConnectionProbe}s about the IO Event ready event. * * @param connection the <tt>Connection</tt> event occurred on. * @param ioEvent the {@link IOEvent}. */ protected static void notifyIOEventReady(AIOConnection connection, IOEvent ioEvent) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onIOEventReadyEvent(connection, ioEvent); } } } /** * Notify registered {@link ConnectionProbe}s about the IO Event enabled event. * * @param connection the <tt>Connection</tt> event occurred on. * @param ioEvent the {@link IOEvent}. */ protected static void notifyIOEventEnabled(AIOConnection connection, IOEvent ioEvent) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onIOEventEnableEvent(connection, ioEvent); } } } /** * Notify registered {@link ConnectionProbe}s about the IO Event disabled event. * * @param connection the <tt>Connection</tt> event occurred on. * @param ioEvent the {@link IOEvent}. */ protected static void notifyIOEventDisabled(AIOConnection connection, IOEvent ioEvent) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onIOEventDisableEvent(connection, ioEvent); } } } /** * Notify registered {@link ConnectionProbe}s about the close event. * * @param connection the <tt>Connection</tt> event occurred on. */ protected static void notifyProbesClose(AIOConnection connection) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onCloseEvent(connection); } } } /** * Notify registered {@link ConnectionProbe}s about the error. * * @param connection the <tt>Connection</tt> event occurred on. */ protected static void notifyProbesError(AIOConnection connection, Throwable error) { final ConnectionProbe[] probes = connection.monitoringConfig.getProbesUnsafe(); if (probes != null) { for (ConnectionProbe probe : probes) { probe.onErrorEvent(connection, error); } } } /** Notify all close listeners */ private void notifyCloseListeners() { CloseListener closeListener; while ((closeListener = closeListeners.poll()) != null) { try { closeListener.onClosed(this); } catch (IOException ignored) { } } } protected abstract void preClose(); }
/** * Test {@link FilterChain} blocking read. * * @author Alexey Stashok */ @SuppressWarnings("unchecked") public class FilterChainReadTest extends TestCase { public static int PORT = 7785; private static final Logger logger = Grizzly.logger(FilterChainReadTest.class); public void testBlockingRead() throws Exception { final String[] clientMsgs = {"XXXXX", "Hello", "from", "client"}; Connection connection = null; int messageNum = 3; final BlockingQueue<String> intermResultQueue = DataStructures.getLTQInstance(String.class); final PUFilter puFilter = new PUFilter(); FilterChain subProtocolChain = puFilter .getPUFilterChainBuilder() .add(new MergeFilter(clientMsgs.length, intermResultQueue)) .add(new EchoFilter()) .build(); puFilter.register(new SimpleProtocolFinder(clientMsgs[0]), subProtocolChain); FilterChainBuilder filterChainBuilder = FilterChainBuilder.newInstance(); filterChainBuilder.add(new TransportFilter()); filterChainBuilder.add(new StringFilter()); filterChainBuilder.add(puFilter); final TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build(); transport.setFilterChain(filterChainBuilder.build()); try { transport.bind(PORT); transport.start(); final BlockingQueue<String> resultQueue = DataStructures.getLTQInstance(String.class); Future<Connection> future = transport.connect("localhost", PORT); connection = future.get(10, TimeUnit.SECONDS); assertTrue(connection != null); FilterChainBuilder clientFilterChainBuilder = FilterChainBuilder.newInstance(); clientFilterChainBuilder.add(new TransportFilter()); clientFilterChainBuilder.add(new StringFilter()); clientFilterChainBuilder.add( new BaseFilter() { @Override public NextAction handleRead(FilterChainContext ctx) throws IOException { resultQueue.add((String) ctx.getMessage()); return ctx.getStopAction(); } }); final FilterChain clientFilterChain = clientFilterChainBuilder.build(); connection.setFilterChain(clientFilterChain); for (int i = 0; i < messageNum; i++) { String clientMessage = ""; for (int j = 0; j < clientMsgs.length; j++) { String msg = clientMsgs[j] + "-" + i; Future<WriteResult> writeFuture = connection.write(msg); assertTrue("Write timeout loop: " + i, writeFuture.get(10, TimeUnit.SECONDS) != null); final String srvInterm = intermResultQueue.poll(10, TimeUnit.SECONDS); assertEquals("Unexpected interm. response (" + i + ", " + j + ")", msg, srvInterm); clientMessage += msg; } final String message = resultQueue.poll(10, TimeUnit.SECONDS); assertEquals("Unexpected response (" + i + ")", clientMessage, message); } } finally { if (connection != null) { connection.closeSilently(); } transport.shutdownNow(); } } public void testBlockingReadWithRemainder() throws Exception { final String[] clientMsgs = {"YYYYY", "Hello", "from", "client"}; Connection connection = null; int messageNum = 3; final StringFilter stringFilter = new StringFilter(); final BlockingQueue<String> intermResultQueue = DataStructures.getLTQInstance(String.class); final PUFilter puFilter = new PUFilter(); FilterChain subProtocolChain = puFilter .getPUFilterChainBuilder() .add(new MergeFilter(clientMsgs.length, intermResultQueue)) .add(new EchoFilter()) .build(); puFilter.register(new SimpleProtocolFinder(clientMsgs[0]), subProtocolChain); FilterChainBuilder filterChainBuilder = FilterChainBuilder.newInstance(); filterChainBuilder.add(new TransportFilter()); filterChainBuilder.add(stringFilter); filterChainBuilder.add(puFilter); TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build(); transport.setFilterChain(filterChainBuilder.build()); try { transport.bind(PORT); transport.start(); final BlockingQueue<String> resultQueue = DataStructures.getLTQInstance(String.class); Future<Connection> future = transport.connect("localhost", PORT); connection = future.get(10, TimeUnit.SECONDS); assertTrue(connection != null); FilterChainBuilder clientFilterChainBuilder = FilterChainBuilder.newInstance(); clientFilterChainBuilder.add(new TransportFilter()); clientFilterChainBuilder.add(stringFilter); clientFilterChainBuilder.add( new BaseFilter() { @Override public NextAction handleRead(FilterChainContext ctx) throws IOException { resultQueue.add((String) ctx.getMessage()); return ctx.getStopAction(); } }); final FilterChain clientFilterChain = clientFilterChainBuilder.build(); connection.setFilterChain(clientFilterChain); final MemoryManager memoryManager = transport.getMemoryManager(); for (int i = 0; i < messageNum; i++) { String clientMessage = ""; CompositeBuffer bb = CompositeBuffer.newBuffer(memoryManager); for (int j = 0; j < clientMsgs.length; j++) { String msg = clientMsgs[j] + "-" + i; clientMessage += msg; Buffer buffer = stringFilter.encode(memoryManager, msg); bb.append(buffer); } Future<WriteResult<WritableMessage, SocketAddress>> writeFuture = connection.write(bb); assertTrue("Write timeout loop: " + i, writeFuture.get(10, TimeUnit.SECONDS) != null); for (int j = 0; j < clientMsgs.length; j++) { String msg = clientMsgs[j] + "-" + i; final String srvInterm = intermResultQueue.poll(10, TimeUnit.SECONDS); assertEquals("Unexpected interm. response (" + i + ", " + j + ")", msg, srvInterm); } final String message = resultQueue.poll(10, TimeUnit.SECONDS); assertEquals("Unexpected response (" + i + ")", clientMessage, message); } } finally { if (connection != null) { connection.closeSilently(); } transport.shutdownNow(); } } public void testBlockingReadError() throws Exception { final String[] clientMsgs = {"ZZZZZ", "Hello", "from", "client"}; Connection connection = null; final BlockingQueue intermResultQueue = DataStructures.getLTQInstance(); final PUFilter puFilter = new PUFilter(); FilterChain subProtocolChain = puFilter .getPUFilterChainBuilder() .add(new MergeFilter(clientMsgs.length, intermResultQueue)) .add(new EchoFilter()) .build(); puFilter.register(new SimpleProtocolFinder(clientMsgs[0]), subProtocolChain); FilterChainBuilder filterChainBuilder = FilterChainBuilder.newInstance(); filterChainBuilder.add(new TransportFilter()); filterChainBuilder.add(new StringFilter()); filterChainBuilder.add(puFilter); TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance().build(); transport.setFilterChain(filterChainBuilder.build()); try { transport.bind(PORT); transport.start(); Future<Connection> future = transport.connect("localhost", PORT); connection = future.get(10, TimeUnit.SECONDS); assertTrue(connection != null); FilterChainBuilder clientFilterChainBuilder = FilterChainBuilder.newInstance(); clientFilterChainBuilder.add(new TransportFilter()); clientFilterChainBuilder.add(new StringFilter()); final FilterChain clientFilterChain = clientFilterChainBuilder.build(); connection.setFilterChain(clientFilterChain); String msg = clientMsgs[0]; Future<WriteResult> writeFuture = connection.write(msg); assertTrue("Write timeout", writeFuture.get(10, TimeUnit.SECONDS) != null); final String srvInterm = (String) intermResultQueue.poll(10, TimeUnit.SECONDS); assertEquals("Unexpected interm. response", msg, srvInterm); connection.closeSilently(); connection = null; final Exception e = (Exception) intermResultQueue.poll(10, TimeUnit.SECONDS); assertTrue( "Unexpected response. Exception: " + e.getClass() + ": " + e.getMessage(), e instanceof IOException); } finally { if (connection != null) { connection.closeSilently(); } transport.shutdownNow(); } } private static final class SimpleProtocolFinder implements ProtocolFinder { public final String name; public SimpleProtocolFinder(final String name) { this.name = name; } @Override public Result find(PUContext puContext, FilterChainContext ctx) { final String requestedProtocolName = ctx.getMessage(); return requestedProtocolName.startsWith(name) ? Result.FOUND : Result.NOT_FOUND; } } private static final class MergeFilter extends BaseFilter { private final int clientMsgs; private final BlockingQueue intermResultQueue; public MergeFilter(int clientMsgs, BlockingQueue intermResultQueue) { this.clientMsgs = clientMsgs; this.intermResultQueue = intermResultQueue; } @Override public NextAction handleRead(final FilterChainContext ctx) throws IOException { String message = (String) ctx.getMessage(); logger.log(Level.INFO, "First chunk come: {0}", message); intermResultQueue.add(message); Connection connection = ctx.getConnection(); connection.setBlockingReadTimeout(10, TimeUnit.SECONDS); try { for (int i = 0; i < clientMsgs - 1; i++) { final ReadResult rr = ctx.read(); final String blckMsg = (String) rr.getMessage(); rr.recycle(); logger.log(Level.INFO, "Blocking chunk come: {0}", blckMsg); intermResultQueue.add(blckMsg); message += blckMsg; } } catch (Exception e) { intermResultQueue.add(e); return ctx.getStopAction(); } ctx.setMessage(message); return ctx.getInvokeAction(); } } }
public class HttpServer { private static final Logger LOGGER = Grizzly.logger(HttpServer.class); /** Configuration details for this server instance. */ private final ServerConfiguration serverConfig = new ServerConfiguration(this); /** Flag indicating whether or not this server instance has been started. */ private State state = State.STOPPED; /** Future to control graceful shutdown status */ private FutureImpl<HttpServer> shutdownFuture; /** HttpHandler, which processes HTTP requests */ private final HttpHandlerChain httpHandlerChain = new HttpHandlerChain(this); /** Mapping of {@link NetworkListener}s, by name, used by this server instance. */ private final Map<String, NetworkListener> listeners = new HashMap<String, NetworkListener>(2); private volatile ExecutorService auxExecutorService; volatile DelayedExecutor delayedExecutor; protected volatile GrizzlyJmxManager jmxManager; protected volatile Object managementObject; // ---------------------------------------------------------- Public Methods /** @return the {@link ServerConfiguration} used to configure this {@link HttpServer} instance */ public final ServerConfiguration getServerConfiguration() { return serverConfig; } /** * Adds the specified <code>listener</code> to the server instance. * * <p>If the server is already running when this method is called, the listener will be started. * * @param listener the {@link NetworkListener} to associate with this server instance. */ public synchronized void addListener(final NetworkListener listener) { if (state != State.RUNNING) { listeners.put(listener.getName(), listener); } else { configureListener(listener); if (!listener.isStarted()) { try { listener.start(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log( Level.SEVERE, "Failed to start listener [{0}] : {1}", new Object[] {listener.toString(), ioe.toString()}); LOGGER.log(Level.SEVERE, ioe.toString(), ioe); } } } } } /** * @param name the {@link NetworkListener} name. * @return the {@link NetworkListener}, if any, associated with the specified <code>name</code>. */ public synchronized NetworkListener getListener(final String name) { return listeners.get(name); } /** * @return a <code>read only</code> {@link Collection} over the listeners associated with this * <code>HttpServer</code> instance. */ public synchronized Collection<NetworkListener> getListeners() { return Collections.unmodifiableCollection(listeners.values()); } /** * Removes the {@link NetworkListener} associated with the specified <code>name</code>. * * <p>If the server is running when this method is invoked, the listener will be stopped before * being returned. * * @param name the name of the {@link NetworkListener} to remove. */ public synchronized NetworkListener removeListener(final String name) { final NetworkListener listener = listeners.remove(name); if (listener != null) { if (listener.isStarted()) { try { listener.shutdownNow(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log( Level.SEVERE, "Failed to shutdown listener [{0}] : {1}", new Object[] {listener.toString(), ioe.toString()}); LOGGER.log(Level.SEVERE, ioe.toString(), ioe); } } } } return listener; } /** * Starts the <code>HttpServer</code>. * * @throws IOException if an error occurs while attempting to start the server. */ public synchronized void start() throws IOException { if (state == State.RUNNING) { return; } else if (state == State.STOPPING) { throw new IllegalStateException( "The server is currently in pending" + " shutdown state. You have to either wait for shutdown to" + " complete or force it by calling shutdownNow()"); } state = State.RUNNING; shutdownFuture = null; configureAuxThreadPool(); delayedExecutor = new DelayedExecutor(auxExecutorService); delayedExecutor.start(); for (final NetworkListener listener : listeners.values()) { configureListener(listener); } if (serverConfig.isJmxEnabled()) { enableJMX(); } for (final NetworkListener listener : listeners.values()) { try { listener.start(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log( Level.FINEST, "Failed to start listener [{0}] : {1}", new Object[] {listener.toString(), ioe.toString()}); LOGGER.log(Level.FINEST, ioe.toString(), ioe); } throw ioe; } } setupHttpHandler(); if (serverConfig.isJmxEnabled()) { for (final JmxEventListener l : serverConfig.getJmxEventListeners()) { l.jmxEnabled(); } } if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, "[{0}] Started.", getServerConfiguration().getName()); } } private void setupHttpHandler() { serverConfig.addJmxEventListener(httpHandlerChain); synchronized (serverConfig.handlersSync) { for (final HttpHandler httpHandler : serverConfig.orderedHandlers) { final String[] mappings = serverConfig.handlers.get(httpHandler); httpHandlerChain.addHandler(httpHandler, mappings); } } httpHandlerChain.start(); } private void tearDownHttpHandler() { httpHandlerChain.destroy(); } /** @return the {@link HttpHandler} used by this <code>HttpServer</code> instance. */ public HttpHandler getHttpHandler() { return httpHandlerChain; } /** @return <code>true</code> if this <code>HttpServer</code> has been started. */ public boolean isStarted() { return state != State.STOPPED; } public Object getManagementObject(boolean clear) { if (!clear && managementObject == null) { synchronized (serverConfig) { if (managementObject == null) { managementObject = MonitoringUtils.loadJmxObject( "org.glassfish.grizzly.http.server.jmx.HttpServer", this, HttpServer.class); } } } try { return managementObject; } finally { if (clear) { managementObject = null; } } } public synchronized GrizzlyFuture<HttpServer> shutdown( final long gracePeriod, final TimeUnit timeUnit) { if (state != State.RUNNING) { return shutdownFuture != null ? shutdownFuture : Futures.createReadyFuture(this); } shutdownFuture = Futures.createSafeFuture(); state = State.STOPPING; final int listenersCount = listeners.size(); final FutureImpl<HttpServer> shutdownFutureLocal = shutdownFuture; final CompletionHandler<NetworkListener> shutdownCompletionHandler = new EmptyCompletionHandler<NetworkListener>() { final AtomicInteger counter = new AtomicInteger(listenersCount); @Override public void completed(final NetworkListener networkListener) { if (counter.decrementAndGet() == 0) { try { shutdownNow(); shutdownFutureLocal.result(HttpServer.this); } catch (Throwable e) { shutdownFutureLocal.failure(e); } } } }; for (NetworkListener listener : listeners.values()) { listener.shutdown(gracePeriod, timeUnit).addCompletionHandler(shutdownCompletionHandler); } return shutdownFuture; } /** Gracefully shuts down the <code>HttpServer</code> instance. */ public synchronized GrizzlyFuture<HttpServer> shutdown() { return shutdown(-1, TimeUnit.MILLISECONDS); } /** Immediately shuts down the <code>HttpServer</code> instance. */ public synchronized void shutdownNow() { if (state == State.STOPPED) { return; } state = State.STOPPED; try { if (serverConfig.isJmxEnabled()) { for (final JmxEventListener l : serverConfig.getJmxEventListeners()) { l.jmxDisabled(); } } tearDownHttpHandler(); final String[] names = listeners.keySet().toArray(new String[listeners.size()]); for (final String name : names) { removeListener(name); } delayedExecutor.stop(); delayedExecutor.destroy(); delayedExecutor = null; stopAuxThreadPool(); if (serverConfig.isJmxEnabled()) { disableJMX(); } } catch (Exception e) { LOGGER.log(Level.WARNING, null, e); } finally { for (final NetworkListener listener : listeners.values()) { final FilterChain filterChain = listener.getTransport().getFilterChain(); filterChain.clear(); } if (shutdownFuture != null) { shutdownFuture.result(this); } } } /** * @return a <code>HttpServer</code> configured to listen to requests on {@link * NetworkListener#DEFAULT_NETWORK_HOST}:{@link NetworkListener#DEFAULT_NETWORK_PORT}, using * the directory in which the server was launched the server's document root */ public static HttpServer createSimpleServer() { return createSimpleServer("."); } /** * @param docRoot the document root, can be <code>null</code> when no static pages are needed * @return a <code>HttpServer</code> configured to listen to requests on {@link * NetworkListener#DEFAULT_NETWORK_HOST}:{@link NetworkListener#DEFAULT_NETWORK_PORT}, using * the specified <code>docRoot</code> as the server's document root */ public static HttpServer createSimpleServer(final String docRoot) { return createSimpleServer(docRoot, NetworkListener.DEFAULT_NETWORK_PORT); } /** * @param docRoot the document root, can be <code>null</code> when no static pages are needed * @param port the network port to which this listener will bind * @return a <code>HttpServer</code> configured to listen to requests on {@link * NetworkListener#DEFAULT_NETWORK_HOST}:<code>port</code>, using the specified <code>docRoot * </code> as the server's document root */ public static HttpServer createSimpleServer(final String docRoot, final int port) { return createSimpleServer(docRoot, NetworkListener.DEFAULT_NETWORK_HOST, port); } /** * @param docRoot the document root, can be <code>null</code> when no static pages are needed * @param range port range to attempt to bind to * @return a <code>HttpServer</code> configured to listen to requests on {@link * NetworkListener#DEFAULT_NETWORK_HOST}:<code>[port-range]</code>, using the specified <code> * docRoot</code> as the server's document root */ public static HttpServer createSimpleServer(final String docRoot, final PortRange range) { return createSimpleServer(docRoot, NetworkListener.DEFAULT_NETWORK_HOST, range); } /** * @param docRoot the document root, can be <code>null</code> when no static pages are needed * @param socketAddress the endpoint address to which this listener will bind * @return a <code>HttpServer</code> configured to listen to requests on <code>socketAddress * </code>, using the specified <code>docRoot</code> as the server's document root */ public static HttpServer createSimpleServer( final String docRoot, final SocketAddress socketAddress) { final InetSocketAddress inetAddr = (InetSocketAddress) socketAddress; return createSimpleServer(docRoot, inetAddr.getHostName(), inetAddr.getPort()); } /** * @param docRoot the document root, can be <code>null</code> when no static pages are needed * @param host the network port to which this listener will bind * @param port the network port to which this listener will bind * @return a <code>HttpServer</code> configured to listen to requests on <code>host</code>:<code> * port</code>, using the specified <code>docRoot</code> as the server's document root */ public static HttpServer createSimpleServer( final String docRoot, final String host, final int port) { return createSimpleServer(docRoot, host, new PortRange(port)); } /** * @param docRoot the document root, can be <code>null</code> when no static pages are needed * @param host the network port to which this listener will bind * @param range port range to attempt to bind to * @return a <code>HttpServer</code> configured to listen to requests on <code>host</code>:<code> * [port-range]</code>, using the specified <code>docRoot</code> as the server's document root */ public static HttpServer createSimpleServer( final String docRoot, final String host, final PortRange range) { final HttpServer server = new HttpServer(); final ServerConfiguration config = server.getServerConfiguration(); if (docRoot != null) { config.addHttpHandler(new StaticHttpHandler(docRoot), "/"); } final NetworkListener listener = new NetworkListener("grizzly", host, range); server.addListener(listener); return server; } // ------------------------------------------------------- Protected Methods protected void enableJMX() { if (jmxManager == null) { synchronized (serverConfig) { if (jmxManager == null) { jmxManager = GrizzlyJmxManager.instance(); } } } jmxManager.registerAtRoot(getManagementObject(false), serverConfig.getName()); } protected void disableJMX() { if (jmxManager != null) { jmxManager.deregister(getManagementObject(true)); } } // --------------------------------------------------------- Private Methods private void configureListener(final NetworkListener listener) { FilterChain chain = listener.getFilterChain(); if (chain == null) { final FilterChainBuilder builder = FilterChainBuilder.newInstance(); builder.add(new TransportFilter()); if (listener.isSecure()) { SSLEngineConfigurator sslConfig = listener.getSslEngineConfig(); if (sslConfig == null) { sslConfig = new SSLEngineConfigurator(SSLContextConfigurator.DEFAULT_CONFIG, false, false, false); listener.setSSLEngineConfig(sslConfig); } final SSLBaseFilter filter = new SSLBaseFilter(sslConfig); builder.add(filter); } final int maxHeaderSize = listener.getMaxHttpHeaderSize() == -1 ? org.glassfish.grizzly.http.HttpServerFilter.DEFAULT_MAX_HTTP_PACKET_HEADER_SIZE : listener.getMaxHttpHeaderSize(); // Passing null value for the delayed executor, because IdleTimeoutFilter should // handle idle connections for us final org.glassfish.grizzly.http.HttpServerFilter httpServerCodecFilter = new org.glassfish.grizzly.http.HttpServerFilter( listener.isChunkingEnabled(), maxHeaderSize, null, listener.getKeepAliveConfig(), null, listener.getMaxRequestHeaders(), listener.getMaxResponseHeaders()); final Set<ContentEncoding> contentEncodings = configureCompressionEncodings(listener); for (ContentEncoding contentEncoding : contentEncodings) { httpServerCodecFilter.addContentEncoding(contentEncoding); } httpServerCodecFilter .getMonitoringConfig() .addProbes(serverConfig.getMonitoringConfig().getHttpConfig().getProbes()); builder.add(httpServerCodecFilter); builder.add( new IdleTimeoutFilter( delayedExecutor, listener.getKeepAliveConfig().getIdleTimeoutInSeconds(), TimeUnit.SECONDS)); final Transport transport = listener.getTransport(); final FileCache fileCache = listener.getFileCache(); fileCache.initialize(delayedExecutor); final FileCacheFilter fileCacheFilter = new FileCacheFilter(fileCache); fileCache .getMonitoringConfig() .addProbes(serverConfig.getMonitoringConfig().getFileCacheConfig().getProbes()); builder.add(fileCacheFilter); final ServerFilterConfiguration config = new ServerFilterConfiguration(serverConfig); if (listener.isSendFileExplicitlyConfigured()) { config.setSendFileEnabled(listener.isSendFileEnabled()); fileCache.setFileSendEnabled(listener.isSendFileEnabled()); } if (listener.getBackendConfig() != null) { config.setBackendConfiguration(listener.getBackendConfig()); } if (listener.getDefaultErrorPageGenerator() != null) { config.setDefaultErrorPageGenerator(listener.getDefaultErrorPageGenerator()); } config.setTraceEnabled(config.isTraceEnabled() || listener.isTraceEnabled()); config.setMaxFormPostSize(listener.getMaxFormPostSize()); config.setMaxBufferedPostSize(listener.getMaxBufferedPostSize()); final HttpServerFilter httpServerFilter = new HttpServerFilter(config, delayedExecutor); httpServerFilter.setHttpHandler(httpHandlerChain); httpServerFilter .getMonitoringConfig() .addProbes(serverConfig.getMonitoringConfig().getWebServerConfig().getProbes()); builder.add(httpServerFilter); chain = builder.build(); final AddOn[] addons = listener.getAddOnSet().getArray(); if (addons != null) { for (AddOn addon : addons) { addon.setup(listener, chain); } } listener.setFilterChain(chain); final int transactionTimeout = listener.getTransactionTimeout(); if (transactionTimeout >= 0) { ThreadPoolConfig threadPoolConfig = transport.getWorkerThreadPoolConfig(); if (threadPoolConfig != null) { threadPoolConfig.setTransactionTimeout( delayedExecutor, transactionTimeout, TimeUnit.SECONDS); } } } configureMonitoring(listener); } protected Set<ContentEncoding> configureCompressionEncodings(final NetworkListener listener) { final CompressionConfig compressionConfig = listener.getCompressionConfig(); if (compressionConfig.getCompressionMode() != CompressionMode.OFF) { final ContentEncoding gzipContentEncoding = new GZipContentEncoding( GZipContentEncoding.DEFAULT_IN_BUFFER_SIZE, GZipContentEncoding.DEFAULT_OUT_BUFFER_SIZE, new CompressionEncodingFilter( compressionConfig, GZipContentEncoding.getGzipAliases())); final ContentEncoding lzmaEncoding = new LZMAContentEncoding( new CompressionEncodingFilter( compressionConfig, LZMAContentEncoding.getLzmaAliases())); final Set<ContentEncoding> set = new HashSet<ContentEncoding>(2); set.add(gzipContentEncoding); set.add(lzmaEncoding); return set; } else { return Collections.emptySet(); } } @SuppressWarnings("unchecked") private void configureMonitoring(final NetworkListener listener) { final Transport transport = listener.getTransport(); final MonitoringConfig<TransportProbe> transportMonitoringCfg = transport.getMonitoringConfig(); final MonitoringConfig<ConnectionProbe> connectionMonitoringCfg = transport.getConnectionMonitoringConfig(); final MonitoringConfig<MemoryProbe> memoryMonitoringCfg = transport.getMemoryManager().getMonitoringConfig(); final MonitoringConfig<ThreadPoolProbe> threadPoolMonitoringCfg = transport.getThreadPoolMonitoringConfig(); transportMonitoringCfg.addProbes( serverConfig.getMonitoringConfig().getTransportConfig().getProbes()); connectionMonitoringCfg.addProbes( serverConfig.getMonitoringConfig().getConnectionConfig().getProbes()); memoryMonitoringCfg.addProbes(serverConfig.getMonitoringConfig().getMemoryConfig().getProbes()); threadPoolMonitoringCfg.addProbes( serverConfig.getMonitoringConfig().getThreadPoolConfig().getProbes()); } private void configureAuxThreadPool() { final AtomicInteger threadCounter = new AtomicInteger(); auxExecutorService = Executors.newCachedThreadPool( new ThreadFactory() { @Override public Thread newThread(Runnable r) { final Thread newThread = new DefaultWorkerThread( AttributeBuilder.DEFAULT_ATTRIBUTE_BUILDER, serverConfig.getName() + "-" + threadCounter.getAndIncrement(), null, r); newThread.setDaemon(true); return newThread; } }); } private void stopAuxThreadPool() { final ExecutorService localThreadPool = auxExecutorService; auxExecutorService = null; if (localThreadPool != null) { localThreadPool.shutdownNow(); } } // ************ Runtime config change listeners ****************** /** Modifies handlers mapping during runtime. */ synchronized void onAddHttpHandler(HttpHandler httpHandler, String[] mapping) { if (isStarted()) { httpHandlerChain.addHandler(httpHandler, mapping); } } /** Modifies handlers mapping during runtime. */ synchronized void onRemoveHttpHandler(HttpHandler httpHandler) { if (isStarted()) { httpHandlerChain.removeHttpHandler(httpHandler); } } }