【技术应用】java实现第三方接口代理
一、前言
平时工作中经常遇到服务http接口代理功能,我们经常用到nginx进行静态资源代理和http请求代理,但是在一些业务场景中会在转发代理http请求时,会增加请求头设置,例如token鉴权
设置到request中,用于请求第三方接口;
在request请求
的基础上新增请求头、转换参数或者转换返回值的功能,实现第三方接口代理功能,同时避免每个接口都要编码处理;
二、思路
处理这个问题的思路有两个:
1)利用网关gateway的特性实现代理功能,在微服务中经常涉及的到,平时用的比较多的是通过yml配置文件实现网关配置;
其实就是通过拦截器filter
对请求实现拦截,重新封装request请求
再转发;
2)实现一个action,用于获取某个公用前缀
的所有请求
,重新封装后再调用第三方请求;
三、准备工作
模拟三方http接口:
package com.example.test01;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@RestController
public class TestAction {
@GetMapping("/test/abc")
public void test(HttpServletRequest request){
Enumeration<String> enumeration = request.getHeaderNames();
System.out.println(enumeration.nextElement());
String auth = request.getHeader("token");
System.out.println("========================token:"+auth);
System.out.println("========================Authentication:"+request.getHeader("Authentication"));
System.out.println("========================test:"+request.getHeader("test"));
}
}
代理程序的pom.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sk</groupId>
<artifactId>springTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springTest</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
</properties>
<dependencies>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>-->
<!--<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${
spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
四、gateway实现动态代理(代码示例)
1、静态代码配置
注:不能设置动态请求header
spring:
application:
name: gateway-application
cloud:
# Spring Cloud Gateway 配置项,对应 GatewayProperties 类
gateway:
# 路由配置项,对应 RouteDefinition 数组
routes:
- id: test01# 路由的编号
uri: https://127.0.0.1:8089 # 路由到的目标地址,只能到域名部分
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/test
filters:
- StripPrefix=1
- AddRequestHeader=token, 1234567
- setRequestHeader=Authentication, 321654
2、gateway实现动态封装request请求
1)示例一
package com.study;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SpringTestApplication {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("route-test",
r -> r.path("/test/**").filters(f->f.addRequestHeader("token","123456")
.setRequestHeader("Authentication","9999999")
.addRequestHeader("test","232333")).
uri("http://127.0.0.1:8089")
);
// addRequestHeader和setRequestHeader区别
return routes.build();
}
public static void main(String[] args) {
SpringApplication.run(SpringTestApplication.class, args);
}
}
执行结果:
========================token:123456
========================Authentication:9999999
========================test:232333
2)示例二
RequestFilter类:
package com.study.config;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class RequestFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest().mutate()
.header("test", "hahahaha")
.header("token", "123456")
.header("Authentication", "654321")
.build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
return 0;
}
}
RouteConfiguration 类:
package com.study.config;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@RequiredArgsConstructor
@Configuration
public class RouteConfiguration {
private final RequestFilter requestFilter;
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("route-test",
r -> r.path("/test/**").filters(f->f.filters(requestFilter)).
uri("http://127.0.0.1:8089")
);
// addRequestHeader和setRequestHeader区别
return routes.build();
}
}
执行结果:
========================token:123456
========================Authentication:654321
========================test:hahahaha
五、http接口实现(代码示例)
package com.study.controller;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
@RestController
public class ProxyController {
private String targetAddr = "http://127.0.0.1:8089";
/**
* 代理所有请求
*
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "/proxy/**", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void proxy(HttpServletRequest request, HttpServletResponse response) throws IOException, URISyntaxException {
// String url = URLDecoder.decode(request.getRequestURL().toString(), "UTF-8");
URI uri = new URI(request.getRequestURI());
String path = uri.getPath();
String query = request.getQueryString();
String target = targetAddr + path.replace("/proxy", "");
if (query != null && !query.equals("") && !query.equals("null")) {
target = target + "?" + query;
}
URI newUri = new URI(target);
// 执行代理查询
String methodName = request.getMethod();
HttpMethod httpMethod = HttpMethod.resolve(methodName);
if (httpMethod == null) {
return;
}
ClientHttpRequest delegate = new SimpleClientHttpRequestFactory().createRequest(newUri, httpMethod);
Enumeration<String> headerNames = request.getHeaderNames();
// 设置请求头
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
Enumeration<String> v = request.getHeaders(headerName);
List<String> arr = new ArrayList<>();
while (v.hasMoreElements()) {
arr.add(v.nextElement());
}
delegate.getHeaders().addAll(headerName, arr);
}
delegate.getHeaders().add("Authentication","1234567");
delegate.getHeaders().add("token","2222222222");
delegate.getHeaders().add("test","3333333333333");
StreamUtils.copy(request.getInputStream(), delegate.getBody());
// 执行远程调用
ClientHttpResponse clientHttpResponse = delegate.execute();
response.setStatus(clientHttpResponse.getStatusCode().value());
// 设置响应头
clientHttpResponse.getHeaders().forEach((key, value) -> value.forEach(it -> {
response.setHeader(key, it);
}));
StreamUtils.copy(clientHttpResponse.getBody(), response.getOutputStream());
}
}
执行结果:
========================token:2222222222
========================Authentication:1234567
========================test:3333333333333
六、总结
nginx
实现代理功能比较好的组件,也依赖于它使用了多路复用功能
,所有java实现http接口代理功能
也可以使用netty
实现,这个后面会实现~~~