private Func <Uri, CancellationToken, Task <WebSocket> > CreateClient(string login, string password)
        {
            var token = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{login}:{password}"));

            return(async(uri, t) =>
            {
                var client = SystemClientWebSocket.CreateClientWebSocket();

                switch (client)
                {
                case System.Net.WebSockets.Managed.ClientWebSocket managed:
                    managed.Options.SetRequestHeader("Authorization", $"Basic {token}");
                    await managed.ConnectAsync(uri, t);

                    break;

                case ClientWebSocket coreSocket:
                    coreSocket.Options.SetRequestHeader("Authorization", $"Basic {token}");
                    await coreSocket.ConnectAsync(uri, t);

                    break;
                }

                return client;
            });
        }
        public Task InitializeWebSocket()
        {
            // do not attempt to initialize if cancellation is requested
            if (Completion != null)
            {
                throw new OperationCanceledException();
            }

            lock (_initializeLock)
            {
                // if an initialization task is already running, return that
                if (_initializeWebSocketTask != null &&
                    !_initializeWebSocketTask.IsFaulted &&
                    !_initializeWebSocketTask.IsCompleted)
                {
                    return(_initializeWebSocketTask);
                }

                // if the websocket is open, return a completed task
                if (_clientWebSocket != null && _clientWebSocket.State == WebSocketState.Open)
                {
                    return(Task.CompletedTask);
                }

                // else (re-)create websocket and connect
                _clientWebSocket?.Dispose();

#if NETFRAMEWORK
                // fix websocket not supported on win 7 using
                // https://github.com/PingmanTools/System.Net.WebSockets.Client.Managed
                _clientWebSocket = SystemClientWebSocket.CreateClientWebSocket();
                switch (_clientWebSocket)
                {
                case ClientWebSocket nativeWebSocket:
                    nativeWebSocket.Options.AddSubProtocol("graphql-ws");
                    nativeWebSocket.Options.ClientCertificates    = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates;
                    nativeWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials;
                    Options.ConfigureWebsocketOptions(nativeWebSocket.Options);
                    break;

                case System.Net.WebSockets.Managed.ClientWebSocket managedWebSocket:
                    managedWebSocket.Options.AddSubProtocol("graphql-ws");
                    managedWebSocket.Options.ClientCertificates    = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates;
                    managedWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials;
                    break;

                default:
                    throw new NotSupportedException($"unknown websocket type {_clientWebSocket.GetType().Name}");
                }
#else
                _clientWebSocket = new ClientWebSocket();
                _clientWebSocket.Options.AddSubProtocol("graphql-ws");
                _clientWebSocket.Options.ClientCertificates    = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates;
                _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials;
                Options.ConfigureWebsocketOptions(_clientWebSocket.Options);
#endif
                return(_initializeWebSocketTask = ConnectAsync(_internalCancellationToken));
            }
        }
 internal WebSocketTransport Build(Action <IEnumerable <JObject> > eventPublisher)
 {
     return(new WebSocketTransport(
                WebSocketFactory ?? (() => SystemClientWebSocket.CreateClientWebSocket()),
                Uri ?? throw new Exception("Please set Uri."),
                ResponseTimeout ?? TimeSpan.FromSeconds(65),
                eventPublisher));
 }
        /// <summary>
        /// WebSocket factory to support Windows 7 and Windows Server 2008.
        /// https://github.com/hardkoded/puppeteer-sharp/issues/1368#issuecomment-580946444
        /// The minimum Windows versions supporting the WebSocket library are Windows 8 and Windows Server 2012.
        /// <see href="https://github.com/hardkoded/puppeteer-sharp#prerequisites"/>.
        /// </summary>
        /// <param name="uri">The URI.</param>
        /// <param name="socketOptions">The socket options.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The WebSocket factory.</returns>
        private static async Task <WebSocket> WebSocketFactory(Uri uri, IConnectionOptions socketOptions, CancellationToken cancellationToken)
        {
            var client = SystemClientWebSocket.CreateClientWebSocket();

            if (client is System.Net.WebSockets.Managed.ClientWebSocket managed)
            {
                managed.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                await managed.ConnectAsync(uri, cancellationToken);
            }
            else
            {
                var coreSocket = client as ClientWebSocket;
                coreSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                await coreSocket.ConnectAsync(uri, cancellationToken);
            }

            return(client);
        }
        public async Task Open_and_close_WebSocket()
        {
            using (var webSocket = SystemClientWebSocket.CreateClientWebSocket())
            {
                Debug.WriteLine("Connecting");
                await webSocket.ConnectAsync(new Uri("ws://localhost:5088/bayeux/"), CancellationToken.None);
                await Delay(10);

                //Debug.WriteLine("Sending");
                //await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("hola")), WebSocketMessageType.Text, endOfMessage: true, cancellationToken: CancellationToken.None);
                //await Delay(10);
                Debug.WriteLine("Closing");
                await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                await Delay(5);

                Debug.WriteLine("Connecting again");
                await webSocket.ConnectAsync(new Uri("ws://localhost:5088/bayeux/"), CancellationToken.None);
                await Delay(5);

                Debug.WriteLine("End");
            }
        }
Exemple #6
0
        /// <summary>
        /// Конвертирование на основе содержимого файла html
        /// </summary>
        /// <param name="htmlContent">содержимое файла html</param>
        /// <param name="executablePath">абсолютный путь к исполняемому файлу chrome.exe</param>
        /// <returns>содержимое результата в формате pdf в байтовом представлении</returns>
        public static async Task <byte[]> ConvertFromContentAsync(string htmlContent, string executablePath = DEFAULT_CHROME_PATH)
        {
            using (var browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                ExecutablePath = executablePath,
                Headless = true,
                WebSocketFactory = async(uri, socketOptions, cancellationToken) =>
                {
                    var client = SystemClientWebSocket.CreateClientWebSocket();
                    if (client is System.Net.WebSockets.Managed.ClientWebSocket managed)
                    {
                        managed.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                        await managed.ConnectAsync(uri, cancellationToken);
                    }
                    else
                    {
                        var coreSocket = client as ClientWebSocket;
                        coreSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                        await coreSocket.ConnectAsync(uri, cancellationToken);
                    }

                    return(client);
                },
            }).ConfigureAwait(false))
Exemple #7
0
        private async void InitWebSocket()
        {
            if (wssPort != "443")
            {
                socketPath = "wss://" + hostName + ":" + wssPort + "/Services/Remote_Control_Socket.cshtml";
            }
            await TestSSL();

            try
            {
                Socket = SystemClientWebSocket.CreateClientWebSocket();
            }
            catch (Exception ex)
            {
                WriteToLog(ex);
                System.Windows.MessageBox.Show("Unable to create web socket.", "Web Sockets Not Supported", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            try
            {
                await Socket.ConnectAsync(new Uri(socketPath), CancellationToken.None);
            }
            catch
            {
                try
                {
                    if (wsPort != "80")
                    {
                        socketPath = "ws://" + hostName + ":" + wsPort + "/Services/Remote_Control_Socket.cshtml";
                    }
                    Socket = SystemClientWebSocket.CreateClientWebSocket();
                    await Socket.ConnectAsync(new Uri(socketPath), CancellationToken.None);
                }
                catch (Exception ex)
                {
                    WriteToLog(ex);
                    System.Windows.MessageBox.Show("Unable to connect to server.", "Connection Failed", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
            }
            if (socketPath.StartsWith("ws://"))
            {
                var result = System.Windows.MessageBox.Show("A secure connection couldn't be established.  SSL is not configured properly on the server.  Do you want to proceed with an unencrypted connection?", "Connection Not Secure", MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (result == MessageBoxResult.No)
                {
                    App.Current.Shutdown();
                    return;
                }
            }
            WriteToLog($"Connection opened on {socketPath}.");
            // Send notification to server that this connection is for a client app.
            var request = new
            {
                Type           = "ConnectionType",
                ConnectionType = "ClientApp",
                ComputerName   = Environment.MachineName,
            };

            await SocketSend(request);

            HandleSocket();
        }
Exemple #8
0
        protected async Task Connect()
        {
            Socket = SystemClientWebSocket.ConnectAsync(new Uri("ws://remote.natfrp.com:2333"), Source.Token).Result;
            await Socket.SendAsync(new ArraySegment <byte>(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new Dictionary <string, object>
            {
                { "version", REMOTE_VERSION },
                { "type", "launcher" },
                { "token", Natfrp.Token },
                { "identifier", Identifier }
            }))), WebSocketMessageType.Text, true, Source.Token);

            Main.LogManager.Log(LogManager.CATEGORY_SERVICE_INFO, "Service", "RemoteManager: 远程管理已连接");

            var remote = new RemotePipeConnection();

            byte[] buffer = new byte[8192];
            while (!Source.IsCancellationRequested)
            {
                // Ensure message is complete
                int length = 0;
                WebSocketReceiveResult result = null;
                while (true)
                {
                    result = await Socket.ReceiveAsync(new ArraySegment <byte>(buffer, length, buffer.Length - length), Source.Token);

                    length += result.Count;
                    if (result.EndOfMessage)
                    {
                        break;
                    }
                    else if (length >= buffer.Length)
                    {
                        Main.LogManager.Log(LogManager.CATEGORY_SERVICE_WARNING, "Service", "RemoteManager: 接收到过长消息, 已断开服务器连接, 将在稍后重连");
                        await Socket.CloseAsync(WebSocketCloseStatus.MessageTooBig, "消息过长", Source.Token);

                        return;
                    }
                }

                // Handle close message
                if (result.MessageType == WebSocketMessageType.Close)
                {
                    switch (result.CloseStatus.Value)
                    {
                    case WebSocketCloseStatus.NormalClosure:
                        Main.LogManager.Log(LogManager.CATEGORY_SERVICE_INFO, "Service", "RemoteManager: 服务端正常断开 [" + result.CloseStatusDescription + "] 将在稍后重连");
                        break;

                    case WebSocketCloseStatus.PolicyViolation:
                        Main.LogManager.Log(LogManager.CATEGORY_SERVICE_WARNING, "Service", "RemoteManager: 服务器拒绝请求, 已停止远程管理功能: " + result.CloseStatusDescription);
                        Stop();
                        return;

                    case WebSocketCloseStatus.InternalServerError:
                        Main.LogManager.Log(LogManager.CATEGORY_SERVICE_WARNING, "Service", "RemoteManager: 服务器内部错误, " + result.CloseStatusDescription);
                        break;

                    default:
                        Main.LogManager.Log(LogManager.CATEGORY_SERVICE_WARNING, "Service", "RemoteManager: 未知错误 [" + result.CloseStatus + "], " + result.CloseStatusDescription);
                        break;
                    }
                    await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, Source.Token);

                    return;
                }

                // Hmm, ensure something unexpected won't crash the socket
                if (length < OVERHEAD)
                {
                    Main.LogManager.Log(LogManager.CATEGORY_SERVICE_WARNING, "Service", "RemoteManager: 收到过短的消息");
                    return;
                }

                // Process payload
                using (var ms = new MemoryStream())
                {
                    ms.Write(buffer, 0, OVERHEAD);
                    switch (buffer[0])
                    {
                    case 0x01: // Heartbeat
                        if (length != OVERHEAD)
                        {
                            Main.LogManager.Log(LogManager.CATEGORY_SERVICE_WARNING, "Service", "RemoteManager: 心跳包长度异常");
                            continue;
                        }
                        break;

                    case 0x02: // Remote Command
                        if (length < 24 + OVERHEAD)
                        {
                            Main.LogManager.Log(LogManager.CATEGORY_SERVICE_WARNING, "Service", "RemoteManager: 收到过短的指令");
                            continue;
                        }

                        byte[] nonce = new byte[24], data = new byte[length - nonce.Length - OVERHEAD];

                        Buffer.BlockCopy(buffer, OVERHEAD, nonce, 0, nonce.Length);
                        Buffer.BlockCopy(buffer, nonce.Length + OVERHEAD, data, 0, data.Length);

                        try
                        {
                            data = SecretBox.Open(data, nonce, EncryptKey);
                        }
                        catch
                        {
                            Main.LogManager.Log(LogManager.CATEGORY_SERVICE_WARNING, "Service", "RemoteManager: 指令解密失败, 原因可能为密钥错误, 如果您无故看到此错误请检查账户是否被盗");
                            break;
                        }
                        remote.Buffer = data;
                        Main.Pipe_DataReceived(remote, data.Length);

                        nonce = SecretBox.GenerateNonce();
                        ms.Write(nonce, 0, nonce.Length);

                        data = SecretBox.Create(remote.Buffer, nonce, EncryptKey);
                        ms.Write(data, 0, data.Length);
                        break;

                    default:
                        Main.LogManager.Log(LogManager.CATEGORY_SERVICE_WARNING, "Service", "RemoteManager: 收到未知消息");
                        continue;
                    }
                    await Socket.SendAsync(new ArraySegment <byte>(ms.ToArray()), WebSocketMessageType.Binary, true, Source.Token);
                }
            }
        }
        /// <summary>
        /// <para>设置一个 Chromium 浏览器 启动选项 的对象,并返回这个对象;此方法兼容 Windows7 / Windows Server 2008</para>
        /// <para>异常可能:程序运行目录下 Chromium 浏览器不可用。</para>
        /// </summary>
        /// <param name="checkIsDownload">检查 是否下载 Chromium 浏览器;默认 false</param>
        /// <param name="isDisplay">Chromium 运行时 是否显示界面;默认 true</param>
        /// <param name="args">要传递给 Chromium 浏览器实例的其他参数。( 此方法会自动传入 "--no-sandbox" 参数 )
        /// <para>参考:https://peter.sh/experiments/chromium-command-line-switches/#no-sandbox </para>
        /// </param>
        /// <param name="ignoredDefaultArgs">如果给出数组,则过滤掉给定的 Puppeteer.DefaultArgs 默认参数。
        /// ( 此方法会自动设置 "--enable-automation" 过滤掉这个参数 )
        /// <para>参考:https://peter.sh/experiments/chromium-command-line-switches/#no-sandbox </para>
        /// </param>
        /// <returns></returns>
        private static async Task <LaunchOptions> SetChromiumLaunchOptions(
            bool checkIsDownload        = false,
            bool isDisplay              = true,
            string[] args               = null,
            string[] ignoredDefaultArgs = null)
        {
            return(await Task.Run(async() =>
            {
                BrowserFetcher browserFetcher = Puppeteer.CreateBrowserFetcher(new BrowserFetcherOptions());
                RevisionInfo revisionInfo = browserFetcher.RevisionInfo(BrowserFetcher.DefaultRevision);

                #region 检查下载 Chromium
                if (!(revisionInfo.Downloaded && revisionInfo.Local))
                {
                    if (checkIsDownload)
                    {
                        #region  载地址 解析;参考于源代码:https://github.com/hardkoded/puppeteer-sharp/blob/37ea56934281209830254df3ec3ffe37c57cfac2/lib/PuppeteerSharp/BrowserFetcher.cs

                        // https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/706915/chrome-win.zip 下载地址( 样例 )

                        // const string DefaultDownloadHost = "https://storage.googleapis.com";
                        // const int DefaultRevision = 706915;

                        // [Platform.Linux] = "{0}/chromium-browser-snapshots/Linux_x64/{1}/{2}.zip",
                        // [Platform.MacOS] = "{0}/chromium-browser-snapshots/Mac/{1}/{2}.zip",
                        // [Platform.Win32] = "{0}/chromium-browser-snapshots/Win/{1}/{2}.zip",
                        // [Platform.Win64] = "{0}/chromium-browser-snapshots/Win_x64/{1}/{2}.zip"

                        // case Platform.Linux:
                        //     return "chrome-linux";
                        // case Platform.MacOS:
                        //     return "chrome-mac";
                        // case Platform.Win32:
                        // case Platform.Win64:
                        //     return revision > 591479 ? "chrome-win" : "chrome-win32";

                        #endregion

                        // 检查 revisionInfo.Revision 这个版本的 Chromium 浏览器 是否 可下载
                        bool isCan = await browserFetcher.CanDownloadAsync(revisionInfo.Revision);
                        if (isCan)
                        {
                            // 下载 revisionInfo.Revision 这个版本的无头浏览器;可能需要等待一些时间
                            await browserFetcher.DownloadAsync(revisionInfo.Revision);
                        }
                        else
                        {
                            throw new Exception($"程序检测出 Chromium 浏览器(默认版本 {revisionInfo.Revision})无法更新!");
                        }
                    }
                    else
                    {
                        throw new Exception("程序运行目录下 Chromium 浏览器不可用。请开发人员检查 程序运行目录下 是否正确安装 Chromium 浏览器。");
                    }
                }
                #endregion

                #region 兼容 Windows7 / Windows Server 2008
                LaunchOptions launchOptions = default(LaunchOptions);
                // 这个判断是为了兼容 Windows7 和 Windows Server 2008
                if (OSHelper.IsWin7Under())
                {
                    launchOptions = new LaunchOptions
                    {
                        WebSocketFactory = async(uri, socketOptions, cancellationToken) =>
                        {
                            WebSocket client = SystemClientWebSocket.CreateClientWebSocket();
                            if (client is System.Net.WebSockets.Managed.ClientWebSocket managed)
                            {
                                managed.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                                await managed.ConnectAsync(uri, cancellationToken);
                            }
                            else
                            {
                                ClientWebSocket coreSocket = client as ClientWebSocket;
                                coreSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                                await coreSocket.ConnectAsync(uri, cancellationToken);
                            }
                            return client;
                        }
                    };
                }
                else
                {
                    launchOptions = new LaunchOptions();
                }
                #endregion

                #region 设置 Args 参数
                string[] argss = default(string[]);
                if (args != null && args.Length > 0)
                {
                    List <string> argsList = args.ToList <string>();
                    argsList.Add("--no-sandbox");
                    argss = argsList.ToArray();
                }
                else
                {
                    argss = new string[] { "--no-sandbox" };
                }
                launchOptions.Args = argss; //这些参数将会传递给 Chromium
                #endregion

                #region 设置 IgnoredDefaultArgs 参数
                string[] defaultArgs = default(string[]);
                if (ignoredDefaultArgs != null && ignoredDefaultArgs.Length > 0)
                {
                    List <string> ignoredDefaultArgsList = ignoredDefaultArgs.ToList <string>();
                    ignoredDefaultArgsList.Add("--enable-automation");
                    defaultArgs = ignoredDefaultArgsList.ToArray();
                }
                else
                {
                    defaultArgs = new string[] { "--enable-automation" };
                }
                launchOptions.IgnoredDefaultArgs = defaultArgs; //这些参数将被 Chromium 忽略
                #endregion

                launchOptions.Headless = !isDisplay; // Headless : true 是无头模式,无界面;false,有界面
                return launchOptions;
            }));
        }
Exemple #10
0
        private WebSocket ObtainConnection()
        {
            lock (this)
            {
                if (_wsCancellationSource.IsCancellationRequested)
                {
                    return(null);
                }

                if (_webSocket == null)
                {
                    _webSocket = SystemClientWebSocket.CreateClientWebSocket();
                }

                switch (_webSocket.State)
                {
                case WebSocketState.None:
                    break;

                case WebSocketState.Connecting:
                case WebSocketState.Open:
                    // All good
                    return(_webSocket);

                case WebSocketState.CloseSent:
                case WebSocketState.CloseReceived:
                case WebSocketState.Closed:
                    _webSocket.Abort();
                    _webSocket.Dispose();
                    _webSocket = SystemClientWebSocket.CreateClientWebSocket();
                    break;

                case WebSocketState.Aborted:
                    _webSocket.Dispose();
                    _webSocket = SystemClientWebSocket.CreateClientWebSocket();
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                Debug.Assert(_webSocket.State == WebSocketState.None);
                // Connect
                Logger.Debug("(重新)连接 EdgeTTS 服务器中...");
                if (_webSocket is ClientWebSocket ws)
                {
                    var options = ws.Options;
                    options.SetRequestHeader("Accept-Encoding", "gzip, deflate, br");
                    options.SetRequestHeader("Cache-Control", "no-cache");
                    options.SetRequestHeader("Pragma", "no-cache");
                }
                else
                {
                    var options = ((System.Net.WebSockets.Managed.ClientWebSocket)_webSocket).Options;
                    options.SetRequestHeader("Accept-Encoding", "gzip, deflate, br");
                    options.SetRequestHeader("Cache-Control", "no-cache");
                    options.SetRequestHeader("Pragma", "no-cache");
                }
                _webSocket.ConnectAsync(new Uri(URL), _wsCancellationSource.Token).Wait();
                return(_webSocket);
            }
        }
Exemple #11
0
        private byte[] Synthesis(XfyunTTSSettings settings, string text)
        {
            var apiSecret = settings.ApiSecret;
            var apiKey    = settings.ApiKey;
            var appId     = settings.AppId;

            if (string.IsNullOrEmpty(apiKey) || string.IsNullOrEmpty(apiSecret) || string.IsNullOrEmpty(appId))
            {
                Logger.Error(strings.msgErrorEmptyApiSecretKey);
                _settingsControl.NotifyEmptyApiKey();
                return(null);
            }

            var base64Text = Convert.ToBase64String(Encoding.UTF8.GetBytes(text));

            if (base64Text.Length > 8000)
            {
                Logger.Error("Convert string too long. No more than 2000 chinese characters.");
                return(null);
            }

            using (var ws = SystemClientWebSocket.CreateClientWebSocket())
            {
                // Connect
                var    date = DateTime.UtcNow.ToString("R");
                var    sign = $"host: tts-api.xfyun.cn\ndate: {date}\nGET /v2/tts HTTP/1.1";
                string sha;
                using (var hash = new HMACSHA256(Encoding.UTF8.GetBytes(apiSecret)))
                {
                    sha = Convert.ToBase64String(hash.ComputeHash(Encoding.UTF8.GetBytes(sign)));
                    hash.Clear();
                }
                var authorization =
                    $"api_key=\"{apiKey}\"," +
                    $" algorithm=\"hmac-sha256\"," +
                    $" headers=\"host date request-line\"," +
                    $" signature=\"{sha}\"";
                var url =
                    $"wss://tts-api.xfyun.cn/v2/tts?host=tts-api.xfyun.cn" +
                    $"&date={WebUtility.UrlEncode(date).Replace("+", "%20")}" +
                    $"&authorization={Convert.ToBase64String(Encoding.UTF8.GetBytes(authorization))}";
                try
                {
                    ws.ConnectAsync(new Uri(url), _wsCancellationSource.Token).Wait();
                }
                catch (AggregateException e)
                {
                    var inner = e.InnerExceptions.First().GetBaseException();
                    if (inner is WebException webException)
                    {
                        var    resp = (HttpWebResponse)webException.Response;
                        string body = null;
                        using (var stream = resp.GetResponseStream())
                        {
                            if (stream != null)
                            {
                                var reader = new StreamReader(stream, string.IsNullOrEmpty(resp.CharacterSet) ? Encoding.UTF8 : Encoding.GetEncoding(resp.CharacterSet));
                                body = reader.ReadToEnd();
                            }
                        }
                        Logger.Error($"Unable to connect to server: {body}");
                        switch (resp.StatusCode)
                        {
                        case HttpStatusCode.Unauthorized:
                        case HttpStatusCode.Forbidden:
                            Logger.Error(strings.msgErrorXfyunAuthFail);
                            return(null);
                        }
                    }

                    throw;
                }

                // Send request
                var request = new TTSRequest
                {
                    Common = new TTSRequest.CommonParam
                    {
                        AppId = settings.AppId,
                    },
                    Business = new TTSRequest.BusinessParam
                    {
                        Voice  = settings.Voice,
                        Pitch  = settings.Pitch * 10,
                        Speed  = settings.Speed * 10,
                        Volume = settings.Volume * 10,
                    },
                    Data = new TTSRequest.DataParam
                    {
                        Status = 2,
                        Text   = base64Text,
                    }
                };
                ws.SendText(JsonConvert.SerializeObject(request), _wsCancellationSource);

                // Start receiving
                var buffer  = new MemoryStream();
                var session = new WebSocketHelper.Session(ws);
                while (true)
                {
                    var message = WebSocketHelper.ReceiveNextMessage(session, _wsCancellationSource);
                    Logger.Debug($"Received WS message\n{message}");
                    if (message.Type == WebSocketMessageType.Text)
                    {
                        var resp = JsonConvert.DeserializeObject <TTSResponse>(message.MessageStr);
                        if (resp.Code == 0)
                        {
                            // Success!
                            if (resp.Data != null)
                            {
                                var data = Convert.FromBase64String(resp.Data.Audio);
                                buffer.Write(data, 0, data.Length);

                                if (resp.Data.Status == 2)
                                {
                                    // Complete!
                                    return(buffer.ToArray());
                                }
                            }
                        }
                        else
                        {
                            Logger.Error($"Unexpected response code received: {resp.Code}: {resp.Message}");
                            switch (resp.Code)
                            {
                            case 10005:
                            case 10313:
                                Logger.Error(strings.msgErrorXfyunWrongAppId);
                                break;

                            case 11200:
                            case 11201:
                                Logger.Error(strings.msgErrorXfyunInsufficientApiQuota);
                                break;
                            }
                            return(null);
                        }
                    }
                    else if (message.Type == WebSocketMessageType.Binary)
                    {
                        throw new IOException("Unexpected binary message received");
                    }
                    else if (message.Type == WebSocketMessageType.Close)
                    {
                        throw new IOException("Unexpected closing of connection");
                    }
                    else
                    {
                        throw new ArgumentOutOfRangeException();
                    }
                }
            }
        }
        /// <summary>
        /// <para>设置一个 Chromium 浏览器 启动选项 的对象,并返回这个对象;此方法兼容 Windows7 / Windows Server 2008</para>
        /// <para>异常可能:程序运行目录下 Chromium 浏览器不可用。</para>
        /// </summary>
        /// <param name="checkIsDownload">检查 是否下载 Chromium 浏览器;默认 false</param>
        /// <param name="isDisplay">Chromium 运行时 是否显示界面;默认 true</param>
        /// <param name="args">要传递给 Chromium 浏览器实例的其他参数。( 此方法会自动传入 "--no-sandbox" 参数 )
        /// <para>参考:https://peter.sh/experiments/chromium-command-line-switches/#no-sandbox </para>
        /// </param>
        /// <param name="ignoredDefaultArgs">如果给出数组,则过滤掉给定的 Puppeteer.DefaultArgs 默认参数。
        /// ( 此方法会自动设置 "--enable-automation" 过滤掉这个参数 )
        /// <para>参考:https://peter.sh/experiments/chromium-command-line-switches/#no-sandbox </para>
        /// </param>
        /// <returns></returns>
        private static async Task <LaunchOptions> SetChromiumLaunchOptions(
            bool checkIsDownload        = false,
            bool isDisplay              = true,
            string[] args               = null,
            string[] ignoredDefaultArgs = null)
        {
            return(await Task.Run(async() =>
            {
                BrowserFetcher browserFetcher = Puppeteer.CreateBrowserFetcher(new BrowserFetcherOptions());
                RevisionInfo revisionInfo = browserFetcher.RevisionInfo(BrowserFetcher.DefaultChromiumRevision);

                #region 检查下载 Chromium
                if (!(revisionInfo.Downloaded && revisionInfo.Local))
                {
                    if (checkIsDownload)
                    {
                        // 检查 revisionInfo.Revision 这个版本的 Chromium 浏览器 是否 可下载
                        bool isCan = await browserFetcher.CanDownloadAsync(revisionInfo.Revision);
                        if (isCan)
                        {
                            // 下载 revisionInfo.Revision 这个版本的无头浏览器;可能需要等待一些时间
                            await browserFetcher.DownloadAsync(revisionInfo.Revision);
                        }
                        else
                        {
                            throw new Exception($"程序检测出 Chromium 浏览器(默认版本 {revisionInfo.Revision})无法更新!");
                        }
                    }
                    else
                    {
                        throw new Exception("程序运行目录下 Chromium 浏览器不可用。请开发人员检查 程序运行目录下 是否正确安装 Chromium 浏览器。");
                    }
                }
                #endregion

                #region 兼容 Windows7 / Windows Server 2008
                LaunchOptions launchOptions = default(LaunchOptions);
                // 这个判断是为了兼容 Windows7 和 Windows Server 2008
                if (OSHelper.IsWin7Under())
                {
                    launchOptions = new LaunchOptions
                    {
                        WebSocketFactory = async(uri, socketOptions, cancellationToken) =>
                        {
                            WebSocket client = SystemClientWebSocket.CreateClientWebSocket();
                            if (client is System.Net.WebSockets.Managed.ClientWebSocket managed)
                            {
                                managed.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                                await managed.ConnectAsync(uri, cancellationToken);
                            }
                            else
                            {
                                ClientWebSocket coreSocket = client as ClientWebSocket;
                                coreSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                                await coreSocket.ConnectAsync(uri, cancellationToken);
                            }
                            return client;
                        }
                    };
                }
                else
                {
                    launchOptions = new LaunchOptions();
                }
                #endregion

                #region 设置 Args 参数
                string[] argss = default(string[]);
                if (args != null && args.Length > 0)
                {
                    List <string> argsList = args.ToList <string>();
                    argsList.Add("--no-sandbox");
                    argss = argsList.ToArray();
                }
                else
                {
                    argss = new string[] { "--no-sandbox" };
                }
                launchOptions.Args = argss; // 这些参数将会传递给 Chromium
                #endregion

                #region 设置 IgnoredDefaultArgs 参数
                string[] defaultArgs = default(string[]);
                if (ignoredDefaultArgs != null && ignoredDefaultArgs.Length > 0)
                {
                    List <string> ignoredDefaultArgsList = ignoredDefaultArgs.ToList <string>();
                    ignoredDefaultArgsList.Add("--enable-automation");
                    defaultArgs = ignoredDefaultArgsList.ToArray();
                }
                else
                {
                    defaultArgs = new string[] { "--enable-automation" };
                }
                launchOptions.IgnoredDefaultArgs = defaultArgs; // 这些参数将被 Chromium 忽略
                #endregion

                launchOptions.Headless = !isDisplay; // Headless : true 是无头模式,无界面;false,有界面
                return launchOptions;
            }));
        }
        /// <summary>
        /// 测试无头浏览器
        /// </summary>
        /// <param name="isDisplay">是否显示界面</param>
        /// <returns></returns>
        private async Task TestHeadlessChromiumAsync(bool isDisplay = true)
        {
            this.Invoke(new Action(() =>
            {
                this.rTxt_log.AppendText("正在启动无头浏览器... \n");
            }));

            #region 兼容 Win7 及以下系统 (包含 Win7)

            LaunchOptions launchOptions;
            if (OSHelper.IsWin7Under())
            {
                launchOptions = new LaunchOptions
                {
                    WebSocketFactory = async(uri, socketOptions, cancellationToken) =>
                    {
                        var client = SystemClientWebSocket.CreateClientWebSocket();
                        if (client is System.Net.WebSockets.Managed.ClientWebSocket managed)
                        {
                            managed.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                            await managed.ConnectAsync(uri, cancellationToken);
                        }
                        else
                        {
                            var coreSocket = client as ClientWebSocket;
                            coreSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);
                            await coreSocket.ConnectAsync(uri, cancellationToken);
                        }
                        return(client);
                    }
                };
            }
            else
            {
                launchOptions = new LaunchOptions();
            }
            launchOptions.Headless = !isDisplay; // Headless true 是无头模式;false 是开发者模式,有界面

            #endregion

            string htmlContent = string.Empty;

            // 启动 Chromium 浏览器
            using (Browser browser = await Puppeteer.LaunchAsync(launchOptions))
            {
                this.Invoke(new Action(() =>
                {
                    this.rTxt_log.AppendText("无头浏览器已启动。 \n");
                }));

                // 新建一个标签页
                using (Page page = await browser.NewPageAsync())
                {
                    // 导航到 url 页
                    await page.GoToAsync(_testUrl);

                    string fileName = DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss");
                    // 保存截图
                    await page.ScreenshotAsync($"{SavePageDirectory}{fileName}.png");

                    // 获取并返回页面的 Html 内容
                    htmlContent = await page.GetContentAsync();

                    // 保存 Html 内容
                    WriteCreate($"{SavePageDirectory}{fileName}.html", htmlContent);

                    this.Invoke(new Action(() =>
                    {
                        this.rTxt_log.AppendText($"测试页面已保存成功。目录 ==> {SavePageDirectory} \n");
                    }));

                    // 界面停留,给开发人员看
                    await Task.Delay(14 * 1000);
                }
            }
        }