BlueprintAsyncAction Delegates not broadcasting from request lambda

(Using 5.6.1)

I’m trying to build a latent blueprint node (using UBlueprintAsyncActionBase) that makes an HTTP request and passes the response to blueprint.

I’m 99% sure this used to work but then I set the project aside for a couple of months and now it’s no longer working.

Here’s the code:

#include "CoreMinimal.h"
#include "Engine/CancellableAsyncAction.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "HttpModule.h"
#include "SendStartGGRequest.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBPOutputNodePin, FString,Response);

UCLASS()
class STARTGG_API USendStartGGRequest : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable, DisplayName = "Send Start.GG Request", meta = (WorldContext = "WorldContext", BlueprintInternalUseOnly = "true"))
	static USendStartGGRequest* SendStartGGRequest(const UObject* WorldContext, FString Query, TMap<FName, FString> Variables);
	

	/** A delegate called when the async action completes. */
	UPROPERTY(BlueprintAssignable)
	FBPOutputNodePin OnComplete;

	/** A delegate called when the async action fails. */
	UPROPERTY(BlueprintAssignable)
	FBPOutputNodePin OnFail;

	// Start UCancellableAsyncAction Functions
	virtual void Activate() override;
	// End UCancellableAsyncAction Functions
private:

	FString ProcessRequestResponse(const FString& ResponseContent);

	void HandleHttpResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);

	FString QueryText;
	TMap<FName, FString> QueryVariables;

	/** The timer handle. */
	FTimerHandle OngoingDelay;
};

#include "SendStartGGRequest.h"
#include "Engine.h"
#include "HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"

USendStartGGRequest* USendStartGGRequest::SendStartGGRequest(const UObject* WorldContext, FString Query, TMap<FName, FString> Variables)
{
	USendStartGGRequest* NewAction = NewObject<USendStartGGRequest>();
	NewAction->QueryText = Query;
	NewAction->QueryVariables = Variables;
	NewAction->RegisterWithGameInstance(WorldContext);
	return NewAction;
}

void USendStartGGRequest::Activate()
{
	// When the async action is ready to activate, set a timer using the world's FTimerManager.
	if (const UWorld* World = GetWorld())
	{
		FString IniLocation = FPaths::ProjectDir().Append("Config/DefaultStartGG").Append(".ini");
		FString RequestURL = "";
		FString AuthorizationKey = "";

		GConfig->GetString(TEXT("/Script/StartGG.StartGGSettings"), TEXT("RequestURL"),RequestURL, IniLocation);
		GConfig->GetString(TEXT("/Script/StartGG.StartGGSettings"), TEXT("APIKey"), AuthorizationKey, IniLocation);

		TSharedRef<IHttpRequest, ESPMode::ThreadSafe> pRequest = FHttpModule::Get().CreateRequest();

		pRequest->SetURL(RequestURL);
		pRequest->SetVerb(TEXT("POST"));
		pRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
		pRequest->SetHeader(TEXT("Authorization"), "Bearer " + AuthorizationKey);

		TSharedRef<FJsonObject> requestContent = MakeShareable(new FJsonObject);
		TSharedPtr<FJsonObject> queryVariables = MakeShareable(new FJsonObject);

		for (auto& variable : QueryVariables) {
			queryVariables->SetStringField(variable.Key.ToString(), variable.Value);
		}

		requestContent->SetStringField(TEXT("query"), QueryText);
		requestContent->SetObjectField(TEXT("variables"), queryVariables);

		FString ContentOutput;

		TSharedRef<TJsonWriter<>> writer = TJsonWriterFactory<>::Create(&ContentOutput);
		FJsonSerializer::Serialize(requestContent, writer);

		pRequest->SetContentAsString(ContentOutput);

		pRequest->OnProcessRequestComplete().BindLambda(
			[&](
				FHttpRequestPtr pRequest,
				FHttpResponsePtr pResponse,
				bool connectedSuccessfully) mutable {

					if (connectedSuccessfully) {

						OnComplete.Broadcast(pResponse->GetContentAsString());
					}
					else {
						switch (pRequest->GetStatus()) {
						case EHttpRequestStatus::Failed:
							UE_LOG(LogTemp, Error, TEXT("Connection failed."));
						default:
							UE_LOG(LogTemp, Error, TEXT("Request failed."));
						}

						OnFail.Broadcast("");
					}
			});

		pRequest->ProcessRequest();
	}
}

The request itself is working fine and I get the data I expect. The issue is that OnComplete.Broadcast() doesn’t trigger the corresponding blueprint node exec pin when it’s inside the lambda function so I can’t progress in the event graph once the request is complete.

I should note that I’m getting a similar issue using the HTTP request node from the Blueprint Http plugin (which 100% used to work).

Tried to replicate the issue on a fresh project, but setting up the request node (from the plugin) makes the editor crash when compiling the blueprint…

Not sure what I’m missing.

have you tried ensuring that OnComplete is on the game thread?

yeah, that didn’t help. Although I just realised that my testing method was the issue. I was using an editor-callable event to test my request and that seems to cause problems.

If I call the event on BeginPlay (for instance), everything works fine.

But last time I looked into it, calling the method from the details panel while the game is running was working. No longer.

Would be good to know if latent nodes are expected to work when called in the editor.

1 Like

Actually…

This doesn’t work when using the Remote Control API to call a function that eventually calls the the latent node…

And that is my main use case.

It definitely used to work, so I’m not sure what’s changed.