/* * Copyright (c) 2014-present, Facebook, Inc. * 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.facebook.stetho.inspector.protocol.module; import java.io.IOException; import java.util.List; import android.content.Context; import com.facebook.stetho.common.Util; import com.facebook.stetho.inspector.jsonrpc.JsonRpcException; import com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer; import com.facebook.stetho.inspector.jsonrpc.JsonRpcResult; import com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError; import com.facebook.stetho.inspector.network.AsyncPrettyPrinterInitializer; import com.facebook.stetho.inspector.network.NetworkPeerManager; import com.facebook.stetho.inspector.network.ResponseBodyData; import com.facebook.stetho.inspector.network.ResponseBodyFileManager; import com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain; import com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod; import com.facebook.stetho.json.annotation.JsonProperty; import com.facebook.stetho.json.annotation.JsonValue; import org.json.JSONException; import org.json.JSONObject; public class Network implements ChromeDevtoolsDomain { private final NetworkPeerManager mNetworkPeerManager; private final ResponseBodyFileManager mResponseBodyFileManager; public Network(Context context) { mNetworkPeerManager = NetworkPeerManager.getOrCreateInstance(context); mResponseBodyFileManager = mNetworkPeerManager.getResponseBodyFileManager(); } @ChromeDevtoolsMethod public void enable(JsonRpcPeer peer, JSONObject params) { mNetworkPeerManager.addPeer(peer); } @ChromeDevtoolsMethod public void disable(JsonRpcPeer peer, JSONObject params) { mNetworkPeerManager.removePeer(peer); } @ChromeDevtoolsMethod public void setUserAgentOverride(JsonRpcPeer peer, JSONObject params) { // Not implemented... } @ChromeDevtoolsMethod public JsonRpcResult getResponseBody(JsonRpcPeer peer, JSONObject params) throws JsonRpcException { try { String requestId = params.getString("requestId"); return readResponseBody(requestId); } catch (IOException e) { throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.INTERNAL_ERROR, e.toString(), null /* data */)); } catch (JSONException e) { throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.INTERNAL_ERROR, e.toString(), null /* data */)); } } private GetResponseBodyResponse readResponseBody(String requestId) throws IOException, JsonRpcException { GetResponseBodyResponse response = new GetResponseBodyResponse(); ResponseBodyData bodyData; try { bodyData = mResponseBodyFileManager.readFile(requestId); } catch (OutOfMemoryError e) { throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.INTERNAL_ERROR, e.toString(), null /* data */)); } response.body = bodyData.data; response.base64Encoded = bodyData.base64Encoded; return response; } /** * Method that allows callers to provide an {@link AsyncPrettyPrinterInitializer} that is * responsible for registering all * {@link com.facebook.stetho.inspector.network.AsyncPrettyPrinter}. * Note that AsyncPrettyPrinterInitializer cannot be null and can only be set once. * @param initializer */ public void setPrettyPrinterInitializer(AsyncPrettyPrinterInitializer initializer) { Util.throwIfNull(initializer); mNetworkPeerManager.setPrettyPrinterInitializer(initializer); } private static class GetResponseBodyResponse implements JsonRpcResult { @JsonProperty(required = true) public String body; @JsonProperty(required = true) public boolean base64Encoded; } public static class RequestWillBeSentParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public String frameId; @JsonProperty(required = true) public String loaderId; @JsonProperty(required = true) public String documentURL; @JsonProperty(required = true) public Request request; @JsonProperty(required = true) public double timestamp; @JsonProperty(required = true) public Initiator initiator; @JsonProperty public Response redirectResponse; @JsonProperty public Page.ResourceType type; } public static class ResponseReceivedParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public String frameId; @JsonProperty(required = true) public String loaderId; @JsonProperty(required = true) public double timestamp; @JsonProperty(required = true) public Page.ResourceType type; @JsonProperty(required = true) public Response response; } public static class LoadingFinishedParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public double timestamp; } public static class LoadingFailedParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public double timestamp; @JsonProperty(required = true) public String errorText; // Chrome introduced this undocumented new addition that, if not sent, will cause the row // to be removed from the UI and raise a JavaScript exception in the console. This is // clearly an upstream bug that needs to be fixed, though we can work around it by // providing this new undocumented field. @JsonProperty public Page.ResourceType type; } public static class DataReceivedParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public double timestamp; @JsonProperty(required = true) public int dataLength; @JsonProperty(required = true) public int encodedDataLength; } public static class Request { @JsonProperty(required = true) public String url; @JsonProperty(required = true) public String method; @JsonProperty(required = true) public JSONObject headers; @JsonProperty public String postData; } public static class Initiator { @JsonProperty(required = true) public InitiatorType type; @JsonProperty public List<Console.CallFrame> stackTrace; } public enum InitiatorType { PARSER("parser"), SCRIPT("script"), OTHER("other"); private final String mProtocolValue; private InitiatorType(String protocolValue) { mProtocolValue = protocolValue; } @JsonValue public String getProtocolValue() { return mProtocolValue; } } public static class Response { @JsonProperty(required = true) public String url; @JsonProperty(required = true) public int status; @JsonProperty(required = true) public String statusText; @JsonProperty(required = true) public JSONObject headers; @JsonProperty public String headersText; @JsonProperty(required = true) public String mimeType; @JsonProperty public JSONObject requestHeaders; @JsonProperty public String requestHeadersTest; @JsonProperty(required = true) public boolean connectionReused; @JsonProperty(required = true) public int connectionId; @JsonProperty(required = true) public Boolean fromDiskCache; @JsonProperty public ResourceTiming timing; } public static class ResourceTiming { @JsonProperty(required = true) public double requestTime; @JsonProperty(required = true) public double proxyStart; @JsonProperty(required = true) public double proxyEnd; @JsonProperty(required = true) public double dnsStart; @JsonProperty(required = true) public double dnsEnd; @JsonProperty(required = true) public double connectionStart; @JsonProperty(required = true) public double connectionEnd; @JsonProperty(required = true) public double sslStart; @JsonProperty(required = true) public double sslEnd; @JsonProperty(required = true) public double sendStart; @JsonProperty(required = true) public double sendEnd; @JsonProperty(required = true) public double receivedHeadersEnd; } public static class WebSocketCreatedParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public String url; } public static class WebSocketClosedParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public double timestamp; } public static class WebSocketWillSendHandshakeRequestParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public double timestamp; @JsonProperty(required = true) public double wallTime; @JsonProperty(required = true) public WebSocketRequest request; } public static class WebSocketRequest { @JsonProperty(required = true) public JSONObject headers; } public static class WebSocketHandshakeResponseReceivedParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public double timestamp; @JsonProperty(required = true) public WebSocketResponse response; } public static class WebSocketResponse { @JsonProperty(required = true) public int status; @JsonProperty(required = true) public String statusText; @JsonProperty(required = true) public JSONObject headers; @JsonProperty public String headersText; @JsonProperty public JSONObject requestHeaders; @JsonProperty public String requestHeadersText; } public static class WebSocketFrameReceivedParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public double timestamp; @JsonProperty(required = true) public WebSocketFrame response; } public static class WebSocketFrameSentParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public double timestamp; @JsonProperty(required = true) public WebSocketFrame response; } public static class WebSocketFrame { @JsonProperty(required = true) public int opcode; @JsonProperty(required = true) public boolean mask; @JsonProperty(required = true) public String payloadData; } public static class WebSocketFrameErrorParams { @JsonProperty(required = true) public String requestId; @JsonProperty(required = true) public double timestamp; @JsonProperty(required = true) public String errorMessage; } }