HttpClient-forward proxy and signature verification

Article directory

  • Introduction
  • practice
    • HttpClientBuilder
    • Customized chain of responsibility
    • test

Introduction

HttpClientBuilder is an http client construction class of Apache. By inheriting the construction class, signature verification can be added, so that signature verification can be carried when sending requests uniformly.

Customized MyHttpClientBuilder adds a new link to the chain of responsibility

Practice

HttpClientBuilder

HttpClientBuilder has many public methods, but they are all final definitions and cannot be overridden by subclasses.

The key transformation lies in decorateProtocolExec

@NoArgsConstructor
public class MyHttpClientBuilder extends HttpClientBuilder {

    private String name;
    private String password;

    public static MyHttpClientBuilder create() {
        return new MyHttpClientBuilder();
    }

    public MyHttpClientBuilder setName(String name) {
        this.name = name;
        return this;
    }

    public MyHttpClientBuilder setPassword(String password) {
        this.password = password;
        return this;
    }

    /**
     * Create main chain
     */
    @Override
    ProteCted ClientexecChain CreateMainexec (httprequestexecutor requestexec, httpClientConnectionManager, ConnectionReuseSESTRATEGY Reusestrategy, Conn EctionKEPALIVESTRATEGY KeepaliveStrategy, httpprocessor proxyhttpprocessor, authenticationStrateAutrategy, AuthenticationsStrategy ProxyAut hstraategy, usertokenhandler userTokenhandler) {{
        return super.createMainExec(requestExec, connManager, reuseStrategy, keepAliveStrategy, proxyHttpProcessor, targetAuthStrategy, proxyAuthStrategy, userTokenHandler);
    }

    /**
     * Decorative main chain
     */
    @Override
    protected ClientExecChain decorateMainExec(ClientExecChain mainExec) {
        return super.decorateMainExec(mainExec);
    }

    /**
     * decorative
     */
    @Override
    protected ClientExecChain decorateProtocolExec(ClientExecChain protocolExec) {
// return super.decorateProtocolExec(protocolExec);
        return new MyClientExecChain(name, password, protocolExec);
    }

    /**
     * Add http client for multi-client management
     */
    @Override
    protected void addCloseable(Closeable closeable) {
        super.addCloseable(closeable);
    }

    /**
     * Initialize InternalHttpClient
     */
    @Override
    public CloseableHttpClient build() {
        return super.build();
    }
}

Customized chain of responsibility

Define the chain of responsibility and add the request header token

public class MyClientExecChain implements ClientExecChain {

    private final String name;
    private final String password;
    private final ClientExecChain mainExec;

    public MyClientExecChain(String name,String password,ClientExecChain mainExec){
        this.name = name;
        this.password = password;
        this.mainExec = mainExec;
    }

    /**
     * The chain of responsibility needs to stipulate the conditions for its effectiveness
     * */
    @Override
    public CloseableHttpResponse execute(HttpRoute route,
                                         HttpRequestWrapper request,
                                         HttpClientContext clientContext,
                                         HttpExecutionAware execAware) throws IOException, HttpException {

        //Domain name to control access
        if ( Objects.nonNull(request.getURI().getHost()) & amp; & amp; request.getURI().getHost().endsWith("127.0.0.1")) {
            return executeMy(route, request, clientContext, execAware);
        } else {
            return mainExec.execute(route, request, clientContext, execAware);
        }
    }

    /**
     * Add content
     * */
    private CloseableHttpResponse executeMy(HttpRoute route, HttpRequestWrapper request, HttpClientContext clientContext,
                                            HttpExecutionAware execAware) throws IOException, HttpException{
        //Add authentication information
        request.addHeader("token", name + "-" + password);

        //Execute mainExec
        CloseableHttpResponse response = mainExec.execute(route, request, clientContext, execAware);

        // Verify successful response
        StatusLine statusLine = response.getStatusLine();
        if (statusLine.getStatusCode() >= 200 & amp; & amp; statusLine.getStatusCode() < 300) {
            // Convert to an entity with repeatable responses
            convertToRepeatableResponseEntity(response);
            if (!(request.getOriginal() instanceof WxPayV3DownloadHttpGet)) {
                if (!validate(response)) {
                    throw new HttpException("Signature verification failed");
                }
            }
        }
        return response;
    }

    /**
     * Convert to an entity with repeatable responses
     * */
    protected void convertToRepeatableResponseEntity(CloseableHttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        if (entity != null & amp; & amp; !entity.isRepeatable()) {
            response.setEntity(newRepeatableEntity(entity));
        }
    }

    /**
     * New entity
     * */
    protected HttpEntity newRepeatableEntity(HttpEntity entity) throws IOException {
        byte[] content = EntityUtils.toByteArray(entity);
        ByteArrayEntity newEntity = new ByteArrayEntity(content);
        newEntity.setContentEncoding(entity.getContentEncoding());
        newEntity.setContentType(entity.getContentType());

        return newEntity;
    }

    /**
     * Verification
     * */
    private boolean validate(CloseableHttpResponse response) throws IOException {
        // If it is not in json format, return directly
        if (!ContentType.APPLICATION_JSON.getMimeType()
                .equals(ContentType.parse(
                        String.valueOf(response.getFirstHeader("Content-Type").getValue())
                ).getMimeType())) {
            return true;
        }
        // Get header information
        Header name = response.getFirstHeader("name");

        //No data exists
        if (name == null) {
            return false;
        }

        return !Objects.equals(name,this.name);
    }

}

Test

When requesting here, it is 127.0.0.1, which is also the domain name intercepted by the chain of responsibility.

 @Test
    void contextLoads() {

        String url = "http://127.0.0.1:9090/http/test";

        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
        // Transaction code
        request.setOutTradeNo("5566");
        //Notification address (callback address)
        request.setNotifyUrl("https://api.qq.com/");
        request.setDescription("This is a description");

        WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
        // Merchant openid
        payer.setOpenid("openid");
        request.setPayer(payer);

        //Build amount information
        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
        //Set currency information
        amount.setCurrency("CNY");
        //Set the amount, calculated in points here
        amount.setTotal(BigDecimal.ONE.multiply(BigDecimal.valueOf(100)).intValue());
        request.setAmount(amount);

        String res = post(url,JSON.toJSONString(request));
        log.info("http request ended:",res);
    }

    private String post(String url,String entity){

        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(RequestConfig.custom()
                //The time to get the connection from the connection pool
                .setConnectionRequestTimeout(1000)
                // Time to establish connection
                .setConnectTimeout(1000)
                //Time to read data
                .setSocketTimeout(3000)
                .build());
        httpPost.setEntity(new StringEntity(entity, ContentType.create("application/json", "utf-8")));

        CloseableHttpClient httpClient = MyHttpClientBuilder
                .create()
                .setName("zhang")
                .setPassword("1234").build();

        try (CloseableHttpResponse response = httpClient.execute(httpPost)) {

            int statusCode = response.getStatusLine().getStatusCode();
            //The post method may not return a value
            String responseString = null;
            if (response.getEntity() != null) {
                responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
            }
            //Status code judgment 200 204 successful
            if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
                this.log.info("\
[Request address]: {}\
[Request data]: {}\
[Response data]: {}", url, entity, responseString);
                return responseString;
            }
            //An error message is returned
            throw new MyException(responseString);
        } catch (Exception e) {
           log.error("Exception:",e);
        } finally {
            // close connection
            httpPost.releaseConnection();
        }
        return null;
    }