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 不再继续走代理逻辑了。

猜你喜欢

转载自www.cnblogs.com/ljgeng/p/11240012.html