A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
VaRestRequestJSON.cpp
Go to the documentation of this file.
1// Copyright 2014-2019 Vladimir Alyamkin. All Rights Reserved.
2
3#include "VaRestRequestJSON.h"
4
5#include "VaRestDefines.h"
6#include "VaRestJsonObject.h"
7#include "VaRestJsonValue.h"
8#include "VaRestLibrary.h"
9#include "VaRestSettings.h"
10
11#include "Engine/Engine.h"
12#include "Engine/EngineTypes.h"
13#include "Engine/LatentActionManager.h"
14#include "Engine/World.h"
15#include "Interfaces/IHttpResponse.h"
16#include "Json.h"
17#include "Runtime/Launch/Resources/Version.h"
18
19FString UVaRestRequestJSON::DeprecatedResponseString(TEXT("DEPRECATED: Please use GetResponseContentAsString() instead"));
20
21template <class T>
23{
24 UObject* Obj = Request.Get();
25 if (Obj != nullptr)
26 {
27 ((UVaRestRequestJSON*)Obj)->Cancel();
28 }
29}
30
31UVaRestRequestJSON::UVaRestRequestJSON(const class FObjectInitializer& PCIP)
32 : Super(PCIP)
33 , BinaryContentType(TEXT("application/octet-stream"))
34{
35 ContinueAction = nullptr;
36
39
40 ResetData();
41}
42
47
49{
50 CustomVerb = Verb;
51}
52
57
58void UVaRestRequestJSON::SetBinaryContentType(const FString& ContentType)
59{
60 BinaryContentType = ContentType;
61}
62
63void UVaRestRequestJSON::SetBinaryRequestContent(const TArray<uint8>& Bytes)
64{
65 RequestBytes = Bytes;
66}
67
69{
70 StringRequestContent = Content;
71}
72
73void UVaRestRequestJSON::SetHeader(const FString& HeaderName, const FString& HeaderValue)
74{
75 RequestHeaders.Add(HeaderName, HeaderValue);
76}
77
79// Destruction and reset
80
86
88{
89 if (RequestJsonObj != nullptr)
90 {
92 }
93 else
94 {
95 RequestJsonObj = NewObject<UVaRestJsonObject>();
96 }
97
98 // See issue #90
99 // HttpRequest = FHttpModule::Get().CreateRequest();
100
101 RequestBytes.Empty();
102 StringRequestContent.Empty();
103}
104
106{
107 if (ResponseJsonObj != nullptr)
108 {
110 }
111 else
112 {
113 ResponseJsonObj = NewObject<UVaRestJsonObject>();
114 }
115
116 if (ResponseJsonValue != nullptr)
117 {
119 }
120 else
121 {
122 ResponseJsonValue = NewObject<UVaRestJsonValue>();
123 }
124
125 ResponseHeaders.Empty();
126 ResponseCode = -1;
127 ResponseSize = 0;
128
129 bIsValidJsonResponse = false;
130
131 // #127 Reset string to deprecated state
133}
134
136{
137 ContinueAction = nullptr;
138
140}
141
143// JSON data accessors
144
150
152{
153 if (JsonObject == nullptr)
154 {
155 UE_LOG(LogVaRest, Error, TEXT("%s: Provided JsonObject is nullptr"), *VA_FUNC_LINE);
156 return;
157 }
158
159 RequestJsonObj = JsonObject;
160}
161
167
169{
170 if (JsonObject == nullptr)
171 {
172 UE_LOG(LogVaRest, Error, TEXT("%s: Provided JsonObject is nullptr"), *VA_FUNC_LINE);
173 return;
174 }
175
176 ResponseJsonObj = JsonObject;
177}
178
184
186// Response data access
187
189{
190 return HttpRequest->GetURL();
191}
192
197
202
204{
205 return ResponseCode;
206}
207
208FString UVaRestRequestJSON::GetResponseHeader(const FString& HeaderName)
209{
210 FString Result;
211
212 FString* Header = ResponseHeaders.Find(HeaderName);
213 if (Header != nullptr)
214 {
215 Result = *Header;
216 }
217
218 return Result;
219}
220
222{
223 TArray<FString> Result;
224 for (TMap<FString, FString>::TConstIterator It(ResponseHeaders); It; ++It)
225 {
226 Result.Add(It.Key() + TEXT(": ") + It.Value());
227 }
228 return Result;
229}
230
232// URL processing
233
234void UVaRestRequestJSON::SetURL(const FString& Url)
235{
236 // Be sure to trim URL because it can break links on iOS
237 FString TrimmedUrl = Url;
238
239 TrimmedUrl.TrimStartInline();
240 TrimmedUrl.TrimEndInline();
241
242 HttpRequest->SetURL(TrimmedUrl);
243}
244
245void UVaRestRequestJSON::ProcessURL(const FString& Url)
246{
247 SetURL(Url);
249}
250
251void UVaRestRequestJSON::ApplyURL(const FString& Url, UVaRestJsonObject*& Result, UObject* WorldContextObject, FLatentActionInfo LatentInfo)
252{
253 // Be sure to trim URL because it can break links on iOS
254 FString TrimmedUrl = Url;
255
256 TrimmedUrl.TrimStartInline();
257 TrimmedUrl.TrimEndInline();
258
259 HttpRequest->SetURL(TrimmedUrl);
260
261 // Prepare latent action
262 if (UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject))
263 {
264 FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
265 FVaRestLatentAction<UVaRestJsonObject*>* Kont = LatentActionManager.FindExistingAction<FVaRestLatentAction<UVaRestJsonObject*>>(LatentInfo.CallbackTarget, LatentInfo.UUID);
266
267 if (Kont != nullptr)
268 {
269 Kont->Cancel();
270 LatentActionManager.RemoveActionsForObject(LatentInfo.CallbackTarget);
271 }
272
273 LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, ContinueAction = new FVaRestLatentAction<UVaRestJsonObject*>(this, Result, LatentInfo));
274 }
275
277}
278
280{
281 if (HttpRequest->GetURL().Len() == 0)
282 {
283 UE_LOG(LogVaRest, Error, TEXT("Request execution attempt with empty URL"));
284 return;
285 }
286
288}
289
291{
292 // Set verb
293 switch (RequestVerb)
294 {
296 HttpRequest->SetVerb(TEXT("GET"));
297 break;
298
300 HttpRequest->SetVerb(TEXT("POST"));
301 break;
302
304 HttpRequest->SetVerb(TEXT("PUT"));
305 break;
306
308 HttpRequest->SetVerb(TEXT("DELETE"));
309 break;
310
312 HttpRequest->SetVerb(CustomVerb);
313 break;
314
315 default:
316 break;
317 }
318
319 // Set content-type
320 switch (RequestContentType)
321 {
323 {
324 HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded"));
325
326 FString UrlParams = "";
327 uint16 ParamIdx = 0;
328
329 // Loop through all the values and prepare additional url part
330 for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt)
331 {
332 FString Key = RequestIt.Key();
333 FString Value = RequestIt.Value().Get()->AsString();
334
335 if (!Key.IsEmpty() && !Value.IsEmpty())
336 {
337 UrlParams += ParamIdx == 0 ? "?" : "&";
338 UrlParams += UVaRestLibrary::PercentEncode(Key) + "=" + UVaRestLibrary::PercentEncode(Value);
339 }
340
341 ParamIdx++;
342 }
343
344 // Apply params
345 HttpRequest->SetURL(HttpRequest->GetURL() + UrlParams);
346
347 // Add optional string content
348 if (!StringRequestContent.IsEmpty())
349 {
350 HttpRequest->SetContentAsString(StringRequestContent);
351 }
352
353 // Check extended log to avoid security vulnerability (#133)
354 if (UVaRestLibrary::GetVaRestSettings()->bExtendedLog)
355 {
356 UE_LOG(LogVaRest, Log, TEXT("%s: Request (urlencoded): %s %s %s %s"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams, *StringRequestContent);
357 }
358 else
359 {
360 UE_LOG(LogVaRest, Log, TEXT("%s: Request (urlencoded): %s %s (check bExtendedLog for additional data)"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL());
361 }
362
363 break;
364 }
366 {
367 HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded"));
368
369 FString UrlParams = "";
370 uint16 ParamIdx = 0;
371
372 // Add optional string content
373 if (!StringRequestContent.IsEmpty())
374 {
375 UrlParams = StringRequestContent;
376 }
377 else
378 {
379 // Loop through all the values and prepare additional url part
380 for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt)
381 {
382 FString Key = RequestIt.Key();
383 FString Value = RequestIt.Value().Get()->AsString();
384
385 if (!Key.IsEmpty() && !Value.IsEmpty())
386 {
387 UrlParams += ParamIdx == 0 ? "" : "&";
388 UrlParams += UVaRestLibrary::PercentEncode(Key) + "=" + UVaRestLibrary::PercentEncode(Value);
389 }
390
391 ParamIdx++;
392 }
393 }
394
395 // Apply params
396 HttpRequest->SetContentAsString(UrlParams);
397
398 // Check extended log to avoid security vulnerability (#133)
399 if (UVaRestLibrary::GetVaRestSettings()->bExtendedLog)
400 {
401 UE_LOG(LogVaRest, Log, TEXT("%s: Request (url body): %s %s %s"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams);
402 }
403 else
404 {
405 UE_LOG(LogVaRest, Log, TEXT("%s: Request (url body): %s %s (check bExtendedLog for additional data)"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL());
406 }
407
408 break;
409 }
411 {
412 HttpRequest->SetHeader(TEXT("Content-Type"), BinaryContentType);
413 HttpRequest->SetContent(RequestBytes);
414
415 UE_LOG(LogVaRest, Log, TEXT("Request (binary): %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL());
416
417 break;
418 }
420 {
421 HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
422
423 // Serialize data to json string
424 FString OutputString;
425 TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
426 FJsonSerializer::Serialize(RequestJsonObj->GetRootObject(), Writer);
427
428 // Set Json content
429 HttpRequest->SetContentAsString(OutputString);
430
431 if (UVaRestLibrary::GetVaRestSettings()->bExtendedLog)
432 {
433 UE_LOG(LogVaRest, Log, TEXT("Request (json): %s %s %sJSON(%s%s%s)JSON"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), LINE_TERMINATOR, LINE_TERMINATOR, *OutputString, LINE_TERMINATOR);
434 }
435 else
436 {
437 UE_LOG(LogVaRest, Log, TEXT("Request (json): %s %s (check bExtendedLog for additional data)"), *HttpRequest->GetVerb(), *HttpRequest->GetURL());
438 }
439
440 break;
441 }
443 {
444 UE_LOG(LogVaRest, Log, TEXT("Request (none): %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL());
445
446 break;
447 }
448 default:
449 break;
450 }
451
452 // Apply additional headers
453 for (TMap<FString, FString>::TConstIterator It(RequestHeaders); It; ++It)
454 {
455 HttpRequest->SetHeader(It.Key(), It.Value());
456 }
457
458 // Bind event
459 HttpRequest->OnProcessRequestComplete().BindUObject(this, &UVaRestRequestJSON::OnProcessRequestComplete);
460
461 // Execute the request
462 HttpRequest->ProcessRequest();
463}
464
466// Request callbacks
467
468void UVaRestRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
469{
470 // Be sure that we have no data from previous response
472
473 // Check we have a response and save response code as int32
474 if (Response.IsValid())
475 {
476 ResponseCode = Response->GetResponseCode();
477 }
478
479 // Check we have result to process futher
480 if (!bWasSuccessful || !Response.IsValid())
481 {
482 UE_LOG(LogVaRest, Error, TEXT("Request failed (%d): %s"), ResponseCode, *Request->GetURL());
483
484 // Broadcast the result event
485 OnRequestFail.Broadcast(this);
486 OnStaticRequestFail.Broadcast(this);
487
488 return;
489 }
490
491#if PLATFORM_DESKTOP
492 // Log response state
493 UE_LOG(LogVaRest, Log, TEXT("Response (%d): %sJSON(%s%s%s)JSON"), ResponseCode, LINE_TERMINATOR, LINE_TERMINATOR, *Response->GetContentAsString(), LINE_TERMINATOR);
494#endif
495
496 // Process response headers
497 TArray<FString> Headers = Response->GetAllHeaders();
498 for (FString Header : Headers)
499 {
500 FString Key;
501 FString Value;
502 if (Header.Split(TEXT(": "), &Key, &Value))
503 {
504 ResponseHeaders.Add(Key, Value);
505 }
506 }
507
509 {
510 // Try to deserialize data to JSON
511 const TArray<uint8>& Bytes = Response->GetContent();
512 ResponseSize = ResponseJsonObj->DeserializeFromUTF8Bytes((const ANSICHAR*)Bytes.GetData(), Bytes.Num());
513
514 // Log errors
515 if (ResponseSize == 0)
516 {
517 // As we assume it's recommended way to use current class, but not the only one,
518 // it will be the warning instead of error
519 UE_LOG(LogVaRest, Warning, TEXT("JSON could not be decoded!"));
520 }
521 }
522 else
523 {
524 // Use default unreal one
525 const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(*Response->GetContentAsString());
526 TSharedPtr<FJsonValue> OutJsonValue;
527 if (FJsonSerializer::Deserialize(Reader, OutJsonValue))
528 {
529 ResponseJsonValue->SetRootValue(OutJsonValue);
530
532 {
534 ResponseSize = Response->GetContentLength();
535 }
536 }
537 }
538
539 // Decide whether the request was successful
540 bIsValidJsonResponse = bWasSuccessful && (ResponseSize > 0);
541
543 {
544 // Save response data as a string
545 ResponseContent = Response->GetContentAsString();
546 ResponseSize = ResponseContent.GetAllocatedSize();
547 }
548
549 // Broadcast the result events on next tick
550 OnRequestComplete.Broadcast(this);
551 OnStaticRequestComplete.Broadcast(this);
552
553 // Finish the latent action
554 if (ContinueAction)
555 {
557 ContinueAction = nullptr;
558
560 }
561}
562
564// Tags
565
567{
568 if (Tag != NAME_None)
569 {
570 Tags.AddUnique(Tag);
571 }
572}
573
575{
576 return Tags.Remove(Tag);
577}
578
579bool UVaRestRequestJSON::HasTag(FName Tag) const
580{
581 return (Tag != NAME_None) && Tags.Contains(Tag);
582}
583
585// Data
586
587FString UVaRestRequestJSON::GetResponseContentAsString(bool bCacheResponseContent)
588{
589 // Check we have valid json response
591 {
592 // We've cached response content in OnProcessRequestComplete()
593 return ResponseContent;
594 }
595
596 // Check we have valid response object
597 if (!ResponseJsonObj || !ResponseJsonObj->IsValidLowLevel())
598 {
599 // Discard previous cached string if we had one
601
602 return TEXT("Invalid response");
603 }
604
605 // Check if we should re-genetate it in runtime
606 if (!bCacheResponseContent)
607 {
608 UE_LOG(LogVaRest, Warning, TEXT("%s: Use of uncashed getter could be slow"), *VA_FUNC_LINE);
609 return ResponseJsonObj->EncodeJson();
610 }
611
612 // Check that we haven't cached content yet
614 {
615 UE_LOG(LogVaRest, Warning, TEXT("%s: Response content string is cached"), *VA_FUNC_LINE);
617 }
618
619 // Return previously cached content now
620 return ResponseContent;
621}
#define VA_FUNC_LINE
EVaRestRequestContentType
UENUM(BlueprintType)
Definition VaRestTypes.h:31
EVaRestRequestVerb
UENUM(BlueprintType)
Definition VaRestTypes.h:15
EVaRestRequestStatus
UENUM(BlueprintType)
Definition VaRestTypes.h:46
virtual void Call(const T &Value)
UCLASS(BlueprintType, Blueprintable)
TSharedRef< FJsonObject > & GetRootObject()
void SetRootObject(const TSharedPtr< FJsonObject > &JsonObject)
FString EncodeJson() const
UFUNCTION(BlueprintCallable, Category = "VaRest|Json")
void Reset()
UFUNCTION(BlueprintCallable, Category = "VaRest|Json")
int32 DeserializeFromUTF8Bytes(const ANSICHAR *Bytes, int32 Size)
UCLASS(BlueprintType, Blueprintable)
void Reset()
UFUNCTION(BlueprintCallable, Category = "VaRest|Json")
void SetRootValue(TSharedPtr< FJsonValue > &JsonValue)
TSharedPtr< FJsonValue > & GetRootValue()
EVaJson GetType() const
UFUNCTION(BlueprintPure, Category = "VaRest|Json")
static UVaRestSettings * GetVaRestSettings()
UFUNCTION(BlueprintPure, Category = "VaRest|Common")
static FString PercentEncode(const FString &Source)
UFUNCTION(BlueprintCallable, Category = "VaRest|Utility")
UCLASS(BlueprintType, Blueprintable)
void SetCustomVerb(FString Verb)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
virtual void ProcessURL(const FString &Url=TEXT("http://alyamkin.com"))
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
UVaRestJsonObject * ResponseJsonObj
UPROPERTY()
EVaRestRequestVerb GetVerb() const
UFUNCTION(BlueprintPure, Category = "VaRest|Request")
void SetRequestObject(UVaRestJsonObject *JsonObject)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
virtual void ApplyURL(const FString &Url, UVaRestJsonObject *&Result, UObject *WorldContextObject, struct FLatentActionInfo LatentInfo)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request", meta = (Latent, LatentInfo = "LatentInfo",...
void ResetRequestData()
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
void Cancel()
UFUNCTION(BlueprintCallable, Category = "VaRest|Response")
void SetBinaryContentType(const FString &ContentType)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
FString GetResponseHeader(const FString &HeaderName)
UFUNCTION(BlueprintPure, Category = "VaRest|Response")
UVaRestJsonObject * RequestJsonObj
UPROPERTY()
void ResetData()
UFUNCTION(BlueprintCallable, Category = "VaRest|Utility")
TArray< uint8 > RequestBytes
FOnRequestComplete OnRequestComplete
UPROPERTY(BlueprintAssignable, Category = "VaRest|Event")
int32 RemoveTag(FName Tag)
UFUNCTION(BlueprintCallable, Category = "VaRest|Utility")
UVaRestJsonObject * GetResponseObject() const
UFUNCTION(BlueprintCallable, Category = "VaRest|Response")
TMap< FString, FString > ResponseHeaders
UVaRestJsonValue * ResponseJsonValue
UPROPERTY()
UVaRestJsonObject * GetRequestObject() const
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
int32 ResponseSize
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "VaRest|Response")
EVaRestRequestStatus GetStatus() const
UFUNCTION(BlueprintPure, Category = "VaRest|Request")
void SetHeader(const FString &HeaderName, const FString &HeaderValue)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
void SetResponseObject(UVaRestJsonObject *JsonObject)
UFUNCTION(BlueprintCallable, Category = "VaRest|Response")
int32 GetResponseCode() const
UFUNCTION(BlueprintPure, Category = "VaRest|Response")
bool bIsValidJsonResponse
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "VaRest|Response")
void OnProcessRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
TArray< FString > GetAllResponseHeaders() const
UFUNCTION(BlueprintPure, Category = "VaRest|Response")
void SetContentType(EVaRestRequestContentType ContentType)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
EVaRestRequestContentType RequestContentType
void SetURL(const FString &Url=TEXT("http://alyamkin.com"))
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
FString GetResponseContentAsString(bool bCacheResponseContent=true)
UFUNCTION(BlueprintCallable, Category = "VaRest|Response")
TMap< FString, FString > RequestHeaders
void AddTag(FName Tag)
UFUNCTION(BlueprintCallable, Category = "VaRest|Utility")
FString ResponseContent
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "VaRest|Response")
FOnRequestFail OnRequestFail
UPROPERTY(BlueprintAssignable, Category = "VaRest|Event")
bool HasTag(FName Tag) const
UFUNCTION(BlueprintCallable, Category = "VaRest|Utility")
FOnStaticRequestFail OnStaticRequestFail
TSharedRef< IHttpRequest, ESPMode::ThreadSafe > HttpRequest
EVaRestRequestVerb RequestVerb
FString GetURL() const
UFUNCTION(BlueprintPure, Category = "VaRest|Request")
void SetVerb(EVaRestRequestVerb Verb)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
virtual void ExecuteProcessRequest()
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
FVaRestLatentAction< UVaRestJsonObject * > * ContinueAction
void SetBinaryRequestContent(const TArray< uint8 > &Content)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
static FString DeprecatedResponseString
void ResetResponseData()
UFUNCTION(BlueprintCallable, Category = "VaRest|Response")
UVaRestJsonValue * GetResponseValue() const
UFUNCTION(BlueprintCallable, Category = "VaRest|Response")
FOnStaticRequestComplete OnStaticRequestComplete
void SetStringRequestContent(const FString &Content)
UFUNCTION(BlueprintCallable, Category = "VaRest|Request")
bool bUseChunkedParser
UPROPERTY(Config, EditAnywhere, Category = "VaRest")