/// <summary> /// Connects to and performs the initial handshake with the remote debugging server, verifying protocol signature and version number, /// and returns the opened stream in a state ready to receive further ptvsd commands (e.g. attach). /// </summary> public static async Task <DebugConnection> ConnectAsync(Uri uri, bool warnAboutAuthenticationErrors, CancellationToken ct) { var transport = DebuggerTransportFactory.Get(uri); if (transport == null) { throw new ConnectionException(ConnErrorMessages.RemoteInvalidUri); } Stream stream = null; do { try { stream = transport.Connect(uri, warnAboutAuthenticationErrors); } catch (AuthenticationException ex) { if (!warnAboutAuthenticationErrors) { // This should never happen, but if it does, we don't want to keep trying. throw new ConnectionException(ConnErrorMessages.RemoteSslError, ex); } string errText = Strings.RemoteProcessAuthenticationErrorWarning.FormatUI(ex.Message); var dlgRes = MessageBox.Show(errText, Strings.ProductTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (dlgRes == DialogResult.Yes) { warnAboutAuthenticationErrors = false; } else { throw new ConnectionException(ConnErrorMessages.RemoteSslError, ex); } } } while (stream == null); var debugConn = new DebugConnection(stream); bool connected = false; try { string serverDebuggerName = string.Empty; int serverDebuggerProtocolVersion = 0; using (var connectedEvent = new AutoResetEvent(false)) { EventHandler <LDP.RemoteConnectedEvent> eventReceived = (object sender, LDP.RemoteConnectedEvent ea) => { serverDebuggerName = ea.debuggerName; serverDebuggerProtocolVersion = ea.debuggerProtocolVersion; try { connectedEvent.Set(); } catch (ObjectDisposedException) { } debugConn.Authenticated(); }; // When the server accepts a connection, it sends an event and then waits for a request debugConn.LegacyRemoteConnected += eventReceived; try { debugConn.StartListening(); bool received = connectedEvent.WaitOne(ConnectTimeoutMs); if (!received) { throw new ConnectionException(ConnErrorMessages.TimeOut); } } finally { debugConn.LegacyRemoteConnected -= eventReceived; } } if (serverDebuggerName != DebuggerSignature) { throw new ConnectionException(ConnErrorMessages.RemoteUnsupportedServer); } // If we are talking the same protocol but different version, reply with signature + version before bailing out // so that ptvsd has a chance to gracefully close the socket on its side. var response = await debugConn.SendRequestAsync(new LDP.RemoteDebuggerAuthenticateRequest() { clientSecret = uri.UserInfo, debuggerName = DebuggerSignature, debuggerProtocolVersion = DebuggerProtocolVersion, }, ct); if (serverDebuggerProtocolVersion != DebuggerProtocolVersion) { throw new ConnectionException(ConnErrorMessages.RemoteUnsupportedServer); } if (!response.accepted) { throw new ConnectionException(ConnErrorMessages.RemoteSecretMismatch); } connected = true; return(debugConn); } catch (IOException ex) { throw new ConnectionException(ConnErrorMessages.RemoteNetworkError, ex); } catch (SocketException ex) { throw new ConnectionException(ConnErrorMessages.RemoteNetworkError, ex); } finally { if (!connected) { debugConn?.Dispose(); } } }
public static async Task <PythonRemoteDebugProcess> ConnectAsync(PythonRemoteDebugPort port, TextWriter debugLog, CancellationToken ct) { PythonRemoteDebugProcess process = null; // Connect to the remote debugging server and obtain process information. If any errors occur, display an error dialog, and keep // trying for as long as user clicks "Retry". while (true) { DebugConnection debugConn = null; ConnectionException connEx = null; try { // Process information is not sensitive, so ignore any SSL certificate errors, rather than bugging the user with warning dialogs. debugConn = await PythonRemoteProcess.ConnectAsync(port.Uri, false, debugLog, ct); } catch (ConnectionException ex) { connEx = ex; } using (debugConn) { if (debugConn != null) { try { var response = await debugConn.SendRequestAsync(new LDP.RemoteDebuggerInfoRequest(), ct); process = new PythonRemoteDebugProcess(port, response.processId, response.executable, response.user, response.pythonVersion); break; } catch (IOException ex) { connEx = new ConnectionException(ConnErrorMessages.RemoteNetworkError, ex); } catch (FailedRequestException ex) { connEx = new ConnectionException(ConnErrorMessages.RemoteNetworkError, ex); } } if (connEx != null) { string errText; switch (connEx.Error) { case ConnErrorMessages.RemoteUnsupportedServer: errText = Strings.RemoteUnsupportedServer_Host.FormatUI(port.Uri); break; case ConnErrorMessages.RemoteSecretMismatch: errText = Strings.RemoteSecretMismatch_Host.FormatUI(new UriBuilder(port.Uri) { UserName = null, Password = null }.Uri); break; case ConnErrorMessages.RemoteSslError: // User has already got a warning dialog and clicked "Cancel" on that, so no further prompts are needed. return(null); default: { // Azure uses HTTP 503 (Service Unavailable) to indicate that websocket connections are not supported. Show a special error message for that. var wsEx = connEx.InnerException as WebSocketException; if (wsEx != null) { var webEx = wsEx.InnerException as WebException; if (webEx != null) { var httpResponse = webEx.Response as HttpWebResponse; if (httpResponse != null && httpResponse.StatusCode == HttpStatusCode.ServiceUnavailable) { errText = Strings.RemoteAzureServiceUnavailable_Host.FormatUI(port.Uri); break; } } } errText = Strings.RemoteServiceUnavailable_Host.FormatUI(port.Uri); for (var ex = connEx.InnerException; ex != null; ex = ex.InnerException) { if (ex.InnerException == null) { errText += "\r\n\r\n{0}\r\n{1}".FormatUI(Strings.AdditionalInformation, ex.Message); } } break; } } DialogResult dlgRes = MessageBox.Show(errText, Strings.ProductTitle, MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); if (dlgRes != DialogResult.Retry) { break; } } } } return(process); }