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

  • 相关阅读:
    开源网站
    Shape 与 InlineShape 的区别
    C#使用Word中的内置对话框实例
    C# Word 类库的深入理解
    C# 线程知识--使用ThreadPool执行异步操作
    C#如何以管理员身份运行程序
    C#的Enum——枚举
    MVC Action 返回类型[转]
    MVC3中Action返回类型ActionResult类型
    ASP.NET MVC 3.0 Controller基础
  • 原文地址:https://www.cnblogs.com/ljgeng/p/11240012.html
Copyright © 2020-2023  润新知