Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import android.os.Handler;
import androidx.annotation.VisibleForTesting;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.Executor;

/** Defines callback methods for the HttpRequestManager. */
interface HttpRequestCallback {
void onComplete(String result);

void onError(Exception error);
}

/**
* Works around on Android WebView postUrl method to accept headers.
*
* <p>Android WebView does not provide a post request method that accepts headers. Only method that
* is provided is {@link android.webkit.WebView#postUrl(String, byte[])} and it accepts only URL and
* HTTP body. CustomHttpPostRequest is implemented to provide this feature since adding a header to
* post requests is a feature that is likely to be wanted.
*
* <p>In the implementation, {@link HttpURLConnection} is used to create a post request with the
* HTTP headers and the HTTP body.
*/
public class HttpRequestManager {
private final Executor executor;
private final Handler resultHandler;

HttpRequestManager(Executor executor, Handler resultHandler) {
this.executor = executor;
this.resultHandler = resultHandler;
}

/**
* Executes the given HTTP request in a background thread. See <a
* href="https://developer.android.com/guide/background/threading">https://developer.android.com/guide/background/threading</a>.
*
* @param request {@link WebViewRequest} to execute.
* @param callback methods to invoke after the HTTP request has completed.
*/
public void requestAsync(final WebViewRequest request, final HttpRequestCallback callback) {
executor.execute(
new Runnable() {
@Override
public void run() {
try {
String responseResult = request(request);
notifyComplete(responseResult, callback);
} catch (IOException e) {
notifyError(e, callback);
}
}
});
}

/**
* Executes the given HTTP request synchronously.
*
* @param request {@link WebViewRequest} to execute.
* @return The response body as a String.
*/
public String request(WebViewRequest request) throws IOException {
URL url = URLFactory.create(request.getUri());
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
try {
// Basic request configuration
httpURLConnection.setConnectTimeout(5000);
httpURLConnection.setRequestMethod(request.getMethod().getValue().toUpperCase());

// Set HTTP headers
for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) {
httpURLConnection.setRequestProperty(entry.getKey(), entry.getValue());
}

// Set HTTP body
if (request.getBody() != null && request.getBody().length > 0) {
// Used to enable streaming of a HTTP request body without internal buffering,
// when the content length is known in advance. It improves the performance
// because otherwise HTTPUrlConnection will be forced to buffer the complete
// request body in memory before it is transmitted, wasting (and possibly exhausting)
// heap and increasing latency.
httpURLConnection.setFixedLengthStreamingMode(request.getBody().length);

httpURLConnection.setDoOutput(true);
OutputStream os = BufferedOutputStreamFactory.create(httpURLConnection.getOutputStream());
os.write(request.getBody());
os.flush();
os.close();
}

// Collect and return response body
String line = "";
StringBuilder contentBuilder = new StringBuilder();
BufferedReader rd =
BufferedReaderFactory.create(
InputStreamReaderFactory.create(httpURLConnection.getInputStream()));
while ((line = rd.readLine()) != null) {
contentBuilder.append(line);
}
return contentBuilder.toString();
} finally {
httpURLConnection.disconnect();
}
}

private void notifyComplete(final String responseResult, final HttpRequestCallback callback) {
resultHandler.post(
new Runnable() {
@Override
public void run() {
callback.onComplete(responseResult);
}
});
}

private void notifyError(final Exception error, final HttpRequestCallback callback) {
resultHandler.post(
new Runnable() {
@Override
public void run() {
callback.onError(error);
}
});
}
/** Factory class for creating a {@link URL} */
static class URLFactory {
/**
* Creates a {@link URL}.
*
* <p><strong>Important:</strong> This method is visible for testing purposes only and should
* never be called from outside this class.
*
* @param url to create the instance for.
* @return The new {@link URL} object.
*/
@VisibleForTesting
public static URL create(String url) throws MalformedURLException {
return new URL(url);
}
}
/** Factory class for creating a {@link BufferedOutputStream} */
static class BufferedOutputStreamFactory {
/**
* Creates a {@link BufferedOutputStream}.
*
* <p><strong>Important:</strong> This method is visible for testing purposes only and should
* never be called from outside this class.
*
* @param stream to create the instance for.
* @return The new {@link BufferedOutputStream} object.
*/
@VisibleForTesting
public static BufferedOutputStream create(OutputStream stream) {
return new BufferedOutputStream(stream);
}
}
/** Factory class for creating a {@link BufferedReader} */
static class BufferedReaderFactory {
/**
* Creates a {@link BufferedReader}.
*
* <p><strong>Important:</strong> This method is visible for testing purposes only and should
* never be called from outside this class.
*
* @param stream to create the instance for.
* @return The new {@link BufferedReader} object.
*/
@VisibleForTesting
public static BufferedReader create(InputStreamReader stream) {
return new BufferedReader(stream);
}
}
/** Factory class for creating a {@link InputStreamReader} */
static class InputStreamReaderFactory {
/**
* Creates a {@link InputStreamReader}.
*
* <p><strong>Important:</strong> This method is visible for testing purposes only and should
* never be called from outside this class.
*
* @param stream to create the instance for.
* @return The new {@link InputStreamReader} object.
*/
@VisibleForTesting
public static InputStreamReader create(InputStream stream) {
return new InputStreamReader(stream);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import java.util.Collections;
import java.util.Map;

/**
* Defines the supported HTTP methods for loading a page in the {@link android.webkit.WebView} and
* the {@link HttpRequestManager}.
*/
enum WebViewLoadMethod {
GET("get"),

POST("post");

private final String value;

WebViewLoadMethod(String value) {
this.value = value;
}

/** Converts to WebViewLoadMethod to String format. */
public String serialize() {
return getValue();
}

/** Returns the enum value. */
public String getValue() {
return value;
}

/** Converts to String to WebViewLoadMethod format. */
public static WebViewLoadMethod deserialize(String value) {
for (WebViewLoadMethod webViewLoadMethod : WebViewLoadMethod.values()) {
if (webViewLoadMethod.value.equals(value)) {
return webViewLoadMethod;
}
}
throw new IllegalArgumentException("No enum value found for '" + value + "'.");
}
}

/**
* Creates a HTTP request object.
*
* <p>Defines the parameters that can be used to load a page in the {@link android.webkit.WebView}
* and the {@link HttpRequestManager}.
*/
public class WebViewRequest {
private final String uri;
private final WebViewLoadMethod method;
private final Map<String, String> headers;
private final byte[] body;

WebViewRequest(String uri, WebViewLoadMethod method, Map<String, String> headers, byte[] body) {
this.uri = uri;
this.method = method;
this.headers = headers == null ? Collections.emptyMap() : headers;
this.body = body;
}

/**
* Deserializes the request and the url to WebViewRequest instance.
*
* @param requestObject is the {@link io.flutter.plugin.common.MethodCall#arguments} to build
* WebViewRequest instance.
*/
@SuppressWarnings("unchecked")
static WebViewRequest fromMap(Map<String, Object> requestObject) {
String uri = (String) requestObject.get("uri");
if (uri == null) {
return null;
}

Map<String, String> headers = (Map<String, String>) requestObject.get("headers");

WebViewLoadMethod invokedMethod =
WebViewLoadMethod.deserialize((String) requestObject.get("method"));

byte[] httpBody = (byte[]) requestObject.get("body");

return new WebViewRequest(uri, invokedMethod, headers, httpBody);
}

/** Returns HTTP method in WebViewLoadMethod format. */
public WebViewLoadMethod getMethod() {
return method;
}

/** Returns base url. */
public String getUri() {
return uri;
}

/** Returns HTTP headers. */
public Map<String, String> getHeaders() {
return headers;
}

/** Returns HTTP body. */
public byte[] getBody() {
return body;
}
}
Loading