All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aliyun.odps.rest.RestClient Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.aliyun.odps.rest;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import javax.net.ssl.SSLHandshakeException;
import javax.xml.bind.JAXBException;

import com.alibaba.fastjson.JSON;
import com.aliyun.odps.NoSuchObjectException;
import com.aliyun.odps.OdpsDeprecatedLogger;
import com.aliyun.odps.OdpsException;
import com.aliyun.odps.Survey;
import com.aliyun.odps.account.Account;
import com.aliyun.odps.commons.transport.Connection;
import com.aliyun.odps.commons.transport.Headers;
import com.aliyun.odps.commons.transport.Request;
import com.aliyun.odps.commons.transport.Request.Method;
import com.aliyun.odps.commons.transport.Response;
import com.aliyun.odps.commons.transport.Transport;
import com.aliyun.odps.commons.util.DateUtils;
import com.aliyun.odps.commons.util.IOUtils;
import com.aliyun.odps.commons.util.SvnRevisionUtils;

/**
 * RESTful API客户端
 */
public class RestClient {

  public static abstract class RetryLogger {

    /**
     * 当 RestClent 发生重试前的回调函数
     *
     * @param e
     *     错误异常
     * @param retryCount
     *     重试计数
     * @param retrySleepTime
     *     下次需要的重试时间
     */
    public abstract void onRetryLog(Throwable e, long retryCount, long retrySleepTime);
  }

  /**
   * 底层网络建立超时时间,10秒
   * magic number
   * allow kernel retry 3 times.
   */
  public static final int DEFAULT_CONNECT_TIMEOUT = 10; // seconds

  /**
   * 底层网络重试次数, 3
   */
  public static final int DEFAULT_CONNECT_RETRYTIMES = 4;

  /**
   * 底层网络连接超时时间, 120秒。
   */
  public static final int DEFAULT_READ_TIMEOUT = 120;// seconds

  /**
   * 是否忽略HTTPS证书验证
   */
  public static final boolean DEFAULT_IGNORE_CERTS = false;

  private final Transport transport;

  private Account account;
  private String endpoint;
  private boolean ignoreCerts = DEFAULT_IGNORE_CERTS;

  private String defaultProject;

  private static final String
      USER_AGENT_PREFIX =
      "JavaSDK" + " Revision:" + SvnRevisionUtils.getSvnRevision()
      + " Version:" + SvnRevisionUtils.getMavenVersion() + " JavaVersion:" + SvnRevisionUtils.getJavaVersion();

  private String userAgent;

  public RetryLogger getRetryLogger() {
    return logger;
  }

  public void setRetryLogger(RetryLogger logger) {
    this.logger = logger;
  }

  private RetryLogger logger = null;

  /**
   * 创建RestClient对象
   *
   * @param transport
   */
  @Survey
  public RestClient(Transport transport) {
    this.transport = transport;
  }

  /**
   * 请求RESTful API
   *
   * @param clazz
   *     返回结果绑定的Java类型
   * @param resource
   *     API资源标识
   * @param method
   *     访问方法
   * @return 与API返回结果绑定的clazz类型对象
   * @throws OdpsException
   */
  public  T request(Class clazz, String resource, String method) throws OdpsException {
    return request(clazz, resource, method, null, null, null);
  }

  /**
   * 请求RESTful API
   *
   * @param clazz
   * @param resource
   * @param method
   * @param params
   * @return
   * @throws OdpsException
   */
  public  T request(Class clazz, String resource, String method, Map params)
      throws OdpsException {
    return request(clazz, resource, method, params, null, null);
  }

  private static final String CHARSET = "UTF-8";

  /**
   * 请求RESTful API
   *
   * @param clazz
   * @param resource
   * @param method
   * @param params
   * @param headers
   * @param body
   * @return
   * @throws OdpsException
   */
  public  T stringRequest(Class clazz, String resource, String method,
                             Map params,
                             Map headers, String body) throws OdpsException {
    try {
      return request(clazz, resource, method, params, headers, body.getBytes(CHARSET));
    } catch (UnsupportedEncodingException e) {
      throw new OdpsException(e.getMessage(), e);
    }
  }

  /**
   * 请求RESTful API
   *
   * @param clazz
   * @param resource
   * @param method
   * @param params
   * @param headers
   * @param body
   * @return
   * @throws OdpsException
   */
  public  T request(Class clazz, String resource, String method, Map params,
                       Map headers, byte[] body) throws OdpsException {

    T r = null;

    Response resp = request(resource, method, params, headers, body);
    try {
      r = JAXBUtils.unmarshal(resp, clazz);
    } catch (JAXBException e) {
      throw new OdpsException("Can't bind xml to " + clazz.getName(), e);
    }

    return r;
  }

  /**
   * 请求RESTful API
   *
   * @param resource
   * @param method
   * @param params
   * @param headers
   * @param body
   * @return
   * @throws OdpsException
   */
  public Response stringRequest(String resource, String method, Map params,
                                Map headers, String body) throws OdpsException {
    try {
      return request(resource, method, params, headers, body.getBytes(CHARSET));
    } catch (UnsupportedEncodingException e) {
      throw new OdpsException(e.getMessage(), e);
    }

  }

  /**
   * 请求RESTful API
   *
   * @param resource
   * @param method
   * @param params
   * @param headers
   * @param body
   * @return
   * @throws OdpsException
   */
  public Response request(String resource, String method, Map params,
                          Map headers,
                          byte[] body) throws OdpsException {
    if (null == body) {
      return request(resource, method, params, headers, null, 0);
    } else {
      return request(resource, method, params, headers, new ByteArrayInputStream(body),
                     body.length);
    }
  }

  /**
   * 请求RESTful API
   *
   * @param resource
   * @param method
   * @param params
   * @param headers
   * @param body
   *     InputStream, 通常FileInputStream/ByteArrayInputStream
   * @param bodyLen
   *     InputStream的文件大小
   * @return
   * @throws OdpsException
   */
  public Response request(String resource, String method, Map params,
                          Map headers,
                          InputStream body, long bodyLen) throws OdpsException {
    int retryTimes = 0;
    if (method.equalsIgnoreCase(Method.GET.toString()) || method
        .equalsIgnoreCase(Method.HEAD.toString())) {
      retryTimes = getRetryTimes();
      if (body != null && body.markSupported()) {
        body.mark(0);
      }
    }

    long retryWaitTime = getConnectTimeout() + getReadTimeout();

    long retryCount = 0;

    while (retryCount <= retryTimes) {
      long startTime = System.currentTimeMillis();
      try {
        Response resp = requestWithNoRetry(resource, method, params, headers, body, bodyLen);

        if (resp == null) {
          throw new OdpsException("Response is null.");
        }

        if (resp.getStatus() / 100 == 4) {
          retryTimes = 0;
          // IF THE HTTP CODE IS 4XX,
          // IT SHOULD NOT RETRY IF THE REQUEST NOT CHANGED
        }

        handleErrorResponse(resp);

        uploadDeprecatedLog();

        return resp;

      } catch (OdpsException e) {
        if (retryTimes == retryCount) {
          throw e;
        }

        resetBody(body);
        ++retryCount;

        if (logger != null) {
          logger.onRetryLog(e, retryCount, retryWaitTime);
        }

        try {
          long endTime = System.currentTimeMillis();
          long sleepTime = retryWaitTime * 1000 - (endTime - startTime);
          if (sleepTime > 0) {
            Thread.sleep(sleepTime);
          }
        } catch (InterruptedException e1) {
        }
        continue;
      }
    }
    throw new OdpsException("Failed in Connection Retry.");
  }

  private void uploadDeprecatedLog() {
    try {
      ConcurrentHashMap deprecatedMaps = OdpsDeprecatedLogger.getDeprecatedCalls();
      if (deprecatedMaps.isEmpty()) {
        return;
      }
      String deprecatedLogs = JSON.toJSONString(deprecatedMaps);
      OdpsDeprecatedLogger.getDeprecatedCalls().clear();

      String project = getDefaultProject();
      if (project == null) {
        return;
      }
      String resource = ResourceBuilder.buildProjectResource(project);
      resource += "/logs";
      byte[] bytes = deprecatedLogs.getBytes(CHARSET);
      ByteArrayInputStream body = new ByteArrayInputStream(bytes);
      requestWithNoRetry(resource, "PUT", null, null, body, bytes.length);
    } catch (Throwable e) {
      //do nothing if error occured
    }
  }

  private void handleErrorResponse(Response resp) throws OdpsException {

    if (!resp.isOK()) {
      ErrorMessage error = null;
      try {
        error = JAXBUtils.unmarshal(resp, ErrorMessage.class);
      } catch (Exception e) {
        //
      }

      OdpsException e = null;
      if (resp.getStatus() == 404) {
        if (error != null) {
          e = new NoSuchObjectException(error.getMessage(), new RestException(error));
        } else {
          e = new NoSuchObjectException("No such object.");
        }
      } else {
        if (error != null) {
          e = new OdpsException(error.getMessage(), new RestException(error));
        } else {         
          e = new OdpsException(String.valueOf(resp.getStatus()));          
        }
      }
      throw e;
    }
  }

  private void resetBody(InputStream body) {
    if (body != null && body.markSupported()) {
      try {
        body.reset();
      } catch (IOException e) {
        // DO NOTHING FOR SUPPORTED MARK STREAM WILL NOT FAILED
      }
    }
  }

  protected Response requestWithNoRetry(String resource, String method, Map params,
                                        Map headers, InputStream body, long bodyLen)
      throws OdpsException {

    Response resp = null;

    if (headers == null) {
      headers = new HashMap();
    }

    try {
      // set Content-Length
      if (body != null) {
        headers.put(Headers.CONTENT_LENGTH, String.valueOf(bodyLen));
        if (!headers.containsKey(Headers.CONTENT_MD5) && (bodyLen > 0)) {
          String contentMd5 = org.apache.commons.codec.binary.Hex
              .encodeHexString(org.apache.commons.codec.digest.DigestUtils.md5(body));
          IOUtils.resetInputStream(body);
          headers.put(Headers.CONTENT_MD5, contentMd5);
        }
      }

      Request req = buildRequest(resource, method, params, headers);
      req.setBody(body);
      req.setBodyLength(bodyLen);

      resp = transport.request(req);

      return resp;

    } catch (SSLHandshakeException e) {
      // FOR HTTPS CERTS CHECK FAILED
      // USE RuntimeException could avoid retry
      throw new RuntimeException(e.getMessage(), e);
    } catch (UnknownHostException e) {
      throw new RuntimeException(e.getMessage(), e);
    } catch (IOException e) {
      throw new OdpsException(e.getMessage(), e);
    }
  }

  /**
   * 获得HTTP连接
   *
   * @param resource
   * @param method
   * @param params
   * @param headers
   * @return
   * @throws OdpsException
   * @throws IOException
   */
  public Connection connect(String resource, String method, Map params,
                            Map headers)
      throws OdpsException, IOException {

    Request req = buildRequest(resource, method, params, headers);
    return transport.connect(req);
  }

  /**
   * 请求RESTful API,如果返回错误码非2xx也不会抛出异常
   *
   * @param resource
   * @param method
   * @param params
   * @param headers
   * @param body
   * @param length
   * @return
   * @throws OdpsException
   * @throws IOException
   */
  @Survey
  public Response requestForRawResponse(String resource, String method, Map params,
                                        Map headers,
                                        InputStream body, int length)
      throws OdpsException, IOException {

    return requestWithNoRetry(resource, method, params, headers, body, length);
  }

  public void setAccount(Account account) {
    this.account = account;
  }

  public Account getAccount() {
    return account;
  }

  public void setEndpoint(String endpoint) {
    this.endpoint = endpoint;
  }

  public String getDefaultProject() {
    return defaultProject;
  }

  public void setDefaultProject(String defaultProject) {
    this.defaultProject = defaultProject;
  }

  public String getEndpoint() {
    return endpoint;
  }

  @Survey
  public Transport getTransport() {
    return transport;
  }

  private Request buildRequest(String resource, String method, Map params,
                               Map headers)
      throws OdpsException {
    if (resource == null || !resource.startsWith("/")) {
      throw new OdpsException("Invalid resource: " + resource);
    }

    if (endpoint == null) {
      throw new OdpsException("Odps endpoint required.");
    }

    Request req = new Request(this);

    // build URL with parameters
    StringBuilder url = new StringBuilder();
    url.append(endpoint).append(resource);

    if (params == null) {
      params = new HashMap();
    }

    if (!params.containsKey("curr_project") && defaultProject != null) {
      params.put("curr_project", defaultProject);
    }

    if (params.size() != 0) {

      req.setParameters(params);

      url.append('?');
      boolean first = true;
      for (Entry kv : params.entrySet()) {

        if (first) {
          first = false;
        } else {
          url.append('&');
        }

        String key = kv.getKey();
        String value = kv.getValue();
        url.append(key);
        if (value != null && value.length() > 0) {
          value = ResourceBuilder.encode(value);
          url.append('=').append(value);
        }
      }
    }

    try {

      req.setURI(new URI(url.toString()));
      req.setMethod(Method.valueOf(method));

      if (headers != null) {
        req.setHeaders(headers);
      }

      // set User-Agent
      if (req.getHeaders().get(Headers.USER_AGENT) == null && userAgent != null) {
        req.setHeader(Headers.USER_AGENT, userAgent);
        req.setHeader("x-odps-user-agent", userAgent);
      }

      req.getHeaders().put("Date", DateUtils.formatRfc822Date(new Date()));

      // Sign the request
      account.getRequestSigner().sign(resource, req);
    } catch (URISyntaxException e) {
      throw new OdpsException(e.getMessage(), e);
    }

    return req;
  }

  /**
   * 获取 User-Agent
   *
   * @return
   */
  public String getUserAgent() {
    return userAgent;
  }

  /**
   * 设置 User-Agent
   *
   * @param userAgent
   */
  public void setUserAgent(String userAgent) {
    this.userAgent = (USER_AGENT_PREFIX + " " + userAgent).trim();
  }

  int connectTimeout = DEFAULT_CONNECT_TIMEOUT;

  /**
   * 设置建立连接超时时间
   *
   * @param timeout
   *     超时时间,单位秒
   */
  public void setConnectTimeout(int timeout) {
    this.connectTimeout = timeout;
  }

  /**
   * 获取建立连接超时时间
   *
   * @return 超时时间,单位秒
   */
  public int getConnectTimeout() {
    return connectTimeout;
  }

  int readTimeout = DEFAULT_READ_TIMEOUT;

  /**
   * 设置网络超时时间
   *
   * @param timeout
   *     超时时间,单位秒
   */
  public void setReadTimeout(int timeout) {
    this.readTimeout = timeout;
  }

  /**
   * 获取建立网络超时时间
   *
   * @return 超时时间,单位秒
   */
  public int getReadTimeout() {
    return readTimeout;
  }

  int retryTimes = DEFAULT_CONNECT_RETRYTIMES;

  /**
   * 获取网络重试次数
   *
   * @return 重试次数
   */
  public int getRetryTimes() {
    return retryTimes;
  }


  /**
   * 设置网络重试次数
   *
   * @param retryTimes
   *     重试次数
   */
  public void setRetryTimes(int retryTimes) {
    this.retryTimes = retryTimes;
  }

  /**
   * 获取是否忽略 Https 验证
   *
   * @return
   */
  public boolean isIgnoreCerts() {
    return ignoreCerts;
  }

  /**
   * 设置是否忽略 Https 验证
   *
   * @param ignoreCerts
   */
  public void setIgnoreCerts(boolean ignoreCerts) {
    this.ignoreCerts = ignoreCerts;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy