Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Proper usage of HTTP within UE4

NotificationsYou must be signed in to change notification settings

nsjames/UE4_Tutorial_Http

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 

Repository files navigation


Http requests in UE4 are fairly straight forward, however here are a few pitfalls and suggestions:

  • UE4 only accepts GET/PUT/POST requests. This means you can not send DELETE/PATCH requests.
  • You can write your own serializers/deserializers, but I highly recommend the use ofUSTRUCT() andFJsonObjectConverter
  • I find it easiest to use the service as an Actor child. You should spawn it into the level once and reference it.

Setup


Before you start make sure you have included the required dependencies.

# Path: Source/YourProject/YourProject.Build.csPrivateDependencyModuleNames.AddRange(new string[] { "Http", "Json", "JsonUtilities" });
  • Http is our trusty ue4 http implementation.
  • Json is the json conversion library
  • JsonUtilities has theFJsonObjectConverter we will be using to convert Json data to Struct data


At this point you could probably just grab the files and go over them, but here's a detailed explanation as well as some reasoning behind design decisions and a friendly reminder to keep code clean.


Table Of Contents


USTRUCTS()

FRequest_Login andFResponse_Login are both used to pass data back and forth betweenUE4 and yourBack-End Server.I wont be touching on back-end servers but I will be showing theJSON that will be sent and returned.

FRequest_Login holds theemail andpassword that we are using to log into our account.

USTRUCT()struct FRequest_Login {GENERATED_BODY()UPROPERTY() FString email;UPROPERTY() FString password;FRequest_Login() {}};

JSON EXAMPLE: { "email":"some@email.com", "password":"strongpassword" }

FResponse_Login holds the returned response from the login request.

USTRUCT()struct FResponse_Login {GENERATED_BODY()UPROPERTY() int id;UPROPERTY() FString name;UPROPERTY() FString hash;FResponse_Login() {}};

JSON EXAMPLE: { "id":1, "name":"Batman", "hash":"asdf-qwer-dfgh-erty" }

Note:You should provide a 'hash' property on every player login success. It will be used to verify their account on every subsequent request; since APIs dont hold state.


Some important variables:

  • FHttpModule* Http; - Holds a reference to UE4's Http implementation. It's used to create request objects.
  • FString ApiBaseUrl -You should replace this with your actual API url.
  • FString AuthorizationHeader - This is thekey for the Authentication header. Your back-end might expect a different form of this such asX-Auth-Token,X-Requested-With or something similar.


Internal Methods:

These are just some methods that you can use to build eloquently written api calls.

    TSharedRef<IHttpRequest> RequestWithRoute(FString Subroute);void SetRequestHeaders(TSharedRef<IHttpRequest>& Request);

BothRequestWithRoute andSetRequestHeaders are used to initializeHttp Request Objects.They shouldn't be called directly, only through the methods below.

TSharedRef<IHttpRequest> GetRequest(FString Subroute);TSharedRef<IHttpRequest> PostRequest(FString Subroute, FString ContentJsonString);

GetRequest andPostRequest are the proper methods to call.I intentionally left outPutRequest so that you may implement it using the same structure.

You might be askingWhy not just have one method that accepts a Verb? - The simple answer is that inserting a string into a parameter is not only sloppy, but will also add error checking and useless complexity to a very simple method.

Let's take a look at the implementations ofPostRequest,RequestWithRoute andSetRequestHeaders

TSharedRef<IHttpRequest> AHttpService::PostRequest(FString Subroute, FString ContentJsonString) {TSharedRef<IHttpRequest> Request = RequestWithRoute(Subroute);Request->SetVerb("POST");Request->SetContentAsString(ContentJsonString);return Request;}TSharedRef<IHttpRequest> AHttpService::RequestWithRoute(FString Subroute) {TSharedRef<IHttpRequest> Request = Http->CreateRequest();Request->SetURL(ApiBaseUrl + Subroute);SetRequestHeaders(Request);return Request;}void AHttpService::SetRequestHeaders(TSharedRef<IHttpRequest>& Request) {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->SetHeader(AuthorizationHeader, AuthorizationHash);}

As you can see,PostRequest is very simple and usesRequestWithRoute to build itself, keeping everything nice and clean.The flow forPostRequest goes as follows:

  • GetRequest Object with asubroute and set it'sHeaders
  • Set the Verb toPOST
  • Set theRequestObjects's Content to aJson formatted string
  • Return theRequestObject


Sending a Request:

    void Send(TSharedRef<IHttpRequest>& Request);

This is really just a semantically named method for cleanliness. As you can see from the code below it really doesn't do much besides clean up the naming conventions and make the code more readable.

void AHttpService::Send(TSharedRef<IHttpRequest>& Request) {Request->ProcessRequest();}


Checking for valid Responses:

ResponseIsValid is used to deeply check if a response is valid.

  • !bWasSuccessful is returned from the Http request made by UE4. It's the first check because if it fails no further information will be given.
  • !Response.IsValid() is also returned from the UE4 request, and means that most likely the request succeeded, but the response can't be parsed.
  • If theResponseCode is notOk ( 200 ) then we will also return false, as well as log out the code returned.
bool AHttpService::ResponseIsValid(FHttpResponsePtr Response, bool bWasSuccessful) {if (!bWasSuccessful || !Response.IsValid()) return false;if (EHttpResponseCodes::IsOk(Response->GetResponseCode())) return true;else {UE_LOG(LogTemp, Warning, TEXT("Http Response returned error code: %d"), Response->GetResponseCode());return false;}}


Serialization and Deserialization

We're going to be usingFJsonObjectConverter to convert json to scructs and structs to json.Like I said above I suggest you useFJsonObjectConverter.Here are some reasons why:

  • Support for TArray, even TArray
  • Support for Enum from string/int ( for example a json of { "itemType":"Weapon" } will becomeEItemType itemType = EItemType::Weapon;
  • Direct conversion into a USTRUCT() which is garbage collected.

Let's look at the two methods that handle serialization and deserialization.

Get Json String From Struct:

template <typename StructType>void AHttpService::GetJsonStringFromStruct(StructType FilledStruct, FString& StringOutput) {FJsonObjectConverter::UStructToJsonObjectString(StructType::StaticStruct(), &FilledStruct, StringOutput, 0, 0);}

This method gets a Json Formatted String from a struct of type and binds it to theStringOutput.We see this in action in theLogin() method.

Get Struct From Json String:

template <typename StructType>void AHttpService::GetStructFromJsonString(FHttpResponsePtr Response, StructType& StructOutput) {StructType StructData;FString JsonString = Response->GetContentAsString();FJsonObjectConverter::JsonObjectStringToUStruct<StructType>(JsonString, &StructOutput, 0, 0);}

This method does the exact opposite ofGetJsonStringFromStruct() and binds a Struct using the Json Formatted String to theStructOutput.We see this in action in theLoginResponse() method.


Okay! Let's look at some real world Http Requests!

I've included a simple login example in the file as well just to illustrate how this all comes together nicely and neatly.

For the Login (Request)

void AHttpService::Login(FRequest_Login LoginCredentials) {FString ContentJsonString;GetJsonStringFromStruct<FRequest_Login>(LoginCredentials, ContentJsonString);TSharedRef<IHttpRequest> Request = PostRequest("user/login", ContentJsonString);Request->OnProcessRequestComplete().BindUObject(this, &AHttpService::LoginResponse);Send(Request);}
  • FString ContentJsonString holds the returned json from theGetJsonStringFromStruct method.
  • TSharedRef<IHttpRequest> Request holds the RequestObject that we get from thePostRequest method. Note that we are passing in a subroute relative to theApiBaseUrl we put into the.h file.
  • Request->OnProcessRequestComplete().BindUObject(this, &AHttpService::LoginResponse); is highly important here. We use this to bind the Resquest's response to a method.
  • Send(Request); does just what it says. Hence the nicely semantic naming convention.

Here's an example of the method being called:

CALLED FROM BeginPlay():    FRequest_Login LoginCredentials;LoginCredentials.email = TEXT("asdf@asdf.com");LoginCredentials.password = TEXT("asdfasdf");Login(LoginCredentials);

For the Login (Response)

void AHttpService::LoginResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) {if (!ResponseIsValid(Response, bWasSuccessful)) return;FResponse_Login LoginResponse;GetStructFromJsonString<FResponse_Login>(Response, LoginResponse);SetAuthorizationHash(LoginResponse.hash);UE_LOG(LogTemp, Warning, TEXT("Id is: %d"), LoginResponse.id);UE_LOG(LogTemp, Warning, TEXT("Name is: %s"), *LoginResponse.name);}

Let's take a moment to look at theMandatory parameters.

  • FHttpRequestPtr Request
  • FHttpResponsePtr Response
  • bool bWasSuccessful - If the response was successful at all. ( Will fail for instance if the server is down. )

You don't have to worry about passing these in, they are passed automatically by UE4.

    if (!ResponseIsValid(Response, bWasSuccessful)) return;

We don't want to continue if the response is bad, so it's good practice to run this method.

    FResponse_Login LoginResponse;    GetStructFromJsonString<FResponse_Login>(Response, LoginResponse);

Here we're binding the Json Response into our customFResponse_Login Struct.

NoteA struct will simply not fill itself out if there are properties missing. Keep that in mind.

    SetAuthorizationHash(LoginResponse.hash);

We can set the hash for further requests here. Really though, we should be passing it back to the specificPlayer and bind it on each request, otherwiseevery player will be the same user.


Passing Back Data

Here's how to pass back some data after the HTTP Request has succeeded.Let's take ourLogin andLoginResponse and revamp them a bit.

header file

    void Login(FRequest_Login LoginCredentials, ACustomPlayerState* PlayerState);void LoginResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, ACustomPlayerState* PlayerState);

cpp file

void AHttpService::Login(FRequest_Login LoginCredentials, ACustomPlayerState* PlayerState) {FString ContentJsonString;GetJsonStringFromStruct<FRequest_Login>(LoginCredentials, ContentJsonString);TSharedRef<IHttpRequest> Request = PostRequest("user/login", ContentJsonString);Request->OnProcessRequestComplete().BindUObject(this, &AHttpService::LoginResponse, PlayerState);Send(Request);}void AHttpService::LoginResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, ACustomPlayerState* PlayerState) {if (!ResponseIsValid(Response, bWasSuccessful)) return;FResponse_Login LoginResponse;GetStructFromJsonString<FResponse_Login>(Response, LoginResponse);    PlayerState->PlayerLoginSuccessful(LoginResponse);}

Notice thePlayerState inside of theRequest->OnProcessRequestComplete().BindUObject(this, &AHttpService::LoginResponse, PlayerState); now.

We're passing a reference so the player state into the delegate to be used when the request finishes.

For the sake of clean code you should not be doing any non-http logic here.Pass your response data somewhere else and handle it there. APIs tend to be quite large and if you put all of your logic inside of your HttpService it will be too large to handle in the future.

About

Proper usage of HTTP within UE4

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages


[8]ページ先頭

©2009-2026 Movatter.jp