(Client) Network Response Mocking Logic for Android Clients (Original PR) (#488)
Summary: Add logic on client side # How it works (from the code) 1. Server side sends request url and method to response data and headers to client side 1.1. This will happen every time server update **any** mock response (add, edit, and remove) 2. Client stores those in map 3. For every network request, 3.1. Check if there is a matching url and method 3.2. If so, create a new response with the data and headers and drop the request 3.3. If not, proceed and send the request and wait for a response `addNetworkInterceptor` is changed to `addInterceptor` to allow short-circuit and proceed without fetching anything. More info can be found at https://square.github.io/okhttp/interceptors/ Note: - This is an original PR. - The content below is from original PR Add network response mocking for Network plugin. See discussion [here](https://github.com/facebook/flipper/issues/475) ## Changelog - Add Network response mocking, currently support Android clients only - Change the Android example app to use `addInterceptor()` instead of `addNetworkInterceptor()` Pull Request resolved: https://github.com/facebook/flipper/pull/488 Test Plan: {F231673798}  - Connect an Android device - Tap on Network plugin - Click on the Mock button - Click on Add Route button, and specify the URL - Edit the mock data in the text area - Optionally, click the Headers tab to edit the headers data - Click close button to close the dialog - Send some network data in your application. You should be able to see the mock data appears in the Network table in those rows highlighted in yellow Reviewed By: passy Differential Revision: D16580291 Pulled By: cekkaewnumchai fbshipit-source-id: fc391f5e7efebc6f51a72b00d16263e009e1fdb0
This commit is contained in:
committed by
Facebook GitHub Bot
parent
4ea1497387
commit
d27e45d7bb
@@ -7,23 +7,37 @@
|
|||||||
|
|
||||||
package com.facebook.flipper.plugins.network;
|
package com.facebook.flipper.plugins.network;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Pair;
|
||||||
|
import com.facebook.flipper.core.FlipperArray;
|
||||||
|
import com.facebook.flipper.core.FlipperConnection;
|
||||||
|
import com.facebook.flipper.core.FlipperObject;
|
||||||
|
import com.facebook.flipper.core.FlipperReceiver;
|
||||||
|
import com.facebook.flipper.core.FlipperResponder;
|
||||||
|
import com.facebook.flipper.plugins.common.BufferingFlipperPlugin;
|
||||||
import com.facebook.flipper.plugins.network.NetworkReporter.RequestInfo;
|
import com.facebook.flipper.plugins.network.NetworkReporter.RequestInfo;
|
||||||
import com.facebook.flipper.plugins.network.NetworkReporter.ResponseInfo;
|
import com.facebook.flipper.plugins.network.NetworkReporter.ResponseInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import okhttp3.Headers;
|
import okhttp3.Headers;
|
||||||
import okhttp3.Interceptor;
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.Protocol;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import okio.Buffer;
|
import okio.Buffer;
|
||||||
import okio.BufferedSource;
|
import okio.BufferedSource;
|
||||||
|
|
||||||
public class FlipperOkhttpInterceptor implements Interceptor {
|
public class FlipperOkhttpInterceptor
|
||||||
|
implements Interceptor, BufferingFlipperPlugin.MockResponseConnectionListener {
|
||||||
|
|
||||||
// By default, limit body size (request or response) reporting to 100KB to avoid OOM
|
// By default, limit body size (request or response) reporting to 100KB to avoid OOM
|
||||||
private static final long DEFAULT_MAX_BODY_BYTES = 100 * 1024;
|
private static final long DEFAULT_MAX_BODY_BYTES = 100 * 1024;
|
||||||
@@ -32,6 +46,16 @@ public class FlipperOkhttpInterceptor implements Interceptor {
|
|||||||
|
|
||||||
public @Nullable NetworkFlipperPlugin plugin;
|
public @Nullable NetworkFlipperPlugin plugin;
|
||||||
|
|
||||||
|
private static class PartialRequestInfo extends Pair<String, String> {
|
||||||
|
PartialRequestInfo(String url, String method) {
|
||||||
|
super(url, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pair of request url and method
|
||||||
|
private Map<PartialRequestInfo, ResponseInfo> mMockResponseMap = new HashMap<>(0);
|
||||||
|
private boolean mIsMockResponseSupported = false;
|
||||||
|
|
||||||
public FlipperOkhttpInterceptor() {
|
public FlipperOkhttpInterceptor() {
|
||||||
this.plugin = null;
|
this.plugin = null;
|
||||||
}
|
}
|
||||||
@@ -46,14 +70,41 @@ public class FlipperOkhttpInterceptor implements Interceptor {
|
|||||||
this.maxBodyBytes = maxBodyBytes;
|
this.maxBodyBytes = maxBodyBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To support mock response, addIntercept must be used (instead of addNetworkIntercept) to allow
|
||||||
|
* short circuit: https://square.github.io/okhttp/interceptors/ *
|
||||||
|
*/
|
||||||
|
public FlipperOkhttpInterceptor(NetworkFlipperPlugin plugin, boolean isMockResponseSupported) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
mIsMockResponseSupported = isMockResponseSupported;
|
||||||
|
if (isMockResponseSupported) {
|
||||||
|
this.plugin.setConnectionListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlipperOkhttpInterceptor(
|
||||||
|
NetworkFlipperPlugin plugin, long maxBodyBytes, boolean isMockResponseSupported) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.maxBodyBytes = maxBodyBytes;
|
||||||
|
mIsMockResponseSupported = isMockResponseSupported;
|
||||||
|
if (isMockResponseSupported) {
|
||||||
|
this.plugin.setConnectionListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response intercept(Interceptor.Chain chain) throws IOException {
|
public Response intercept(Interceptor.Chain chain) throws IOException {
|
||||||
Request request = chain.request();
|
Request request = chain.request();
|
||||||
String identifier = UUID.randomUUID().toString();
|
String identifier = UUID.randomUUID().toString();
|
||||||
plugin.reportRequest(convertRequest(request, identifier));
|
plugin.reportRequest(convertRequest(request, identifier));
|
||||||
Response response = chain.proceed(request);
|
|
||||||
|
// Check if there is a mock response
|
||||||
|
Response mockResponse = mIsMockResponseSupported ? getMockResponse(request) : null;
|
||||||
|
Response response = mockResponse != null ? mockResponse : chain.proceed(request);
|
||||||
|
|
||||||
ResponseBody body = response.body();
|
ResponseBody body = response.body();
|
||||||
ResponseInfo responseInfo = convertResponse(response, body, identifier);
|
ResponseInfo responseInfo = convertResponse(response, body, identifier);
|
||||||
|
responseInfo.isMock = mockResponse != null;
|
||||||
plugin.reportResponse(responseInfo);
|
plugin.reportResponse(responseInfo);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -104,4 +155,96 @@ public class FlipperOkhttpInterceptor implements Interceptor {
|
|||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerMockResponse(PartialRequestInfo partialRequest, ResponseInfo response) {
|
||||||
|
if (!mMockResponseMap.containsKey(partialRequest)) {
|
||||||
|
mMockResponseMap.put(partialRequest, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Response getMockResponse(Request request) {
|
||||||
|
final String url = request.url().toString();
|
||||||
|
final String method = request.method();
|
||||||
|
final PartialRequestInfo partialRequest = new PartialRequestInfo(url, method);
|
||||||
|
|
||||||
|
if (!mMockResponseMap.containsKey(partialRequest)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ResponseInfo mockResponse = mMockResponseMap.get(partialRequest);
|
||||||
|
if (mockResponse == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Response.Builder builder = new Response.Builder();
|
||||||
|
builder
|
||||||
|
.request(request)
|
||||||
|
.protocol(Protocol.HTTP_1_1)
|
||||||
|
.code(mockResponse.statusCode)
|
||||||
|
.message(mockResponse.statusReason)
|
||||||
|
.receivedResponseAtMillis(System.currentTimeMillis())
|
||||||
|
.body(ResponseBody.create(MediaType.parse("application/text"), mockResponse.body));
|
||||||
|
|
||||||
|
if (mockResponse.headers != null && !mockResponse.headers.isEmpty()) {
|
||||||
|
for (final NetworkReporter.Header header : mockResponse.headers) {
|
||||||
|
if (!TextUtils.isEmpty(header.name) && !TextUtils.isEmpty(header.value)) {
|
||||||
|
builder.header(header.name, header.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ResponseInfo convertFlipperObjectRouteToResponseInfo(FlipperObject route) {
|
||||||
|
final String data = route.getString("data");
|
||||||
|
final String requestUrl = route.getString("requestUrl");
|
||||||
|
final String method = route.getString("method");
|
||||||
|
FlipperArray headersArray = route.getArray("headers");
|
||||||
|
if (TextUtils.isEmpty(requestUrl) || TextUtils.isEmpty(method)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ResponseInfo mockResponse = new ResponseInfo();
|
||||||
|
mockResponse.body = data.getBytes();
|
||||||
|
mockResponse.statusCode = HttpURLConnection.HTTP_OK;
|
||||||
|
mockResponse.statusReason = "OK";
|
||||||
|
if (headersArray != null) {
|
||||||
|
final List<NetworkReporter.Header> headers = new ArrayList<>();
|
||||||
|
for (int j = 0; j < headersArray.length(); j++) {
|
||||||
|
final FlipperObject header = headersArray.getObject(j);
|
||||||
|
headers.add(new NetworkReporter.Header(header.getString("key"), header.getString("value")));
|
||||||
|
}
|
||||||
|
mockResponse.headers = headers;
|
||||||
|
}
|
||||||
|
return mockResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnect(FlipperConnection connection) {
|
||||||
|
connection.receive(
|
||||||
|
"mockResponses",
|
||||||
|
new FlipperReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(FlipperObject params, FlipperResponder responder) throws Exception {
|
||||||
|
FlipperArray array = params.getArray("routes");
|
||||||
|
mMockResponseMap.clear();
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
final FlipperObject route = array.getObject(i);
|
||||||
|
final String requestUrl = route.getString("requestUrl");
|
||||||
|
final String method = route.getString("method");
|
||||||
|
ResponseInfo mockResponse = convertFlipperObjectRouteToResponseInfo(route);
|
||||||
|
if (mockResponse != null) {
|
||||||
|
registerMockResponse(new PartialRequestInfo(requestUrl, method), mockResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responder.success();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisconnect() {
|
||||||
|
mMockResponseMap.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public final class FlipperInitializer {
|
|||||||
final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
|
final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
|
||||||
|
|
||||||
final NetworkFlipperPlugin networkPlugin = new NetworkFlipperPlugin();
|
final NetworkFlipperPlugin networkPlugin = new NetworkFlipperPlugin();
|
||||||
final FlipperOkhttpInterceptor interceptor = new FlipperOkhttpInterceptor(networkPlugin);
|
final FlipperOkhttpInterceptor interceptor = new FlipperOkhttpInterceptor(networkPlugin, true);
|
||||||
|
|
||||||
// Normally, you would want to make this dependent on a BuildConfig flag, but
|
// Normally, you would want to make this dependent on a BuildConfig flag, but
|
||||||
// for this demo application we can safely assume that you always want to debug.
|
// for this demo application we can safely assume that you always want to debug.
|
||||||
@@ -60,7 +60,7 @@ public final class FlipperInitializer {
|
|||||||
|
|
||||||
final OkHttpClient okHttpClient =
|
final OkHttpClient okHttpClient =
|
||||||
new OkHttpClient.Builder()
|
new OkHttpClient.Builder()
|
||||||
.addNetworkInterceptor(interceptor)
|
.addInterceptor(interceptor)
|
||||||
.connectTimeout(60, TimeUnit.SECONDS)
|
.connectTimeout(60, TimeUnit.SECONDS)
|
||||||
.readTimeout(60, TimeUnit.SECONDS)
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
.writeTimeout(10, TimeUnit.MINUTES)
|
.writeTimeout(10, TimeUnit.MINUTES)
|
||||||
|
|||||||
Reference in New Issue
Block a user