package com.qiniu.android.http; import com.qiniu.android.common.Constants; import com.qiniu.android.dns.DnsManager; import com.qiniu.android.dns.Domain; import com.qiniu.android.storage.UpCancellationSignal; import com.qiniu.android.storage.UpToken; import com.qiniu.android.utils.AsyncRun; import com.qiniu.android.utils.StringMap; import com.qiniu.android.utils.StringUtils; import org.json.JSONObject; import java.io.IOException; import java.net.InetAddress; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Dns; import okhttp3.HttpUrl; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import static com.qiniu.android.http.ResponseInfo.NetworkError; /** * Created by bailong on 15/11/12. */ public final class Client { public static final String ContentTypeHeader = "Content-Type"; public static final String DefaultMime = "application/octet-stream"; public static final String JsonMime = "application/json"; public static final String FormMime = "application/x-www-form-urlencoded"; private final UrlConverter converter; private OkHttpClient httpClient; public Client() { this(null, 10, 30, null, null); } public Client(ProxyConfiguration proxy, int connectTimeout, int responseTimeout, UrlConverter converter, final DnsManager dns) { this.converter = converter; OkHttpClient.Builder builder = new OkHttpClient.Builder(); if (proxy != null) { builder.proxy(proxy.proxy()); if (proxy.user != null && proxy.password != null) { builder.proxyAuthenticator(proxy.authenticator()); } } if (dns != null) { builder.dns(new Dns() { @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { InetAddress[] ips; try { ips = dns.queryInetAdress(new Domain(hostname)); } catch (IOException e) { e.printStackTrace(); throw new UnknownHostException(e.getMessage()); } if (ips == null) { throw new UnknownHostException(hostname + " resolve failed"); } List<InetAddress> l = new ArrayList<>(); Collections.addAll(l, ips); return l; } }); } builder.networkInterceptors().add(new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { Request request = chain.request(); final long before = System.currentTimeMillis(); okhttp3.Response response = chain.proceed(request); final long after = System.currentTimeMillis(); ResponseTag tag = (ResponseTag) request.tag(); String ip = ""; try { ip = chain.connection().socket().getRemoteSocketAddress().toString(); } catch (Exception e) { e.printStackTrace(); } tag.ip = ip; tag.duration = after - before; return response; } }); builder.connectTimeout(connectTimeout, TimeUnit.SECONDS); builder.readTimeout(responseTimeout, TimeUnit.SECONDS); builder.writeTimeout(0, TimeUnit.SECONDS); httpClient = builder.build(); } private static String via(okhttp3.Response response) { String via; if (!(via = response.header("X-Via", "")).equals("")) { return via; } if (!(via = response.header("X-Px", "")).equals("")) { return via; } if (!(via = response.header("Fw-Via", "")).equals("")) { return via; } return via; } private static String ctype(okhttp3.Response response) { MediaType mediaType = response.body().contentType(); if (mediaType == null) { return ""; } return mediaType.type() + "/" + mediaType.subtype(); } private static JSONObject buildJsonResp(byte[] body) throws Exception { String str = new String(body, Constants.UTF_8); // 允许 空 字符串 if (StringUtils.isNullOrEmpty(str)) { return new JSONObject(); } return new JSONObject(str); } private static ResponseInfo buildResponseInfo(okhttp3.Response response, String ip, long duration, final UpToken upToken) { int code = response.code(); String reqId = response.header("X-Reqid"); reqId = (reqId == null) ? null : reqId.trim().split(",")[0]; byte[] body = null; String error = null; try { body = response.body().bytes(); } catch (IOException e) { error = e.getMessage(); } JSONObject json = null; if (ctype(response).equals(Client.JsonMime) && body != null) { try { json = buildJsonResp(body); if (response.code() != 200) { String err = new String(body, Constants.UTF_8); error = json.optString("error", err); } } catch (Exception e) { if (response.code() < 300) { error = e.getMessage(); } } } else { error = body == null ? "null body" : new String(body); } HttpUrl u = response.request().url(); return ResponseInfo.create(json, code, reqId, response.header("X-Log"), via(response), u.host(), u.encodedPath(), ip, u.port(), duration, getContentLength(response), error, upToken); } private static long getContentLength(okhttp3.Response response) { try { RequestBody body = response.request().body(); if (body == null) { return 0; } return body.contentLength(); } catch (Throwable t) { return -1; } } private static void onRet(okhttp3.Response response, String ip, long duration, final UpToken upToken, final CompletionHandler complete) { final ResponseInfo info = buildResponseInfo(response, ip, duration, upToken); AsyncRun.runInMain(new Runnable() { @Override public void run() { complete.complete(info, info.response); } }); } public void asyncSend(final Request.Builder requestBuilder, StringMap headers, final UpToken upToken, final CompletionHandler complete) { if (headers != null) { headers.forEach(new StringMap.Consumer() { @Override public void accept(String key, Object value) { requestBuilder.header(key, value.toString()); } }); } requestBuilder.header("User-Agent", UserAgent.instance().getUa(upToken.accessKey)); final ResponseTag tag = new ResponseTag(); httpClient.newCall(requestBuilder.tag(tag).build()).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); int statusCode = NetworkError; String msg = e.getMessage(); if (e instanceof CancellationHandler.CancellationException) { statusCode = ResponseInfo.Cancelled; } else if (e instanceof UnknownHostException) { statusCode = ResponseInfo.UnknownHost; } else if (msg != null && msg.indexOf("Broken pipe") == 0) { statusCode = ResponseInfo.NetworkConnectionLost; } else if (e instanceof SocketTimeoutException) { statusCode = ResponseInfo.TimedOut; } else if (e instanceof java.net.ConnectException) { statusCode = ResponseInfo.CannotConnectToHost; } HttpUrl u = call.request().url(); ResponseInfo info = ResponseInfo.create(null, statusCode, "", "", "", u.host(), u.encodedPath(), "", u.port(), tag.duration, -1, e.getMessage(), upToken); complete.complete(info, null); } @Override public void onResponse(Call call, okhttp3.Response response) throws IOException { ResponseTag tag = (ResponseTag) response.request().tag(); onRet(response, tag.ip, tag.duration, upToken, complete); } }); } public void asyncPost(String url, byte[] body, StringMap headers, final UpToken upToken, ProgressHandler progressHandler, CompletionHandler completionHandler, UpCancellationSignal c) { asyncPost(url, body, 0, body.length, headers, upToken, progressHandler, completionHandler, c); } public void asyncPost(String url, byte[] body, int offset, int size, StringMap headers, final UpToken upToken, ProgressHandler progressHandler, CompletionHandler completionHandler, CancellationHandler c) { if (converter != null) { url = converter.convert(url); } RequestBody rbody; if (body != null && body.length > 0) { MediaType t = MediaType.parse(DefaultMime); rbody = RequestBody.create(t, body, offset, size); } else { rbody = RequestBody.create(null, new byte[0]); } if (progressHandler != null || c != null) { rbody = new CountingRequestBody(rbody, progressHandler, c); } Request.Builder requestBuilder = new Request.Builder().url(url).post(rbody); asyncSend(requestBuilder, headers, upToken, completionHandler); } public void asyncMultipartPost(String url, PostArgs args, final UpToken upToken, ProgressHandler progressHandler, CompletionHandler completionHandler, CancellationHandler c) { RequestBody file; if (args.file != null) { file = RequestBody.create(MediaType.parse(args.mimeType), args.file); } else { file = RequestBody.create(MediaType.parse(args.mimeType), args.data); } asyncMultipartPost(url, args.params, upToken, progressHandler, args.fileName, file, completionHandler, c); } private void asyncMultipartPost(String url, StringMap fields, final UpToken upToken, ProgressHandler progressHandler, String fileName, RequestBody file, CompletionHandler completionHandler, CancellationHandler cancellationHandler) { if (converter != null) { url = converter.convert(url); } final MultipartBody.Builder mb = new MultipartBody.Builder(); mb.addFormDataPart("file", fileName, file); fields.forEach(new StringMap.Consumer() { @Override public void accept(String key, Object value) { mb.addFormDataPart(key, value.toString()); } }); mb.setType(MediaType.parse("multipart/form-data")); RequestBody body = mb.build(); if (progressHandler != null || cancellationHandler != null) { body = new CountingRequestBody(body, progressHandler, cancellationHandler); } Request.Builder requestBuilder = new Request.Builder().url(url).post(body); asyncSend(requestBuilder, null, upToken, completionHandler); } public void asyncGet(String url, StringMap headers, final UpToken upToken, CompletionHandler completionHandler) { Request.Builder requestBuilder = new Request.Builder().get().url(url); asyncSend(requestBuilder, headers, upToken, completionHandler); } public ResponseInfo syncGet(String url, StringMap headers) { Request.Builder requestBuilder = new Request.Builder().get().url(url); return send(requestBuilder, headers); } private ResponseInfo send(final Request.Builder requestBuilder, StringMap headers) { if (headers != null) { headers.forEach(new StringMap.Consumer() { @Override public void accept(String key, Object value) { requestBuilder.header(key, value.toString()); } }); } requestBuilder.header("User-Agent", UserAgent.instance().getUa("")); long start = System.currentTimeMillis(); okhttp3.Response res = null; ResponseTag tag = new ResponseTag(); Request req = requestBuilder.tag(tag).build(); try { res = httpClient.newCall(req).execute(); } catch (IOException e) { e.printStackTrace(); return ResponseInfo.create(null, NetworkError, "", "", "", req.url().host(), req.url().encodedPath(), tag.ip, req.url().port(), tag.duration, -1, e.getMessage(), UpToken.NULL); } return buildResponseInfo(res, tag.ip, tag.duration, UpToken.NULL); } public ResponseInfo syncMultipartPost(String url, PostArgs args, final UpToken upToken) { RequestBody file; if (args.file != null) { file = RequestBody.create(MediaType.parse(args.mimeType), args.file); } else { file = RequestBody.create(MediaType.parse(args.mimeType), args.data); } return syncMultipartPost(url, args.params, upToken, args.fileName, file); } private ResponseInfo syncMultipartPost(String url, StringMap fields, final UpToken upToken, String fileName, RequestBody file) { final MultipartBody.Builder mb = new MultipartBody.Builder(); mb.addFormDataPart("file", fileName, file); fields.forEach(new StringMap.Consumer() { @Override public void accept(String key, Object value) { mb.addFormDataPart(key, value.toString()); } }); mb.setType(MediaType.parse("multipart/form-data")); RequestBody body = mb.build(); Request.Builder requestBuilder = new Request.Builder().url(url).post(body); return syncSend(requestBuilder, null, upToken); } public ResponseInfo syncSend(final Request.Builder requestBuilder, StringMap headers, final UpToken upToken) { if (headers != null) { headers.forEach(new StringMap.Consumer() { @Override public void accept(String key, Object value) { requestBuilder.header(key, value.toString()); } }); } requestBuilder.header("User-Agent", UserAgent.instance().getUa(upToken.accessKey)); final ResponseTag tag = new ResponseTag(); Request req = null; try { req = requestBuilder.tag(tag).build(); okhttp3.Response response = httpClient.newCall(req).execute(); return buildResponseInfo(response, tag.ip, tag.duration, upToken); } catch (Exception e) { e.printStackTrace(); int statusCode = NetworkError; String msg = e.getMessage(); if (e instanceof UnknownHostException) { statusCode = ResponseInfo.UnknownHost; } else if (msg != null && msg.indexOf("Broken pipe") == 0) { statusCode = ResponseInfo.NetworkConnectionLost; } else if (e instanceof SocketTimeoutException) { statusCode = ResponseInfo.TimedOut; } else if (e instanceof java.net.ConnectException) { statusCode = ResponseInfo.CannotConnectToHost; } HttpUrl u = req.url(); return ResponseInfo.create(null, statusCode, "", "", "", u.host(), u.encodedPath(), "", u.port(), 0, 0, e.getMessage(), upToken); } } private static class ResponseTag { public String ip = ""; public long duration = -1; } }