UE5のBlueprintでGraphQLをリクエストする

# UE5で簡単なGraphQLのやり取りを行いたい

マーケットプレイスで検索すると、GraphQLを扱えるようになるコンテンツが見つかります

ただ、そこそこいい値段がするので、サーバーとの簡単な（認証など不要な）やり取りができるコードを書いてみました

## 環境
| ソフトウェア | バージョン | 
|:-----------|------------:|
| MacOS       | 10.15.7        | 
| UnrealEngine | 5.0.0 |
| Visual Studio Code |  1.61.2 |
| Xcode | 12.4 |
| .NET SDK |5.0.402|

## 基本設計

UObjectを継承して、Blueprint上でクエリー文の入力と、イベントによる結果の受信を行います
プロジェクト名はGraphqlTestとして進めます

## コード
`Build.cs`ファイルの`PublicDependencyModuleNames`に`Http`,`Json`,`JsonUtilities`を追加します

```C++:GraphqlTest.Build.cs
 PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Http", "Json", "JsonUtilities" });
```

```C++:GraphqlObject.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Http.h" // Http関連が使用できるように追加
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "GraphqlObject.generated.h"

/**
 * 
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FBlueprintAssignableSignature);

UCLASS(Blueprintable, BlueprintType)
class GRAPHQLTEST_API UGraphqlObject : public UObject
{
	GENERATED_BODY()

public:
	UGraphqlObject();

	FHttpModule *Http;

private:

public:
	// GraphQLのサーバをBlueprintから指定できるようにプロパティを公開
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Graphql")
	FString url;

	// 受信結果をBlueprintから参照できるように読み込み専用のプロパティを追加
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Graphql")
	FString ContentAsString;

	// 自身のインスタンスを作成するメソッド
	UFUNCTION(BlueprintPure, Category = "Graphql", meta = (DisplayName = "Create Object", Keywords = "Create Graphql Object", ShortTooltip = "Creates an empty Graphql Object"))
	static UGraphqlObject *Create();

	// HTTP通信を行ってレスポンスが返ってきた際のイベント処理
	void OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);

	// 受診後のイベントを発行するためのメソッド
	UPROPERTY(BlueprintAssignable, Category = "Dynamic Delegate Component")
	FBlueprintAssignableSignature OnResponseReceivedDispatcherCalled;

	// クエリーを発行するためのメソッド
	UFUNCTION(BlueprintCallable, Category = "Graphql")
	void Query(FString document);
};

```

```C++:GraphqlObject.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "GraphqlObject.h"

UGraphqlObject::UGraphqlObject() 
	: Super()
{
    Http = &FHttpModule::Get();
}

UGraphqlObject* UGraphqlObject::Create() {
    return NewObject<UGraphqlObject>();
}

void UGraphqlObject::OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
    if (!Response.IsValid())
    {
        GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, "No Valid");
    }
    else if (EHttpResponseCodes::IsOk(Response->GetResponseCode()))
    {
        ContentAsString = Response->GetContentAsString();

        TSharedPtr<FJsonObject> JsonObject;
        TSharedRef<TJsonReader<> > Reader = TJsonReaderFactory<>::Create(ContentAsString);

        // Jsonオブジェクトをデシリアライズ
        if (FJsonSerializer::Deserialize(Reader, JsonObject))
        {
            GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, "Received JSON String");

            //ここに、JSON StringをパースしてJSON Objectとして保持する工程を追加する

            OnResponseReceivedDispatcherCalled.Broadcast();
        }
    }
}

void UGraphqlObject::Query(FString document)
{
    // Jsonデータの作成
    TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
    JsonObject->SetStringField("query", document);
    // JsonStringにJson書き出し
    FString JsonString;
    TSharedRef<TJsonWriter<TCHAR> > JsonWriter = TJsonWriterFactory<>::Create(&JsonString);
    FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter);

    // Httpリクエストの作成
    TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
    Request->OnProcessRequestComplete().BindUObject(this, &UGraphqlObject::OnResponseReceived);
    Request->SetURL(url);
    Request->SetVerb("POST");
    Request->SetHeader(TEXT("User-Agent"), TEXT("X-UnrealEngine-Agent"));
    Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
    Request->SetHeader(TEXT("Accepts"), TEXT("application/json"));
    Request->SetContentAsString(JsonString);
    Request->ProcessRequest();
}
```

## Blueprint上での組み立て
![ノードのつなげ方.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601616/27972bf4-335e-b834-ba8a-b81e5463cdff.png)
図の上段が、BeginPlayから始まるGraphqlObjectの準備とイベント処理

図の下段は、クエリーを発行する処理
Queryの後にDelayなどがつながっているのはテスト用につなげたもので、不要です

![インスタンス化.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601616/9952a508-d6fa-7313-3ffe-58144dd7180b.png)
変数に自作したクラスをセットして、Blueprint上にドラッグ＆ドロップで配置

そこからノードをつなげていくことで、クエリーを実行できるが、createメソッドで作ったオブジェクトを、Blueprintの変数に再代入しないと、オブジェクトの要素にアクセスできません

UActorを継承すれば、こんなヘンテコなつなげ方をする必要はないのだけれど、Actorとしての機能は必要ないと考えて、UObjectを継承しています

### 追記(2021/10/29)
以下のようなノード接続が可能でした
![直接生成可能に.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601616/8e98f297-630d-c449-9c5e-fafd674b0c2a.png)


## JSONはそのままではBlueprintに出力できない

何かしらでラップしないとBlueprintでは扱えないようですが、ここでは触れません

今回は文字として出力しています

以下のソースが参考になると思います

https://github.com/Cl1608Ho/JsonBlueprint.git

## さいごに

マーケットプレイスにあるGraphQLが扱えるようになるものは、受信後の処理をイベントではなく、Latentノードというもので実装している

こちらの方がBlueprint上での取り扱いが直感的にできそう

## 参考
- [UE4でHTTP通信をしてみる – ②HTTP通信用クラスの作成 –](https://tech.pjin.jp/blog/2019/07/18/ue4_http_02/)
- [GraphQL Serving over HTTP](https://graphql.org/learn/serving-over-http/)