Forest Send Http Request

923 阅读6分钟

Forest Send Http Request

1.Dependency

一個是forest的依賴,一個是json的依賴。

<dependency>
    <groupId>com.dtflys.forest</groupId>
    <artifactId>forest-spring-boot-starter</artifactId>
    <version>1.5.30</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.73</version>
</dependency>

2.Config

# 后端HTTP框架(默认为 okhttp3)
forest.backend=okhttp3
# 连接池最大连接数(默认为 500)
forest.max-connections=1000
# 每个路由的最大连接数(默认为 500)
forest.max-route-connections=500
# [自v1.5.22版本起可用] 最大请求等待队列大小
forest.max-request-queue-size=800
# [自v1.5.21版本起可用] 最大异步线程数
forest.max-async-thread-size=300
# [自v1.5.22版本起可用] 最大异步线程池队列大小
forest.max-async-queue-size=16
# (已不推荐使用) 请求超时时间,单位为毫秒(默认为 3000)
forest.timeout=3000
# 连接超时时间,单位为毫秒(默认为 timeout)
forest.connect-timeout=3000
# 数据读取超时时间,单位为毫秒(默认为 timeout)
forest.read-timeout=3000
# 请求失败后重试次数(默认为 0 次不重试)
forest.max-retry-count=0
# 单向验证的HTTPS的默认TLS协议(默认为 TLS)
forest.ssl-protocol=TLS
# 打开或关闭日志(默认为 true)
forest.log-enabled=true
# 打开/关闭Forest请求日志(默认为 true)
forest.log-request=true
# 打开/关闭Forest响应状态日志(默认为 true)
forest.log-response-status=true
# 打开/关闭Forest响应内容日志(默认为 false)
forest.log-response-content=true
# [自v1.5.27版本起可用] 异步模式(默认为 platform)
forest.async-mode=platform
# 声明全局变量,变量名: username,变量值: foo
forest.variables.username=foo
# 声明全局变量,变量名: userpwd,变量值: bar
forest.variables.userpwd=bar
# url
forest.variables.baseurl=https://www.baidu.com
# 在spring上下文中bean的id,默认值为forestConfiguration
forest.bean-id: config0

然后便可以在 Spring 中通过 Bean 的名称引用到它
@Resource(name = "config0")
private ForestConfiguration config0;

3.Interface

3.1 Simple Request

最簡單的請求,默認get方式。

public interface MyClient {

    @Request("http://localhost:8080/hello")
    String simpleRequest();

}

3.2 Complex Request

同樣是get請求,添加了請求頭。

public interface MyClient {

    @Request(
            url = "http://localhost:8080/hello/user",
            headers = "Accept: text/plain"
    )
    String sendRequest(@Query("uname") String username);
}

調用的方式比較簡單,直接注入Client,調用裡面的方法即可。

@Resource
MyClient myClient;

myClient.sendRequest("foo");

3.3 Request Mapping

image.png

3.4 String Concatenation

1.對請求的url進行拼接

@BaseRequest(baseURL = "http://localhost:8080")
public interface MyClient {
  
    @Get("/hello/user")
    String send1(@Query("username") String username);

    @Get("http://www.xxx.com/hello/user")
    String send2(@Query("username") String username);
}

2.url從配置文件獲取,再進行拼接。

# 提前在applicaton.properties中定義baseurl
forest.variables.baseUrl=https://www.baidu.com

@BaseRequest(baseURL = "${baseUrl}")
public interface MyClient {

    @Get("/hello/user")     
    String send1(@Query("username") String username);

    @Get("/hello/user")
    String send2(@Var("baseUrl") String baseUrl);
  
}

3.可以通過參數局部替換url或者整體引入

/**
 * 整个完整的URL都通过参数传入
 * {0}代表引用第一个参数
 */
@Get("{0}")
String send1(String myURL);

/**
 * 整个完整的URL都通过 @Var 注解修饰的参数动态传入
 */
@Get("{myURL}")
String send2(@Var("myURL") String myURL);

/**
 * 通过参数转入的值作为URL的一部分
 */
@Get("http://{myURL}/abc")
String send3(@Var("myURL") String myURL);

/**
 * 参数转入的值可以作为URL的任意一部分
 */
@Get("http://localhost:8080/test/{myURL}?a=1&b=2")
String send4(@Var("myURL") String myURL);

4.Interceptor

定义一个拦截器需要实现com.dtflys.forest.interceptor.Interceptor接口

public class SimpleInterceptor<T> implements Interceptor<T> {

    private final static Logger log = LoggerFactory.getLogger(SimpleInterceptor.class);

    /**
     * 该方法在被调用时,并在beforeExecute前被调用 
     * @Param request Forest请求对象
     * @Param args 方法被调用时传入的参数数组 
     */
    @Override
    public void onInvokeMethod(ForestRequest req, ForestMethod method, Object[] args) {
        log.info("on invoke method");
        // req 为Forest请求对象,即 ForestRequest 类实例
        // method 为Forest方法对象,即 ForestMethod 类实例
        // addAttribute作用是添加和Forest请求对象以及该拦截器绑定的属性
        addAttribute(req, "A", "value1");
        addAttribute(req, "B", "value2");
    }

    /**
     * 在请求体数据序列化后,发送请求数据前调用该方法
     * 默认为什么都不做
     * 注: multlipart/data类型的文件上传格式的 Body 数据不会调用该回调函数
     *
     * @param request Forest请求对象
     * @param encoder Forest转换器
     * @param encodedData 序列化后的请求体数据
     */
    public byte[] onBodyEncode(ForestRequest request, ForestEncoder encoder, byte[] encodedData) {
        // request: Forest请求对象
        // encoder: 此次转换请求数据的序列化器
        // encodedData: 序列化后的请求体字节数组
        // 返回的字节数组将替换原有的序列化结果
        // 默认不做任何处理,直接返回参数 encodedData
        return encodedData;
    }


    /**
     * 该方法在请求发送之前被调用, 若返回false则不会继续发送请求
     * @Param request Forest请求对象
     */
    @Override
    public boolean beforeExecute(ForestRequest req) {
        log.info("invoke Simple beforeExecute");
        // 执行在发送请求之前处理的代码
        req.addHeader("accessToken", "11111111");  // 添加Header
        req.addQuery("username", "foo");  // 添加URL的Query参数
        return true;  // 继续执行请求返回true
    }

    /**
     * 该方法在请求成功响应时被调用
     */
    @Override
    public void onSuccess(T data, ForestRequest req, ForestResponse res) {
        log.info("invoke Simple onSuccess");
        // 执行成功接收响应后处理的代码
        int status = res.getStatusCode(); // 获取请求响应状态码
        String content = res.getContent(); // 获取请求的响应内容
        String result = (String)data;  // data参数是方法返回类型对应的返回数据结果,注意需要视情况修改对应的类型否则有可能出现类转型异常
        result = res.getResult(); // getResult()也可以获取返回的数据结果
        response.setResult("修改后的结果: " + result);  // 可以修改请求响应的返回数据结果
        
        // 使用getAttributeAsString取出属性,这里只能取到与该Forest请求对象,以及该拦截器绑定的属性
        String attrValue1 = getAttributeAsString(req, "A1");

    }

    /**
     * 该方法在请求发送失败时被调用
     */
    @Override
    public void onError(ForestRuntimeException ex, ForestRequest req, ForestResponse res) {
        log.info("invoke Simple onError");
        // 执行发送请求失败后处理的代码
        int status = res.getStatusCode(); // 获取请求响应状态码
        String content = res.getContent(); // 获取请求的响应内容
        String result = res.getResult(); // 获取方法返回类型对应的返回数据结果
    }

    /**
     * 该方法在请求发送之后被调用
     */
    @Override
    public void afterExecute(ForestRequest req, ForestResponse res) {
        log.info("invoke Simple afterExecute");
        // 执行在发送请求之后处理的代码
        int status = res.getStatusCode(); // 获取请求响应状态码
        String content = res.getContent(); // 获取请求的响应内容
        String result = res.getResult(); // 获取方法返回类型对应的最终数据结果
    }
}

4.1攔截一個方法

public interface SimpleClient {

    @Request(
            url = "http://localhost:8080/hello/user?username=foo",
            headers = {"Accept:text/plain"},
            interceptor = SimpleInterceptor.class
    )
    String simple();
}

4.2攔截所有方法

@BaseRequest(
baseURL = "<http://localhost:8080>",
interceptor = {SimpleInterceptor1.class, SimpleInterceptor2.class, ...})
public interface SimpleClient {
      // ... ...
}

4.3最終版,攔截器存儲Cookie

/**
 * 处理Cookie的拦截器
 */
public class CookieInterceptor implements Interceptor {
    
    // Cookie在本地存储的缓存
    private Map<String, List<ForestCookie>> cookieCache = new ConcurrentHashMap<>();

    /**
     * 在请求响应成功后,需要保存Cookie时调用该方法
     *
     * @param request Forest请求对象
     * @param cookies Cookie集合,通过响应返回的Cookie都从该集合获取
     */
    @Override
    public void onSaveCookie(ForestRequest request, ForestCookies cookies) {
        // 获取请求URI的主机名
        String host = request.getURI().getHost();
        // 将从服务端获得的Cookie列表放入缓存,主机名作为Key
        cookieCache.put(host, cookies.allCookies());
    }

    /**
     * 在发送请求前,需要加载Cookie时调用该方法
     *
     * @param request Forest请求对象
     * @param cookies Cookie集合, 需要通过请求发送的Cookie都添加到该集合
     */
    @Override
    public void onLoadCookie(ForestRequest request, ForestCookies cookies) {
        // 获取请求URI的主机名
        String host = request.getURI().getHost();
        // 从缓存中获取之前获得的Cookie列表,主机名作为Key
        List<ForestCookie> cookieList = cookieCache.get(host);
        // 将缓存中的Cookie列表添加到请求Cookie列表中,准备发送到服务端
        // 默认情况下,只有符合条件 (和请求同域名、同URL路径、未过期) 的 Cookie 才能被添加到请求中
        cookies.addAllCookies(cookieList);
    }

    /**
     * 该方法在请求发送之前被调用, 若返回false则不会继续发送请求
     * @Param request Forest请求对象
     */
    @Override
    public boolean beforeExecute(ForestRequest req) {
        log.info("invoke Simple beforeExecute");
        // 执行在发送请求之前处理的代码
        req.addHeader("accessToken", "11111111");  // 添加Header
        req.addQuery("username", "foo");  // 添加URL的Query参数
        return true;  // 继续执行请求返回true
    }

    @Override
    public void onError(ForestRuntimeException ex, ForestRequest request, ForestResponse response) {
        // ... ...
    }

    @Override
    public void onSuccess(Object data, ForestRequest request, ForestResponse response) {
        // ... ...
    }
}
    

4.4Basic Auth

@Override
public boolean beforeExecute(ForestRequest request) {
    ForestConfiguration configuration = request.getConfiguration();
    String username = (String)configuration.getVariableValue("username");
    String password = (String)configuration.getVariableValue("userpwd");

    String secretKey = username
            + ":"
            + password;
    byte[] tokenBytes = secretKey.getBytes();
    String  base64Token = Base64.getEncoder().encodeToString(tokenBytes);

    //String base64Token = new String(Base64.getEncoder().encode((username + ":" + password).getBytes(StandardCharsets.UTF_8)));
    request.addHeader("Authorization", "Basic " + base64Token);
    request.addHeader("Accept", "*/*");
    logger.info("this is beforeExecute");
    return true;
}