Undertow 實現反向代理


Undertow 作為一個web服務器,是支持反向代理的,比較適用小型平台或者開發測試場景, 以下是使用記錄

首先在pom中引用undertow作為web服務器,我使用的springboot項目,配置參考

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-tomcat</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-undertow</artifactId>
		</dependency>
		<!--  
		<dependency>
			<groupId>org.mitre.dsmiley.httpproxy</groupId>
			<artifactId>smiley-http-proxy-servlet</artifactId>
			<version>1.11</version>
		</dependency>
		-->
	</dependencies>

注: 上面注釋的部分,是因為開始用httpclient 做代理,使用也比較簡單,有興趣可以百度一下,后來猜想undertow應該具有反向代理的功能,再引入smilley就有些多余了

undertow 默認的反向代理只能代理整個服務,不能類似nginx 代理某個具體路徑,而恰巧我需要該項目能夠將 /base路徑開始的請求代理轉發到其他服務,故而這里取了個巧,先看代碼

package com.iflytek.research.datawood.config;

import java.net.URI;

import javax.annotation.Resource;

import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.undertow.server.HandlerWrapper;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.DisallowedMethodsHandler;
import io.undertow.server.handlers.proxy.ProxyClient;
import io.undertow.server.handlers.proxy.ProxyHandler;
import io.undertow.server.handlers.proxy.SimpleProxyClientProvider;
import io.undertow.util.HttpString;

/**
 * @author ljgeng
 *
 */
@Configuration
public class UndertowConfiguration {
    
    @Resource
    private ProxyProperties ProxyProperties;
    
	@Bean
	public WebServerFactoryCustomizer<UndertowServletWebServerFactory> custom(){
		return new WebServerFactoryCustomizer<UndertowServletWebServerFactory>() {
		    
			@Override
			public void customize(UndertowServletWebServerFactory factory) {
				factory.addDeploymentInfoCustomizers(deploymentInfo -> {
		            deploymentInfo.addInitialHandlerChainWrapper(new GatewayProxyHandlerWrapper());
		        });
			}
			
			
		}; 
	} 
	
	/**
	 * Gateway  反向代理
	 *  http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#reverse-proxy
	 * @author ljgeng
	 *
	 */
	public class GatewayProxyHandlerWrapper implements HandlerWrapper {

        @Override
        public HttpHandler wrap(HttpHandler handler) {
            // 代理路徑
            ProxyClient proxyClientProvider = new ProxyClientProvider(URI.create(ProxyProperties.getGateway().getUrl()));
            HttpString[] disallowedHttpMethods = { HttpString.tryFromString("TRACE"),
                    HttpString.tryFromString("TRACK") };
            return new ProxyHandler(proxyClientProvider, 0,  new DisallowedMethodsHandler(handler, disallowedHttpMethods));
        }
	    
	}
	
	public class ProxyClientProvider extends SimpleProxyClientProvider {
        public ProxyClientProvider(URI uri) {
            super(uri);
        }
        
        /**
         *  重寫findTarget 方法: 指定轉發的路徑。
         */
        @Override
        public ProxyTarget findTarget(HttpServerExchange exchange) {
            // 代理/base路徑
            if (exchange.getRequestPath().startsWith("/base")) {
                // 修改路徑,去除base
                exchange.setResolvedPath("/base");
                return super.findTarget(exchange);
            }
            return null;
        }
	    
	}
	
}

主要看ProxyClientProvider,它重新實現了SimpleProxyClientProvider findTarget方法,當請求路徑以/base開始時調用父類方法,否則返回null. 方法exchange.setResolvedPath("/base") 是讓代理的路徑不再包含base。 可能有人會好奇,為什么這里返回null就可以繞過代理,弄清這個問題的話就要看下ProxyHandler的handleRequest方法到底做了什么(undertow通過handler的handleRequest對請求進行下一步處理)

public void handleRequest(final HttpServerExchange exchange) throws Exception {
        final ProxyClient.ProxyTarget target = proxyClient.findTarget(exchange);
        if (target == null) {
            log.debugf("No proxy target for request to %s", exchange.getRequestURL());
            next.handleRequest(exchange);
            return;
        }
        if(exchange.isResponseStarted()) {
            //we can't proxy a request that has already started, this is basically a server configuration error
            UndertowLogger.REQUEST_LOGGER.cannotProxyStartedRequest(exchange);
            exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
            exchange.endExchange();
            return;
        }
        final long timeout = maxRequestTime > 0 ? System.currentTimeMillis() + maxRequestTime : 0;
        int maxRetries = maxConnectionRetries;
        if(target instanceof ProxyClient.MaxRetriesProxyTarget) {
            maxRetries = Math.max(maxRetries, ((ProxyClient.MaxRetriesProxyTarget) target).getMaxRetries());
        }
        final ProxyClientHandler clientHandler = new ProxyClientHandler(exchange, target, timeout, maxRetries, idempotentRequestPredicate);
        if (timeout > 0) {
            final XnioExecutor.Key key = WorkerUtils.executeAfter(exchange.getIoThread(), new Runnable() {
                @Override
                public void run() {
                    clientHandler.cancel(exchange);
                }
            }, maxRequestTime, TimeUnit.MILLISECONDS);
            exchange.putAttachment(TIMEOUT_KEY, key);
            exchange.addExchangeCompleteListener(new ExchangeCompletionListener() {
                @Override
                public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) {
                    key.remove();
                    nextListener.proceed();
                }
            });
        }
        exchange.dispatch(exchange.isInIoThread() ? SameThreadExecutor.INSTANCE : exchange.getIoThread(), clientHandler);
    }

可以很明顯看到,如果target是null, 就走下一個handler 不再繼續走代理邏輯了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM