in ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java [264:557]
public void sendRequestInternal(
String method,
String url,
final int requestId,
ReadableArray headers,
ReadableMap data,
final String responseType,
final boolean useIncrementalUpdates,
int timeout,
boolean withCredentials) {
final RCTDeviceEventEmitter eventEmitter = getEventEmitter("sendRequestInternal");
try {
Uri uri = Uri.parse(url);
// Check if a handler is registered
for (UriHandler handler : mUriHandlers) {
if (handler.supports(uri, responseType)) {
WritableMap res = handler.fetch(uri);
ResponseUtil.onDataReceived(eventEmitter, requestId, res);
ResponseUtil.onRequestSuccess(eventEmitter, requestId);
return;
}
}
} catch (IOException e) {
ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), e);
return;
}
Request.Builder requestBuilder;
try {
requestBuilder = new Request.Builder().url(url);
} catch (Exception e) {
ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), null);
return;
}
if (requestId != 0) {
requestBuilder.tag(requestId);
}
OkHttpClient.Builder clientBuilder = mClient.newBuilder();
applyCustomBuilder(clientBuilder);
if (!withCredentials) {
clientBuilder.cookieJar(CookieJar.NO_COOKIES);
}
// If JS is listening for progress updates, install a ProgressResponseBody that intercepts the
// response and counts bytes received.
if (useIncrementalUpdates) {
clientBuilder.addNetworkInterceptor(
new Interceptor() {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
ProgressResponseBody responseBody =
new ProgressResponseBody(
originalResponse.body(),
new ProgressListener() {
long last = System.nanoTime();
@Override
public void onProgress(
long bytesWritten, long contentLength, boolean done) {
long now = System.nanoTime();
if (!done && !shouldDispatch(now, last)) {
return;
}
if (responseType.equals("text")) {
// For 'text' responses we continuously send response data with progress
// info to
// JS below, so no need to do anything here.
return;
}
ResponseUtil.onDataReceivedProgress(
eventEmitter, requestId, bytesWritten, contentLength);
last = now;
}
});
return originalResponse.newBuilder().body(responseBody).build();
}
});
}
// If the current timeout does not equal the passed in timeout, we need to clone the existing
// client and set the timeout explicitly on the clone. This is cheap as everything else is
// shared under the hood.
// See https://github.com/square/okhttp/wiki/Recipes#per-call-configuration for more information
if (timeout != mClient.connectTimeoutMillis()) {
clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
}
OkHttpClient client = clientBuilder.build();
Headers requestHeaders = extractHeaders(headers, data);
if (requestHeaders == null) {
ResponseUtil.onRequestError(eventEmitter, requestId, "Unrecognized headers format", null);
return;
}
String contentType = requestHeaders.get(CONTENT_TYPE_HEADER_NAME);
String contentEncoding = requestHeaders.get(CONTENT_ENCODING_HEADER_NAME);
requestBuilder.headers(requestHeaders);
// Check if a handler is registered
RequestBodyHandler handler = null;
if (data != null) {
for (RequestBodyHandler curHandler : mRequestBodyHandlers) {
if (curHandler.supports(data)) {
handler = curHandler;
break;
}
}
}
RequestBody requestBody;
if (data == null
|| method.toLowerCase(Locale.ROOT).equals("get")
|| method.toLowerCase(Locale.ROOT).equals("head")) {
requestBody = RequestBodyUtil.getEmptyBody(method);
} else if (handler != null) {
requestBody = handler.toRequestBody(data, contentType);
} else if (data.hasKey(REQUEST_BODY_KEY_STRING)) {
if (contentType == null) {
ResponseUtil.onRequestError(
eventEmitter, requestId, "Payload is set but no content-type header specified", null);
return;
}
String body = data.getString(REQUEST_BODY_KEY_STRING);
MediaType contentMediaType = MediaType.parse(contentType);
if (RequestBodyUtil.isGzipEncoding(contentEncoding)) {
requestBody = RequestBodyUtil.createGzip(contentMediaType, body);
if (requestBody == null) {
ResponseUtil.onRequestError(eventEmitter, requestId, "Failed to gzip request body", null);
return;
}
} else {
// Use getBytes() to convert the body into a byte[], preventing okhttp from
// appending the character set to the Content-Type header when otherwise unspecified
// https://github.com/facebook/react-native/issues/8237
Charset charset =
contentMediaType == null
? StandardCharsets.UTF_8
: contentMediaType.charset(StandardCharsets.UTF_8);
requestBody = RequestBody.create(contentMediaType, body.getBytes(charset));
}
} else if (data.hasKey(REQUEST_BODY_KEY_BASE64)) {
if (contentType == null) {
ResponseUtil.onRequestError(
eventEmitter, requestId, "Payload is set but no content-type header specified", null);
return;
}
String base64String = data.getString(REQUEST_BODY_KEY_BASE64);
MediaType contentMediaType = MediaType.parse(contentType);
requestBody = RequestBody.create(contentMediaType, ByteString.decodeBase64(base64String));
} else if (data.hasKey(REQUEST_BODY_KEY_URI)) {
if (contentType == null) {
ResponseUtil.onRequestError(
eventEmitter, requestId, "Payload is set but no content-type header specified", null);
return;
}
String uri = data.getString(REQUEST_BODY_KEY_URI);
InputStream fileInputStream =
RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri);
if (fileInputStream == null) {
ResponseUtil.onRequestError(
eventEmitter, requestId, "Could not retrieve file for uri " + uri, null);
return;
}
requestBody = RequestBodyUtil.create(MediaType.parse(contentType), fileInputStream);
} else if (data.hasKey(REQUEST_BODY_KEY_FORMDATA)) {
if (contentType == null) {
contentType = "multipart/form-data";
}
ReadableArray parts = data.getArray(REQUEST_BODY_KEY_FORMDATA);
MultipartBody.Builder multipartBuilder =
constructMultipartBody(parts, contentType, requestId);
if (multipartBuilder == null) {
return;
}
requestBody = multipartBuilder.build();
} else {
// Nothing in data payload, at least nothing we could understand anyway.
requestBody = RequestBodyUtil.getEmptyBody(method);
}
requestBuilder.method(
method, wrapRequestBodyWithProgressEmitter(requestBody, eventEmitter, requestId));
addRequest(requestId);
client
.newCall(requestBuilder.build())
.enqueue(
new Callback() {
@Override
public void onFailure(Call call, IOException e) {
if (mShuttingDown) {
return;
}
removeRequest(requestId);
String errorMessage =
e.getMessage() != null
? e.getMessage()
: "Error while executing request: " + e.getClass().getSimpleName();
ResponseUtil.onRequestError(eventEmitter, requestId, errorMessage, e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (mShuttingDown) {
return;
}
removeRequest(requestId);
// Before we touch the body send headers to JS
ResponseUtil.onResponseReceived(
eventEmitter,
requestId,
response.code(),
translateHeaders(response.headers()),
response.request().url().toString());
try {
// OkHttp implements something called transparent gzip, which mean that it will
// automatically add the Accept-Encoding gzip header and handle decoding
// internally.
// The issue is that it won't handle decoding if the user provides a
// Accept-Encoding
// header. This is also undesirable considering that iOS does handle the decoding
// even
// when the header is provided. To make sure this works in all cases, handle gzip
// body
// here also. This works fine since OKHttp will remove the Content-Encoding header
// if
// it used transparent gzip.
// See
// https://github.com/square/okhttp/blob/5b37cda9e00626f43acf354df145fd452c3031f1/okhttp/src/main/java/okhttp3/internal/http/BridgeInterceptor.java#L76-L111
ResponseBody responseBody = response.body();
if ("gzip".equalsIgnoreCase(response.header("Content-Encoding"))
&& responseBody != null) {
GzipSource gzipSource = new GzipSource(responseBody.source());
String contentType = response.header("Content-Type");
responseBody =
ResponseBody.create(
contentType != null ? MediaType.parse(contentType) : null,
-1L,
Okio.buffer(gzipSource));
}
// Check if a handler is registered
for (ResponseHandler handler : mResponseHandlers) {
if (handler.supports(responseType)) {
WritableMap res = handler.toResponseData(responseBody);
ResponseUtil.onDataReceived(eventEmitter, requestId, res);
ResponseUtil.onRequestSuccess(eventEmitter, requestId);
return;
}
}
// If JS wants progress updates during the download, and it requested a text
// response,
// periodically send response data updates to JS.
if (useIncrementalUpdates && responseType.equals("text")) {
readWithProgress(eventEmitter, requestId, responseBody);
ResponseUtil.onRequestSuccess(eventEmitter, requestId);
return;
}
// Otherwise send the data in one big chunk, in the format that JS requested.
String responseString = "";
if (responseType.equals("text")) {
try {
responseString = responseBody.string();
} catch (IOException e) {
if (response.request().method().equalsIgnoreCase("HEAD")) {
// The request is an `HEAD` and the body is empty,
// the OkHttp will produce an exception.
// Ignore the exception to not invalidate the request in the
// Javascript layer.
// Introduced to fix issue #7463.
} else {
ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), e);
}
}
} else if (responseType.equals("base64")) {
responseString = Base64.encodeToString(responseBody.bytes(), Base64.NO_WRAP);
}
ResponseUtil.onDataReceived(eventEmitter, requestId, responseString);
ResponseUtil.onRequestSuccess(eventEmitter, requestId);
} catch (IOException e) {
ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), e);
}
}
});
}