async Task ReceiveLoop()
        {
            ArraySegment <byte> buffer = new ArraySegment <byte>(new byte[RECEIVE_BUFFER_SIZE]);

            using (MemoryStream ms = new MemoryStream())
            {
                WebSocketReceiveResult result = null;
                bool isClosing = false;

                try
                {
                    while (socket.State == WebSocketState.Open)
                    {
                        // Reset memory stream for next message
                        ms.Position = 0;
                        ms.SetLength(0);

                        // Continue receiving data until a full message is read or the socket is no longer open.
                        do
                        {
                            try
                            {
                                // This call only throws WebSocketExceptions.
                                result = await socket.ReceiveAsync(buffer, abortCancellationSource.Token).ConfigureAwait(false);
                            }
                            catch (OperationCanceledException)
                            {
                                log.LogVerbose($"[ReceiveLoop] Socket aborted while receiving.");
                                break;
                            }
                            catch (WebSocketException wsex)
                            {
                                // Only two errors here should be InvalidState (if socket is aborted),
                                // or ConnectionClosedPrematurely (with an inner exception detailing what happened).

                                if (wsex.WebSocketErrorCode == WebSocketError.InvalidState)
                                {
                                    log.LogVerbose($"[ReceiveLoop] Socket was aborted while receiving message. code = {wsex.WebSocketErrorCode}");
                                }
                                else if (wsex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
                                {
                                    log.LogError($"[ReceiveLoop] Socket closed prematurely while receiving: code = {wsex.WebSocketErrorCode}");

                                    // Notify inherting object
                                    OnClosedPrematurely();
                                }
                                else
                                {
                                    log.LogError("[ReceiveLoop] Socket encountered error while receiving: " +
                                                 $"code = {wsex.WebSocketErrorCode}, error = {wsex}");

                                    // Notify inherting object
                                    OnClosedPrematurely();
                                }

                                break;
                            }

                            if (result.MessageType == WebSocketMessageType.Close)
                            {
                                // Server is disconnecting us
                                isClosing = true;

                                log.LogVerbose($"[ReceiveLoop] Received close: {result.CloseStatusDescription} " +
                                               $"{result.CloseStatus} ({(int)result.CloseStatus})");

                                if (socket.State == WebSocketState.Open ||
                                    socket.State == WebSocketState.CloseReceived ||
                                    socket.State == WebSocketState.CloseSent)
                                {
                                    try
                                    {
                                        log.LogVerbose("[ReceiveLoop] Completing close handshake with status NormalClosure (1000)...");

                                        // Complete the closing handshake
                                        await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", abortCancellationSource.Token)
                                        .ConfigureAwait(false);

                                        log.LogVerbose("[ReceiveLoop] Completed close handshake.");
                                    }
                                    catch (OperationCanceledException)
                                    {
                                        log.LogVerbose($"[ReceiveLoop] Socket aborted while closing.");
                                    }
                                    catch (Exception ex)
                                    {
                                        log.LogError($"[ReceiveLoop] Failed to complete closing handshake: {ex}");
                                    }
                                }
                                else
                                {
                                    log.LogVerbose("[ReceiveLoop] Close handshake completed by remote end.");
                                }

                                // Notify inheriting object
                                OnCloseReceived(result.CloseStatus.Value, result.CloseStatusDescription);
                                break;
                            }
                            else
                            {
                                // Data message, append buffer to memory stream
                                ms.Write(buffer.Array, 0, result.Count);
                            }
                        }while (socket.State == WebSocketState.Open && !result.EndOfMessage);

                        if (isClosing || socket.State == WebSocketState.Aborted)
                        {
                            break;
                        }

                        // Parse the message
                        string message = null;

                        try
                        {
                            message = await ParseMessage(result.MessageType, ms).ConfigureAwait(false);
                        }
                        catch (Exception ex)
                        {
                            log.LogError($"[ReceiveLoop] Failed to parse message: {ex}");
                        }

                        if (message != null)
                        {
                            if (DiscordApiData.TryParseJson(message, out DiscordApiData data))
                            {
                                try
                                {
                                    // Notify inheriting object that a payload has been received.
                                    await OnPayloadReceived(data).ConfigureAwait(false);
                                }
                                // Payload handlers can send other payloads which can result in two
                                // valid exceptions that we do not want to bubble up.
                                catch (InvalidOperationException)
                                {
                                    // Socket was closed between receiving a payload and handling it
                                    log.LogVerbose("Received InvalidOperationException from OnPayloadReceived, " +
                                                   "stopping receive loop...");

                                    break;
                                }
                                catch (DiscordWebSocketException dwex)
                                {
                                    if (dwex.Error == DiscordWebSocketError.ConnectionClosed)
                                    {
                                        // Socket was closed while a payload handler was sending another payload
                                        break;
                                    }
                                    else
                                    {
                                        // Unexpected error occured, we should only let it bubble up if the socket
                                        // is not open after the exception.
                                        if (socket.State == WebSocketState.Open)
                                        {
                                            log.LogError("[ReceiveLoop] Unexpected error from OnPayloadReceived: " +
                                                         $"code = {dwex.Error}, error = {dwex}");
                                        }
                                        else
                                        {
                                            // Don't log since that will be handled below
                                            throw;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                log.LogError($"[ReceiveLoop] Failed to parse JSON: \"{message}\"");
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    log.LogError($"[ReceiveLoop] Uncaught exception: {ex}");
                    OnClosedPrematurely();
                }
            }
        }
Ejemplo n.º 2
0
        /// <exception cref="DiscordHttpApiException"></exception>
        async Task <DiscordApiData> ParseResponse(HttpResponseMessage response, RateLimitHeaders rateLimitHeaders)
        {
            // Read response payload as string
            string json;

            if (response.StatusCode == HttpStatusCode.NoContent)
            {
                json = null;
            }
            else
            {
                json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            }

            // Attempt to parse the payload as JSON.
            DiscordApiData data;

            if (DiscordApiData.TryParseJson(json, out data))
            {
                if (response.IsSuccessStatusCode)
                {
                    // If successful, no more action is required.
                    return(data);
                }
                else
                {
                    string message = null;
                    DiscordHttpErrorCode errorCode = DiscordHttpErrorCode.None;

                    // Get the Discord-specific error code if it exists.
                    if ((int)response.StatusCode == 429)
                    {
                        errorCode = DiscordHttpErrorCode.TooManyRequests;
                    }
                    else if (data.ContainsKey("code"))
                    {
                        long?code = data.GetInt64("code");
                        if (code.HasValue)
                        {
                            errorCode = (DiscordHttpErrorCode)code;
                        }
                    }

                    // Get the message.
                    if (data.ContainsKey("content"))
                    {
                        IList <DiscordApiData> content = data.GetArray("content");

                        StringBuilder sb = new StringBuilder();
                        for (int i = 0; i < content.Count; i++)
                        {
                            sb.Append(content[i]);

                            if (i < content.Count - 1)
                            {
                                sb.Append(", ");
                            }
                        }

                        message = sb.ToString();
                    }
                    else if (data.ContainsKey("message"))
                    {
                        message = data.GetString("message");
                    }
                    else if (response.StatusCode == HttpStatusCode.BadRequest && data.Type == DiscordApiDataType.Container)
                    {
                        StringBuilder sb = new StringBuilder();
                        foreach (KeyValuePair <string, DiscordApiData> pair in data.Entries)
                        {
                            sb.Append($"{pair.Key}: ");

                            if (pair.Value.Type != DiscordApiDataType.Array)
                            {
                                // Shouldn't happen, but if it does then this error is not one we can parse.
                                // Set sb to null so that message ends up null and let the application get
                                // the raw JSON payload so they at least know what happened until we can
                                // implement the correct parser.
                                sb = null;
                                break;
                            }

                            bool addComma = false;
                            foreach (DiscordApiData errorData in pair.Value.Values)
                            {
                                if (addComma)
                                {
                                    sb.Append(", ");
                                }

                                sb.Append(errorData.ToString());

                                addComma = true;
                            }

                            sb.AppendLine();
                        }

                        message = sb?.ToString();
                    }

                    // Throw the appropriate exception
                    if (message != null) // If null, let the "unknown error" exception be thrown
                    {
                        if ((int)response.StatusCode == 429 && rateLimitHeaders != null)
                        {
                            throw new DiscordHttpRateLimitException(rateLimitHeaders, message, errorCode, response.StatusCode);
                        }
                        else
                        {
                            throw new DiscordHttpApiException(message, errorCode, response.StatusCode);
                        }
                    }
                }
            }

            throw new DiscordHttpApiException($"Unknown error. Response: {json}",
                                              DiscordHttpErrorCode.None, response.StatusCode);
        }