| // 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(); |
| } |
| } |