com.facebook.stetho.inspector.ChromeDevtoolsServer.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.stetho.inspector.ChromeDevtoolsServer.java

Source

/*
 * 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.
 */
//
// Copyright 2004-present Facebook. All Rights Reserved.

package com.facebook.stetho.inspector;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import android.util.Log;

import com.facebook.stetho.common.LogRedirector;
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.PendingRequest;
import com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError;
import com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcRequest;
import com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcResponse;
import com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;
import com.facebook.stetho.json.ObjectMapper;
import com.facebook.stetho.websocket.CloseCodes;
import com.facebook.stetho.websocket.SimpleEndpoint;
import com.facebook.stetho.websocket.SimpleSession;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * Implements a limited version of the Chrome Debugger WebSocket protocol (using JSON-RPC 2.0).
 * The most up-to-date documentation can be found in the Blink source code:
 * <a href="https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/devtools/protocol.json&q=protocol.json&sq=package:chromium&type=cs">protocol.json</a>
 */
public class ChromeDevtoolsServer implements SimpleEndpoint {
    private static final String TAG = "ChromeDevtoolsServer";

    public static final String PATH = "/inspector";

    private final ObjectMapper mObjectMapper;
    private final MethodDispatcher mMethodDispatcher;
    private final Map<SimpleSession, JsonRpcPeer> mPeers = Collections
            .synchronizedMap(new HashMap<SimpleSession, JsonRpcPeer>());

    public ChromeDevtoolsServer(Iterable<ChromeDevtoolsDomain> domainModules) {
        mObjectMapper = new ObjectMapper();
        mMethodDispatcher = new MethodDispatcher(mObjectMapper, domainModules);
    }

    @Override
    public void onOpen(SimpleSession session) {
        LogRedirector.d(TAG, "onOpen");
        mPeers.put(session, new JsonRpcPeer(mObjectMapper, session));
    }

    @Override
    public void onClose(SimpleSession session, int code, String reasonPhrase) {
        LogRedirector.d(TAG, "onClose: reason=" + code + " " + reasonPhrase);

        JsonRpcPeer peer = mPeers.remove(session);
        if (peer != null) {
            peer.invokeDisconnectReceivers();
        }
    }

    @Override
    public void onMessage(SimpleSession session, byte[] message, int messageLen) {
        LogRedirector.d(TAG, "Ignoring binary message of length " + messageLen);
    }

    @Override
    public void onMessage(SimpleSession session, String message) {
        if (LogRedirector.isLoggable(TAG, Log.VERBOSE)) {
            LogRedirector.v(TAG, "onMessage: message=" + message);
        }
        try {
            JsonRpcPeer peer = mPeers.get(session);
            Util.throwIfNull(peer);

            handleRemoteMessage(peer, message);
        } catch (IOException e) {
            if (LogRedirector.isLoggable(TAG, Log.VERBOSE)) {
                LogRedirector.v(TAG, "Unexpected I/O exception processing message: " + e);
            }
            closeSafely(session, CloseCodes.UNEXPECTED_CONDITION, e.getClass().getSimpleName());
        } catch (MessageHandlingException e) {
            LogRedirector.i(TAG, "Message could not be processed by implementation: " + e);
            closeSafely(session, CloseCodes.UNEXPECTED_CONDITION, e.getClass().getSimpleName());
        } catch (JSONException e) {
            LogRedirector.v(TAG, "Unexpected JSON exception processing message", e);
            closeSafely(session, CloseCodes.UNEXPECTED_CONDITION, e.getClass().getSimpleName());
        }
    }

    private void closeSafely(SimpleSession session, int code, String reasonPhrase) {
        session.close(code, reasonPhrase);
    }

    private void handleRemoteMessage(JsonRpcPeer peer, String message)
            throws IOException, MessageHandlingException, JSONException {
        // Parse as a generic JSONObject first since we don't know if this is a request or response.
        JSONObject messageNode = new JSONObject(message);
        if (messageNode.has("method")) {
            handleRemoteRequest(peer, messageNode);
        } else if (messageNode.has("result")) {
            handleRemoteResponse(peer, messageNode);
        } else {
            throw new MessageHandlingException("Improper JSON-RPC message: " + message);
        }
    }

    private void handleRemoteRequest(JsonRpcPeer peer, JSONObject requestNode) throws MessageHandlingException {
        JsonRpcRequest request;
        request = mObjectMapper.convertValue(requestNode, JsonRpcRequest.class);

        JSONObject result = null;
        JSONObject error = null;
        try {
            result = mMethodDispatcher.dispatch(peer, request.method, request.params);
        } catch (JsonRpcException e) {
            logDispatchException(e);
            error = mObjectMapper.convertValue(e.getErrorMessage(), JSONObject.class);
        }
        if (request.id != null) {
            JsonRpcResponse response = new JsonRpcResponse();
            response.id = request.id;
            response.result = result;
            response.error = error;
            JSONObject jsonObject = mObjectMapper.convertValue(response, JSONObject.class);
            String responseString;
            try {
                responseString = jsonObject.toString();
            } catch (OutOfMemoryError e) {
                // JSONStringer can cause an OOM when the Json to handle is too big.
                response.result = null;
                response.error = mObjectMapper.convertValue(e.getMessage(), JSONObject.class);
                jsonObject = mObjectMapper.convertValue(response, JSONObject.class);
                responseString = jsonObject.toString();
            }
            peer.getWebSocket().sendText(responseString);
        }
    }

    private static void logDispatchException(JsonRpcException e) {
        JsonRpcError errorMessage = e.getErrorMessage();
        switch (errorMessage.code) {
        case METHOD_NOT_FOUND:
            LogRedirector.d(TAG, "Method not implemented: " + errorMessage.message);
            break;
        default:
            LogRedirector.w(TAG, "Error processing remote message", e);
        }
    }

    private void handleRemoteResponse(JsonRpcPeer peer, JSONObject responseNode)
            throws MismatchedResponseException {
        JsonRpcResponse response = mObjectMapper.convertValue(responseNode, JsonRpcResponse.class);
        PendingRequest pendingRequest = peer.getAndRemovePendingRequest(response.id);
        if (pendingRequest == null) {
            throw new MismatchedResponseException(response.id);
        }
        if (pendingRequest.callback != null) {
            pendingRequest.callback.onResponse(peer, response);
        }
    }

    @Override
    public void onError(SimpleSession session, Throwable ex) {
        LogRedirector.e(TAG, "onError: ex=" + ex.toString());
    }
}