/* * 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.network; import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; /** * Interface that callers must invoke in order to supply data to the Network tab in * the WebKit Inspector. For HTTP specific traffic, the following call flow must be met: * * <pre> * requestWillBeSent +---> responseHeadersReceived +---> interpretResponseStream * | | | * | `---> dataSent | * | | * `-----------------------------`--------> httpExchangeFailed * </pre> * * <p>Note that {@link #interpretResponseStream} combined with {@link DefaultResponseHandler} * will automatically invoke {@link #dataReceived}, {@link #responseReadFailed} and * {@link #responseReadFinished}. If you use your own custom {@link ResponseHandler} you * must be sure to invoke these methods manually.</p> * * <p>For arbitrary sockets or explicitly for WebSockets, the following call flow must be met:</p> * * <pre> * webSocketCreated +---> webSocketWillSendHandshakeRequest ----> webSocketHandshakeResponseReceived * | | * | ,----------------------+-----------------+--------, * | v v | * +---------> [ webSocketFrameSent | webSocketFrameReceiver ] ---, | * | ^ ^ | | * | `----------------------+-----------------+ | * | | | * `---------> webSocketClosed <----------------------------------' | * ^ | * `----------------------------------------------------' * </pre> * * <p>Explicitly worth nothing is that regular sockets in an Android app can be treated as * WebSockets for the purpose of arbitrary socket inspection and can skip * {@link #webSocketWillSendHandshakeRequest} and {@link #webSocketHandshakeResponseReceived} * which are only used for the WebSocket-specific HTTP upgrade.</p> */ public interface NetworkEventReporter { /** * Returns true if there is at least one peer listening for network events; false otherwise. * This value is provided as an optimization to avoid expensive work when the WebKit Inspector is * not being used. It is otherwise safe to invoke methods defined in this interface when * the value is false. */ boolean isEnabled(); /** * Indicates that a request is about to be sent, but has not yet been delivered over the wire. * * @param request Request descriptor. */ void requestWillBeSent(InspectorRequest request); /** * Indicates that a response message was just received from the network, but the body * has not yet been read. * * @param response Response descriptor. */ void responseHeadersReceived(InspectorResponse response); /** * Indicates that communication with the server has failed. You are expected to call this for any * exception before you call {@link #interpretResponseStream}. After * {@link #interpretResponseStream} is called we will reporting any * {@link IOException} during reading from the {@link InputStream}. * * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()} * @param errorText Text to report for the error; using {@link IOException#toString()} is * recommended. */ void httpExchangeFailed(String requestId, String errorText); /** * Intercept the stream as given by the underlying HTTP library that contains the body of the * response. In order to have the response show up in inspector (and to have the request be * completed successfully) you need to call this AND read until exhaustion/EOF of the returned * stream. * * <p> * We will internally signal a failure if there is an {@link IOException} received while reading * from the stream. * * <p> * Do not invoke {@link #httpExchangeFailed(String, String)} after calling this method. * * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()} * @param contentType The {@code Content-Type} header value that was specified in * {@link InspectorResponse}. This header is used to determine the appropriate * storage format for the body. For instance, {@code image/*} is necessary to cause * images to appear in the Inspector UI. * @param contentEncoding The {@code Content-Encoding} header value that was specified in * {@link InspectorResponse}. This header is used to determine what type of decompression * is to be applied when delivering the raw response stream to the debugging interface. * If null, no decompression will be used. * @param inputStream Response stream if applicable ("HEAD" for instance does not have a body). * {@code null} otherwise. * @param responseHandler Callback to forward stream events back to the relevant event reporter * methods. Recommend using {@link DefaultResponseHandler} for most callers. * * @return {@link InputStream} that has been intercepted if WebkitInspector is active and enabled * otherwise it will return {@code inputStream} */ @Nullable InputStream interpretResponseStream( String requestId, @Nullable String contentType, @Nullable String contentEncoding, @Nullable InputStream inputStream, ResponseHandler responseHandler); /** * Indicates that there was a failure while reading from response stream. If you use * {@link #interpretResponseStream} with {@link DefaultResponseHandler} (as is recommended), * this method will be invoked automatically for you. * * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()} * @param errorText Text to report for the error; using {@link IOException#toString()} is * recommended. */ void responseReadFailed(String requestId, String errorText); /** * Indicates that the response stream has been fully exhausted and the request is now * complete. If you use {@link #interpretResponseStream} with {@link DefaultResponseHandler} * (as is recommended), this method will be invoked automatically for you. * * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()} */ void responseReadFinished(String requestId); /** * Indicates that raw data was sent over the network. It is permissible to invoke this * method just once after the full size of the request is known. * <p> * Invoking this method is optional and merely provides additional timing metrics and actual * payload sizes to the Inspector UI. * * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()} * @param dataLength Uncompressed data segment length * @param encodedDataLength Compressed data segment length */ void dataSent(String requestId, int dataLength, int encodedDataLength); /** * Indicates that raw data was received from the network. * * @see #dataSent */ void dataReceived(String requestId, int dataLength, int encodedDataLength); /** * Provides unique request id for {@link InspectorRequest#id()}. */ String nextRequestId(); /** * Invoked when a socket is created and implicitly being connected (but not necessarily connected * yet). If a websocket is being used, proceed to {@link #webSocketWillSendHandshakeRequest}. * Otherwise you may proceed next to {@code webSocketFrame*} methods. */ void webSocketCreated(String requestId, String url); /** * Socket has been closed for unknown reasons. Consider first invoking * {@link #webSocketFrameError} even for standard sockets to provide context. */ void webSocketClosed(String requestId); /** * Invoked specifically for websockets to communicate the WebSocket upgrade messages. Not * necessary to call for standard sockets. */ void webSocketWillSendHandshakeRequest(InspectorWebSocketRequest request); /** * Delivers the reply from the peer in response to the WebSocket upgrade request. */ void webSocketHandshakeResponseReceived(InspectorWebSocketResponse response); /** * Send a "websocket" frame from our app to the remote peer. Standard sockets can simply emulate * this by capturing each socket send as a frame of either * {@link InspectorWebSocketFrame#OPCODE_BINARY} or * {@link InspectorWebSocketFrame#OPCODE_TEXT}. Note that binary * payloads are not visualized in Chrome but can be sent by simply assuming the data is UTF-8 * encoded (yes, really: * https://chromium.googlesource.com/chromium/blink/+/master/Source/core/inspector/InspectorResourceAgent.cpp#850). */ void webSocketFrameSent(InspectorWebSocketFrame frame); /** * The receive side of {@link #webSocketFrameSent}. */ void webSocketFrameReceived(InspectorWebSocketFrame frame); /** * Indicates a web socket (or standard socket) error has occurred though this doesn't * explicitly close the socket (see {@link #webSocketClosed}) but it does let the UI * know that it should denote the closure as forceful or as a failure in some way. */ void webSocketFrameError(String requestId, String errorMessage); /** * Represents the request that will be sent over HTTP. Note that for many implementations * of HTTP the request constructed may differ from the request actually sent over the wire. * For instance, additional headers like {@code Host}, {@code User-Agent}, {@code Content-Type}, * etc may not be part of this request but should be injected if necessary. Some stacks offer * inspection of the raw request about to be sent to the server which is preferable. */ interface InspectorRequest extends InspectorRequestCommon { /** * Provide an extra integer to decorate the {@link #friendlyName()}. This shows up next to * it in the WebKit Inspector UI and can be used to indicate things like request priority. */ @Nullable Integer friendlyNameExtra(); String url(); /** * HTTP method ("GET", "POST", "DELETE", etc). */ String method(); /** * Provide the body if part of an entity-enclosing request (like "POST" or "PUT"). May * return null otherwise. */ @Nullable byte[] body() throws IOException; } interface InspectorResponse extends InspectorResponseCommon { String url(); /** * True if the response was furnished on a re-used socket; false otherwise or if unknown. */ boolean connectionReused(); /** * Unique connection identifier representing the socket that was used to furnish the response. */ int connectionId(); /** * True if the response was furnished by disk cache; false otherwise or if unknown. */ boolean fromDiskCache(); } interface InspectorWebSocketRequest extends InspectorRequestCommon { } interface InspectorWebSocketResponse extends InspectorResponseCommon { /** * Optional and redundant set of headers from {@link InspectorWebSocketRequest} that are for * some mysterious reason are included here in the response by Chrome. */ @Nullable InspectorHeaders requestHeaders(); } interface InspectorWebSocketFrame { String requestId(); int OPCODE_CONTINUATION = 0; int OPCODE_TEXT = 1; int OPCODE_BINARY = 2; int OPCODE_CONNECTION_CLOSE = 8; int OPCODE_PING = 9; int OPCODE_PONG = 10; int opcode(); boolean mask(); String payloadData(); } interface InspectorRequestCommon extends InspectorHeaders { /** * Unique identifier for this request. This identifier must be used in all other network * events corresponding to this request. Identifiers may be re-used for HTTP requests or * WebSockets that have exhuasted the state machine to its final closed/finished state. */ String id(); /** * Arbitrary debug-friendly name of the request. */ String friendlyName(); } interface InspectorResponseCommon extends InspectorHeaders { /** @see InspectorRequest#id() */ String requestId(); int statusCode(); String reasonPhrase(); } interface InspectorHeaders { int headerCount(); String headerName(int index); String headerValue(int index); @Nullable String firstHeaderValue(String name); } }