// Copyright 2018 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.skylarkdebug.server; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.skylarkdebugging.SkylarkDebuggingProtos.DebugEvent; import com.google.devtools.build.lib.skylarkdebugging.SkylarkDebuggingProtos.DebugRequest; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import javax.annotation.Nullable; /** * Manages the connection to and communication to/from the debugger client. Reading and writing are * internally synchronized by {@link DebugServerTransport}. */ final class DebugServerTransport { /** Sets up the server transport and blocks while waiting for an incoming connection. */ static DebugServerTransport createAndWaitForClient( EventHandler eventHandler, ServerSocket serverSocket, boolean verboseLogging) throws IOException { // TODO(bazel-team): reject all connections after the first eventHandler.handle(Event.progress("Waiting for debugger...")); Socket clientSocket = serverSocket.accept(); eventHandler.handle(Event.info("Debugger connection successfully established.")); return new DebugServerTransport( eventHandler, serverSocket, clientSocket, clientSocket.getInputStream(), clientSocket.getOutputStream(), verboseLogging); } private final EventHandler eventHandler; private final ServerSocket serverSocket; private final Socket clientSocket; private final InputStream requestStream; private final OutputStream eventStream; private final boolean verboseLogging; private DebugServerTransport( EventHandler eventHandler, ServerSocket serverSocket, Socket clientSocket, InputStream requestStream, OutputStream eventStream, boolean verboseLogging) { this.eventHandler = eventHandler; this.serverSocket = serverSocket; this.clientSocket = clientSocket; this.requestStream = requestStream; this.eventStream = eventStream; this.verboseLogging = verboseLogging; } /** * Blocks waiting for a properly-formed client request. Returns null if the client connection is * closed. */ @Nullable DebugRequest readClientRequest() { synchronized (requestStream) { try { DebugRequest request = DebugRequest.parseDelimitedFrom(requestStream); if (verboseLogging) { eventHandler.handle(Event.debug("Received debug client request:\n" + request)); } return request; } catch (IOException e) { handleParsingError(e); return null; } } } private void handleParsingError(IOException e) { if (isClosed()) { // an IOException is expected when the client disconnects -- no need to log an error return; } String message = "Error parsing debug request: " + e.getMessage(); postEvent(DebugEventHelper.error(message)); eventHandler.handle(Event.error(message)); } /** Posts a debug event. */ void postEvent(DebugEvent event) { if (verboseLogging) { eventHandler.handle(Event.debug("Sending debug event:\n" + event)); } synchronized (eventStream) { try { event.writeDelimitedTo(eventStream); } catch (IOException e) { eventHandler.handle(Event.error("Failed to send debug event to client: " + e.getMessage())); } } } void close() throws IOException { clientSocket.close(); serverSocket.close(); } boolean isClosed() { return serverSocket.isClosed() || clientSocket.isClosed(); } }