/* * 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.dumpapp; import com.facebook.stetho.common.LogUtil; import com.facebook.stetho.server.SocketLike; import com.facebook.stetho.server.SocketLikeHandler; import java.io.DataInputStream; import java.io.IOException; import java.util.Arrays; /** * Provides a kind of CLI-over-HTTP support for the ./scripts/dumpapp tool. * <p> * This handler accepts a list of text-based arguments to a FAB endpoint and responds with * a stream as furnished by the Dumper implementation on the app side. A special "exit code" * property is also returned that the dumpapp tool uses to pass along the exit code of the * script. */ public class DumpappSocketLikeHandler implements SocketLikeHandler { public static final byte[] PROTOCOL_MAGIC = new byte[] { 'D', 'U', 'M', 'P' }; public static final int PROTOCOL_VERSION = 1; private final Dumper mDumper; public DumpappSocketLikeHandler(Dumper dumper) { mDumper = dumper; } @Override public void onAccepted(SocketLike socket) throws IOException { DataInputStream in = new DataInputStream(socket.getInput()); // Get through the initial hello... establishConversation(in); Framer framer = new Framer(in, socket.getOutput()); String[] args = readArgs(framer); dump(mDumper, framer, args); } static void dump(Dumper dumper, Framer framer, String[] args) throws IOException { try { // We intentionally do not catch-all and write an exit code here. // // The dumper catches all expected exceptions and translates // them to an exit code, so the normal case is all good. // // DumpappOutputBrokenException is thrown in cases where we know // we are unable to write any more, including possibly while // writing the error code itself. // // Because other unchecked exceptions could also be thrown in // cases where the underlying stream is broken, and making // further calls on the broken stream (to write an exit code) // can corrupt the stream and throw still more unchecked // exceptions, we cannot safely write an exit code in this case. int exitCode = dumper.dump( framer.getStdin(), framer.getStdout(), framer.getStderr(), args); framer.writeExitCode(exitCode); } catch (DumpappOutputBrokenException e) { // This exception indicates we must stop all writes to the underlying stream // because there was IOException. We interpret this to mean that we should // also shutdown the whole pipeline, similar to how SIGPIPE would behave // for command-line apps. } } private void establishConversation(DataInputStream in) throws IOException { byte[] magic = new byte[4]; in.readFully(magic); if (!Arrays.equals(PROTOCOL_MAGIC, magic)) { throw logAndThrowProtocolException( "Incompatible protocol, are you using an old dumpapp script?"); } int version = in.readInt(); if (version != PROTOCOL_VERSION) { throw logAndThrowProtocolException( "Expected version=" + PROTOCOL_VERSION + "; got=" + version); } } private static IOException logAndThrowProtocolException(String message) throws IOException { LogUtil.w(message); throw new IOException(message); } private String[] readArgs(Framer framer) throws IOException { synchronized (framer) { byte type = framer.readFrameType(); switch (type) { case Framer.ENTER_FRAME_PREFIX: int argc = framer.readInt(); String[] argv = new String[argc]; for (int i = 0; i < argc; i++) { argv[i] = framer.readString(); } return argv; default: throw new DumpappFramingException("Expected enter frame, got: " + type); } } } }