cn.jiguang.common.connection.NativeHttpClient Maven / Gradle / Ivy
package cn.jiguang.common.connection;
import cn.jiguang.common.ClientConfig;
import cn.jiguang.common.resp.APIConnectionException;
import cn.jiguang.common.resp.APIRequestException;
import cn.jiguang.common.resp.ResponseWrapper;
import cn.jiguang.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.activation.MimetypesFileTypeMap;
import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.Map;
/**
* The implementation has no connection pool mechanism, used origin java connection.
*
* 本实现没有连接池机制,基于 Java 原始的 HTTP 连接实现。
*
* 遇到连接超时,会自动重连指定的次数(默认为 3);如果是读取超时,则不会自动重连。
*
* 可选支持 HTTP 代理,同时支持 2 种方式:1) HTTP 头上加上 Proxy-Authorization 信息;2)全局配置 Authenticator.setDefault;
*/
public class NativeHttpClient implements IHttpClient {
private static final Logger LOG = LoggerFactory.getLogger(NativeHttpClient.class);
private static final String KEYWORDS_CONNECT_TIMED_OUT = "connect timed out";
private static final String KEYWORDS_READ_TIMED_OUT = "Read timed out";
private final int _connectionTimeout;
private final int _readTimeout;
private final int _maxRetryTimes;
private final String _sslVer;
private final String _encryptType;
private String _authCode;
private HttpProxy _proxy;
public NativeHttpClient(String authCode, HttpProxy proxy, ClientConfig config) {
_maxRetryTimes = config.getMaxRetryTimes();
_connectionTimeout = config.getConnectionTimeout();
_readTimeout = config.getReadTimeout();
_sslVer = config.getSSLVersion();
_encryptType = config.getEncryptType();
_authCode = authCode;
_proxy = proxy;
String message = MessageFormat.format("Created instance with "
+ "connectionTimeout {0}, readTimeout {1}, maxRetryTimes {2}, SSL Version {3}",
_connectionTimeout, _readTimeout, _maxRetryTimes, _sslVer);
LOG.debug(message);
if (null != _proxy && _proxy.isAuthenticationNeeded()) {
Authenticator.setDefault(new SimpleProxyAuthenticator(
_proxy.getUsername(), _proxy.getPassword()));
}
initSSL(_sslVer);
}
public ResponseWrapper sendGet(String url)
throws APIConnectionException, APIRequestException {
return sendGet(url, null);
}
public ResponseWrapper sendGet(String url, String content)
throws APIConnectionException, APIRequestException {
return doRequest(url, content, RequestMethod.GET);
}
public ResponseWrapper sendDelete(String url)
throws APIConnectionException, APIRequestException {
return sendDelete(url, null);
}
public ResponseWrapper sendDelete(String url, String content)
throws APIConnectionException, APIRequestException {
return doRequest(url, content, RequestMethod.DELETE);
}
public ResponseWrapper sendPost(String url, String content)
throws APIConnectionException, APIRequestException {
return doRequest(url, content, RequestMethod.POST);
}
public ResponseWrapper sendPut(String url, String content)
throws APIConnectionException, APIRequestException {
return doRequest(url, content, RequestMethod.PUT);
}
public ResponseWrapper doRequest(String url, String content,
RequestMethod method) throws APIConnectionException, APIRequestException {
ResponseWrapper response = null;
for (int retryTimes = 0; ; retryTimes++) {
try {
response = _doRequest(url, content, method);
break;
} catch (SocketTimeoutException e) {
if (KEYWORDS_READ_TIMED_OUT.equals(e.getMessage())) {
// Read timed out. For push, maybe should not re-send.
throw new APIConnectionException(READ_TIMED_OUT_MESSAGE, e, true);
} else { // connect timed out
if (retryTimes >= _maxRetryTimes) {
throw new APIConnectionException(CONNECT_TIMED_OUT_MESSAGE, e, retryTimes);
} else {
LOG.debug("connect timed out - retry again - " + (retryTimes + 1));
}
}
}
}
return response;
}
private ResponseWrapper _doRequest(String url, String content,
RequestMethod method) throws APIConnectionException, APIRequestException,
SocketTimeoutException {
LOG.debug("Send request - " + method.toString() + " " + url);
if (null != content) {
LOG.debug("Request Content - " + content);
}
HttpURLConnection conn = null;
OutputStream out = null;
InputStream in = null;
InputStreamReader reader = null;
StringBuffer sb = new StringBuffer();
ResponseWrapper wrapper = new ResponseWrapper();
try {
URL aUrl = new URL(url);
conn = getConnectionByUrl(aUrl);
conn.setConnectTimeout(_connectionTimeout);
conn.setReadTimeout(_readTimeout);
conn.setUseCaches(false);
conn.setRequestMethod(method.name());
if (!StringUtils.isEmpty(_encryptType)) {
conn.setRequestProperty("X-Encrypt-Type", _encryptType);
}
conn.setRequestProperty("User-Agent", JPUSH_USER_AGENT);
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Accept-Charset", CHARSET);
conn.setRequestProperty("Charset", CHARSET);
conn.setRequestProperty("Authorization", _authCode);
conn.setRequestProperty("Content-Type", CONTENT_TYPE_JSON);
if (null == content) {
conn.setDoOutput(false);
} else {
conn.setDoOutput(true);
byte[] data = content.getBytes(CHARSET);
conn.setRequestProperty("Content-Length", String.valueOf(data.length));
out = conn.getOutputStream();
out.write(data);
out.flush();
}
int status = conn.getResponseCode();
if (status / 100 == 2) {
in = conn.getInputStream();
} else {
in = conn.getErrorStream();
}
if (null != in) {
reader = new InputStreamReader(in, CHARSET);
char[] buff = new char[1024];
int len;
while ((len = reader.read(buff)) > 0) {
sb.append(buff, 0, len);
}
}
String responseContent = sb.toString();
wrapper.responseCode = status;
wrapper.responseContent = responseContent;
String quota = conn.getHeaderField(RATE_LIMIT_QUOTA);
String remaining = conn.getHeaderField(RATE_LIMIT_Remaining);
String reset = conn.getHeaderField(RATE_LIMIT_Reset);
wrapper.setRateLimit(quota, remaining, reset);
if (status >= 200 && status < 300) {
LOG.debug("Succeed to get response OK - responseCode:" + status);
LOG.debug("Response Content - " + responseContent);
} else if (status >= 300 && status < 400) {
LOG.warn("Normal response but unexpected - responseCode:" + status + ", responseContent:" + responseContent);
} else {
LOG.warn("Got error response - responseCode:" + status + ", responseContent:" + responseContent);
switch (status) {
case 400:
LOG.warn("Your request params is invalid. Please check them according to error message.");
wrapper.setErrorObject();
break;
case 401:
LOG.warn("Authentication failed! Please check authentication params according to docs.");
wrapper.setErrorObject();
break;
case 403:
LOG.warn("Request is forbidden! Maybe your appkey is listed in blacklist or your params is invalid.");
wrapper.setErrorObject();
break;
case 404:
LOG.warn("Request page is not found! Maybe your params is invalid.");
wrapper.setErrorObject();
break;
case 410:
LOG.warn("Request resource is no longer in service. Please according to notice on official website.");
wrapper.setErrorObject();
case 429:
LOG.warn("Too many requests! Please review your appkey's request quota.");
wrapper.setErrorObject();
break;
case 500:
case 502:
case 503:
case 504:
LOG.warn("Seems encountered server error. Maybe JPush is in maintenance? Please retry later.");
break;
default:
LOG.warn("Unexpected response.");
}
throw new APIRequestException(wrapper);
}
} catch (SocketTimeoutException e) {
if (e.getMessage().contains(KEYWORDS_CONNECT_TIMED_OUT)) {
throw e;
} else if (e.getMessage().contains(KEYWORDS_READ_TIMED_OUT)) {
throw new SocketTimeoutException(KEYWORDS_READ_TIMED_OUT);
}
LOG.debug(IO_ERROR_MESSAGE, e);
throw new APIConnectionException(IO_ERROR_MESSAGE, e);
} catch (IOException e) {
LOG.debug(IO_ERROR_MESSAGE, e);
throw new APIConnectionException(IO_ERROR_MESSAGE, e);
} finally {
if (null != out) {
try {
out.close();
} catch (IOException e) {
LOG.warn("Failed to close stream.", e);
}
}
if (null != conn) {
conn.disconnect();
}
if (null != in) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return wrapper;
}
protected void initSSL(String sslVer) {
TrustManager[] tmCerts = new TrustManager[1];
tmCerts[0] = new SimpleTrustManager();
try {
SSLContext sslContext = SSLContext.getInstance(sslVer);
sslContext.init(null, tmCerts, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HostnameVerifier hostnameVerifier = new SimpleHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
} catch (Exception e) {
LOG.error("Init SSL error", e);
}
}
public String formUploadByPut(String urlStr, Map textMap,
Map fileMap, String contentType) {
return formUpload(urlStr, textMap, fileMap, contentType, "PUT");
}
public String formUploadByPost(String urlStr, Map textMap,
Map fileMap, String contentType) {
return formUpload(urlStr, textMap, fileMap, contentType, "POST");
}
private String formUpload(String urlStr, Map textMap,
Map fileMap, String contentType, String requestMethod) {
String res = "";
HttpURLConnection conn = null;
// boundary就是request头和上传文件内容的分隔符
String BOUNDARY = "---------------------------" + System.currentTimeMillis();
try {
URL url = new URL(urlStr);
conn = getConnectionByUrl(url);
conn.setConnectTimeout(5000);
conn.setReadTimeout(30000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Authorization", _authCode);
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
OutputStream out = new DataOutputStream(conn.getOutputStream());
// text
if (textMap != null) {
StringBuffer strBuf = new StringBuffer();
Iterator iter = textMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String inputName = (String) entry.getKey();
String inputValue = (String) entry.getValue();
if (inputValue == null) {
continue;
}
strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n");
strBuf.append(inputValue);
}
out.write(strBuf.toString().getBytes());
}
// file
if (fileMap != null) {
Iterator iter = fileMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String inputName = (String) entry.getKey();
String inputValue = (String) entry.getValue();
if (inputValue == null) {
continue;
}
File file = new File(inputValue);
String filename = file.getName();
//没有传入文件类型,同时根据文件获取不到类型,默认采用application/octet-stream
contentType = new MimetypesFileTypeMap().getContentType(file);
//contentType非空采用filename匹配默认的图片类型
if (!"".equals(contentType)) {
if (filename.endsWith(".png")) {
contentType = "image/png";
} else if (filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".jpe")) {
contentType = "image/jpeg";
} else if (filename.endsWith(".gif")) {
contentType = "image/gif";
} else if (filename.endsWith(".ico")) {
contentType = "image/image/x-icon";
}
}
if (contentType == null || "".equals(contentType)) {
contentType = "application/octet-stream";
}
StringBuffer strBuf = new StringBuffer();
strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename + "\"\r\n");
strBuf.append("Content-Type:" + contentType + "\r\n\r\n");
out.write(strBuf.toString().getBytes());
DataInputStream in = new DataInputStream(new FileInputStream(file));
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close();
}
}
byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
out.write(endData);
out.flush();
out.close();
// 读取返回数据
StringBuffer strBuf = new StringBuffer();
InputStream is = null;
int responseCode = conn.getResponseCode();
BufferedReader reader;
if (responseCode == 200) {
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} else {
reader = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
}
String line = null;
while ((line = reader.readLine()) != null) {
strBuf.append(line).append("\n");
}
res = strBuf.toString();
reader.close();
reader = null;
} catch (FileNotFoundException e) {
LOG.error("formUpload error", e);
throw new RuntimeException("formUpload error", e);
} catch (Exception e) {
LOG.error("formUpload error", e);
} finally {
if (conn != null) {
conn.disconnect();
conn = null;
}
}
return res;
}
public HttpURLConnection getConnectionByUrl(URL url) throws IOException {
HttpURLConnection conn;
if (null != _proxy) {
conn = (HttpURLConnection) url.openConnection(_proxy.getNetProxy());
if (_proxy.isAuthenticationNeeded()) {
conn.setRequestProperty("Proxy-Authorization", _proxy.getProxyAuthorization());
}
} else {
conn = (HttpURLConnection) url.openConnection();
}
return conn;
}
private static class SimpleHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
private static class SimpleTrustManager implements TrustManager, X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return;
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return;
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
public static class SimpleProxyAuthenticator extends Authenticator {
private String username;
private String password;
public SimpleProxyAuthenticator(String username, String password) {
this.username = username;
this.password = password;
}
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
this.username,
this.password.toCharArray());
}
}
}