在 .Net 中使用 GraphQL 客户端库实施 AWS Appsync

2024-01-05

我正在尝试实现类似于此 python 示例但在 .net 中的应用程序同步订阅https://aws.amazon.com/blogs/mobile/appsync-websockets-python/ https://aws.amazon.com/blogs/mobile/appsync-websockets-python/

我使用 nuget 包 GraphQL.Client 开始此操作https://www.nuget.org/packages/GraphQL.Client https://www.nuget.org/packages/GraphQL.Client查询/突变的执行工作正常,就像自述文件中给出的那样https://github.com/graphql-dotnet/graphql-client https://github.com/graphql-dotnet/graphql-client但订阅不起作用。

我使用 GraphQL.Client 的代码:

using var graphQLClient = new GraphQLHttpClient("https://<MY-API-PATH>.appsync-realtime-api.<AWS-region>.amazonaws.com/graphql", new NewtonsoftJsonSerializer());

 graphQLClient.HttpClient.DefaultRequestHeaders.Add("host", "<API HOST without https or absolute path and 'realtime-' text in the api address>"); //As given in the python example

graphQLClient.HttpClient.DefaultRequestHeaders.Add("x-api-key", "<API KEY>");
var req= new GraphQLRequest
{
    Query = @"subscription SubscribeToEventComments{ subscribeToEventComments(eventId: 'test'){  content }}",
    Variables = new{}
};

IObservable<GraphQLResponse<Response>> subscriptionStream = graphQLClient.CreateSubscriptionStream<Response>(req, (Exception ex) =>
{
      Console.WriteLine("Error: {0}", ex.ToString());
});

var subscription = subscriptionStream.Subscribe(response =>
{
                Console.WriteLine($"Response'{Newtonsoft.Json.JsonConvert.SerializeObject(response)}' ");
},
ex =>
{
Console.WriteLine("Error{0}", ex.ToString());
});

它给出了异常“远程方在没有完成关闭握手的情况下关闭了 WebSocket 连接。”

堆栈跟踪:

在 System.Net.WebSockets.ManagedWebSocket.d__662.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.获取结果() 在 C:\Users\UserName\Source\repos\graphql-client\src\GraphQL.Client\Websocket\GraphQLHttpWebSocket.cs 中的 GraphQL.Client.Http.Websocket.GraphQLHttpWebSocket.d__40.MoveNext() 处:第 546 行

然后我尝试不使用此 nuget 并使用标准 websocket

没有 nuget 的代码:

static public async Task CallWebsocket()
        {
            try
            {
                _client = new ClientWebSocket();
                _client.Options.AddSubProtocol("graphql-ws");
                _client.Options.SetRequestHeader("host", "<HOST URL without wss but now with 'realtime' text in api url because otherwise we are getting SSL error>");
                _client.Options.SetRequestHeader("x-api-key", "<API KEY>");

                await _client.ConnectAsync(new Uri("https://<MY-APPSYNC_API_PATH>.appsync-realtime-api.<AWS-region>.amazonaws.com/graphql"), CancellationToken.None);
                await SendCommand();
                var docList = await Receive();
            }
            catch(Exception ex)
            {

            }
        }

       static  private async Task SendCommand()
        {
            ArraySegment<byte> outputBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes("'query' : 'subscription SubscribeToEventComments{ subscribeToEventComments(eventId: 'test'){  content }}'"));
            await _client.SendAsync(outputBuffer, WebSocketMessageType.Text, true, CancellationToken.None);
        }
        static private async Task<string> Receive()
        {
            var receiveBufferSize = 1536;
            byte[] buffer = new byte[receiveBufferSize];
            var result = await _client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            var resultJson = (new UTF8Encoding()).GetString(buffer);
            return resultJson;
        }

我遇到以下异常:

内部异常:“已建立的连接被主机中的软件中止。”

内部异常消息:“无法从传输连接读取数据:已建立的连接被主机中的软件中止。”

Message:“远程方在未完成关闭握手的情况下关闭了 WebSocket 连接。”

任何人都可以帮助正确实施。


Nuget 无法与 AppSync 订阅一起开箱即用,因此您需要为此编写自己的客户端代码,就像您在第二个(非 nuget)示例中尝试的那样。

现在,对于第二个示例,请再看一下蟒蛇的例子 https://aws.amazon.com/blogs/mobile/appsync-websockets-python/你的问题中提到。有几个步骤未包含在您的代码中。我将枚举所需的步骤,并尝试将它们从 python 代码移植到 C#(请注意,我手头没有 C# 环境,因此可能存在语法错误,但此代码应该非常接近您需要的内容)

步骤 0 - AppSync 端点

假设调用的结果aws appsync get-graphql-api --api-id example123456你的 API 是:

{
    "graphqlApi": {
        "name": "myNewRealTimeGraphQL-API",
        "authenticationType": "<API_KEY>",
        "tags": {},
        "apiId": "example123456",
        "uris": {
            "GRAPHQL": "https://abc.appsync-api.us-west-2.amazonaws.com/graphql",
            "REALTIME": "wss://abc.appsync-realtime-api.us-west-2.amazonaws.com/graphql"
        },
        "arn": "arn:aws:appsync:us-west-2: xxxxxxxxxxxx:apis/xxxxxxxxxxxx"
    }
}

第 1 步 - 构建连接 URL

第 2 步 - 连接到 WebSocket 端点

这包括按照 python 文章中提到的协议发送 connection_init 消息

第 3 步 - 根据协议等待 connection_ack

再次强调,这是按照协议进行的

第 4 步 - 注册订阅

第 5 步 - 发送突变

此步骤不在本响应中,但可以通过 AWS 控制台完成

第 6 步 - 等待“数据”消息

这些是 AppSync 发送的实时事件

第 7 步 - 取消注册订阅

第 8 步 - 断开连接

// These are declared at the same level as your _client

// This comes from the graphqlApi.uris.GRAPHQL in step 0, set as a var here for clarity
_gqlHost  = "abc.appsync-api.us-west-2.amazonaws.com";

// This comes from the graphqlApi.uris.REALTIME in step 0, set as a var here for clarity
_realtimeUri = "wss://abc.appsync-realtime-api.us-west-2.amazonaws.com/graphql";

_apiKey = "<API KEY>";

static public async Task CallWebsocket()
{
    
    // Step 1
    // This is JSON needed by the server, it will be converted to base64
    // (note: might be better to use something like Json.NET for this task)
    var header = var test = $@"{{
        ""host"":""{_gqlHost}"",
        ""x-api-key"": ""{_apiKey}""
    }}";

    // Now we need to encode the previous JSON to base64
    var headerB64 = System.Convert.ToBase64String(
        System.Text.Encoding.UTF8.GetBytes(header));

    UriBuilder connectionUriBuilder = new UriBuilder(_realtimeUri);
    connectionUriBuilder.Query = $"header={headerB64}&payload=e30=";
    
    try
    {
        _client = new ClientWebSocket();
        _client.Options.AddSubProtocol("graphql-ws");

        // Step 2
        await _client.ConnectAsync(connectionUriBuilder.Uri), CancellationToken.None);
        // Step 3
        await SendConnectionInit();
        await Receive();
    }
    catch(Exception ex)
    {

    }
}

static  private async Task SendConnectionInit()
{
    ArraySegment<byte> outputBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(@"{""type"": ""connection_init""}"));
    await _client.SendAsync(outputBuffer, WebSocketMessageType.Text, true, CancellationToken.None);
}

static  private async Task SendSubscription()
{
    // This detail is important, note that the subscription is a stringified JSON that will be embeded in the "data" field below
    var subscription = $@"{{\""query\"": \""subscription SubscribeToEventComments{{ subscribeToEventComments{{ content }} }}\"", \""variables\"": {{}} }}";
    
    var register = $@"{{
            ""id"": ""<SUB_ID>"",
            ""payload"": {{
                ""data"": ""{subscription}"",
                ""extensions"": {{
                    ""authorization"": {{
                        ""host"": ""{_gqlHost}"",
                        ""x-api-key"":""{_apiKey}""
                    }}
                }}
            }},
            ""type"": ""start""
        }}";
        
    // The output should look like below, note again the "data" field contains a stringified JSON that represents the subscription 
    /*
    {
        "id": "<SUB_ID>",
        "payload": {
            "data": "{\"query\": \"subscription SubscribeToEventComments{ subscribeToEventComments{ content}}\", \"variables\": {} }",
            "extensions": {
                "authorization": {
                    "host": "abc.appsync-api.us-west-2.amazonaws.com",
                    "x-api-key":"<API KEY>"
                }
            }
        },
        "type": "start"
    }
    */

    ArraySegment<byte> outputBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(register));
    await _client.SendAsync(outputBuffer, WebSocketMessageType.Text, true, CancellationToken.None);
}

static  private async Task Deregister()
{
    var deregister = $@"{{
                            ""type"": ""stop"",
                            ""id"": ""<SUB_ID>""
                        }}"
    ArraySegment<byte> outputBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(deregister));
    await _client.SendAsync(outputBuffer, WebSocketMessageType.Text, true, CancellationToken.None);
}

static private async Task Receive()
{
    while (_socket.State == WebSocketState.Open)
    {
        ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[8192]);
        WebSocketReceiveResult result= null;
        using (var ms = new MemoryStream())
        {
            // This loop is needed because the server might send chunks of data that need to be assembled by the client
            // see: https://stackoverflow.com/questions/23773407/a-websockets-receiveasync-method-does-not-await-the-entire-message
            do
            {
                result = await socket.ReceiveAsync(buffer, CancellationToken.None);
                ms.Write(buffer.Array, buffer.Offset, result.Count);
            }
            while (!result.EndOfMessage);

            ms.Seek(0, SeekOrigin.Begin);

            using (var reader = new StreamReader(ms, Encoding.UTF8))
            {
                // convert stream to string
                var message = reader.ReadToEnd();
                Console.WriteLine(message)
                // quick and dirty way to check response
                if (message.Contains("connection_ack"))
                {
                    // Step 4
                    await SendSubscription();
                } else if (message.Contains("data"))  // Step 6
                {
                    // Step 7 
                    await Deregister();
                    // Step 8
                    await _client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
                }
            }
        }
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 .Net 中使用 GraphQL 客户端库实施 AWS Appsync 的相关文章

  • C# 和月历,选择多个日期

    我正在制作一个程序 可以帮助人们用 C 为某个部门 预订 订单 他们需要能够选择不同月份的多个日期 我更愿意拥有它 这样他们就可以单击一个日期 然后按住 Shift 键单击另一个日期以选择这两个日期之间的所有日期 并控制单击以进行单选 取消
  • C# Outlook 从收件人获取 CompanyName 属性

    我目前正在使用 C 编写 Outlook 2010 AddIn 我想要的是从我从 AppointmentItem 中提取的 Recipient 对象中获取 CompanyName 属性 因此 有了 AppointmentItem 的收件人
  • C++中的类要具备什么条件才能成为容器?

    我是 C 编程新手 偶然发现了这个术语containers举例如下vector deque map etc 一个企业的最低要求应该是什么class应该满足被称为container in C 我将从 范围 这个概念开始 Range 只有两个方
  • 在 Mac OS X 上安装 libxml2 时出现问题

    我正在尝试在我的 Mac 操作系统 10 6 4 上安装 libxml2 我实际上正在尝试在 Python 中运行 Scrapy 脚本 这需要我安装 Twisted Zope 现在还需要安装 libxml2 我已经下载了最新版本 2 7 7
  • 以下 PLINQ 代码没有改进

    我没有看到使用以下代码的处理速度有任何改进 IEnumerable
  • C# Winforms Designer 无法打开,因为它无法在同一程序集中找到类型

    我收到以下错误 找不到类型 My Special UserControl 请确保引用包含此类型的程序集 如果此类型是您的开发项目的一部分 请确保已使用当前平台或任何 CPU 的设置成功构建该项目 但没有任何意义的是My Special Us
  • 提升mapped_file_source、对齐方式和页面大小

    我正在尝试在性能很重要的上下文中解析一些大小高达几百兆字节的文本文件 因此我使用 boostmapped file source 解析器期望源以空字节终止 因此我想检查文件大小是否是页面大小的精确倍数 如果是 则使用较慢的非内存映射方法 我
  • 名称查找、实例化点 (POI) 和基本类型

    以下代码针对 X 进行编译 但不适用于 double struct X void foo double void foo X namespace NN struct A void foo A foo double error foo not
  • 检测 TextBox 中的 Tab 键按下

    I am trying to detect the Tab key press in a TextBox I know that the Tab key does not trigger the KeyDown KeyUp or the K
  • 为什么 std::function 不是有效的模板参数,而函数指针却是?

    我已经定义了名为的类模板CallBackAtInit其唯一目的是在初始化时调用函数 构造函数 该函数在模板参数中指定 问题是模板不接受std function作为参数 但它们接受函数指针 为什么 这是我的代码 include
  • 如何设置消息队列的所有者?

    System Messaging MessageQueue 类不提供设置队列所有权的方法 如何以编程方式设置 MSMQ 消息队列的所有者 简短的答案是 p invoke 对 windows api 函数的调用MQSetQueueSecuri
  • 从点云检测平面集

    我有一组点云 我想测试3D房间中是否有角落 所以我想讨论一下我的方法 以及在速度方面是否有更好的方法 因为我想在手机上测试它 我将尝试使用霍夫变换来检测线 然后我将尝试查看是否有三条线相交 并且它们也形成了两个相交的平面 如果点云数据来自深
  • 在 C++ 代码 gdb 中回溯指针

    我在运行 C 应用程序时遇到段错误 在 gdb 中 它显示我的一个指针位置已损坏 但我在应用程序期间创建了 10 万个这样的对象指针 我怎样才能看到导致崩溃的一个 我可以在 bt 命令中执行任何操作来查看该指针的生命周期吗 谢谢 鲁奇 据我
  • 测验;这个编译了吗?如果是的话它会返回什么(我知道答案)

    我最近发现这个错字 if name find string npos 显然开发者的意思是输入 if name find string npos 但令我惊讶的是发现错误甚至编译 Wall Werror 没有尝试过 pedantic 那么 咖啡
  • C++ 模板可以提供 N 个给定类的公共父类吗?

    我正在寻找一个 C 模板 它可以找到一组给定类的共同父级 例如 class Animal class Mammal public Animal class Fish public Animal class Cat public Mammal
  • 在二进制数据文件的标头中放入什么

    我有一个模拟 可以读取我们创建的大型二进制数据文件 10 到 100 GB 出于速度原因 我们使用二进制 这些文件依赖于系统 是从我们运行的每个系统上的文本文件转换而来的 所以我不关心可移植性 当前的文件是 POD 结构的许多实例 使用 f
  • 解释这段代码的工作原理;子进程如何返回值以及在哪里返回值?

    我不明白子进程如何返回该值以及返回给谁 输出为 6 7 问题来源 http www cs utexas edu mwalfish classes s11 cs372h hw sol1 html http www cs utexas edu
  • 值和类型的简洁双向静态 1:1 映射

    我将从我想象如何使用我想要创建的代码开始 它不必完全像这样 但它是我在标题中所说的 简洁 的一个很好的例子 就我而言 它是将类型映射到相关的枚举值 struct bar foo
  • 使用 IdentityDbContext 和 Code First 自动迁移表位置和架构的实体框架?

    我正在尝试使用 IdentityDbContext 类设置自动迁移更新 并将更改传播到整个数据库的实际 DbContext 在进入代码之前 在使用自动迁移实现 IdentityDbContext 时 我收到此错误 影响迁移历史系统表位置的自
  • MSVC编译器下使用最大成员初始化联合

    我正在尝试初始化一个LARGE INTEGER在 C 库中为 0 确切地说是 C 03 以前 初始化是 static LARGE INTEGER freq 0 在 MinGW 下它产生了一个警告 缺少成员 LARGE INTEGER Hig

随机推荐