Undertow 作为一个web服务器,是支持反向代理的,比较适用小型平台或者开发测试场景, 以下是使用记录
首先在pom中引用undertow作为web服务器,我使用的springboot项目,配置参考
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
org.springframework.boot
spring-boot-starter-undertow
注: 上面注释的部分,是因为开始用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 custom(){
return new WebServerFactoryCustomizer() {
@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 不再继续走代理逻辑了。