[C++] HTTP response returns nothing in a function call of a function

I am creating a class that calls an API. Everything is fine if I make a call in a basic function.

FHttpRequestRef Request = FHttpModule::Get().CreateRequest();
Request->SetURL(apiBaseUrl + "/api/url");
Request->SetVerb("POST");
Request->SetHeader("Authorization", "Bearer " + GetCurrentToken());
Request->SetHeader("Content-Type", "application/json");
Request->SetContentAsString("JSON_BOBY");
Request->OnProcessRequestComplete().BindUObject(this, &AClass::OnResponse);
Request->ProcessRequest();

But I created a static function to be called from the BP. But when I call another function from this one, the api receives the request but I don’t receive the answer for an unknown reason.

void AClass::StaticFunction(AClass* class, FString string01, float amount, FString string02) {
	class->Orders(string01, amount, string02);
}

void AClass::Orders(FString string01, float amount, FString string02) {

	FHttpRequestRef Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(apiBaseUrl + "/api/url");
	Request->SetVerb("POST");
	Request->SetHeader("Authorization", "Bearer " + GetCurrentToken());
	Request->SetHeader("Content-Type", "application/json");
	Request->SetContentAsString("JSON_BOBY");
	Request->OnProcessRequestComplete().BindUObject(this, &AClass::OnResponse);
	Request->ProcessRequest();

}

void AClass::OnResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{

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

	TSharedPtr<FJsonObject> ResponseObj;
	TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
	FJsonSerializer::Deserialize(Reader, ResponseObj);

	UE_LOG(LogTemp, Display, TEXT("Response %s"), *Response->GetContentAsString());

}

Thank you in advance if you have a lead or an answer.

If it’s a static function there is no this so you are most likely binding the completion callback to null or another object. Not sure what this actually points to in a static function, if it even compiles.

You can bind completion callback to a static function instead :

Request->OnProcessRequestComplete().BindStatic(&AClass::OnResponse);
1 Like

Unfortunately BindStatic does not work in this case

Ah yes I missed the part where you are passing the object as parameter, to call member functions within.

There’s something I don’t get though, if you are able to call object->Orders(arguments), what is the point of making a static function to be used like StaticOrders(object, arguments) ?

Something’s not right, can you show working call in blueprints and non-working call in blueprints ?

1 Like

I started by creating a static method to be called with the BP.

void AClass::StaticFunction(AClass* class, FString string01, float amount, FString string02) {
	class->Orders(string01, amount, string02);
}

But I can’t use BindUObject in this method which is static. But if this function is not static I could not call it with the BP.
So I created a new non-static function that is called in the static function.

You can call non-static functions from BP. But you need a reference to an object to call its functions.

I suspect you tried to work around the problem by first creating a static function and then passing an arbitrary variable to solve errors, but your variable doesn’t point to anything ?

You could most definitely to the same with a non-static functions. Mark Orders as BlueprintCallable and you’ll be able to call it when you drag off from your “Class” variable.

However since “Class” is null (unless I’m wrong), you’ll get a blueprint runtime error telling you that you are trying to call or access a null object reference.

C++ doesn’t spot any error and still manages to call the function due to how the language works, but it won’t trigger the completion callback because the passed in object was null.

In C++ it’s your job to check validity of pointers in some way, for example :

void AClass::StaticFunction(AClass* class, FString string01, float amount, FString string02)
{
    if (class)
        class->Orders(string01, amount, string02);
    else
        UE_LOG(LogTemp, Warning, TEXT("StaticFunction: class reference is null !!"));
}

Is there any specific reason to keep both Orders and OnResponse non-static ? I don’t see any reference to anything in there.

If you need to cascade the completion trigger down the line, I can think of several approaches.

One of them would be to create a wrapper object for the request, which is kind of what you started to do. The only part which I suspect you are missing, is to actually create an instance of that object whenever you need to do a request. In that case you don’t need any static function. Use SpawnActor or CreateObject to create your object, then call Orders within your object.

Another approach is to do the request in a static function, passing an object which contains a callback function (a base class or an interface with an event or dispatcher), bind completion with BindWeakLambda, and trigger your callback function from there.

If you give me a bit more info on how you expect to call the request and use its result, I can help set it up.

1 Like

Thank you @Chatouille for your time and your answers. Thank you for taking the time to explain in detail, it allows me to better understand my mistakes and my bad vision.

I would like to be able to call my function in any BluePrint. And I would like to have two outputs, when the result is positive and negative.

The Destroy Session node is a good example, it has two outputs depending on the state of the request.

Destroy Session

The goal is with this function, to call from any BP, the API and get a result expressed via an output. I hope I am precise and clear enough in my explanations.

Hmm latent actions are a bit specific. It requires creating an additional LatentAction object and bind pointers as output pins.

I can straight up give you an old implementation of mine that uses latent action.

It’s a bit more complex than not using latent action, but does roughly the same thing I described above.

In short, I created a wrapper actor to contain a request. When I call my static request method, I spawn the request actor and execute the request. Upon completion, I set a flag to true and the latent action knows to trigger the output execution pin.

// .h

class ABlueprintHttpRequest : public AActor
{
	GENERATED_UCLASS_BODY()

	TSharedPtr<IHttpRequest> Request;
	bool Done = false;

	bool Success = false;
	int32 Code = 0;
	FString Body;

	void ProcessGet(const FString& Url);

	void OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
};

UCLASS()
class UBlueprintHttp : public UBlueprintFunctionLibrary
{
	GENERATED_UCLASS_BODY()

	UFUNCTION(BlueprintCallable, meta = (
		Keywords = "http request get",
		Latent,
		WorldContext = "WorldContextObject",
		DefaultToSelf = "WorldContextObject",
		LatentInfo = "LatentInfo"
	), Category = "BlueprintHttp")
	static void HttpGet(UObject* WorldContextObject,
		/* in */ const FString& Url,
		/* out */ bool& Success,
		/* out */ FString& Body,
		/* out */ int32& Code,
		FLatentActionInfo LatentInfo
	);
};
//.cpp

class FBlueprintHttpAction : public FPendingLatentAction
{
private:
	ABlueprintHttpRequest* Req;

	// Output pins
	bool* pSuccess;
	int32* pCode;
	FString* pBody;

public:
	FName ExecutionFunction;
	int32 OutputLink;
	FWeakObjectPtr CallbackTarget;

	FBlueprintHttpAction(ABlueprintHttpRequest* Req, bool* pSuccess, int32* pCode, FString* pBody, const FLatentActionInfo& LatentInfo)
		: Req(Req)
		, pSuccess(pSuccess)
		, pCode(pCode)
		, pBody(pBody)
		, ExecutionFunction(LatentInfo.ExecutionFunction)
		, OutputLink(LatentInfo.Linkage)
		, CallbackTarget(LatentInfo.CallbackTarget)
	{
	}

	virtual void UpdateOperation(FLatentResponse& Response) override
	{
		if (Req)
		{
			if (Req->Done)
			{
				(*pSuccess) = Req->Success;
				(*pCode) = Req->Code;
				(*pBody) = Req->Body;
				Response.FinishAndTriggerIf(true, ExecutionFunction, OutputLink, CallbackTarget);

				// Request is not needed anymore, clean it up
				Req->Destroy();
				Req = nullptr;
			}
		}
		else
		{
			// Request wrapper got cleaned up (levelchange/exit) before request finished
			// Note that the underlying request is not level-bound and will finish anyways (unless exit?)
			Response.DoneIf(true);
		}
	}

	// Object owning this latent node got destroyed - useless to wait for result
	// Should we cancel the underlying request or let it finish ?
	virtual void NotifyObjectDestroyed() override
	{
		if (Req)
		{
			//Req->Cancel();
			Req->Destroy();
			Req = nullptr;
		}
	}

};

// Blueprint static call
void UBlueprintHttp::HttpGet(UObject* WorldContextObject,
	/* in */ const FString& Url,
	/* out */ bool& Success,
	/* out */ FString& Body,
	/* out */ int32& Code,
	FLatentActionInfo LatentInfo)
{
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject))
	{
		FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
		if (LatentActionManager.FindExistingAction<FBlueprintHttpAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL)
		{
			UE_LOG(LogTemp, Log, TEXT("[BlueprintHttp] Http GET %s"), *Url);

			FActorSpawnParameters SpawnParams;
			SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
			SpawnParams.bNoFail = true;
			auto Req = World->SpawnActor<ABlueprintHttpRequest>(SpawnParams);

			FBlueprintHttpAction* HttpAction = new FBlueprintHttpAction(Req, &Success, &Code, &Body, LatentInfo);
			LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, HttpAction);
			Req->ProcessGet(Url);
		}
	}
}

ABlueprintHttpRequest::ABlueprintHttpRequest(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	PrimaryActorTick.bCanEverTick = false;

	Request = FHttpModule::Get().CreateRequest();
	Request->SetHeader("User-Agent", "X-UnrealEngine-Agent");
	Request->SetHeader("Content-Type", "application/json");
	Request->SetHeader("Accept", "application/json");
}

void ABlueprintHttpRequest::ProcessGet(const FString& Url)
{
	Request->SetURL(Url);
	Request->SetVerb("GET");
	Request->OnProcessRequestComplete().BindUObject(this, &ABlueprintHttpRequest::OnResponseReceived);
	Request->ProcessRequest();
}

void ABlueprintHttpRequest::OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	// Happens when eg. hostname doesn't resolve
	if (!Response.IsValid())
	{
		Done = true;
		this->Request.Reset();
		return;
	}

	Success = bWasSuccessful;
	Code = Response->GetResponseCode();
	Body = Response->GetContentAsString();

	if (bWasSuccessful)
	{
		//...
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("[BlueprintHttpRequest] Request failed (%i)"), Code);
	}

	Done = true;
	this->Request.Reset();	// our reference is no longer necessary
}

This is old 4.15 code but it shows the mechanisms.

I wouldn’t really recommend doing a latent action though, as those come with a slew of pitfalls. I had to use latent action for specific reasons, but if I had to do it in a “normal” project I would do it differently :

  • I would still wrap the request in an object, although probably subclass UObject instead of AActor. In the wrapper I would add a blueprint-bindable event dispatcher for request completion. Upon completion, simply broadcast the dispatcher with results.

  • Don’t use a latent action. Instead, create the request wrapper object and return it to the caller (blueprint). Then blueprint is responsible for binding the dispatcher to some custom event.

1 Like

Searching further, I found the delegate BindLambda. This allows me to create a callback and have my different outputs. What do you think?

Request->OnProcessRequestComplete().BindLambda([Callback](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
  Callback.ExecuteIfBound(Response->GetContentAsString(), Response->GetResponseCode(), bWasSuccessful);
});

Yes this looks even better!

1 Like