玩转WEB接口之四 【HTTP调试测试神器 httpbin实战】

一, 原始需求

萌新小明,入职某网络科技公司,职位互联网开发工程狮.

最近接到一个需求,与某第三方系统对接. 对方提供了接口文档. 小明已经按照接口文档开发好了代码.

现在小明想通过httpbin来测试发送的请求是否正确,他该怎么做?

二, 需求梳理

1. 接口信息

接口地址 请求方式 参数
https://test.00fly.online/get/method1 get param1、param2
https://test.00fly.online/post/method1 post param1、param2、file
https://test.00fly.online/json/method1 jsonbody post param1、param2

2. httpbin信息

服务地址: https://http.00fly.online

3. 流程梳理

观察流程图,发现接口作为httpbin服务的反向代理方来对外提供服务.这样访问接口的时候,便能看到提交的详细请求信息。

一般情况下,实现反向代理常见有2种方案:

  1. 微服务的网关技术
  2. nginx

三, 网关实现

1. 准备工作

test.00fly.online域名ssl证书申请和本地host配置先行,具体请移步 玩转WEB接口之三 【HTTPS证书申请】

2. 源码传送

项目使用Spingcloud gateway 技术来实现

代码结构如下:
如何使用下面的备份文件恢复成原始的项目代码,请移步查阅:神奇代码恢复工具

注意:需要手工放置test.00fly.online.pfx \src\main\resources\data\ssl

//goto 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.2.5.RELEASE</version>
		<relativePath />
	</parent>
	<groupId>***.fly</groupId>
	<artifactId>springcloud-gateway</artifactId>
	<version>0.0.1</version>
	<name>springcloud-gateway</name>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.SR3</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-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.***mons</groupId>
			<artifactId>***mons-lang3</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</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>
//goto src\main\java\***\fly\gateway\config\A***essLogGlobalFilter.java
package ***.fly.gateway.config;

import java.***.I***SocketAddress;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.***ponent;
import org.springframework.web.server.ServerWebExchange;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

/**
 * 全局过滤器
 */
@Slf4j
@***ponent
@Order(value = Integer.MIN_VALUE)
public class A***essLogGlobalFilter implements GlobalFilter
{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
        // filter的前置处理
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        I***SocketAddress remoteAddress = request.getRemoteAddress();
        return chain.filter(exchange) // filter的后置处理
            .then(Mono.fromRunnable(() -> {
                ServerHttpResponse response = exchange.getResponse();
                HttpStatus statusCode = response.getStatusCode();
                log.info("#### 请求路径:{}, 远程IP地址:{}, 响应码:{}", path, remoteAddress, statusCode);
            }));
    }
}
//goto src\main\java\***\fly\gateway\config\AuthorizeGatewayFilterFactory.java
package ***.fly.gateway.config;

import java.util.Arrays;
import java.util.List;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.***ponent;
import org.springframework.util.StringUtils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 局部过滤器 名称必须是xxxGatewayFilterFactory形式
 */
@Slf4j
@***ponent
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config>
{
    private static final String AUTHORIZE_TOKEN = "token";
    
    // 构造函数,加载Config
    public AuthorizeGatewayFilterFactory()
    {
        // 固定写法
        super(AuthorizeGatewayFilterFactory.Config.class);
        log.info("--------Loaded GatewayFilterFactory [Authorize]");
    }
    
    // 读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder()
    {
        // Config.enabled
        return Arrays.asList("enabled");
    }
    
    @Override
    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config)
    {
        return (exchange, chain) -> {
            // 判断是否开启授权验证
            if (!config.isEnabled())
            {
                return chain.filter(exchange);
            }
            
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            // 从请求头中获取token
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            if (token == null)
            {
                // 从请求头参数中获取token
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }
            
            ServerHttpResponse response = exchange.getResponse();
            // 如果token为空,直接返回401,未授权
            if (StringUtils.isEmpty(token))
            {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                // 处理完成,直接拦截,不再进行下去
                return response.set***plete();
            }
            /**
             * todo chain.filter(exchange) 之前的都是过滤器的前置处理
             *
             * chain.filter().then( 过滤器的后置处理........... )
             */
            // 授权正常,继续下一个过滤器链的调用
            return chain.filter(exchange);
        };
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config
    {
        // 控制是否开启认证
        private boolean enabled;
    }
}
//goto src\main\java\***\fly\gateway\config\ResolverConfig.java
package ***.fly.gateway.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import reactor.core.publisher.Mono;

/**
 * 限流解析器器
 */
@Configuration
public class ResolverConfig
{
    @Bean
    @Primary
    KeyResolver apiKeyResolver()
    {
        // 按URL限流,即以每秒内请求数按URL分组统计,超出限流的url请求都将返回429状态
        return exchange -> Mono.just(exchange.getRequest().getPath().toString());
    }
    
    @Bean
    KeyResolver userKeyResolver()
    {
        // 按用户限流
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
    }
    
    @Bean
    KeyResolver ipKeyResolver()
    {
        // 按IP来限流
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
}
//goto src\main\java\***\fly\gateway\web\IndexController.java
package ***.fly.gateway.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController
{
    @RequestMapping("/show/test1")
    public Object test1()
    {
        return "i am test1";
    }
    
    @RequestMapping("/show2/test2")
    public Object test2()
    {
        return "i am test2";
    }
}
//goto src\main\java\***\fly\GatewayApplication.java
package ***.fly;

import org.apache.***mons.lang3.StringUtils;
import org.apache.***mons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.***mandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.core.env.Environment;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication implements ***mandLineRunner
{
    @Value("${server.port}")
    Integer port;
    
    @Autowired
    Environment environment;
    
    public static void main(String[] args)
    {
        SpringApplication.run(GatewayApplication.class, args);
    }
    
    @Override
    public void run(String... args)
        throws Exception
    {
        if (SystemUtils.IS_OS_WINDOWS && port > 0)
        {
            log.info("★★★★★★★★  now open Browser ★★★★★★★★ ");
            log.info("请修改hosts: 127.0.0.1 test.00fly.online");
            String url = "https://test.00fly.online:" + port;
            
            String profile = StringUtils.join(environment.getActiveProfiles());
            switch (profile)
            {
                case "httpbin":
                    Runtime.getRuntime().exec("cmd /c start " + url + "/user-agent");
                    Runtime.getRuntime().exec("cmd /c start " + url + "/headers");
                    Runtime.getRuntime().exec("cmd /c start " + url + "/get");
                    Runtime.getRuntime().exec("cmd /c start " + url);
                    break;
                
                case "demo":
                    Runtime.getRuntime().exec("cmd /c start " + url + "/get/method1");
                    break;
                
                default:
                    Runtime.getRuntime().exec("cmd /c start " + url + "/gateway/show/test1");
                    Runtime.getRuntime().exec("cmd /c start " + url + "/show2/test2");
                    break;
            }
        }
    }
}
//goto src\main\resources\application-demo.yml
spring:
  cloud:
    gateway:
      routes:
        # 路由:method=get访问https://http.00fly.online/get
        - id: get-route
          uri: https://http.00fly.online
          predicates:
            - Method=GET
          filters:
            - SetPath=/get
            - RewritePath=/(?<segment>/?.*), /get
            
        # 路由:method=post访问https://http.00fly.online/post
        - id: post-route
          uri: https://http.00fly.online/post
          predicates:
            - Method=POST
          filters:
            - SetPath=/post
            - RewritePath=/(?<segment>/?.*), /post
//goto src\main\resources\application-dev.yml
spring:
  cloud:
    gateway:
      # 路由数组:指当请求满足什么样的断言时,转发到哪个服务上
      routes:
        # 路由标识,要求唯一,名称任意
        - id: route-1
          # 请求最终被转发到的目标地址
          uri: https://test.00fly.online:443
          # 设置断言
          predicates:
            - Path=/gateway/show/{segment}
          filters:
            # StripPrefix:去除原始请求路径中的前1级路径,即/gateway
            - StripPrefix=1

            #局部过滤器
            - AddResponseHeader=X-Response-Foo, Bar
            ## AuthorizeGatewayFilterFactory自定义过滤器配置,值为true需要验证授权,false不需要
            - Authorize=false

//goto src\main\resources\application-httpbin.yml
spring:
  cloud:
    gateway:
      # 路由数组:指当请求满足什么样的断言时,转发到哪个服务上
      routes:
        # 路由标识,要求唯一
        - id: host_route
          # 请求最终被转发到的目标地址
          uri: https://http.00fly.online
          # 设置断言
          predicates:
            - Host=test.00fly.online
//goto src\main\resources\application-test.yml
spring:
  cloud:
    gateway:
      # 路由数组:指当请求满足什么样的断言时,转发到哪个服务上
      routes:
        # 路由标识,要求唯一,名称任意
        - id: path-route
          # 请求最终被转发到的目标地址
          uri: https://www.baidu.***
          # 设置断言
          predicates:
            - Path=/bd/**
          filters:
            # StripPrefix:去除原始请求路径中的前1级路径,即/bd
            - StripPrefix=1
//goto src\main\resources\application.yml
server:
  port: 443
  ssl:
    key-store: classpath:data/ssl/test.00fly.online.pfx
    key-store-password: 12345asdfg
    keyStoreType: PKCS12
  servlet:
    context-path: /
    session:
      timeout: 1800
spring:
  application:
    name: gateway
  profiles:
    active:
    - demo

https://gitee.***/00fly/effict-side/tree/master/springcloud-gateway

3. 代码运行

直接运行 GatewayApplication

或者打包运行

mvn clean package

#直接运行
cd target
java -jar springcloud-gateway-0.0.1.jar

启动成功后系统自动打开浏览器访问: https://test.00fly.online/get/method1

返回json数据如下:

{
  "args": {}, 
  "headers": {
    "A***ept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", 
    "A***ept-Encoding": "gzip, deflate, br", 
    "A***ept-Language": "zh-***,zh;q=0.9,en;q=0.8", 
    "Cache-Control": "max-age=0", 
    "Connection": "close", 
    "Content-Length": "0", 
    "Forwarded": "proto=https;host=test.00fly.online;for=\"127.0.0.1:55669\"", 
    "Host": "http.00fly.online", 
    "Sec-Ch-Ua": "\"Not A(Brand\";v=\"99\", \"Google Chrome\";v=\"121\", \"Chromium\";v=\"121\"", 
    "Sec-Ch-Ua-Mobile": "?0", 
    "Sec-Ch-Ua-Platform": "\"Windows\"", 
    "Sec-Fetch-Dest": "document", 
    "Sec-Fetch-Mode": "navigate", 
    "Sec-Fetch-Site": "none", 
    "Sec-Fetch-User": "?1", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", 
    "X-Forwarded-Host": "test.00fly.online"
  }, 
  "origin": "127.0.0.1, 61.169.67.18", 
  "url": "https://test.00fly.online/get"
}

4. PostMan测试

https://test.00fly.online/post/method1 测试结果

https://test.00fly.online/json/method1 测试结果

四, nginx实现(待调试)

nginx如何实现下面的功能?

get请求统一转发到https://http.00fly.online/get

post请求统一转发到https://http.00fly.online/post

目前还没找到具体的实现方法,哪位大佬有这方面的经验或想法,欢迎交流,不胜感激!


未完待续,有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!

-over-

转载请说明出处内容投诉
CSS教程_站长资源网 » 玩转WEB接口之四 【HTTP调试测试神器 httpbin实战】

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买