【技术应用】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实现,这个后面会实现~~~

猜你喜欢

转载自blog.csdn.net/weixin_37598243/article/details/128309580