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 不再繼續走代理邏輯了。