书接上回,在内部服务代理其他服务时, 这次使用 webclient
作为请求客户端
import static org.springframework.web.reactive.HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
@Slf4j
@RestController
public class ProxyController {
@Autowired
private WebClient.Builder builder;
private WebClient webClient;
public WebClient client() {
if (webClient == null) {
synchronized (ProxyController.class) {
webClient = builder.build();
}
}
return webClient;
}
/**
*
* @param prefix
* @param exchange
* @return
*/
private String path(String prefix, ServerWebExchange exchange) {
//获取 完整的请求 url
String path = (String) exchange.getAttributes().get(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
return path.substring(prefix.length());
}
@RequestMapping("/proxy/**")
public Mono<Void> proxy(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
//拼接代理转发的 url
URI newUri = rebuildURI(exchange);
HttpMethod method = request.getMethod();
WebClient.RequestBodySpec bodySpec = client()
.method(method).uri(newUri)
// copy header
.headers(httpHeaders -> httpHeaders.addAll(exchange.getRequest().getHeaders()));
log.info("request url: {}", newUri);
WebClient.RequestHeadersSpec<?> headersSpec;
if (requiresBody(method)) {
headersSpec = bodySpec.body(BodyInserters.fromDataBuffers(request.getBody()));
} else {
headersSpec = bodySpec;
}
return headersSpec.exchangeToMono(res -> {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().putAll(res.headers().asHttpHeaders());
response.setStatusCode(res.statusCode());
if (res == null) {
return Mono.empty();
}
return exchange.getResponse().writeWith(res.body(BodyExtractors.toDataBuffers()));
}).doOnError(t -> {
log.error("proxy error: ", t);
});
}
private URI rebuildURI(ServerWebExchange exchange) {
URI original = exchange.getRequest().getURI();
String path = path("/proxy", exchange);
String scheme = "http";
String host = "localhost";
int port = 5556;
//这个方式可以 copy get 请求的参数
return UriComponentsBuilder.fromUri(original).scheme(scheme).host(host)
.port(port).replacePath(path).build(containsEncodedParts(original)).toUri();
}
private boolean requiresBody(HttpMethod method) {
switch (method) {
case PUT:
case POST:
case PATCH:
return true;
default:
return false;
}
}
public static boolean containsEncodedParts(URI uri) {
boolean encoded = (uri.getRawQuery() != null && uri.getRawQuery().contains("%"))
|| (uri.getRawPath() != null && uri.getRawPath().contains("%"));
// Verify if it is really fully encoded. Treat partial encoded as unencoded.
if (encoded) {
try {
UriComponentsBuilder.fromUri(uri).build(true);
return true;
} catch (IllegalArgumentException ignored) {
if (log.isTraceEnabled()) {
log.trace("Error in containsEncodedParts", ignored);
}
}
return false;
}
return encoded;
}
}
测试
controller:
@GetMapping("/test")
public Mono<R<String>> hello2(@RequestParam String name) {
System.out.println("name: " + name);
if (name.equals("1")){
throw new RuntimeException("异常了");
}
return Mono.justOrEmpty(R.ok("hello"));
}
访问代理: http://localhost:5556/proxy/test?name=tom
, 结果:
{
"data": "hello",
"msg": "success",
"code": 1
}
测试后发现 ,像 post 表单,json, 上传文件,都正常
good luck!