Ctrip http 方式网络请求的流程

1  在实际的开发中,如果要获取后台值,我们会用增量或者轻量级的服务,我们来看看在携程中,http 服务的使用方式

具体的场景是在播放器上,现实一段的文案,通过http 服务的方式去请求:

具体代码是:

  public static class GetHotelVideoInfo extends BaseHTTPRequest {
        private int HotelId;
        private int HotelStar;
        private int HotelDataType;
        public GetHotelVideoInfo(int hotelId, int hotelStar, boolean isOverSea) {
            this.HotelId = hotelId;
            this.HotelStar = hotelStar;
            this.HotelDataType = isOverSea ? 2 : 1;
        }
        @Override
        public String getPath() {
            return "";
        }
        @Override
        public String getUrl() {
            return Env.isFAT() ? TESTEVN_URL : PRODUCT_URL;
        }
    }
    private void sendPlayCountService() {
        if (mHotelVideo == null) {
            return;
        }
        if (mHotelId <= 0) {
            return;
        }
        SOAHTTPHelperV2.getInstance().sendRequest(new GetHotelVideoInfo(mHotelId, mHotelStarLevel, mIsOverSea), new SOAHTTPHelperV2.HttpCallback<com.alibaba.fastjson.JSONObject>() {
            @Override
            public void onFailed(BaseHTTPRequest requst, Exception e) {
            }
            @Override
            public void onSuccess(com.alibaba.fastjson.JSONObject jsonObject) {
                if (jsonObject == null) {
                    return;
                }
                mPlayCountText = jsonObject.getString("PlayText");
            }
        });
    }

可以看出,我们是为了获取playText 这个字断的值,我们的request 是继承了 BaseHTTPRequest,这个类中初始化了一些必备的参数,我们看看有哪些


    public enum Method {
        GET, POST, MULTIPART_POST
    }
    // 请求方法
    @ParamsIgnore
    private Method method = Method.POST;
    // 默认序列化编码
    @ParamsIgnore
    private String encoding = "utf-8";
    // 超时时间 默认10*1000
    @ParamsIgnore
    private int timeout = 30 * 1000;
    // 协议 默认https
    @ParamsIgnore
    private boolean https = true;
    / /默认的请求 head 字段
    protected JSONObject head = CtripHTTPClientV2.buildRequestHeadForFastjson(null);
    @ParamsIgnore
    private Map<String, Object> params;
    @ParamsIgnore
    String fullUrl = "";
    @ParamsIgnore
    private boolean isRetry = false;

我们注意到其中有一段代码,

    // 默认的请求 head 字段
    protected JSONObject head = CtripHTTPClientV2.buildRequestHeadForFastjson(null);

我们看看其中的具体实现方式:

  @Override
            public JSONObject getFastJsonHead(Map<String, Object> extension) {
                try {
                    SharedPreferences sharedPreferences = PreferenceManager
                            .getDefaultSharedPreferences(FoundationContextHolder
                                    .context);
                    // 添加head
                    com.alibaba.fastjson.JSONObject headObject = new com.alibaba.fastjson.JSONObject();
                    headObject.put("syscode", CtripConfig.SYSTEMCODE);
                    headObject.put("lang", CtripConfig.LANGUAGE);
                    headObject.put("auth", CtripLoginManager.getLoginTicket());
                    headObject.put("cid", ClientID.getClientID());
                    headObject.put("ctok", DeviceInfoUtil.getAndroidID());
                    headObject.put("cver", CtripConfig.VERSION);
                    headObject.put("sid", CtripConfig.SOURCEID);
                    headObject.put("sauth", sharedPreferences.getString("sauth", ""));
                    if (extension != null && extension.keySet().size() > 0) {
                        com.alibaba.fastjson.JSONArray extensionArray = new com.alibaba.fastjson.JSONArray();
                        for (String key : extension.keySet()) {
                            JSONObject ext = new JSONObject();
                            ext.put("name", key);
                            ext.put("value", extension.get(key));
                            extensionArray.add(ext);
                        }
                        headObject.put("extension", extensionArray);
                    }
                    return headObject;
                } catch (Exception e) {
                    e.printStackTrace();
                    return new JSONObject();
                }
            }

我们看到这里其实是网络请求中,要带的一些设备,用户等相关信息,我们接着朝下看:

 /**
     * 发送Http 请求
     * @param request http请求,继承自BaseRequest
     * @param callback 请求回调
     *
     * **/
    public <T extends BaseHTTPRequest> String sendRequest(final T request, final HttpCallback<JSONObject> callback) {
        try {
            if (request == null) {
                invokeFailedCallback(callback, request, new NullPointerException("request is null!"));
                return null;
            }
           return doRequest(request, JSONObject.class, callback);
        } catch (Exception e) {
            invokeFailedCallback(callback, request, e);
        }

        return null;
    }

其中核心的就是 

doRequest(request, JSONObject.class, callback);

我们接着看这里具体是做什么的:

 /**
     * 发送Http 请求具体实现
     * @param request http请求,继承自BaseRequest
     * @param responseClass http api 返回对象
     * @param callback 请求回调
     *
     * **/
    private <T extends BaseHTTPRequest, V> String doRequest(final T request, final Class<V> responseClass, final HttpCallback<V> callback) throws NoSuchAlgorithmException, KeyManagementException {
        final long startTimestamp = System.currentTimeMillis();
        CtripHTTPCallbackV2 ctripHTTPCallback = new CtripHTTPCallbackV2() {
            @Override
            public void onFailure(CtripHttpFailure failure) {
                long costTime=System.currentTimeMillis()-startTimestamp;
                if(failure!=null
                        && failure.getException()!=null
                        && TCP_CONNECTION_FAIL.equals(failure.getException().getMessage())
                        && costTime < request.getTimeout()){
                    try {
                        String url = generateUrl(request);
                        if(HttpConfig.getHttpConfig().httpRequestInterceptor!=null && HttpConfig.getHttpConfig().httpRequestInterceptor.needInterceptSOARequest(url,"")) {
                            int retryTimeout=(int)(request.getTimeout()-costTime);
                            LogUtil.d("http do request retry retryTimeout="+retryTimeout);
                            request.setRetry(true);
                            request.setTimeout(retryTimeout < kMinTimeout ? kMinTimeout : retryTimeout);
                            doRequest(request, responseClass, callback);
                            LogUtil.d("http do request retry");
                        }
                    }catch (Exception e){
                        LogUtil.e("retry error:"+e.getMessage());
                    }
                    return;
                }
                invokeFailedCallback(callback, request, failure.getException());
            }

            @Override
            public void onResponse(CtripHttpResponse response) {
                if (callback != null) {
                    try {
                        V v = null;
                        if (responseClass != null) {
                            v = parseResponse(response.getResponse());
                        }
                        // 解析ResponseStatus
                        if (isAckFailed(v)) {
                            invokeFailedCallback(callback, request, new SOAACKException("soa http ACK is failed"));
                            return;
                        }
                        invokeSuccessCallback(callback, v);
                    } catch (Exception e) {
                        invokeFailedCallback(callback, request, e);
                    }
                }
            }
            private V parseResponse(Response response) throws IOException {
                String responseStr = new String(response.body().bytes(), request.getEncoding());
                LogUtil.d("SOAHTTPHelper response:" + response.request().url().toString() + ", " + responseStr);
                if (responseClass == JSONObject.class) {
                    return (V) JSON.parseObject(responseStr);
                } else {
                    return JsonUtils.parse(responseStr, responseClass);
                }
            }
        };
        String requestTag=null;
        // 生成URL
        String url = generateUrl(request);
        // 非pro环境 打出请求log
        if (HttpConfig.getHttpConfig().canShowLog()) {
            LogUtil.d("SOAHTTPHelper request:" + url + "," + JsonUtils.toJson(request.getParams()));
        }
        LogUtil.d("SOAHTTPHelper request:" + url + ",isRetry=" + request.isRetry());
        String bodyData=JsonUtils.toJson(request.getParams());
        if(!request.isRetry() && HttpConfig.getHttpConfig().httpRequestInterceptor!=null
                && HttpConfig.getHttpConfig().httpRequestInterceptor.needInterceptSOARequest(url,bodyData)){
            requestTag = HttpConfig.getHttpConfig().httpRequestInterceptor.interceptSOARequest(
                    url,
                    request.getMethod().name(),
                    HttpConfig.getHttpConfig().httpHeaderFactory.getFastJsonHead(null),
                    bodyData,
                    System.currentTimeMillis()+"",
                    request.getTimeout(),
                    "Native SOA",ctripHTTPCallback);
            return url+":"+requestTag;
        }

        switch (request.getMethod()) {
            case POST:
                requestTag = httpClient.asyncPost(url, request.getParams(), ctripHTTPCallback, request.getTimeout(), HttpConfig.getHttpConfig().httpHeaderFactory.getHttpHeaders());
                break;
            case GET:
                requestTag = httpClient.asyncGet(url, request.getParams(), ctripHTTPCallback, request.getTimeout(), HttpConfig.getHttpConfig().httpHeaderFactory.getHttpHeaders());
                break;
            case MULTIPART_POST:
                LogUtil.e("multipart post not supported yet!");
                // not supported yet;
                break;
        }
        return requestTag;
    }

这段代码比较长,上面是网络请求成功后的回调,我们从

  String requestTag=null;
        // 生成URL
        String url = generateUrl(request);

我们来看下 这段 generateUrl 的实现方式为: 

  @Override
            public String getBaseUrl(String pathUrl, boolean isHttps) {
                String URL_FAT_PREFIX = "gateway.m.fws.qa.nt.ctripcorp.com/restapi/soa2/";
                String URL_UAT_PREFIX = "gateway.m.uat.qa.nt.ctripcorp.com/restapi/soa2/";
                String URL_PRO_PREFIX = "m.ctrip.com/restapi/soa2/";
                String URL_PRO_PREFIX_HTTPS = "m.ctrip.com/restapi/soa2/";
                StringBuilder urlBuilder = new StringBuilder();
                urlBuilder.append(isHttps ? "https://" : "http://");

                if (Env.isFAT()) {
                    urlBuilder.append(URL_FAT_PREFIX);
                } else if (Env.isUAT()) {
                    urlBuilder.append(URL_UAT_PREFIX);
                } else {
                    urlBuilder.append(isHttps ? URL_PRO_PREFIX_HTTPS : URL_PRO_PREFIX);
                }

                return urlBuilder.toString();
            }

我们接着朝下看:

 String bodyData=JsonUtils.toJson(request.getParams());

这个String 类型的格式:

我们看到bodyData 中的数据 获取的方式是:

 // 通过反射获取所有字段键值对
    public Map<String, Object> getParams() {
        Map<String, Object> params = this.params;
        if (params == null) {
            params = new HashMap<>();
        }
        for(Class<?> clazz = this.getClass(); clazz != Object.class ; clazz = clazz.getSuperclass()) {
            Field[] fileds =  clazz.getDeclaredFields();
            for(Field field : fileds) {
                // 过滤注解忽略的字段
                if (field.isAnnotationPresent(ParamsIgnore.class)) {
                    continue;
                }
                //过滤匿名内部类持有的外部类字段
                if(field.getName().contains("this$") && "1010".equals(Integer.toHexString(field.getModifiers()))){
                    continue;
                }
                field.setAccessible(true);
                try {
                    if(params.containsKey("head") && "head".equals(field.getName())){
                        continue;
                    }
                    params.put(field.getName(), field.get(this));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return params;
    }

通过分析我们看到,bodyData 中的数据就是通过反射的方式,获取到resquest 这个类中的所有字段

我们接着朝下看:看到这段代码是:

   if(!request.isRetry() && HttpConfig.getHttpConfig().httpRequestInterceptor!=null
                && HttpConfig.getHttpConfig().httpRequestInterceptor.needInterceptSOARequest(url,bodyData)){
            requestTag = HttpConfig.getHttpConfig().httpRequestInterceptor.interceptSOARequest(
                    url,
                    request.getMethod().name(),
                    HttpConfig.getHttpConfig().httpHeaderFactory.getFastJsonHead(null),
                    bodyData,
                    System.currentTimeMillis()+"",
                    request.getTimeout(),
                    "Native SOA",ctripHTTPCallback);


            return url+":"+requestTag;
        }

这里有判断条件的,默认条件下 !request.isRetry() 是为true 的,我们看关键的一段代码是:

HttpConfig.getHttpConfig().httpRequestInterceptor.needInterceptSOARequest(url,bodyData)

我们看看这段代码中的实现方式:

     @Override
            public boolean needInterceptSOARequest(String url,String bodyData) {
                long bodySize = 0;
                try {
                    if(!StringUtil.isEmpty(bodyData))
                        bodySize = bodyData.getBytes().length * 8;
                } catch (Exception e) {
                    LogUtil.e("erorr when cacl body size");
                }
                if(bodySize > CtripAppHttpSotpManager.TCP_HTTP_MAX_BODY_SIZE)
                    LogUtil.d("needInterceptSOARequest","bodySize > CtripAppHttpSotpManager.TCP_HTTP_MAX_BODY_SIZE");

                return  CtripSOTPConfig.needHttpToTcpForSoa(url)
                        && !CtripSOTPConfig.isUrlInLocalBlackList(url)//本地黑名单
                        && bodySize < CtripAppHttpSotpManager.TCP_HTTP_MAX_BODY_SIZE;//请求体小于最大限制
            }

我们看到这里,看到这里有 bodySize 的控制大小,关键看renturn 中的判断条件,

我们来看看 

 CtripSOTPConfig.needHttpToTcpForSoa(url)

看看它的具体的实现方式:

 public static boolean needHttpToTcpForSoa(String url) {
        if (TextUtils.isEmpty(url)) {
            return false;
        }
        try {
            Uri urlFull = Uri.parse(url);
            if (isHttpToTcpEnabledForSoaWithLocal()
                    && !isUrlInNativeSOTPBlackList(url)//服务端下发的黑名单
                    && "m.ctrip.com".equals(urlFull.getHost())
                    && urlFull.getPath().startsWith("/restapi/soa2")) {
                return true;
            }
        } catch (Exception e) {
            LogUtil.e("error when parse http tp tcp url", e);
            return false;
        }
        return false;
    }

可以看出来,这里是类似对些特定的服务地址处理的,对于我举的例子中的url 的格式为:

"https://m.ctrip.com/restapi/soa2/12465/h5-json/getHotelVideoPlayInfo";

我们可以看到这个url 在needHttpToTcpForSoa 这个方法函数,通常我们请求是小于,请求bodysize 的,

所以我们走到了判断条件的方法体中,

HttpConfig.getHttpConfig().httpRequestInterceptor.interceptSOARequest(
                    url,
                    request.getMethod().name(),
                    HttpConfig.getHttpConfig().httpHeaderFactory.getFastJsonHead(null),
                    bodyData,
                    System.currentTimeMillis()+"",
                    request.getTimeout(),
                    "Native SOA",ctripHTTPCallback);

我们看看这里的实现方式:

    final long startTimestamp = System.currentTimeMillis();
                return CtripAppHttpSotpManager.sendAppHttpRequestForSOA(url, method, headerJson, bodyData, sequenceId, timeout, fromCode, new CtripAppHttpSotpManager.NetworkRequestCallbackListener() {
                    @Override
                    public void onCallBack(boolean success, String errorCode, TaskFailEnum failEnum, JSONObject responseJson) {
                        MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
                        RequestBody requestBody=RequestBody.create(mediaType,bodyData);
                        //构造Request
                        Request.Builder requestBuilder = new Request.Builder();
                        requestBuilder.url(url);
                        requestBuilder.method(method,requestBody);
                        requestBuilder.tag("useSotp");
                        Request request=requestBuilder.build();
                        if (success) {
                            //构造Response
                            Response.Builder responseBuilder = new Response.Builder();
                            responseBuilder.request(request);
                            responseBuilder.body(ResponseBody.create(mediaType, responseJson.getString("body")));
                            responseBuilder.protocol(Protocol.HTTP_1_1);
                            responseBuilder.code(200);
                            Response response=responseBuilder.build();

                            CtripHttpResponse ctripHttpResponse = new CtripHttpResponse();
                            ctripHttpResponse.setResponse(response);
                            try {
                                LogUtil.d("interceptSOARequest", "interceptSOARequest success,callback.onResponse--"+url);
                                ctripHTTPCallbackV2.onResponse(ctripHttpResponse);
                                CtripHTTPClientV2.getInstance().logHTTPRequestMetrics(request,response,"",startTimestamp,false);
                            } catch (Exception e) {
                                LogUtil.d("interceptSOARequest", "interceptSOARequest exception:" + e.getMessage());
                            }
                        } else {
                            LogUtil.d("interceptSOARequest errorCode="+errorCode);
                            //203错误才走http重试
                            if (failEnum == TaskFailEnum.CONNECTION_FAIL) {
                                CtripHttpFailure ctripHttpFailure = new CtripHttpFailure();
                                ctripHttpFailure.setException(new IOException(SOAHTTPHelperV2.TCP_CONNECTION_FAIL));
                                ctripHTTPCallbackV2.onFailure(ctripHttpFailure);
                            } else if (StringUtil.equals(errorCode,CtripHTTPClientV2.RESPONSE_CODE_431+"")
                                    || StringUtil.equals(errorCode,CtripHTTPClientV2.RESPONSE_CODE_432+"")
                                    || StringUtil.equals(errorCode,CtripHTTPClientV2.RESPONSE_CODE_429+"")
                                    || StringUtil.equals(errorCode,CtripHTTPClientV2.RESPONSE_CODE_430+"")){
                                antiBot(errorCode);
                            }else {
                                CtripHttpFailure ctripHttpFailure = new CtripHttpFailure();
                                ctripHttpFailure.setException(new IOException(errorCode + ""));
                                ctripHTTPCallbackV2.onFailure(ctripHttpFailure);
                                LogUtil.d("interceptSOARequest", "interceptSOARequest fail,callback.onFailure--" + url);
                                errorCode=errorCode+";"+Task.getFailCode(failEnum);
                                CtripHTTPClientV2.getInstance().logHTTPRequestMetrics(request, null, errorCode, startTimestamp, false);
                            }
                            LogUtil.d("interceptSOARequest","errorCode="+errorCode);
                        }
                    }
                });
            }

好长的代码,我们看下,代码中的实现方式:

sendAppHttpRequestForSOA

的实现方式为:


    public static String sendAppHttpRequestForSOA(String url, String method, JSONObject headerJson, String bodyData, final String sequenceId, long timeout, final String fromCode, final CtripAppHttpSotpManager.NetworkRequestCallbackListener listener) {
        final long requestStartTime = System.currentTimeMillis();
        CtripAppHttpRequest request = new CtripAppHttpRequest();
        if(url == null) {
            url = "";
        }

        if(headerJson == null) {
            headerJson = new JSONObject();
        }

        if(StringUtil.equalsIgnoreCase(method, "get")) {
            method = "GET";
        } else {
            method = "POST";
        }

        if(!headerJson.containsKey("User-Agent")) {
            headerJson.put("User-Agent", AppInfoConfig.getAppUserAgent());
        }

        if(!headerJson.containsKey("Accept")) {
            headerJson.put("Accept", "application/json");
        }

        if(!headerJson.containsKey("Content-Type")) {
            headerJson.put("Content-Type", "application/json;charset=utf-8");
        }

        if(bodyData == null) {
            bodyData = "";
        }

        final String requestUrl = url.trim();
        JSONObject bodyJson = new JSONObject();
        bodyJson.put("headers", headerJson.toString());
        bodyJson.put("cookies", CookieManager.getInstance().getCookie(requestUrl));
        bodyJson.put("url", requestUrl);
        bodyJson.put("method", method.toUpperCase());
        bodyJson.put("body", bodyData);
        request.body = bodyJson.toString();
        LogUtil.d("CtripAppHttpSotpManager", "请求报文:" + request.body);
        SenderResultModel resultModel = sendAppHttpRequest(request, new CtripAppHttpSotpManager.CallBack() {
            public void onAppHttpRequestResult(BusinessResponseEntity responseEntity, int error) {
                double elapseTime = (double)(System.currentTimeMillis() - requestStartTime) / 1000.0D;
                HashMap<String, String> tagMap = new HashMap();
                tagMap.put("url", requestUrl);
                tagMap.put("log_from", fromCode);
                JSONObject result = new JSONObject();
                Pair codePairx;
                if(error != 0) {
                    result.put("sequenceId", sequenceId);
                    String errorCode = String.valueOf(error);
                    tagMap.put("error_code", responseEntity == null?"1":Task.getFailCode(responseEntity.getFailType()));
                    codePairx = CtripHTTPClientV2.getInstance().parseSOACode(requestUrl);
                    if(codePairx != null) {
                        tagMap.put("serviceCode", codePairx.first);
                        tagMap.put("operation", codePairx.second);
                    } else {
                        tagMap.put("serviceCode", "");
                        tagMap.put("operation", "");
                    }

                    tagMap.put("cookies", CookieManager.getInstance().getCookie(requestUrl));
                    LogUtil.logMetrics("o_sotp_send_http_request_fail", Double.valueOf(elapseTime), tagMap);
                    TaskFailEnum taskFailEnum = TaskFailEnum.NO_FAIL;
                    if(responseEntity != null) {
                        taskFailEnum = responseEntity.getFailType();
                    } else if(error == 90003) {
                        taskFailEnum = TaskFailEnum.GET_CONNECTION_FAIL;
                    }

                    listener.onCallBack(false, errorCode, taskFailEnum, result);
                    LogUtil.d("CtripAppHttpSotpManager", "请求失败:" + result.toString() + "," + errorCode);
                } else {
                    try {
                        CtripAppHttpResponse response = (CtripAppHttpResponse)responseEntity.getResponseBean();
                        org.json.JSONObject jsonObject = new org.json.JSONObject(response.body);
                        String header = jsonObject.optString("headers", "");
                        org.json.JSONObject headerJson = new org.json.JSONObject(header);
                        String status = headerJson.optString("Status", "1");
                        String cookies = jsonObject.optString("cookies", "");
                        String url = jsonObject.optString("url", "");
                        String body = jsonObject.optString("body", "");
                        CookieManager.getInstance().setCookie(requestUrl, cookies);
                        tagMap.put("http_status", status);
                        String traceId = headerJson.optString("CLOGGING_TRACE_ID", "");
                        String messageId = headerJson.optString("RootMessageId", "");
                        if(!TextUtils.isEmpty(traceId)) {
                            tagMap.put("CLOGGING_TRACE_ID", traceId);
                        }

                        if(!TextUtils.isEmpty(messageId)) {
                            tagMap.put("RootMessageId", messageId);
                        }

                        tagMap.put("http_status", status);
                        Pair codePair;
                        if(status.equals("200")) {
                            result.put("sequenceId", sequenceId);
                            result.put("status", status);
                            result.put("response_header", header);
                            result.put("body", body);
                            listener.onCallBack(true, "", responseEntity.getFailType(), result);
                            codePair = CtripHTTPClientV2.getInstance().parseSOACode(requestUrl);
                            if(codePair != null) {
                                tagMap.put("serviceCode", codePair.first);
                                tagMap.put("operation", codePair.second);
                            } else {
                                tagMap.put("serviceCode", "");
                                tagMap.put("operation", "");
                            }

                            tagMap.put("cookies", CookieManager.getInstance().getCookie(requestUrl));
                            LogUtil.logMetrics("o_sotp_send_http_request_success", Double.valueOf(elapseTime), tagMap);
                            LogUtil.d("CtripAppHttpSotpManager", "请求成功-返回报文" + result.toString());
                        } else {
                            result.put("sequenceId", sequenceId);
                            tagMap.put("error_code", responseEntity == null?"1":Task.getFailCode(responseEntity.getFailType()));
                            codePair = CtripHTTPClientV2.getInstance().parseSOACode(requestUrl);
                            if(codePair != null) {
                                tagMap.put("serviceCode", codePair.first);
                                tagMap.put("operation", codePair.second);
                            } else {
                                tagMap.put("serviceCode", "");
                                tagMap.put("operation", "");
                            }

                            tagMap.put("cookies", CookieManager.getInstance().getCookie(requestUrl));
                            LogUtil.logMetrics("o_sotp_send_http_request_fail", Double.valueOf(elapseTime), tagMap);
                            listener.onCallBack(false, status, responseEntity.getFailType(), result);
                            LogUtil.d("CtripAppHttpSotpManager", "请求失败:" + result.toString());
                        }
                    } catch (Exception var18) {
                        var18.printStackTrace();
                        result.put("sequenceId", sequenceId);
                        tagMap.put("error_code", "-30001");
                        tagMap.put("errorDescription", "parse response error:" + var18.getMessage());
                        codePairx = CtripHTTPClientV2.getInstance().parseSOACode(requestUrl);
                        if(codePairx != null) {
                            tagMap.put("serviceCode", codePairx.first);
                            tagMap.put("operation", codePairx.second);
                        } else {
                            tagMap.put("serviceCode", "");
                            tagMap.put("operation", "");
                        }

                        tagMap.put("cookies", CookieManager.getInstance().getCookie(requestUrl));
                        LogUtil.logMetrics("o_sotp_send_http_request_fail", Double.valueOf(elapseTime), tagMap);
                        listener.onCallBack(false, "-30001", responseEntity.getFailType(), result);
                        LogUtil.d("CtripAppHttpSotpManager", "请求失败:" + result.toString() + "," + "-30001");
                    }
                }

            }
        }, timeout);
        return resultModel.getToken();

其实这段的代码实现两个功能,一是用sotp 服务发送服务,它的序列化放式用的json ,二是处理返回的结果:

我们先看sotp 服务发送的服务,我们看代码

sendAppHttpRequest

的实现方式:

 public static SenderResultModel sendAppHttpRequest(CtripAppHttpRequest request, final CtripAppHttpSotpManager.CallBack callback, long timeout) {
        final SenderResultModel resultModel = Sender.createSenderResult("CtripAppHttpRequest");
        List var5 = tokenList;
        synchronized(tokenList) {
            tokenList.add(resultModel.getToken());
        }
        if(timeout < 5000L || timeout > 120000L) {
            timeout = 30000L;
        }
        BusinessRequestEntity requestEntity = BusinessRequestEntity.getInstance();
        requestEntity.setRequestBean(request);
        requestEntity.setShortConn(true);
        requestEntity.setProtocolBuffer(false);
        Sender.senderService(resultModel, callBack, new BusinessRequestEntity[]{requestEntity});
        return resultModel;
    }

我们看到这段时中下面的几行代码,是用sotp 的短连接的方式实现网络请求,这个我在上篇的博客

https://blog.csdn.net/ahubenkui/article/details/81417386

中写道过,走的携程自己封装的SOTP 服务,

我们再看看后面的网络回来的数据,

if(status.equals("200")) {
                            result.put("sequenceId", sequenceId);
                            result.put("status", status);
                            result.put("response_header", header);
                            result.put("body", body);
                            listener.onCallBack(true, "", responseEntity.getFailType(), result);

我们看到返回的 关键字段通过回调通过 result 这个值返回,

我们再看之前的代码,

@Override
                    public void onCallBack(boolean success, String errorCode, TaskFailEnum failEnum, JSONObject responseJson) {
                        MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
                        RequestBody requestBody=RequestBody.create(mediaType,bodyData);
                        //构造Request
                        Request.Builder requestBuilder = new Request.Builder();
                        requestBuilder.url(url);
                        requestBuilder.method(method,requestBody);
                        requestBuilder.tag("useSotp");
                        Request request=requestBuilder.build();
                        if (success) {
                            //构造Response
                            Response.Builder responseBuilder = new Response.Builder();
                            responseBuilder.request(request);
                            responseBuilder.body(ResponseBody.create(mediaType, responseJson.getString("body")));
                            responseBuilder.protocol(Protocol.HTTP_1_1);
                            responseBuilder.code(200);
                            Response response=responseBuilder.build();

                            CtripHttpResponse ctripHttpResponse = new CtripHttpResponse();
                            ctripHttpResponse.setResponse(response);
                            try {
                                LogUtil.d("interceptSOARequest", "interceptSOARequest success,callback.onResponse--"+url);
                                ctripHTTPCallbackV2.onResponse(ctripHttpResponse);
                                CtripHTTPClientV2.getInstance().logHTTPRequestMetrics(request,response,"",startTimestamp,false);
                            } catch (Exception e) {
                                LogUtil.d("interceptSOARequest", "interceptSOARequest exception:" + e.getMessage());
                            }
                        } else {
                            LogUtil.d("interceptSOARequest errorCode="+errorCode);
                            //203错误才走http重试
                            if (failEnum == TaskFailEnum.CONNECTION_FAIL) {
                                CtripHttpFailure ctripHttpFailure = new CtripHttpFailure();
                                ctripHttpFailure.setException(new IOException(SOAHTTPHelperV2.TCP_CONNECTION_FAIL));
                                ctripHTTPCallbackV2.onFailure(ctripHttpFailure);
                            } else if (StringUtil.equals(errorCode,CtripHTTPClientV2.RESPONSE_CODE_431+"")
                                    || StringUtil.equals(errorCode,CtripHTTPClientV2.RESPONSE_CODE_432+"")
                                    || StringUtil.equals(errorCode,CtripHTTPClientV2.RESPONSE_CODE_429+"")
                                    || StringUtil.equals(errorCode,CtripHTTPClientV2.RESPONSE_CODE_430+"")){
                                antiBot(errorCode);
                            }else {
                                CtripHttpFailure ctripHttpFailure = new CtripHttpFailure();
                                ctripHttpFailure.setException(new IOException(errorCode + ""));
                                ctripHTTPCallbackV2.onFailure(ctripHttpFailure);
                                LogUtil.d("interceptSOARequest", "interceptSOARequest fail,callback.onFailure--" + url);
                                errorCode=errorCode+";"+Task.getFailCode(failEnum);
                                CtripHTTPClientV2.getInstance().logHTTPRequestMetrics(request, null, errorCode, startTimestamp, false);
                            }
                            LogUtil.d("interceptSOARequest","errorCode="+errorCode);
                        }


                    }
                });

            }

我们看到这里是用的okhttp 进行包装,包括code,等信息。

我们接着分析:

  switch (request.getMethod()) {
            case POST:
                requestTag = httpClient.asyncPost(url, request.getParams(), ctripHTTPCallback, request.getTimeout(), HttpConfig.getHttpConfig().httpHeaderFactory.getHttpHeaders());
                break;
            case GET:
                requestTag = httpClient.asyncGet(url, request.getParams(), ctripHTTPCallback, request.getTimeout(), HttpConfig.getHttpConfig().httpHeaderFactory.getHttpHeaders());
                break;
            case MULTIPART_POST:
                LogUtil.e("multipart post not supported yet!");
                // not supported yet;
                break;
        }

我们看到当我们默认的post 的方式:

我们看下里面的具体实现方式:

  /**
     * 异步调用HTTP的post方法
     *
     * @param url              目标url
     * @param json             JSON字符串
     * @param responseCallback 回调
     * @param timeoutMillis    超时,单位毫秒, 如果超时时间>kMaxTimeout(120*1000ms)或者小于kMinTimeout(5*1000ms),都会使用默认超时时间kDefaultTimeout(30*1000ms)
     * @param headers          自定义HTTP Headers
     * @return 返回request的Tag,取消请求的时候需要使用
     */
    public String asyncPostWithTimeout(String url, String json, final CtripHTTPCallbackV2 responseCallback
            , final int timeoutMillis, Map<String, String> headers, boolean isFromCRN) {

        String retTag = getRequestTagByURL(url);

        // okClient.setReadTimeout(formatTimeout(timeoutMillis), TimeUnit.MILLISECONDS);

        Request.Builder builder = new Request.Builder()
                .url(url)
                .tag(retTag);

        RequestBody jsonBody = RequestBody.create(MediaType_JSON, json);

        if (headers != null) {
            for (String header : headers.keySet()) {
                if (TextUtils.equals("Content-Type", header)) {
                    jsonBody = RequestBody.create(MediaType.parse(headers.get(header)), json);
                }
                builder.header(header, headers.get(header));
            }
        }
        if(CookieManager.getInstance().getCookie(url)!=null && HttpConfig.getHttpConfig().getHttpRequestInterceptor().autoSetCookie()) {
            builder.header("cookie", CookieManager.getInstance().getCookie(url));
        }
        LogUtil.d("url="+url+"cookie="+CookieManager.getInstance().getCookie(url));

        final Request request = builder.post(jsonBody).build();

        final Call call = okClient.newCall(request);
        CallbackWithTimeout cbWithTimeout = wrapCallbackWithTimeout(call, request, responseCallback, timeoutMillis, isFromCRN);
        call.enqueue(cbWithTimeout);
        return retTag;
    }

我们看到,这里我们是拼装header 的方式,和设置cookie

然后重新写Call ,我们来看下 CallbackWithTimeout 这个类中的具体实现方式,

 private CallbackWithTimeout wrapCallbackWithTimeout(final Call call,
                                                        final Request request,
                                                        final CtripHTTPCallbackV2 responseCallback,
                                                        int timeoutMillis,
                                                        final boolean isFromCRN) {
        if (call == null || request == null) {
            return null;
        }

        final long startWrapTime = System.currentTimeMillis();
        final boolean correctingTimeout = true;
        final OkHandler handler = new OkHandler(sharedThread.getLooper());
        handler.request = request;
        handler.call = call;
        Message message = Message.obtain();
        message.what = 0;
        CallbackWithTimeout CallbackWithTimeout = new CallbackWithTimeout() {
            @Override
            public void onFailure(final Call call, final IOException e) {
                handler.removeMessages(0, this);
                if (call.isCanceled()) {
                    return;
                }
                RequestSaveBean requestSaveBean = requestsHashMap.get(call.request());
                synchronized (CtripHTTPClientV2.class) {
                    requestsHashMap.remove(call.request());
                }

                try {
                    if (httpResponseObserver != null) {
                        httpResponseObserver.onFailed(call.request().url().toString(), e);
                    }
                } catch (Exception e1) {
                    LogUtil.e("error when do http failed callbacl");
                }

                if (!mTimeout) {
                    if (timeoutException.equals(e)) {
                        mTimeout = true;
                    }
                    logHTTPRequestMetrics(request, null, "HTTP Request失败:" + e.getMessage(), requestSaveBean == null ? startWrapTime : requestSaveBean.startTime, isFromCRN);
                    ThreadUtils.runOnBackgroundThread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                if (responseCallback != null) {
                                    CtripHttpFailure failure = new CtripHttpFailure();
                                    failure.setCall(call);
                                    failure.setException(e);
                                    responseCallback.onFailure(failure);
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
            @Override
            public void onResponse(final Call call, final Response response) throws IOException {
                handler.removeMessages(0, this);
                if (call.isCanceled()) {
                    return;
                }
                if (!mTimeout) {
                    LogUtil.e("CtripHttpV2", "Response : " + response.toString());
                    int responseCode=response.code();
                    if(responseCode==RESPONSE_CODE_431 || responseCode==RESPONSE_CODE_432 || responseCode==RESPONSE_CODE_430 || responseCode==RESPONSE_CODE_429){
                        HttpConfig.getHttpConfig().httpRequestInterceptor.antiBot(responseCode+"");
                        return;
                    }
                    //Response code非2xx都应该走Failure回调
                    RequestSaveBean requestSaveBean = requestsHashMap.get(call.request());
                    synchronized (CtripHTTPClientV2.class) {
                        requestsHashMap.remove(call.request());
                    }
                    if (response != null && response.isSuccessful()) {
                        logHTTPRequestMetrics(request, response, null, requestSaveBean == null ? startWrapTime : requestSaveBean.startTime, isFromCRN);
                        if (responseCallback != null) {
                            final CtripHttpResponse httpResponse = new CtripHttpResponse();
                            httpResponse.setCall(call);
                            httpResponse.setResponse(response);
                            ThreadUtils.runOnBackgroundThread(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        responseCallback.onResponse(httpResponse);
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                            });
                        }
                        try {
                            if (httpResponseObserver != null) {
                                httpResponseObserver.onSuccess(call.request().url().toString());
                            }
                        } catch (Exception e1) {
                            LogUtil.e("error when do http success callbacl");
                        }
                    } else {
                        logHTTPRequestMetrics(request, response, "HTTP Response code:" + response.code(), requestSaveBean == null ? startWrapTime : requestSaveBean.startTime, isFromCRN);
                        final CtripHttpFailure failure = new CtripHttpFailure();
                        failure.setCall(call);
                        failure.setException(new SOAIOExceptionV2("Http Response error", response));
                        ThreadUtils.runOnBackgroundThread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    if (responseCallback != null) {
                                        responseCallback.onFailure(failure);
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                        try {
                            if (httpResponseObserver != null) {
                                httpResponseObserver.onFailed(call.request().url().toString(), failure.getException());
                            }
                        } catch (Exception e1) {
                            LogUtil.e("error when do http success callbacl");
                        }
                    }
                }
            }
        };
        message.obj = CallbackWithTimeout;
        if (correctingTimeout) {
            RequestSaveBean saveBean = new RequestSaveBean();
            saveBean.message = message;
            saveBean.time = formatTimeout(timeoutMillis);
            saveBean.mOkHandler = handler;
            saveBean.inqueueTime = System.currentTimeMillis();
            requestsHashMap.put(request, saveBean);
        } else {
            handler.sendMessageDelayed(message, formatTimeout(timeoutMillis));
        }
        return CallbackWithTimeout;
    }

我们看到 代码中设置了handler 我们看到,这是我们的控制,设置了些参数

        final OkHandler handler = new OkHandler(sharedThread.getLooper());
        handler.request = request;
        handler.call = call;
        Message message = Message.obtain();
        message.what = 0;

这个handler 是什么时候触发的,我们看下代码,在最后一段是

 handler.sendMessageDelayed(message, formatTimeout(timeoutMillis));

但是触发条件始终为false ,所以我们不关心这个地方的具体的实现,我们看下call 返回成功做的操作,

if (!mTimeout) {
                    LogUtil.e("CtripHttpV2", "Response : " + response.toString());
                    int responseCode=response.code();
                    if(responseCode==RESPONSE_CODE_431 || responseCode==RESPONSE_CODE_432 || responseCode==RESPONSE_CODE_430 || responseCode==RESPONSE_CODE_429){
                        HttpConfig.getHttpConfig().httpRequestInterceptor.antiBot(responseCode+"");
                        return;
                    }
                    //Response code非2xx都应该走Failure回调
                    RequestSaveBean requestSaveBean = requestsHashMap.get(call.request());
                    synchronized (CtripHTTPClientV2.class) {
                        requestsHashMap.remove(call.request());
                    }

                    if (response != null && response.isSuccessful()) {
                        logHTTPRequestMetrics(request, response, null, requestSaveBean == null ? startWrapTime : requestSaveBean.startTime, isFromCRN);
                        if (responseCallback != null) {
                            final CtripHttpResponse httpResponse = new CtripHttpResponse();
                            httpResponse.setCall(call);
                            httpResponse.setResponse(response);
                            ThreadUtils.runOnBackgroundThread(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        responseCallback.onResponse(httpResponse);
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                            });
                        }

                        try {
                            if (httpResponseObserver != null) {
                                httpResponseObserver.onSuccess(call.request().url().toString());
                            }
                        } catch (Exception e1) {
                            LogUtil.e("error when do http success callbacl");
                        }

首先我们看到,首先根据返回的状态码,做反爬虫的的处理,然后就是将返回的结果,然后就是将常规的返回response。

  OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(kMaxTimeout, TimeUnit.MILLISECONDS);
        builder.readTimeout(kMaxTimeout, TimeUnit.MILLISECONDS);
        builder.writeTimeout(kMaxTimeout, TimeUnit.MILLISECONDS);
        builder.connectionPool(new ConnectionPool(5, 60000L, TimeUnit.MILLISECONDS));
        builder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                RequestSaveBean requestSaveBean = requestsHashMap.get(chain.request());
                if (requestSaveBean != null) {
                    requestSaveBean.startTime = System.currentTimeMillis();
                    requestSaveBean.mOkHandler.sendMessageDelayed(requestSaveBean.message, requestSaveBean.time);
                }
                try {
                    Response response = chain.proceed(chain.request());
                    return response;
                } catch (IOException e) {
                    throw e;
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new IOException("Other Exception:" + e.getMessage());
                }
            }
        });

可以看到这里是,自定义了一个拦截器。这里的拦截器是处理释放缓存的。

这里推荐一个介绍okHttp 很详细的网站

https://www.jianshu.com/p/a68dc1ca6120

猜你喜欢

转载自blog.csdn.net/ahubenkui/article/details/81488325