Microsoft Azure Cdn 接口对接详解

背景

随着公司跟chinaCache公司的cdn服务到期,最终选择了微软的Azure Cdn进行合作,因此需要在将原有的接口上进行拓展,支持Azure Cdn接口的支持。

官网对接文档:https://docs.azure.cn/zh-cn/cdn/cdn-api-signature

各位假如直接看官方接口文档进行对接,可能多少会有点懵,因为接口比较多,不知道具体要对接哪几个,以及请求header入参等多少都会有些坑。

 

如果大家以前公司用的是CHINACACHE公司的cdn产品的话,应该都知道这个对接,是直接通过输入用户名、密码、task的json格式进行传输。而Azure CDN 跟这种方式传输还是蛮大差别的。最近接口对接也已经上线, 我这边整理了自己对接的一些感受,还有遇到过的坑,希望对有需要的朋友有帮助。

步骤

 

建议可以先去看下官方的对接文档,先大致熟悉下对接流程。

我这边主要需要用到以下几个菜单,大家可以直接参考官网

  •    CDN API签名机制
  •    节点管理-获取订阅下所有节点信息
  •    缓存刷新-添加缓存刷新

对接流程也是按照上面几个步骤进行的,下面我会结合代码分别进行介绍

1.CDN API签名机制

 

这一步主要根据入参条件不同生成不同的签名字符串,签名生成方法封装到AzureSignTool

package com.trendy.ccp.server.cdn.tool;

 

import java.net.URL;

import java.util.Map;

import java.util.TreeMap;

import java.util.stream.Collectors;

 

import javax.crypto.Mac;

import javax.crypto.spec.SecretKeySpec;

 

/****

*

* @ClassName: AzureSignTool

* @Description: 微软生产api签名

* @author youqiang.xiong

* @date 2018年1月10日下午5:30:35

*

*/

publicclass AzureSignTool {

 

    /**

     * Calculate the authorization header

     *

     * @param requestURL Complete request URL with scheme, host, path and queries

     * @param requestTime UTC request time with format "yyyy-MM-dd hh:mm:ss"

     * @param keyID API key ID

     * @param keyValue API key value

     * @param httpMethod HTTP method in upper case

     * @return Calculated authorization header

     */

    publicstatic String calculateAuthorizationHeader(String requestURL,

            String requestTime, String keyID, String keyValue, String httpMethod)

            throws Exception {

        URL url = new URL(requestURL);

        String path = url.getPath();

 

        // Get query parameters

        String query = url.getQuery();

        String[] params = query.split("&");

        Map<String, String> paramMap = new TreeMap<String, String>();

        for (String param : params) {

            String[] paramterParts = param.split("=");

            if (paramterParts.length != 2) {

                continue;

            }

 

            paramMap.put(paramterParts[0], paramterParts[1]);

        }

 

        String orderedQueries = paramMap.entrySet().stream()

                .map(entry -> entry.getKey() + ":" + entry.getValue())

                .collect(Collectors.joining(", "));

 

        String content = String.format("%s\r\n%s\r\n%s\r\n%s", path,

                orderedQueries, requestTime, httpMethod);

        Mac sha256HMAC = Mac.getInstance("HmacSHA256");

        SecretKeySpec secret_key = new SecretKeySpec(keyValue.getBytes(),"HmacSHA256");

        sha256HMAC.init(secret_key);

        byte[] bytes = sha256HMAC.doFinal(content.getBytes());

 

        StringBuffer hash = new StringBuffer();

        for (inti = 0; i < bytes.length; i++) {

            String hex = Integer.toHexString(0xFF & bytes[i]);

            if (hex.length() == 1) {

                hash.append('0');

            }

            hash.append(hex);

        }

 

        return String.format("AzureCDN %s:%s", keyID, hash.toString()

                .toUpperCase());

    }

 

String orderedQueries = paramMap.entrySet().stream()

                .map(entry -> entry.getKey() + ":" + entry.getValue())

                .collect(Collectors.joining(", "));


注意:以上这段代码采用的jdk1.8stream语法,需要使用jdk1.8进行编译,如果原来并未使用1.8的话,需要将1.8的语法转化成低版本的语法

 

     

   Map<String, String> paramMap = new HashMap<String, String>();

        paramMap.put("a", "1");

        paramMap.put("b", "2");

        paramMap.put("c", "3");

        paramMap.put("d", "4");

        

        StringBuffer sb = new StringBuffer("");

        for(Map.Entry<String, String> entry :paramMap.entrySet()){

            sb.append(entry.getKey()+":"+entry.getValue()).append(", ");

        }

        

        System.out.println("jdk1.6:" + sb.substring(0,sb.length()-2));

        

        String orderedQueries = paramMap.entrySet().stream()

                .map(entry -> entry.getKey() + ":" + entry.getValue())

                .collect(Collectors.joining(", "));

        System.out.println("jdk1.8:"+orderedQueries);

这里我的测试用例,可以将jdk1.8转换成jdk1.6的语法

打印结果如下所示:

 

jdk1.6:a:1, b:2, c:3, d:4

jdk1.8:a:1, b:2, c:3, d:4

 

2.HttpKit请求工具类

 

因为所有接口的请求都需要通过http封装header信息和body信息发送getpost请求,因此我将getpost的方法进行封装到HttpKit工具类中

package com.trendy.ccp.server.cdn.tool;

 

import java.io.IOException;

import java.io.InputStream;

import java.util.Map;

 

import org.apache.commons.httpclient.Header;

import org.apache.commons.httpclient.HttpClient;

import org.apache.commons.httpclient.HttpException;

import org.apache.commons.httpclient.methods.StringRequestEntity;

import org.apache.commons.io.IOUtils;

 

import com.trendy.fw.common.transfer.CharsetGetMethod;

import com.trendy.fw.common.transfer.CharsetPostMethod;

import com.trendy.fw.common.transfer.HttpClientResultBean;

 

/****

*

* @ClassName: HttpKit

* @Description: 接口请求封装类,目前只提供get和post两个方法

* @author youqiang.xiong

* @date 2018年1月29日下午4:50:10

*

*/

public class HttpKit {

 

    

    /*****

     * 通过http的get方式,获取服务器的数据

     * @param url

     * @param headerMap

     * @param charset

     * @return

     */

    public static HttpClientResultBean getContext(String url,Map<String, String> headerMap, String charset){

        

        

        HttpClientResultBean result = new HttpClientResultBean();

        HttpClient httpClient = new HttpClient();

        CharsetGetMethod getMethod = new CharsetGetMethod(url, charset);

        InputStream responseStream = null;

        if(headerMap.keySet().size() > 0){

            for(Map.Entry<String, String> entry:headerMap.entrySet()){

                Header header = new Header();

                header.setName(entry.getKey());

                header.setValue(entry.getValue());

                getMethod.addRequestHeader(header);

            }

        }

        try {

            int e = httpClient.executeMethod(getMethod);

            if (e == 200) {

                responseStream = getMethod.getResponseBodyAsStream();

                result.setResult(true);

                result.setResultByteContent(IOUtils.toByteArray(responseStream));

                result.setResultContent(new String(result.getResultByteContent(), charset));

            } else {

                responseStream = getMethod.getResponseBodyAsStream();

                result.setResult(false);

                result.setResultContent(new String(IOUtils.toByteArray(responseStream), charset));

            }

        } catch (Exception arg15) {

            result.setResult(false);

            result.setResultContent(arg15.toString());

        } finally {

            if (responseStream != null) {

                try {

                    responseStream.close();

                    responseStream = null;

                } catch (Exception arg14) {

                }

            }

 

            if (getMethod != null) {

                getMethod.releaseConnection();

                getMethod = null;

            }

 

        }

        return result;

    

    }

    

    /****

     * 通过http的post方式,提交数据到服务器

     * @param url

     * @param headerMap

     * @param data

     * @param charset

     * @return

     */

    public static HttpClientResultBean postContent(String url,Map<String, String> headerMap, String data,

            String charset) {

        HttpClientResultBean result = new HttpClientResultBean();

        HttpClient httpClient = new HttpClient();

        CharsetPostMethod postMethod = new CharsetPostMethod(url, charset);

        if(headerMap.keySet().size() > 0){

            for(Map.Entry<String, String> entry:headerMap.entrySet()){

                Header header = new Header();

                header.setName(entry.getKey());

                header.setValue(entry.getValue());

                postMethod.addRequestHeader(header);

            }

        }

        postMethod.setRequestEntity(new StringRequestEntity(data));

        

        try {

            int e = httpClient.executeMethod(postMethod);

            if (e == 200 || e == 202) {

                result.setResult(true);

                result.setResultByteContent(IOUtils.toByteArray(postMethod

                        .getResponseBodyAsStream()));

                result.setResultContent(new String(result

                        .getResultByteContent(), charset));

            } else {

                result.setResult(false);

                postMethod.getParams().setParameter(

                        "http.protocol.content-charset", charset);

            }

        } catch (HttpException arg11) {

            result.setResult(false);

            result.setResultContent(arg11.toString());

        } catch (IOException arg12) {

            result.setResult(false);

            result.setResultContent(arg12.toString());

        } finally {

            if (postMethod != null) {

                postMethod.releaseConnection();

                postMethod = null;

            }

 

        }

 

        return result;

    }

     

}


 

3.封装请求的实体类

 

将入参的请求固定格式进行封装MircosoftRequestParam

package com.trendy.ccp.server.cdn.config;

 

/****

*

* @ClassName: MircosoftRequestParam

* @Description: 微软CDN 接口请求参数固定格式类

* @author youqiang.xiong

* @date 2018年1月12日下午4:13:19

*

*/

publicclass MircosoftRequestParam {

 

    /***

     * 获取订阅下所有节点信息,符合yyyy-MM-dd hh:mm:ss格式的UTC当前请求时间

     */

    publicstatic String MICROSOFT_CDN_GET_NODES_HEADER1 = "x-azurecdn-request-date";

    /****

     * 获取订阅下所有节点信息,必填授权

     */

    publicstatic String MICROSOFT_CDN_GET_NODES_HEADER2 = "Authorization";

    

    /***

     * 添加缓存刷新,符合yyyy-MM-dd hh:mm:ss格式的UTC当前请求时间

     */

    publicstatic String MICROSOFT_CDN_CACHE_REFRESH_HEADER1 = "x-azurecdn-request-date";

    /****

     * 添加缓存刷新,必填授权

     */

    publicstatic String MICROSOFT_CDN_CACHE_REFRESH_HEADER2 = "Authorization";

    /****

     * 添加缓存刷新,必填。application/json

     */

    publicstatic String MICROSOFT_CDN_CACHE_REFRESH_HEADER3 = "content-type";

     

}



4.缓存配置类

新增一个常量定义类MircosoftCacheConfig 配置类

package com.trendy.ccp.server.cdn.config;

 

import java.util.HashMap;

import java.util.Map;

 

import com.trendy.fw.common.config.Constants;

import com.trendy.fw.common.util.PropertiesKit;

 

/****

*

* @ClassName: MircosoftCacheConfig

* @Description: 微软CDN配置

* 1. 读取cdn节点属性配置初始化到hostEndpointMap (域名->节点ID)

* 2. 定义cdn两个接口常量和参数常量等

* @author youqiang.xiong

* @date 2018年1月10日上午11:30:22

*

*/

publicclassMircosoftCacheConfig {

    

    publicstaticfinal String AZURE_PROP_FILE_NAME = Constants.PROP_FILE_PATH + "/azure_cdn_endpoint";

 

    //Microsoft MICROSOFT cdn配置

    publicstatic Map<String, String> hostEndpointMap = new HashMap<String, String>();

     

    

    static{

        hostEndpointMap = PropertiesKit.getBundleAllProperties(AZURE_PROP_FILE_NAME);

    }

     

    

    publicstatic Map<String, String> getHostEndpointMap() {

        returnhostEndpointMap;

    }

  

    //Microsoft MICROSOFT cdn配置

    publicstatic String MICROSOFT_CDN_SUB_SCRIPTIONID = "MICROSOFT_CDN_SUB_SCRIPTIONID";

    publicstatic String MICROSOFT_CDN_KEY_ID = "MICROSOFT_CDN_KEY_ID";

    publicstatic String MICROSOFT_CDN_KEY_VALUE = "MICROSOFT_CDN_KEY_VALUE";

     

    //接口url,主要两个接口获取订阅下所有节点信息、缓存刷新接口

    publicstatic String MICROSOFT_CDN_URL_GET_NODES = "https://restapi.cdn.azure.cn/subscriptions/subscriptionId/endpoints?apiVersion=1.0";

    publicstatic String MICROSOFT_CDN_URL_CACHE_REFRESH = "https://restapi.cdn.azure.cn/subscriptions/subscriptionId/endpoints/endpointId/purges?apiVersion=1.0";

    //url中的两个常量定义

    publicstatic String MICROSOFT_CDN_URL_PARAM_SUB_SCRIPTIONID = "subscriptionId";

    publicstatic String MICROSOFT_CDN_URL_PARAM_ENDPOINTID = "endpointId";

     

    

}


 

5.域名-节点ID属性配置文件

 

新增一个属性配置文件azure_cdn_endpoint.properties,大致内容如下,这些内容来自于调"用节点管理-获取订阅下所有节点信息" 接口返回的数据,然后解析成域名和endpointID这样的关系,存入属性配置文件中,以供缓存MircosoftCacheConfig配置类进行初始化到hostEndpointMap

好处:减少获取节点接口调用,提升访问效率(毕竟接口调用是需要时间的,经过跟cdn运维人员确认,每个订阅ID下的节点数据都会一样,所以可以事先调用一次接口获取所有的节点)

my.covengarden.com=052b3030-f5dd-11e7-be87-0017fa000,

act.ochirly.com.cn=07ae85d2-f1bf-11e7-be87-0017fa000",

passport.ochirlyonline.com.cn=091491cd-f5be-11e7-be87-0017fa000,

passport.covengarden.com=0cfde6b1-f5df-11e7-be87-0017fa000"


6.单元测试类

 

提供两个接口的测试入口

1. 获取订阅下所有节点信息 2.缓存指定目录和文件接口

package com.trendy.ccp.server.cdn.tool;

 

import java.util.ArrayList;

import java.util.Date;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

 

import org.apache.commons.httpclient.util.DateUtil;

import org.junit.Test;

 

import com.trendy.ccp.server.cdn.config.MircosoftCacheConfig;

import com.trendy.ccp.server.cdn.config.MircosoftRequestParam;

import com.trendy.fw.common.config.Constants;

import com.trendy.fw.common.transfer.HttpClientResultBean;

import com.trendy.fw.common.util.JsonKit;

 

/****

* 接口测试类

* @ClassName: HttpTest

* @Description 主要两个接口 1. 获取订阅下所有节点信息 2.缓存指定目录和文件接口

* @author youqiang.xiong

* @date 2018年1月29日 下午3:55:03

*

*/

public class AzureCdnTest {

 

    

    private String subId = "c3068370-40e0-49dc-aa3e-244f070603ee";

    private String keyId = "52b67e39-856c-4930-a8e9-acf551201c39";

    private String keyValue = "MWZkZGU5YWMtNjI1NS00YjFkLThiZTMtODRjOTE3MWNiYWYw";

    

    /****

     *获取订阅下所有节点信息

     */

    @Test

    public void testGetEndpoint(){

 

        

        String url = MircosoftCacheConfig.MICROSOFT_CDN_URL_GET_NODES;

        if(url.contains(MircosoftCacheConfig.MICROSOFT_CDN_URL_PARAM_SUB_SCRIPTIONID)){

            url = url.replaceAll(MircosoftCacheConfig.MICROSOFT_CDN_URL_PARAM_SUB_SCRIPTIONID, subId);

        }

        System.out.println("url:\t" +url);

        String requestTime = DateUtil.formatDate(new Date(), "yyyy-MM-dd hh:mm:ss");

        System.out.println("requestTime:\t"+requestTime);

        String httpMethod = "GET";

        try {

            String authorizationHeader = AzureSignTool.calculateAuthorizationHeader(url, requestTime, keyId, keyValue, httpMethod);

            System.out.println("Signature:\t"+authorizationHeader);

            Map<String, String> paramMap = new HashMap<String, String>();

            paramMap.put(MircosoftRequestParam.MICROSOFT_CDN_GET_NODES_HEADER1, requestTime);

            paramMap.put(MircosoftRequestParam.MICROSOFT_CDN_GET_NODES_HEADER2, authorizationHeader);

            HttpClientResultBean responseBean = HttpKit.getContext(url, paramMap, Constants.CODE_UNICODE);

            System.out.println(responseBean.getResultContent());

            

            

        } catch (Exception e) {

            e.printStackTrace();

        }

 

        

    }

     

    

    /****

     * 刷新指定 文件 和目录下的缓存

     */

    @Test

    public void testCndReresh(){

        

    

        String endpoint = "";

        

        HashMap<String, Object> map = new HashMap<String, Object>();

        List<String> urlList = new ArrayList<String>();

        List<String> dirList = new ArrayList<String>();

        dirList.add("http://ochirly.trendy-global.com/cn/");

        map.put("Files", urlList);

        map.put("Directories", dirList);

        

        String key = "ochirly.trendy-global.com";

        //第一步获取的endpoints,根据域名,从原有的endpoints中找到对应的endpoint

        endpoint = MircosoftCacheConfig.getHostEndpointMap().get(key);

        

        String task = JsonKit.toJson(map);

        System.out.println("body:\t"+task);

        // 首先根据KeyId,keyValue,url生产authorization

        String url = MircosoftCacheConfig.MICROSOFT_CDN_URL_CACHE_REFRESH;

        if (url.contains(MircosoftCacheConfig.MICROSOFT_CDN_URL_PARAM_SUB_SCRIPTIONID)) {

            url = url.replaceAll(MircosoftCacheConfig.MICROSOFT_CDN_URL_PARAM_SUB_SCRIPTIONID,subId);

        }

        if (url.contains(MircosoftCacheConfig.MICROSOFT_CDN_URL_PARAM_ENDPOINTID)) {

            url = url.replaceAll(MircosoftCacheConfig.MICROSOFT_CDN_URL_PARAM_ENDPOINTID,endpoint);

        }

        System.out.println("url:\t" +url);

        String requestTime = DateUtil.formatDate(new Date(), "yyyy-MM-dd hh:mm:ss");

        String httpMethod = "POST";

        try {

            String authorizationHeader = AzureSignTool.calculateAuthorizationHeader(url, requestTime, keyId,keyValue, httpMethod);

            System.out.println("时间:\t" + requestTime);

            System.out.println("签名:\t"+authorizationHeader);

            System.out.println("content-type:\tapplication/json");

            Map<String, String> paramMap = new HashMap<String, String>();

            paramMap.put(MircosoftRequestParam.MICROSOFT_CDN_CACHE_REFRESH_HEADER1,    requestTime);

            paramMap.put(MircosoftRequestParam.MICROSOFT_CDN_CACHE_REFRESH_HEADER2,authorizationHeader);

            paramMap.put(MircosoftRequestParam.MICROSOFT_CDN_CACHE_REFRESH_HEADER3,"application/json");

            HttpClientResultBean responseBean = HttpKit.postContent(url,paramMap, task, Constants.CODE_UNICODE);

            System.out.println(responseBean.getResultContent());

        } catch (Exception e) {

    

        }

      

    }

      

}     

           

步骤

  1. 执行testGetEndpoint 这个接口会输出 订阅id下所有的endpointiD接口,整理并解析这份json格式,转换成一份host-endpointId的映射对象,保存到上一步的 azure_cdn_endpoint.properties配置文件中

     

说明:

String subId = ";

String keyId = "";

String keyValue = "";

这3个参数跟公司的同事获取(应该就是CDN服务商提供)

 

  1. 整理好属性配置文件后,执行testCndReresh 方法就可以成功刷新对应文件和目录下的缓存

特别声明遇到过的大坑

String requestTime = DateUtil.formatDate(new Date(), "yyyy-MM-dd hh:mm:ss");

日期格式一定要是yyyy-MM-dd hh:mm:ss要不能接口调用会报异常,就因为这个日期格式的错误,还花了不少的没必要的时间

 

总结

 

上面介绍的步骤,是我开发过程中的主要思路和步骤,您们需要关注的并不需要以上那么多,因为我已经做了封装,你们只需要按照下面的步骤可以直接进行对接。

  1. 获取API key IDAPI key valuesubscriptionId(订阅ID)
  2. 进入到第6步,执行testGetEndpoint,输出节点信息 
  3. 整理host和endpointid的对应关系
  4. 将上一步的数据存放第5步的azure_cdn_endpoint.properties配置文件中
  5. 进入到第6步,修改testCndReresh中的url和dir改成自己的数据,然后执行testCndReresh方法即可。

      

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值