springboot整合undertow服务器的源码从老生常谈的createWebServer
方法谈起。spring会在生成所有bean后到创建web容器,此时会到容器找到ServletWebServerFactory
接口bean,spring会根据引入的框架确定生成的ServletWebServerFactory
,我们在maven中引入了undertow后,由UndertowServletWebServerFactory
实现。
private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) {ServletWebServerFactory factory = getWebServerFactory();this.webServer = factory.getWebServer(getSelfInitializer());getBeanFactory().registerSingleton("webServerGracefulShutdown",new WebServerGracefulShutdownLifecycle(this.webServer));getBeanFactory().registerSingleton("webServerStartStop",new WebServerStartStopLifecycle(this, this.webServer));}else if (servletContext != null) {try {getSelfInitializer().onStartup(servletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context", ex);}}initPropertySources();}
UndertowServletWebServerFactory
类的getWebServer
会创建WebServer。
public WebServer getWebServer(ServletContextInitializer... initializers) {Builder builder = this.delegate.createBuilder(this);DeploymentManager manager = createManager(initializers);return getUndertowWebServer(builder, manager, getPort());}
先用默认的配置构建Builder对象,再使用读取的配置。
Builder createBuilder(AbstractConfigurableWebServerFactory factory) {Ssl ssl = factory.getSsl();InetAddress address = factory.getAddress();int port = factory.getPort();Builder builder = Undertow.builder();if (this.bufferSize != null) {builder.setBufferSize(this.bufferSize);}if (this.ioThreads != null) {builder.setIoThreads(this.ioThreads);}if (this.workerThreads != null) {builder.setWorkerThreads(this.workerThreads);}if (this.directBuffers != null) {builder.setDirectBuffers(this.directBuffers);}if (ssl != null && ssl.isEnabled()) {new SslBuilderCustomizer(factory.getPort(), address, ssl, factory.getSslStoreProvider()).customize(builder);Http2 http2 = factory.getHttp2();if (http2 != null) {builder.setServerOption(UndertowOptions.ENABLE_HTTP2, http2.isEnabled());}}else {builder.addHttpListener(port, (address != null) ? address.getHostAddress() : "0.0.0.0");}builder.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 0);for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {customizer.customize(builder);}return builder;}
之后再注册Servlet和Filter过滤器到容器中。
public void deploy() {final DeploymentInfo deploymentInfo = originalDeployment.clone();if (deploymentInfo.getServletStackTraces() == ServletStackTraces.ALL) {UndertowServletLogger.REQUEST_LOGGER.servletStackTracesAll(deploymentInfo.getDeploymentName());}deploymentInfo.validate();final DeploymentImpl deployment = new DeploymentImpl(this, deploymentInfo, servletContainer);this.deployment = deployment;final ServletContextImpl servletContext = new ServletContextImpl(servletContainer, deployment);deployment.setServletContext(servletContext);handleExtensions(deploymentInfo, servletContext);final List<ThreadSetupHandler> setup = new ArrayList<>();setup.add(ServletRequestContextThreadSetupAction.INSTANCE);setup.add(new ContextClassLoaderSetupAction(deploymentInfo.getClassLoader()));setup.addAll(deploymentInfo.getThreadSetupActions());deployment.setThreadSetupActions(setup);deployment.getServletPaths().setWelcomePages(deploymentInfo.getWelcomePages());if (deploymentInfo.getDefaultEncoding() != null) {deployment.setDefaultCharset(Charset.forName(deploymentInfo.getDefaultEncoding()));}if(deploymentInfo.getDefaultRequestEncoding() != null) {deployment.setDefaultRequestCharset(Charset.forName(deploymentInfo.getDefaultRequestEncoding()));} else if (deploymentInfo.getDefaultEncoding() != null) {deployment.setDefaultRequestCharset(Charset.forName(deploymentInfo.getDefaultEncoding()));}if(deploymentInfo.getDefaultResponseEncoding() != null) {deployment.setDefaultResponseCharset(Charset.forName(deploymentInfo.getDefaultResponseEncoding()));} else if (deploymentInfo.getDefaultEncoding() != null) {deployment.setDefaultResponseCharset(Charset.forName(deploymentInfo.getDefaultEncoding()));}handleDeploymentSessionConfig(deploymentInfo, servletContext);deployment.setSessionManager(deploymentInfo.getSessionManagerFactory().createSessionManager(deployment));deployment.getSessionManager().setDefaultSessionTimeout(deploymentInfo.getDefaultSessionTimeout());try {deployment.createThreadSetupAction(new ThreadSetupHandler.Action<Void, Object>() {@Overridepublic Void call(HttpServerExchange exchange, Object ignore) throws Exception {final ApplicationListeners listeners = createListeners();listeners.start();deployment.setApplicationListeners(listeners);//now create the servlets and filters that we know about. We can still get more latercreateServletsAndFilters(deployment, deploymentInfo);//first initialize the temp dirinitializeTempDir(servletContext, deploymentInfo);//then run the SCI'sfor (final ServletContainerInitializerInfo sci : deploymentInfo.getServletContainerInitializers()) {final InstanceHandle<? extends ServletContainerInitializer> instance = sci.getInstanceFactory().createInstance();try {instance.getInstance().onStartup(sci.getHandlesTypes(), servletContext);} finally {instance.release();}}deployment.getSessionManager().registerSessionListener(new SessionListenerBridge(deployment, listeners, servletContext));for(SessionListener listener : deploymentInfo.getSessionListeners()) {deployment.getSessionManager().registerSessionListener(listener);}initializeErrorPages(deployment, deploymentInfo);initializeMimeMappings(deployment, deploymentInfo);listeners.contextInitialized();//runHttpHandler wrappedHandlers = ServletDispatchingHandler.INSTANCE;wrappedHandlers = wrapHandlers(wrappedHandlers, deploymentInfo.getInnerHandlerChainWrappers());wrappedHandlers = new RedirectDirHandler(wrappedHandlers, deployment.getServletPaths());if(!deploymentInfo.isSecurityDisabled()) {HttpHandler securityHandler = setupSecurityHandlers(wrappedHandlers);wrappedHandlers = new PredicateHandler(DispatcherTypePredicate.REQUEST, securityHandler, wrappedHandlers);}HttpHandler outerHandlers = wrapHandlers(wrappedHandlers, deploymentInfo.getOuterHandlerChainWrappers());wrappedHandlers = new PredicateHandler(DispatcherTypePredicate.REQUEST, outerHandlers, wrappedHandlers);wrappedHandlers = handleDevelopmentModePersistentSessions(wrappedHandlers, deploymentInfo, deployment.getSessionManager(), servletContext);MetricsCollector metrics = deploymentInfo.getMetricsCollector();if(metrics != null) {wrappedHandlers = new MetricsChainHandler(wrappedHandlers, metrics, deployment);}if( deploymentInfo.getCrawlerSessionManagerConfig() != null ) {wrappedHandlers = new CrawlerSessionManagerHandler(deploymentInfo.getCrawlerSessionManagerConfig(), wrappedHandlers);}final ServletInitialHandler servletInitialHandler = SecurityActions.createServletInitialHandler(deployment.getServletPaths(), wrappedHandlers, deployment, servletContext);HttpHandler initialHandler = wrapHandlers(servletInitialHandler, deployment.getDeploymentInfo().getInitialHandlerChainWrappers());initialHandler = new HttpContinueReadHandler(initialHandler);if(deploymentInfo.getUrlEncoding() != null) {initialHandler = Handlers.urlDecodingHandler(deploymentInfo.getUrlEncoding(), initialHandler);}deployment.setInitialHandler(initialHandler);deployment.setServletHandler(servletInitialHandler);deployment.getServletPaths().invalidate(); //make sure we have a fresh set of servlet pathsservletContext.initDone();return null;}}).call(null, null);} catch (Exception e) {throw new RuntimeException(e);}//any problems with the paths won't get detected until the data is initialize//so we force initialization heredeployment.getServletPaths().initData();for(ServletContextListener listener : deploymentInfo.getDeploymentCompleteListeners()) {listener.contextInitialized(new ServletContextEvent(servletContext));}state = State.DEPLOYED;}
到WebServer
接口的start()
方法就会启动undertow服务器了,本质就是通过XNIO框架监听服务器端口号,接收请求并处理。处理请求时的io线程模型分数据处理线程和业务处理线程。
public synchronized void start() {UndertowLogger.ROOT_LOGGER.infof("starting server: %s", Version.getFullVersionString());xnio = Xnio.getInstance(Undertow.class.getClassLoader());channels = new ArrayList<>();try {if (internalWorker) {worker = xnio.createWorker(OptionMap.builder().set(Options.WORKER_IO_THREADS, ioThreads).set(Options.CONNECTION_HIGH_WATER, 1000000).set(Options.CONNECTION_LOW_WATER, 1000000).set(Options.WORKER_TASK_CORE_THREADS, workerThreads).set(Options.WORKER_TASK_MAX_THREADS, workerThreads).set(Options.TCP_NODELAY, true).set(Options.CORK, true).addAll(workerOptions).getMap());}OptionMap socketOptions = OptionMap.builder().set(Options.WORKER_IO_THREADS, worker.getIoThreadCount()).set(Options.TCP_NODELAY, true).set(Options.REUSE_ADDRESSES, true).set(Options.BALANCING_TOKENS, 1).set(Options.BALANCING_CONNECTIONS, 2).set(Options.BACKLOG, 1000).addAll(this.socketOptions).getMap();OptionMap serverOptions = OptionMap.builder().set(UndertowOptions.NO_REQUEST_TIMEOUT, 60 * 1000).addAll(this.serverOptions).getMap();ByteBufferPool buffers = this.byteBufferPool;if (buffers == null) {buffers = new DefaultByteBufferPool(directBuffers, bufferSize, -1, 4);}listenerInfo = new ArrayList<>();for (ListenerConfig listener : listeners) {UndertowLogger.ROOT_LOGGER.debugf("Configuring listener with protocol %s for interface %s and port %s", listener.type, listener.host, listener.port);final HttpHandler rootHandler = listener.rootHandler != null ? listener.rootHandler : this.rootHandler;OptionMap socketOptionsWithOverrides = OptionMap.builder().addAll(socketOptions).addAll(listener.overrideSocketOptions).getMap();......if (listener.type == ListenerType.HTTP) {HttpOpenListener openListener = new HttpOpenListener(buffers, undertowOptions);HttpHandler handler = rootHandler;if (http2) {handler = new Http2UpgradeHandler(handler);}openListener.setRootHandler(handler);final ChannelListener<StreamConnection> finalListener;if (listener.useProxyProtocol) {finalListener = new ProxyProtocolOpenListener(openListener, null, buffers, OptionMap.EMPTY);} else {finalListener = openListener;}ChannelListener<AcceptingChannel<StreamConnection>> acceptListener = ChannelListeners.openListenerAdapter(finalListener);AcceptingChannel<? extends StreamConnection> server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), acceptListener, socketOptionsWithOverrides);server.resumeAccepts();channels.add(server);listenerInfo.add(new ListenerInfo("http", server.getLocalAddress(), openListener, null, server));} ......}