Java tutorial
/* * Copyright (c) 2015-present, Parse, LLC. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.parse; import android.net.SSLCertificateSocketFactory; import android.net.SSLSessionCache; import com.parse.http.ParseHttpBody; import com.parse.http.ParseHttpRequest; import com.parse.http.ParseHttpResponse; import com.parse.http.ParseNetworkInterceptor; import com.squareup.okhttp.Call; import com.squareup.okhttp.Headers; import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.MediaType; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import com.squareup.okhttp.ResponseBody; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import bolts.Capture; import okio.BufferedSink; import okio.BufferedSource; import okio.Okio; /** package */ class ParseOkHttpClient extends ParseHttpClient<Request, Response> { private final static String OKHTTP_GET = "GET"; private final static String OKHTTP_POST = "POST"; private final static String OKHTTP_PUT = "PUT"; private final static String OKHTTP_DELETE = "DELETE"; private OkHttpClient okHttpClient; public ParseOkHttpClient(int socketOperationTimeout, SSLSessionCache sslSessionCache) { okHttpClient = new OkHttpClient(); okHttpClient.setConnectTimeout(socketOperationTimeout, TimeUnit.MILLISECONDS); okHttpClient.setReadTimeout(socketOperationTimeout, TimeUnit.MILLISECONDS); // Don't handle redirects. We copy the setting from AndroidHttpClient. // For detail, check https://quip.com/Px8jAxnaun2r okHttpClient.setFollowRedirects(false); okHttpClient.setSslSocketFactory( SSLCertificateSocketFactory.getDefault(socketOperationTimeout, sslSessionCache)); } @Override /* package */ ParseHttpResponse executeInternal(ParseHttpRequest parseRequest) throws IOException { Request okHttpRequest = getRequest(parseRequest); Call okHttpCall = okHttpClient.newCall(okHttpRequest); Response okHttpResponse = okHttpCall.execute(); return getResponse(okHttpResponse); } @Override /* package */ ParseHttpResponse getResponse(Response okHttpResponse) throws IOException { // Status code int statusCode = okHttpResponse.code(); // Content InputStream content = okHttpResponse.body().byteStream(); // Total size int totalSize = (int) okHttpResponse.body().contentLength(); // Reason phrase String reasonPhrase = okHttpResponse.message(); // Headers Map<String, String> headers = new HashMap<>(); for (String name : okHttpResponse.headers().names()) { headers.put(name, okHttpResponse.header(name)); } // Content type String contentType = null; ResponseBody body = okHttpResponse.body(); if (body != null && body.contentType() != null) { contentType = body.contentType().toString(); } return new ParseHttpResponse.Builder().setStatusCode(statusCode).setContent(content).setTotalSize(totalSize) .setReasonPhrase(reasonPhrase).setHeaders(headers).setContentType(contentType).build(); } @Override /* package */ Request getRequest(ParseHttpRequest parseRequest) throws IOException { Request.Builder okHttpRequestBuilder = new Request.Builder(); ParseHttpRequest.Method method = parseRequest.getMethod(); // Set method switch (method) { case GET: okHttpRequestBuilder.get(); break; case DELETE: okHttpRequestBuilder.delete(); break; case POST: case PUT: // Since we need to set body and method at the same time for POST and PUT, we will do it in // the following. break; default: // This case will never be reached since we have already handled this case in // ParseRequest.newRequest(). throw new IllegalStateException("Unsupported http method " + method.toString()); } // Set url okHttpRequestBuilder.url(parseRequest.getUrl()); // Set Header Headers.Builder okHttpHeadersBuilder = new Headers.Builder(); for (Map.Entry<String, String> entry : parseRequest.getAllHeaders().entrySet()) { okHttpHeadersBuilder.add(entry.getKey(), entry.getValue()); } // OkHttp automatically add gzip header so we do not need to deal with it Headers okHttpHeaders = okHttpHeadersBuilder.build(); okHttpRequestBuilder.headers(okHttpHeaders); // Set Body ParseHttpBody parseBody = parseRequest.getBody(); ParseOkHttpRequestBody okHttpRequestBody = null; if (parseBody instanceof ParseByteArrayHttpBody) { okHttpRequestBody = new ParseOkHttpRequestBody(parseBody); } switch (method) { case PUT: okHttpRequestBuilder.put(okHttpRequestBody); break; case POST: okHttpRequestBuilder.post(okHttpRequestBody); break; } return okHttpRequestBuilder.build(); } private ParseHttpRequest getParseHttpRequest(Request okHttpRequest) { ParseHttpRequest.Builder parseRequestBuilder = new ParseHttpRequest.Builder(); // Set method switch (okHttpRequest.method()) { case OKHTTP_GET: parseRequestBuilder.setMethod(ParseHttpRequest.Method.GET); break; case OKHTTP_DELETE: parseRequestBuilder.setMethod(ParseHttpRequest.Method.DELETE); break; case OKHTTP_POST: parseRequestBuilder.setMethod(ParseHttpRequest.Method.POST); break; case OKHTTP_PUT: parseRequestBuilder.setMethod(ParseHttpRequest.Method.PUT); break; default: // This should never happen throw new IllegalArgumentException("Invalid http method " + okHttpRequest.method()); } // Set url parseRequestBuilder.setUrl(okHttpRequest.urlString()); // Set Header for (Map.Entry<String, List<String>> entry : okHttpRequest.headers().toMultimap().entrySet()) { parseRequestBuilder.addHeader(entry.getKey(), entry.getValue().get(0)); } // Set Body ParseOkHttpRequestBody okHttpBody = (ParseOkHttpRequestBody) okHttpRequest.body(); if (okHttpBody != null) { parseRequestBuilder.setBody(okHttpBody.getParseHttpBody()); } return parseRequestBuilder.build(); } /** * For OKHttpClient, since it does not expose any interface for us to check the raw response * stream, we have to use OKHttp networkInterceptors. Instead of using our own interceptor list, * we use OKHttp inner interceptor list. * @param parseNetworkInterceptor */ @Override /* package */ void addExternalInterceptor(final ParseNetworkInterceptor parseNetworkInterceptor) { okHttpClient.networkInterceptors().add(new Interceptor() { @Override public Response intercept(final Chain okHttpChain) throws IOException { Request okHttpRequest = okHttpChain.request(); // Transfer OkHttpRequest to ParseHttpRequest final ParseHttpRequest parseRequest = getParseHttpRequest(okHttpRequest); // Capture OkHttpResponse final Capture<Response> okHttpResponseCapture = new Capture<>(); final ParseHttpResponse parseResponse = parseNetworkInterceptor .intercept(new ParseNetworkInterceptor.Chain() { @Override public ParseHttpRequest getRequest() { return parseRequest; } @Override public ParseHttpResponse proceed(ParseHttpRequest parseRequest) throws IOException { // Use OKHttpClient to send request Request okHttpRequest = ParseOkHttpClient.this.getRequest(parseRequest); Response okHttpResponse = okHttpChain.proceed(okHttpRequest); okHttpResponseCapture.set(okHttpResponse); return getResponse(okHttpResponse); } }); final Response okHttpResponse = okHttpResponseCapture.get(); // Ideally we should build newOkHttpResponse only based on parseResponse, however // ParseHttpResponse does not have all the info we need to build the newOkHttpResponse, so // we rely on the okHttpResponse to generate the builder and change the necessary info // inside Response.Builder newOkHttpResponseBuilder = okHttpResponse.newBuilder(); // Set status newOkHttpResponseBuilder.code(parseResponse.getStatusCode()) .message(parseResponse.getReasonPhrase()); // Set headers if (parseResponse.getAllHeaders() != null) { for (Map.Entry<String, String> entry : parseResponse.getAllHeaders().entrySet()) { newOkHttpResponseBuilder.header(entry.getKey(), entry.getValue()); } } // Set body newOkHttpResponseBuilder.body(new ResponseBody() { @Override public MediaType contentType() { if (parseResponse.getContentType() == null) { return null; } return MediaType.parse(parseResponse.getContentType()); } @Override public long contentLength() throws IOException { return parseResponse.getTotalSize(); } @Override public BufferedSource source() throws IOException { // We need to use the proxy stream from interceptor to replace the origin network // stream, so when the stream is read by Parse, the network stream is proxyed in the // interceptor. if (parseResponse.getContent() == null) { return null; } return Okio.buffer(Okio.source(parseResponse.getContent())); } }); return newOkHttpResponseBuilder.build(); } }); } private static class ParseOkHttpRequestBody extends RequestBody { private ParseHttpBody parseBody; public ParseOkHttpRequestBody(ParseHttpBody parseBody) { this.parseBody = parseBody; } @Override public long contentLength() throws IOException { return parseBody.getContentLength(); } @Override public MediaType contentType() { String contentType = parseBody.getContentType(); return contentType == null ? null : MediaType.parse(parseBody.getContentType()); } @Override public void writeTo(BufferedSink bufferedSink) throws IOException { parseBody.writeTo(bufferedSink.outputStream()); } public ParseHttpBody getParseHttpBody() { return parseBody; } } }