Java tutorial
/* * Copyright (C) 2012 Google Inc. * Copyright (C) 2015 Keith M. Hughes. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.robotbrains.support.web.server.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.SelfSignedCertificate; import io.smartspaces.SimpleSmartSpacesException; import io.smartspaces.SmartSpacesException; import io.smartspaces.util.web.MimeResolver; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.Configurator; import org.robotbrains.support.web.server.HttpAuthProvider; import org.robotbrains.support.web.server.HttpDynamicPostRequestHandler; import org.robotbrains.support.web.server.HttpDynamicRequestHandler; import org.robotbrains.support.web.server.HttpRequest; import org.robotbrains.support.web.server.HttpResponse; import org.robotbrains.support.web.server.HttpStaticContentRequestHandler; import org.robotbrains.support.web.server.WebResourceAccessManager; import org.robotbrains.support.web.server.WebServer; import org.robotbrains.support.web.server.WebServerWebSocketHandlerFactory; import com.google.common.collect.Lists; /** * A web server based on Netty. * * @author Keith M. Hughes */ public class NettyWebServer implements WebServer { static final boolean SSL = false; static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8443" : "8080")); public static void main(String[] args) throws Exception { Configurator.initialize(null, new ConfigurationSource(NettyWebServer.class.getClassLoader().getResourceAsStream("log4j.xml"))); NettyWebServer server = new NettyWebServer(8095, LogManager.getFormatterLogger("HelloWorld")); server.startup(); server.addDynamicContentHandler("/foo", true, new HttpDynamicRequestHandler() { @Override public void handle(HttpRequest request, HttpResponse response) { try { response.getOutputStream().write("Hello, world".getBytes()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); } /** * Name of the server. */ private String serverName; /** * The port for the server. */ private int port; /** * HTTP headers to be sent on all responses. */ private Map<String, String> globalHttpContentHeaders = new HashMap<>(); /** * The handler for all web server requests. */ private NettyWebServerHandler serverHandler; /** * The group for handling boss events. */ private EventLoopGroup bossGroup; /** * The group for handling worker events. */ private EventLoopGroup workerGroup; /** * {@code true} if running in debug mode. */ private boolean debugMode; /** * {@code true} if support being a secure server. */ private boolean secureServer; /** * The certificate chain file for an SSL connection. Can be {@code null}. */ private File sslCertChainFile; /** * The key file for an SSL connection. Can be {@code null}. */ private File sslKeyFile; /** * The SSL context for HTTPS connections. Will be {@code null} if the server * is not labeled secure. */ private SslContext sslContext; /** * The default MIME resolver to use. */ private MimeResolver defaultMimeResolver; /** * The complete collection of static content handlers. */ private List<HttpStaticContentRequestHandler> staticContentRequestHandlers = new ArrayList<>(); /** * The complete collection of dynamic GET request handlers. */ private List<HttpDynamicRequestHandler> dynamicGetRequestHandlers = new ArrayList<>(); /** * The complete collection of dynamic POST request handlers. */ private List<HttpDynamicPostRequestHandler> dynamicPostRequestHandlers = new ArrayList<>(); /** * All GET request handlers handled by this instance. */ private List<NettyHttpGetRequestHandler> httpGetRequestHandlers = new ArrayList<>(); /** * All POST request handlers handled by this instance. */ private List<NettyHttpPostRequestHandler> httpPostRequestHandlers = new ArrayList<>(); /** * The logger to use. */ private Logger log; /** * Construct a new web server. * * @param log * the logger to use */ public NettyWebServer(int port, Logger log) { this.log = log; this.port = port; } @Override public void startup() { try { // Configure SSL. SslContext sslCtx; if (SSL) { SelfSignedCertificate ssc = new SelfSignedCertificate(); sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); } else { sslCtx = null; } serverHandler = new NettyWebServerHandler(this); bossGroup = new NioEventLoopGroup(1); workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(ServerChannelWithId.class) .childHandler(new NettyWebServerInitializer(sslCtx, this, serverHandler)); b.bind(port).sync(); } catch (Throwable e) { throw SmartSpacesException.newFormattedException(e, "Could not create web server"); } } @Override public void shutdown() { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } @Override public void addStaticContentHandler(String uriPrefix, File baseDir) { addStaticContentHandler(uriPrefix, baseDir, null); } @Override public void addStaticContentHandler(String uriPrefix, File baseDir, Map<String, String> extraHttpContentHeaders) { addStaticContentHandler(uriPrefix, baseDir, extraHttpContentHeaders, null); } @Override public void addStaticContentHandler(String uriPrefix, File baseDir, Map<String, String> extraHttpContentHeaders, HttpDynamicRequestHandler fallbackHandler) { if (!baseDir.exists()) { throw new SmartSpacesException(String.format("Cannot find web folder %s", baseDir.getAbsolutePath())); } NettyHttpDynamicGetRequestHandlerHandler fallbackNettyHandler = fallbackHandler == null ? null : new NettyHttpDynamicGetRequestHandlerHandler(serverHandler, uriPrefix, false, fallbackHandler, extraHttpContentHeaders); NettyStaticContentHandler staticContentHandler = new NettyStaticContentHandler(serverHandler, uriPrefix, baseDir, extraHttpContentHeaders, fallbackNettyHandler); staticContentHandler.setAllowLinks(isDebugMode()); if (isDebugMode()) { getLog().warn("Enabling web-server link following because of debug mode -- not secure."); } staticContentHandler.setMimeResolver(defaultMimeResolver); addHttpGetRequestHandler(staticContentHandler); staticContentRequestHandlers.add(staticContentHandler); } @Override public void addDynamicContentHandler(String uriPrefix, boolean usePath, HttpDynamicRequestHandler handler) { addDynamicContentHandler(uriPrefix, usePath, handler, null); } @Override public void addDynamicContentHandler(String uriPrefix, boolean usePath, HttpDynamicRequestHandler handler, Map<String, String> extraHttpContentHeaders) { addHttpGetRequestHandler(new NettyHttpDynamicGetRequestHandlerHandler(serverHandler, uriPrefix, usePath, handler, extraHttpContentHeaders)); dynamicGetRequestHandlers.add(handler); } @Override public void addDynamicPostRequestHandler(String uriPrefix, boolean usePath, HttpDynamicPostRequestHandler handler) { addDynamicPostRequestHandler(uriPrefix, usePath, handler, null); } @Override public void addDynamicPostRequestHandler(String uriPrefix, boolean usePath, HttpDynamicPostRequestHandler handler, Map<String, String> extraHttpContentHeaders) { addHttpPostRequestHandler(new NettyHttpDynamicPostRequestHandlerHandler(serverHandler, uriPrefix, usePath, handler, extraHttpContentHeaders)); dynamicPostRequestHandlers.add(handler); } @Override public List<HttpStaticContentRequestHandler> getStaticContentRequestHandlers() { return Lists.newArrayList(staticContentRequestHandlers); } @Override public List<HttpDynamicRequestHandler> getDynamicRequestHandlers() { return Lists.newArrayList(dynamicGetRequestHandlers); } @Override public List<HttpDynamicPostRequestHandler> getDynamicPostRequestHandlers() { return Lists.newArrayList(dynamicPostRequestHandlers); } @Override public void setWebSocketHandlerFactory(String webSocketUriPrefix, WebServerWebSocketHandlerFactory webSocketHandlerFactory) { serverHandler.setWebSocketHandlerFactory(webSocketUriPrefix, webSocketHandlerFactory); } @Override public String getServerName() { return serverName; } @Override public void setServerName(String serverName) { this.serverName = serverName; } @Override public void setPort(int port) { this.port = port; } @Override public int getPort() { return port; } @Override public void addContentHeader(String name, String value) { globalHttpContentHeaders.put(name, value); } @Override public void addContentHeaders(Map<String, String> headers) { globalHttpContentHeaders.putAll(headers); } @SuppressWarnings("unchecked") @Override public <T extends MimeResolver> T getDefaultMimeResolver() { return (T) defaultMimeResolver; } @Override public void setDefaultMimeResolver(MimeResolver resolver) { defaultMimeResolver = resolver; } @Override public void setDebugMode(boolean debugMode) { this.debugMode = debugMode; } /** * Check if this server is running in debug mode. * * @return {@code true} if this server is running in debug mode */ public boolean isDebugMode() { return debugMode; } @Override public boolean isSecureServer() { return secureServer; } @Override public void setSecureServer(boolean secureServer) { this.secureServer = secureServer; } @Override public void setSslCertificates(File sslCertChainFile, File sslKeyFile) { if (((sslCertChainFile == null) && (sslKeyFile != null)) || (sslCertChainFile != null) && (sslKeyFile == null)) { throw new SimpleSmartSpacesException( "Both a certificate chain file and a private key file must be supplied"); } if (sslCertChainFile != null && !sslCertChainFile.isFile()) { throw new SimpleSmartSpacesException(String.format("The certificate chain file %s does not exist", sslCertChainFile.getAbsolutePath())); } if (sslKeyFile != null && !sslKeyFile.isFile()) { throw new SimpleSmartSpacesException( String.format("The private key file %s does not exist", sslKeyFile.getAbsolutePath())); } this.sslCertChainFile = sslCertChainFile; this.sslKeyFile = sslKeyFile; } @Override public void setAuthProvider(HttpAuthProvider authProvider) { serverHandler.setAuthProvider(authProvider); } @Override public void setAccessManager(WebResourceAccessManager accessManager) { serverHandler.setAccessManager(accessManager); } /** * Get the content headers which should go onto every HTTP response. * * @return the globalHttpContentHeaders */ public Map<String, String> getGlobalHttpContentHeaders() { return globalHttpContentHeaders; } /** * Get the logger for the web server. * * @return the logger */ public Logger getLog() { return log; } /** * Register a new GET request handler to the server. * * @param handler * the handler to add */ private void addHttpGetRequestHandler(NettyHttpGetRequestHandler handler) { httpGetRequestHandlers.add(handler); } /** * Locate a registered GET request handler that can handle the given request. * * @param nettyRequest * the Netty request * * @return the first handler that handles the request, or {@code null} if none */ NettyHttpGetRequestHandler locateGetRequestHandler(io.netty.handler.codec.http.HttpRequest nettyRequest) { for (NettyHttpGetRequestHandler handler : httpGetRequestHandlers) { if (handler.isHandledBy(nettyRequest)) { return handler; } } return null; } /** * Register a new POST request handler to the server. * * @param handler * the handler to add */ private void addHttpPostRequestHandler(NettyHttpPostRequestHandler handler) { httpPostRequestHandlers.add(handler); } /** * Locate a registered POST request handler that can handle the given request. * * @param nettyRequest * the Netty request * * @return the first handler that handles the request, or {@code null} if none */ NettyHttpPostRequestHandler locatePostRequestHandler(io.netty.handler.codec.http.HttpRequest nettyRequest) { for (NettyHttpPostRequestHandler handler : httpPostRequestHandlers) { if (handler.isHandledBy(nettyRequest)) { return handler; } } return null; } }