(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;
|
||||
|
||||
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.ResponseInfo;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Protocol;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.Buffer;
|
||||
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
|
||||
private static final long DEFAULT_MAX_BODY_BYTES = 100 * 1024;
|
||||
@@ -32,6 +46,16 @@ public class FlipperOkhttpInterceptor implements Interceptor {
|
||||
|
||||
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() {
|
||||
this.plugin = null;
|
||||
}
|
||||
@@ -46,14 +70,41 @@ public class FlipperOkhttpInterceptor implements Interceptor {
|
||||
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
|
||||
public Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
String identifier = UUID.randomUUID().toString();
|
||||
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();
|
||||
ResponseInfo responseInfo = convertResponse(response, body, identifier);
|
||||
responseInfo.isMock = mockResponse != null;
|
||||
plugin.reportResponse(responseInfo);
|
||||
return response;
|
||||
}
|
||||
@@ -104,4 +155,96 @@ public class FlipperOkhttpInterceptor implements Interceptor {
|
||||
}
|
||||
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 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
|
||||
// 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 =
|
||||
new OkHttpClient.Builder()
|
||||
.addNetworkInterceptor(interceptor)
|
||||
.addInterceptor(interceptor)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(10, TimeUnit.MINUTES)
|
||||
|
||||
Reference in New Issue
Block a user