content: /*
   * 微信公众平台(JAVA) SDK
   *
   * Copyright (c) 2014, Ansitech Network Technology Co.,Ltd All rights reserved.
   * 
   * http://www.weixin4j.org/
   *
   * Licensed 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 org.weixin4j.http;
  
  import com.alibaba.fastjson.JSONException;
  import org.weixin4j.model.media.Attachment;
  import com.alibaba.fastjson.JSONObject;
  import java.io.BufferedInputStream;
  import org.weixin4j.Configuration;
  import org.weixin4j.WeixinException;
  import java.io.BufferedReader;
  import java.io.DataInputStream;
  import java.io.DataOutputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.net.URL;
  import java.security.KeyManagementException;
  import java.security.KeyStore;
  import java.security.KeyStoreException;
  import java.security.NoSuchAlgorithmException;
  import java.security.NoSuchProviderException;
  import java.security.UnrecoverableKeyException;
  import java.security.cert.CertificateException;
  import javax.net.ssl.HttpsURLConnection;
  import javax.net.ssl.KeyManagerFactory;
  import javax.net.ssl.SSLContext;
  import javax.net.ssl.SSLSocketFactory;
  import javax.net.ssl.TrustManager;
  
  /**
   * 请求微信平台及响应的客户端类
   *
   * <p>
   * 每一次请求即对应一个<tt>HttpsClient</tt>，
   * 每次登陆产生一个<tt>OAuth</tt>用户连接,使用<tt>OAuthToken</tt>
   * 可以不用重复向微信平台发送登陆请求，在没有过期时间内，可继续请求。</p>
   *
   * @author yangqisheng
   * @since 0.0.1
   */
  public class HttpsClient implements java.io.Serializable {
  
      private static final int OK = 200;  // OK: Success!
      private static final int CONNECTION_TIMEOUT = Configuration.getConnectionTimeout();
      private static final int READ_TIMEOUT = Configuration.getReadTimeout();
      private static final String DEFAULT_CHARSET = "UTF-8";
      private static final String GET = "GET";
      private static final String POST = "POST";
  
      public HttpsClient() {
      }
  
      /**
       * Post JSON数据
       *
       * 默认https方式
       *
       * @param url 提交地址
       * @param json JSON数据
       * @return 输出流对象
       * @throws org.weixin4j.WeixinException 微信操作异常
       */
      public Response post(String url, JSONObject json) throws WeixinException {
          //将JSON数据转换为String字符串
          String jsonString = json == null ? null : json.toString();
          if (Configuration.isDebug()) {
              System.out.println("URL POST 数据：" + jsonString);
          }
          //提交数据
          return httpsRequest(url, POST, jsonString, false, null, null);
      }
  
      /**
       * Get 请求
       *
       * 默认https方式
       *
       * @param url 请求地址
       * @return 输出流对象
       * @throws org.weixin4j.WeixinException 微信操作异常
       */
      public Response get(String url) throws WeixinException {
          return httpsRequest(url, GET, null, false, null, null);
      }
  
      /**
       * Post XML格式数据
       *
       * @param url 提交地址
       * @param xml XML数据
       * @return 输出流对象
       * @throws org.weixin4j.WeixinException 微信操作异常
       */
      public Response postXml(String url, String xml) throws WeixinException {
          return httpsRequest(url, POST, xml, false, null, null);
      }
  
      /**
       * Post XML格式数据
       *
       * @param url 提交地址
       * @param xml XML数据
       * @param certPath 证书地址
       * @param certSecret 证书密钥
       * @return 输出流对象
       * @throws org.weixin4j.WeixinException 微信操作异常
       */
      public Response postXml(String url, String xml, String certPath, String certSecret) throws WeixinException {
          return httpsRequest(url, POST, xml, true, certPath, certSecret);
      }
  
      /**
       * 通过https协议请求url
       * 
       * @param url 请求地址
       * @param method 请求方式
       * @param postData 请求数据
       * @param needCert 是否需要证书
       * @param certPath 商户证书路径
       * @param certSecret 商户证书密钥
       * @return
       * @throws WeixinException 微信操作异常
       */
      private Response httpsRequest(String url, String method, String postData, boolean needCert, String certPath, String certSecret)
              throws WeixinException {
          Response res = null;
          OutputStream output;
          HttpsURLConnection https;
          try {
              //创建https请求连接
              https = getHttpsURLConnection(url);
              //判断https是否为空，如果为空返回null响应
              if (https != null) {
                  //设置Header信息，包括https证书
                  setHttpsHeader(https, method, needCert, certPath, certSecret);
                  //判断是否需要提交数据
                  if (method.equals(POST) && null != postData) {
                      //讲参数转换为字节提交
                      byte[] bytes = postData.getBytes(DEFAULT_CHARSET);
                      //设置头信息
                      https.setRequestProperty("Content-Length", Integer.toString(bytes.length));
                      //开始连接
                      https.connect();
                      //获取返回信息
                      output = https.getOutputStream();
                      output.write(bytes);
                      output.flush();
                      output.close();
                  } else {
                      //开始连接
                      https.connect();
                  }
                  //创建输出对象
                  res = new Response(https);
                  //获取响应代码
                  if (res.getStatus() == OK) {
                      return res;
                  }
              }
          } catch (NoSuchAlgorithmException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (KeyManagementException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (NoSuchProviderException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (IOException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (KeyStoreException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (CertificateException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (UnrecoverableKeyException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          }
          return res;
      }
  
      /**
       * 获取https请求连接
       *
       * @param url 连接地址
       * @return https连接对象
       * @throws IOException IO异常
       */
      private HttpsURLConnection getHttpsURLConnection(String url) throws IOException {
          URL urlGet = new URL(url);
          //创建https请求
          HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlGet.openConnection();
          return httpsUrlConnection;
      }
  
      private void setHttpsHeader(HttpsURLConnection httpsUrlConnection, String method, boolean needCert, String certPath, String certSecret)
              throws NoSuchAlgorithmException, KeyManagementException, NoSuchProviderException,
              IOException, KeyStoreException, CertificateException, UnrecoverableKeyException {
          //不需要维修证书，则使用默认证书
          if (!needCert) {
              //创建https请求证书
              TrustManager[] tm = {new MyX509TrustManager()};
              //创建证书上下文对象
              SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
              //初始化证书信息
              sslContext.init(null, tm, new java.security.SecureRandom());
              // 从上述SSLContext对象中得到SSLSocketFactory对象  
              SSLSocketFactory ssf = sslContext.getSocketFactory();
              //设置ssl证书
              httpsUrlConnection.setSSLSocketFactory(ssf);
          } else {
              //指定读取证书格式为PKCS12
              KeyStore keyStore = KeyStore.getInstance("PKCS12");
              //读取本机存放的PKCS12证书文件
              FileInputStream instream = new FileInputStream(new File(certPath));
              try {
                  //指定PKCS12的密码
                  keyStore.load(instream, certSecret.toCharArray());
              } finally {
                  instream.close();
              }
              KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
              kmf.init(keyStore, certSecret.toCharArray());
              //创建管理jks密钥库的x509密钥管理器，用来管理密钥，需要key的密码  
              SSLContext sslContext = SSLContext.getInstance("TLSv1");
              // 构造SSL环境，指定SSL版本为3.0，也可以使用TLSv1，但是SSLv3更加常用。  
              sslContext.init(kmf.getKeyManagers(), null, null);
              // 从上述SSLContext对象中得到SSLSocketFactory对象  
              SSLSocketFactory ssf = sslContext.getSocketFactory();
              //设置ssl证书
              httpsUrlConnection.setSSLSocketFactory(ssf);
          }
          //设置header信息
          httpsUrlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
          //设置User-Agent信息
          httpsUrlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36");
          //设置可接受信息
          httpsUrlConnection.setDoOutput(true);
          //设置可输入信息
          httpsUrlConnection.setDoInput(true);
          //设置请求方式
          httpsUrlConnection.setRequestMethod(method);
          //设置连接超时时间
          if (CONNECTION_TIMEOUT > 0) {
              httpsUrlConnection.setConnectTimeout(CONNECTION_TIMEOUT);
          } else {
              //默认10秒超时
              httpsUrlConnection.setConnectTimeout(10000);
          }
          //设置请求超时
          if (READ_TIMEOUT > 0) {
              httpsUrlConnection.setReadTimeout(READ_TIMEOUT);
          } else {
              //默认10秒超时
              httpsUrlConnection.setReadTimeout(10000);
          }
          //设置编码
          httpsUrlConnection.setRequestProperty("Charsert", "UTF-8");
      }
  
      /**
       * 上传文件
       *
       * @param url 上传地址
       * @param file 上传文件对象
       * @return 服务器上传响应结果
       * @throws org.weixin4j.WeixinException 微信操作异常
       */
      public String uploadHttps(String url, File file) throws WeixinException {
          HttpsURLConnection https = null;
          StringBuffer bufferRes = new StringBuffer();
          try {
              // 定义数据分隔线 
              String BOUNDARY = "----WebKitFormBoundaryiDGnV9zdZA1eM1yL";
              //创建https请求连接
              https = getHttpsURLConnection(url);
              //设置header和ssl证书
              setHttpsHeader(https, POST, false, null, null);
              //不缓存
              https.setUseCaches(false);
              //保持连接
              https.setRequestProperty("connection", "Keep-Alive");
              //设置文档类型
              https.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
  
              //定义输出流
              OutputStream out = null;
              //定义输入流
              DataInputStream dataInputStream;
              try {
                  out = new DataOutputStream(https.getOutputStream());
                  byte[] end_data = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();// 定义最后数据分隔线  
                  StringBuilder sb = new StringBuilder();
                  sb.append("--");
                  sb.append(BOUNDARY);
                  sb.append("\r\n");
                  sb.append("Content-Disposition: form-data;name=\"media\";filename=\"").append(file.getName()).append("\"\r\n");
                  sb.append("Content-Type:application/octet-stream\r\n\r\n");
                  byte[] data = sb.toString().getBytes();
                  out.write(data);
                  //读取文件流
                  dataInputStream = new DataInputStream(new FileInputStream(file));
                  int bytes;
                  byte[] bufferOut = new byte[1024];
                  while ((bytes = dataInputStream.read(bufferOut)) != -1) {
                      out.write(bufferOut, 0, bytes);
                  }
                  out.write("\r\n".getBytes()); //多个文件时，二个文件之间加入这个  
                  dataInputStream.close();
                  out.write(end_data);
                  out.flush();
              } finally {
                  if (out != null) {
                      out.close();
                  }
              }
  
              // 定义BufferedReader输入流来读取URL的响应  
              InputStream ins = null;
              try {
                  ins = https.getInputStream();
                  BufferedReader read = new BufferedReader(new InputStreamReader(ins, "UTF-8"));
                  String valueString;
                  bufferRes = new StringBuffer();
                  while ((valueString = read.readLine()) != null) {
                      bufferRes.append(valueString);
                  }
              } finally {
                  if (ins != null) {
                      ins.close();
                  }
              }
          } catch (IOException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (NoSuchAlgorithmException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (KeyManagementException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (NoSuchProviderException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (KeyStoreException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (CertificateException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (UnrecoverableKeyException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } finally {
              if (https != null) {
                  // 关闭连接
                  https.disconnect();
              }
          }
          return bufferRes.toString();
      }
  
      /**
       * 下载附件
       *
       * @param url 附件地址
       * @return 附件对象
       * @throws org.weixin4j.WeixinException 微信操作异常
       */
      public Attachment downloadHttps(String url) throws WeixinException {
          //定义下载附件对象
          Attachment attachment = null;
          HttpsURLConnection https;
          try {
              //创建https请求连接
              https = getHttpsURLConnection(url);
              //设置header和ssl证书
              setHttpsHeader(https, POST, false, null, null);
              //不缓存
              https.setUseCaches(false);
              //保持连接
              https.setRequestProperty("connection", "Keep-Alive");
  
              //获取输入流
              InputStream in = https.getInputStream();
  
              //初始化返回附件对象
              attachment = new Attachment();
              String contentType = https.getContentType();
              //出现错误时，返回错误消息
              if (contentType.contains("text/plain")) {
                  // 定义BufferedReader输入流来读取URL的响应  
                  BufferedReader read = new BufferedReader(new InputStreamReader(in, "UTF-8"));
                  String valueString;
                  StringBuilder bufferRes = new StringBuilder();
                  while ((valueString = read.readLine()) != null) {
                      bufferRes.append(valueString);
                  }
                  String textString = bufferRes.toString();
                  if (textString.contains("video_url")) {
                      try {
                          JSONObject result = JSONObject.parseObject(textString);
                          if (result.containsKey("errcode") && result.getIntValue("errcode") != 0) {
                              attachment.setError(result.getString("errmsg"));
                          } else if (result.containsKey("video_url")) {
                              //发起get请求获取视频流
                              HttpClient httpClient = new HttpClient();
                              return httpClient.download(result.getString("video_url"));
                          } else {
                              //未知格式
                              attachment.setError(textString);
                          }
                      } catch (JSONException ex) {
                          attachment.setError(textString);
                      }
                  } else {
                      attachment.setError(textString);
                  }
              } else if (contentType.contains("application/json")) {
                  // 定义BufferedReader输入流来读取URL的响应  
                  BufferedReader read = new BufferedReader(new InputStreamReader(in, "UTF-8"));
                  String valueString;
                  StringBuilder bufferRes = new StringBuilder();
                  while ((valueString = read.readLine()) != null) {
                      bufferRes.append(valueString);
                  }
                  String jsonString = bufferRes.toString();
                  JSONObject result = JSONObject.parseObject(jsonString);
                  if (result.containsKey("errcode") && result.getIntValue("errcode") != 0) {
                      attachment.setError(result.getString("errmsg"));
                  } else if (result.containsKey("video_url")) {
                      //发起get请求获取视频流
                      HttpClient httpClient = new HttpClient();
                      return httpClient.download(result.getString("video_url"));
                  } else {
                      //未知格式
                      attachment.setError(jsonString);
                  }
              } else {
                  BufferedInputStream bis = new BufferedInputStream(in);
                  String ds = https.getHeaderField("Content-disposition");
                  String fullName = ds.substring(ds.indexOf("filename=\"") + 10, ds.length() - 1);
                  String relName = fullName.substring(0, fullName.lastIndexOf("."));
                  String suffix = fullName.substring(relName.length() + 1);
  
                  attachment.setFullName(fullName);
                  attachment.setFileName(relName);
                  attachment.setSuffix(suffix);
                  attachment.setContentLength(https.getHeaderField("Content-Length"));
                  attachment.setContentType(https.getHeaderField("Content-Type"));
  
                  attachment.setFileStream(bis);
              }
          } catch (IOException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (NoSuchAlgorithmException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (KeyManagementException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (NoSuchProviderException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (KeyStoreException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (CertificateException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } catch (UnrecoverableKeyException ex) {
              throw new WeixinException(ex.getMessage(), ex);
          } finally {
          }
          return attachment;
      }
  }
  