├── .gitignore ├── README.md ├── Resources └── Icon128.png ├── Source └── UnrealHttpServer │ ├── Private │ ├── Handler │ │ ├── BaseHandler.cpp │ │ ├── BaseHandler.h │ │ ├── PlayerHandler.cpp │ │ └── PlayerHandler.h │ ├── Log.cpp │ ├── Log.h │ ├── Service │ │ ├── PlayerService.cpp │ │ └── PlayerService.h │ ├── UnrealHttpServer.cpp │ ├── Util │ │ ├── WebUtil.cpp │ │ └── WebUtil.h │ ├── WebServer.cpp │ └── WebServer.h │ ├── Public │ └── UnrealHttpServer.h │ └── UnrealHttpServer.Build.cs └── UnrealHttpServer.uplugin /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries 2 | Content 3 | Intermediate 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnrealHttpServer 2 | 3 | An unreal4 plugin with an HTTP Web server 4 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utmhikari/UnrealHttpServer/603bfa64b25307762a7949d74d143cf6d336fc88/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Handler/BaseHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "Handler/BaseHandler.h" 2 | #include "Util/WebUtil.h" 3 | #include "Engine.h" 4 | #include "Log.h" 5 | 6 | namespace UnrealHttpServer 7 | { 8 | TUniquePtr FBaseHandler::HealthCheck(const FHttpServerRequest& Request) 9 | { 10 | UE_LOG(UHttpLog, Log, TEXT("Health Check")); 11 | if (GEngine != nullptr) 12 | { 13 | GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Green, TEXT("Health Check Successfully!")); 14 | } 15 | return FWebUtil::SuccessResponse("Health Check Successfully!"); 16 | } 17 | } -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Handler/BaseHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Runtime/Online/HTTPServer/Public/HttpServerRequest.h" 4 | #include "Runtime/Online/HTTPServer/Public/HttpResultCallback.h" 5 | 6 | namespace UnrealHttpServer 7 | { 8 | class FBaseHandler 9 | { 10 | public: 11 | /** 12 | * Health Check 13 | */ 14 | static TUniquePtr HealthCheck(const FHttpServerRequest& Request); 15 | 16 | 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Handler/PlayerHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "Handler/PlayerHandler.h" 2 | #include "Log.h" 3 | #include "Util/WebUtil.h" 4 | #include "Service/PlayerService.h" 5 | #include "Runtime/JsonUtilities/Public/JsonObjectConverter.h" 6 | #include "GameFramework/Pawn.h" 7 | 8 | namespace UnrealHttpServer 9 | { 10 | TUniquePtr FPlayerHandler::GetPlayerLocation(const FHttpServerRequest& Request) 11 | { 12 | APawn* PlayerPawn = FPlayerService::GetPlayerPawn(); 13 | if (PlayerPawn == nullptr) 14 | { 15 | return FWebUtil::ErrorResponse(TEXT("Failed to get valid player pawn instance!")); 16 | } 17 | FVector PlayerLocation = PlayerPawn->GetActorLocation(); 18 | TSharedPtr Body = MakeShareable(new FJsonObject()); 19 | Body->SetNumberField(TEXT("x"), PlayerLocation.X); 20 | Body->SetNumberField(TEXT("y"), PlayerLocation.Y); 21 | Body->SetNumberField(TEXT("z"), PlayerLocation.Z); 22 | return FWebUtil::SuccessResponse(Body); 23 | } 24 | 25 | TUniquePtr FPlayerHandler::SetPlayerLocation(const FHttpServerRequest& Request) 26 | { 27 | // get request json and player pawn 28 | TSharedPtr RequestBody = FWebUtil::GetRequestJsonBody(Request); 29 | if (RequestBody == nullptr) 30 | { 31 | return FWebUtil::ErrorResponse(TEXT("Failed to parse request body to json!")); 32 | } 33 | APawn* PlayerPawn = FPlayerService::GetPlayerPawn(); 34 | if (PlayerPawn == nullptr) 35 | { 36 | return FWebUtil::ErrorResponse(TEXT("Failed to get valid player pawn instance!")); 37 | } 38 | // set new location 39 | FVector NewLocation; 40 | NewLocation.X = RequestBody->GetNumberField(TEXT("x")); 41 | NewLocation.Y = RequestBody->GetNumberField(TEXT("y")); 42 | NewLocation.Z = RequestBody->GetNumberField(TEXT("z")); 43 | UE_LOG(UHttpLog, Log, TEXT("Set player new location to (%.2f, %.2f, %.2f)"), NewLocation.X, NewLocation.Y, NewLocation.Z); 44 | if (!PlayerPawn->SetActorLocation(NewLocation, false, nullptr, ETeleportType::ResetPhysics)) 45 | { 46 | return FWebUtil::ErrorResponse(TEXT("Failed to set player location!")); 47 | } 48 | return FWebUtil::SuccessResponse(TEXT("Player location set successfully!")); 49 | } 50 | 51 | TUniquePtr FPlayerHandler::GetPlayerRotation(const FHttpServerRequest& Request) 52 | { 53 | APawn* PlayerPawn = FPlayerService::GetPlayerPawn(); 54 | if (PlayerPawn == nullptr) 55 | { 56 | return FWebUtil::ErrorResponse(TEXT("Failed to get valid player pawn instance!")); 57 | } 58 | FRotator PlayerRotation = PlayerPawn->GetActorRotation(); 59 | TSharedPtr Body = MakeShareable(new FJsonObject()); 60 | Body->SetNumberField(TEXT("pitch"), PlayerRotation.Pitch); 61 | Body->SetNumberField(TEXT("yaw"), PlayerRotation.Yaw); 62 | Body->SetNumberField(TEXT("roll"), PlayerRotation.Roll); 63 | return FWebUtil::SuccessResponse(Body); 64 | } 65 | 66 | TUniquePtr FPlayerHandler::SetPlayerRotation(const FHttpServerRequest& Request) 67 | { 68 | // get request json and player pawn 69 | TSharedPtr RequestBody = FWebUtil::GetRequestJsonBody(Request); 70 | if (RequestBody == nullptr) 71 | { 72 | return FWebUtil::ErrorResponse(TEXT("Failed to parse request body to json!")); 73 | } 74 | APawn* PlayerPawn = FPlayerService::GetPlayerPawn(); 75 | if (PlayerPawn == nullptr) 76 | { 77 | return FWebUtil::ErrorResponse(TEXT("Failed to get valid player pawn instance!")); 78 | } 79 | // set new rotation 80 | FRotator NewRotation; 81 | NewRotation.Pitch = RequestBody->GetNumberField("pitch"); 82 | NewRotation.Yaw = RequestBody->GetNumberField("yaw"); 83 | NewRotation.Roll = RequestBody->GetNumberField("roll"); 84 | UE_LOG(UHttpLog, Log, TEXT("Set player new rotation to (pitch: %.2f, yaw: %.2f, roll: %.2f)"), NewRotation.Pitch, NewRotation.Yaw, NewRotation.Roll); 85 | if (!PlayerPawn->SetActorRotation(NewRotation, ETeleportType::ResetPhysics)) 86 | { 87 | return FWebUtil::ErrorResponse(TEXT("Failed to set player rotation!")); 88 | } 89 | return FWebUtil::SuccessResponse(TEXT("Player rotation set successfully!")); 90 | } 91 | } -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Handler/PlayerHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include "Runtime/Online/HTTPServer/Public/HttpServerRequest.h" 5 | #include "Runtime/Online/HTTPServer/Public/HttpResultCallback.h" 6 | 7 | namespace UnrealHttpServer 8 | { 9 | class FPlayerHandler 10 | { 11 | public: 12 | /** 13 | * get player location 14 | */ 15 | static TUniquePtr GetPlayerLocation(const FHttpServerRequest& Request); 16 | 17 | /** 18 | * set player location 19 | */ 20 | static TUniquePtr SetPlayerLocation(const FHttpServerRequest& Request); 21 | 22 | /** 23 | * get player rotation 24 | */ 25 | static TUniquePtr GetPlayerRotation(const FHttpServerRequest& Request); 26 | 27 | /** 28 | * set player rotation 29 | */ 30 | static TUniquePtr SetPlayerRotation(const FHttpServerRequest& Request); 31 | }; 32 | } -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Log.cpp: -------------------------------------------------------------------------------- 1 | #include "Log.h" 2 | 3 | DEFINE_LOG_CATEGORY(UHttpLog) 4 | -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Logging/LogMacros.h" 5 | 6 | DECLARE_LOG_CATEGORY_EXTERN(UHttpLog, Log, All); 7 | -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Service/PlayerService.cpp: -------------------------------------------------------------------------------- 1 | #include "PlayerService.h" 2 | #include "Log.h" 3 | #include "Engine.h" 4 | 5 | 6 | namespace UnrealHttpServer 7 | { 8 | /* ================= Public Methods ==================== */ 9 | 10 | APawn* FPlayerService::GetPlayerPawn() 11 | { 12 | if (GEngine == nullptr) 13 | { 14 | UE_LOG(UHttpLog, Warning, TEXT("Cannot find GEngine!")); 15 | return nullptr; 16 | } 17 | auto WorldContexts = GEngine->GetWorldContexts(); 18 | if (WorldContexts.Num() == 0) 19 | { 20 | UE_LOG(UHttpLog, Warning, TEXT("No world context!")); 21 | return nullptr; 22 | } 23 | auto World = WorldContexts[0].World(); 24 | if (World == nullptr) 25 | { 26 | UE_LOG(UHttpLog, Warning, TEXT("No current world!")); 27 | return nullptr; 28 | } 29 | auto FirstPlayerController = World->GetFirstPlayerController(); 30 | if (FirstPlayerController == nullptr) 31 | { 32 | GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Failed to get first player controller!")); 33 | return nullptr; 34 | } 35 | auto PlayerPawn = FirstPlayerController->GetPawn(); 36 | if (PlayerPawn == nullptr) 37 | { 38 | GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Player is not a pawn or not under control!")); 39 | } 40 | return PlayerPawn; 41 | } 42 | } -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Service/PlayerService.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | #include "GameFramework/Controller.h" 6 | 7 | namespace UnrealHttpServer 8 | { 9 | class FPlayerService 10 | { 11 | public: 12 | /** 13 | * Get player pawn 14 | */ 15 | static APawn* GetPlayerPawn(); 16 | }; 17 | } -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/UnrealHttpServer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "UnrealHttpServer.h" 4 | #include "WebServer.h" 5 | 6 | 7 | #define LOCTEXT_NAMESPACE "FUnrealHttpServerModule" 8 | 9 | void FUnrealHttpServerModule::StartupModule() 10 | { 11 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 12 | UnrealHttpServer::FWebServer::Stop(); 13 | if (!GIsEditor) 14 | { 15 | Port = DEFAULT_PORT; 16 | UnrealHttpServer::FWebServer::Start(Port); 17 | } 18 | } 19 | 20 | void FUnrealHttpServerModule::ShutdownModule() 21 | { 22 | // This function may be called during shutdown to clean up your module. For modules that supporWt dynamic reloading, 23 | // we call this function before unloading the module. 24 | UnrealHttpServer::FWebServer::Stop(); 25 | } 26 | 27 | #undef LOCTEXT_NAMESPACE 28 | 29 | IMPLEMENT_MODULE(FUnrealHttpServerModule, UnrealHttpServer) -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Util/WebUtil.cpp: -------------------------------------------------------------------------------- 1 | #include "Util/WebUtil.h" 2 | #include "Log.h" 3 | #include "Engine.h" 4 | #include "Runtime/Json/Public/Serialization/JsonTypes.h" 5 | #include "Runtime/Json/Public/Dom/JsonValue.h" 6 | #include "Runtime/Json/Public/Serialization/JsonWriter.h" 7 | #include "Runtime/Json/Public/Serialization/JsonSerializer.h" 8 | 9 | namespace UnrealHttpServer 10 | { 11 | /** ========================== Public Methods ======================= */ 12 | 13 | FHttpRouteHandle FWebUtil::BindRoute(const TSharedPtr& HttpRouter, FString Path, const EHttpServerRequestVerbs& Verb, const FHttpResponser& HttpResponser) 14 | { 15 | // VERB_NONE not supported! 16 | if (HttpRouter == nullptr || Verb == EHttpServerRequestVerbs::VERB_NONE) 17 | { 18 | return nullptr; 19 | } 20 | 21 | FString VerbString = GetHttpVerbStringFromEnum(Verb); 22 | UE_LOG(UHttpLog, Log, TEXT("Binding router: %s\t%s"), *VerbString, *Path); 23 | 24 | // check if HTTP path is valid 25 | FHttpPath HttpPath(Path); 26 | if (!HttpPath.IsValidPath()) 27 | { 28 | UE_LOG(UHttpLog, Warning, TEXT("Invalid http path: %s"), *Path); 29 | #if WITH_EDITOR 30 | if (GEngine != nullptr) 31 | { 32 | GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, 33 | FString::Printf(TEXT("Bind HTTP router failed! invalid path: %s"), *Path)); 34 | } 35 | #endif 36 | return nullptr; 37 | } 38 | 39 | // bind router 40 | auto RouteHandle = HttpRouter->BindRoute(HttpPath, Verb, FWebUtil::CreateHandler(HttpResponser)); 41 | if (RouteHandle == nullptr) 42 | { 43 | UE_LOG(UHttpLog, Warning, TEXT("Bind failed: %s\t%s"), *VerbString, *Path); 44 | return nullptr; 45 | } 46 | #if WITH_EDITOR 47 | if (GEngine != nullptr) 48 | { 49 | GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, 50 | FString::Printf(TEXT("Bind HTTP router: %s\t%s"), *VerbString, *Path)); 51 | } 52 | #endif 53 | return RouteHandle; 54 | } 55 | 56 | FHttpRequestHandler FWebUtil::CreateHandler(const FHttpResponser& HttpResponser) 57 | { 58 | return [HttpResponser](const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete) 59 | { 60 | auto Response = HttpResponser(Request); 61 | if (Response == nullptr) 62 | { 63 | return false; 64 | } 65 | OnComplete(MoveTemp(Response)); 66 | return true; 67 | }; 68 | } 69 | 70 | TSharedPtr FWebUtil::GetRequestJsonBody(const FHttpServerRequest& Request) 71 | { 72 | // check if content type is application/json 73 | bool IsUTF8JsonContent = IsUTF8JsonRequestContent(Request); 74 | if (!IsUTF8JsonContent) 75 | { 76 | UE_LOG(UHttpLog, Warning, TEXT("caught request not in utf-8 application/json body content!")); 77 | return nullptr; 78 | } 79 | 80 | // body to utf8 string 81 | TArray RequestBodyBytes = Request.Body; 82 | FString RequestBodyString = FString(UTF8_TO_TCHAR(RequestBodyBytes.GetData())); 83 | #if WITH_EDITOR 84 | if (GEngine != nullptr) 85 | { 86 | GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Green, RequestBodyString); 87 | } 88 | #endif 89 | 90 | // string to json 91 | TSharedRef> JsonReader = TJsonReaderFactory<>::Create(RequestBodyString); 92 | TSharedPtr RequestBody; 93 | if (!FJsonSerializer::Deserialize(JsonReader, RequestBody)) 94 | { 95 | UE_LOG(UHttpLog, Warning, TEXT("failed to parse request string to json: %s"), *RequestBodyString); 96 | return nullptr; 97 | } 98 | return RequestBody; 99 | } 100 | 101 | template 102 | static UStructType* FWebUtil::GetRequestUStructBody(const FHttpServerRequest& Request) 103 | { 104 | TSharedPtr JsonBody = FWebUtil::GetRequestJsonBody(Request); 105 | if (JsonBody == nullptr) 106 | { 107 | return nullptr; 108 | } 109 | UStructType* UStructBody; 110 | if (!FJsonObjectConverter::JsonObjectToUStruct(JsonBody, UStructBody)) 111 | { 112 | UE_LOG(UHttpLog, Warning, TEXT("failed to parse json body to ustruct!")) 113 | return nullptr; 114 | } 115 | return UStructBody; 116 | } 117 | 118 | TUniquePtr FWebUtil::SuccessResponse(TSharedPtr Data, FString Message) 119 | { 120 | return JsonResponse(Data, Message, true, SUCCESS_CODE); 121 | } 122 | 123 | TUniquePtr FWebUtil::SuccessResponse(TSharedPtr Data) 124 | { 125 | return SuccessResponse(Data, TEXT("")); 126 | } 127 | 128 | TUniquePtr FWebUtil::SuccessResponse(FString Message) 129 | { 130 | return SuccessResponse(MakeShareable(new FJsonObject()), Message); 131 | } 132 | 133 | TUniquePtr FWebUtil::ErrorResponse(TSharedPtr Data, FString Message, int32 Code) 134 | { 135 | if (Code == SUCCESS_CODE) 136 | { 137 | Code = DEFAULT_ERROR_CODE; 138 | } 139 | return JsonResponse(Data, Message, false, Code); 140 | } 141 | 142 | TUniquePtr FWebUtil::ErrorResponse(TSharedPtr Data, FString Message) 143 | { 144 | return ErrorResponse(Data, Message, DEFAULT_ERROR_CODE); 145 | } 146 | 147 | TUniquePtr FWebUtil::ErrorResponse(FString Message, int32 Code) 148 | { 149 | return ErrorResponse(MakeShareable(new FJsonObject()), Message, Code); 150 | } 151 | 152 | TUniquePtr FWebUtil::ErrorResponse(FString Message) 153 | { 154 | return ErrorResponse(MakeShareable(new FJsonObject()), Message, DEFAULT_ERROR_CODE); 155 | } 156 | 157 | /** ========================== Private Methods ======================= */ 158 | 159 | FString FWebUtil::GetHttpVerbStringFromEnum(const EHttpServerRequestVerbs& Verb) 160 | { 161 | switch (Verb) 162 | { 163 | case EHttpServerRequestVerbs::VERB_GET: 164 | return TEXT("GET"); 165 | case EHttpServerRequestVerbs::VERB_POST: 166 | return TEXT("POST"); 167 | case EHttpServerRequestVerbs::VERB_PUT: 168 | return TEXT("PUT"); 169 | case EHttpServerRequestVerbs::VERB_DELETE: 170 | return TEXT("DELETE"); 171 | case EHttpServerRequestVerbs::VERB_PATCH: 172 | return TEXT("PATCH"); 173 | case EHttpServerRequestVerbs::VERB_OPTIONS: 174 | return TEXT("OPTIONS"); 175 | default: 176 | return TEXT("UNKNOWN_VERB"); 177 | } 178 | } 179 | 180 | TUniquePtr FWebUtil::JsonResponse(TSharedPtr Data, FString Message, bool Success, int32 Code) 181 | { 182 | TSharedPtr JsonObject = MakeShareable(new FJsonObject()); 183 | JsonObject->SetObjectField(TEXT("data"), Data); 184 | JsonObject->SetStringField(TEXT("message"), Message); 185 | JsonObject->SetBoolField(TEXT("success"), Success); 186 | JsonObject->SetNumberField(TEXT("code"), (double)Code); 187 | FString JsonString; 188 | TSharedRef> Writer = TJsonWriterFactory<>::Create(&JsonString); 189 | FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); 190 | return FHttpServerResponse::Create(JsonString, TEXT("application/json")); 191 | } 192 | 193 | bool FWebUtil::IsUTF8JsonRequestContent(const FHttpServerRequest& Request) 194 | { 195 | bool bIsUTF8JsonContent = false; 196 | for (auto& HeaderElem : Request.Headers) 197 | { 198 | auto LowerKey = HeaderElem.Key.ToLower(); 199 | if (LowerKey == TEXT("content-type")) 200 | { 201 | for (auto& Value : HeaderElem.Value) 202 | { 203 | auto LowerValue = Value.ToLower(); 204 | // not strict check 205 | if (LowerValue.Contains(TEXT("charset=")) && !LowerValue.Contains(TEXT("charset=utf-8"))) 206 | { 207 | return false; 208 | } 209 | if (LowerValue.Contains(TEXT("application/json")) || LowerValue.Contains(TEXT("text/json"))) 210 | { 211 | bIsUTF8JsonContent = true; 212 | } 213 | } 214 | } 215 | #if WITH_EDITOR 216 | auto Value = FString::Join(HeaderElem.Value, TEXT(",")); 217 | FString Message; 218 | Message.Append(HeaderElem.Key); 219 | Message.AppendChars(TEXT(": "), 2); 220 | Message.Append(Value); 221 | if (GEngine != nullptr) 222 | { 223 | GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Yellow, Message); 224 | } 225 | #endif 226 | } 227 | return bIsUTF8JsonContent; 228 | } 229 | } -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/Util/WebUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | #include "Runtime/Online/HTTPServer/Public/HttpServerModule.h" 6 | #include "Runtime/Online/HTTPServer/Public/HttpServerRequest.h" 7 | #include "Runtime/Json/Public/Dom/JsonObject.h" 8 | #include "Runtime/Online/HTTPServer/Public/HttpServerResponse.h" 9 | #include "Runtime/Online/HTTPServer/Public/HttpRouteHandle.h" 10 | #include "Runtime/Online/HTTPServer/Public/IHttpRouter.h" 11 | 12 | 13 | namespace UnrealHttpServer 14 | { 15 | /** 16 | * HTTP responser function 17 | */ 18 | typedef TFunction(const FHttpServerRequest& Request)> FHttpResponser; 19 | 20 | class FWebUtil 21 | { 22 | public: 23 | /** 24 | * Bind a route with handler 25 | */ 26 | static FHttpRouteHandle BindRoute(const TSharedPtr& HttpRouter, FString Path, const EHttpServerRequestVerbs& Verb, const FHttpResponser& HttpResponser); 27 | 28 | /** 29 | * Create HTTP request handler (controller) 30 | * In UE4, invoke OnComplete and return false will cause crash 31 | * CreateHandler method is used to wrap the responser, in order to avoid the crash 32 | */ 33 | static FHttpRequestHandler CreateHandler(const FHttpResponser& HttpResponser); 34 | 35 | /** 36 | * Get request json body, parse TArray to TSharedPtr 37 | */ 38 | static TSharedPtr GetRequestJsonBody(const FHttpServerRequest& Request); 39 | 40 | /** 41 | * Get Struct body (based on json body, the struct type should be UStruct) 42 | */ 43 | template 44 | static UStructType* GetRequestUStructBody(const FHttpServerRequest& Request); 45 | 46 | /** 47 | * Success response (data & message) 48 | */ 49 | static TUniquePtr SuccessResponse(TSharedPtr Data, FString Message); 50 | 51 | /** 52 | * Success response (data only) 53 | */ 54 | static TUniquePtr SuccessResponse(TSharedPtr Data); 55 | 56 | /** 57 | * Success response (message only) 58 | */ 59 | static TUniquePtr SuccessResponse(FString Message); 60 | 61 | /** 62 | * Error response (data & message & code) 63 | */ 64 | static TUniquePtr ErrorResponse(TSharedPtr Data, FString Message, int32 Code); 65 | 66 | /** 67 | * Error response (data & message) 68 | */ 69 | static TUniquePtr ErrorResponse(TSharedPtr Data, FString Message); 70 | 71 | /** 72 | * Error response (message & code) 73 | */ 74 | static TUniquePtr ErrorResponse(FString Message, int32 Code); 75 | 76 | /** 77 | * Error response (message only) 78 | */ 79 | static TUniquePtr ErrorResponse(FString Message); 80 | private: 81 | /* Success code in response body */ 82 | static const int32 SUCCESS_CODE = 0; 83 | /* Default error code in response body */ 84 | static const int32 DEFAULT_ERROR_CODE = -1; 85 | 86 | /** 87 | * get verb string from enumerate (for logging use) 88 | */ 89 | static FString GetHttpVerbStringFromEnum(const EHttpServerRequestVerbs& Verb); 90 | 91 | /** 92 | * Create json response from data, message, success status and user defined error code 93 | */ 94 | static TUniquePtr JsonResponse(TSharedPtr Data, FString Message, bool Success, int32 Code); 95 | 96 | /** 97 | * Check if the body content will be parsed as UTF-8 json by header 98 | */ 99 | static bool IsUTF8JsonRequestContent(const FHttpServerRequest& Request); 100 | }; 101 | } -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/WebServer.cpp: -------------------------------------------------------------------------------- 1 | #include "WebServer.h" 2 | #include "Util/WebUtil.h" 3 | #include "Engine.h" 4 | #include "Log.h" 5 | #include "Runtime/Online/HTTPServer/Public/HttpServerModule.h" 6 | #include "Runtime/Online/HTTPServer/Public/HttpPath.h" 7 | 8 | 9 | // Handlers 10 | #include "Handler/BaseHandler.h" 11 | #include "Handler/PlayerHandler.h" 12 | 13 | 14 | namespace UnrealHttpServer 15 | { 16 | void FWebServer::Start(uint32 Port) 17 | { 18 | auto HttpServerModule = &FHttpServerModule::Get(); 19 | UE_LOG(UHttpLog, Log, TEXT("Starting UnrealHttpServer Server...")); 20 | TSharedPtr HttpRouter = HttpServerModule->GetHttpRouter(Port); 21 | BindRouters(HttpRouter); 22 | // Start Listeners 23 | HttpServerModule->StartAllListeners(); 24 | if (GEngine != nullptr) 25 | { 26 | GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Green, TEXT("UnrealHttpServer Server Started")); 27 | } 28 | } 29 | 30 | void FWebServer::Stop() 31 | { 32 | UE_LOG(UHttpLog, Log, TEXT("Stopping UnrealHttpServer Server...")); 33 | auto HttpServerModule = &FHttpServerModule::Get(); 34 | HttpServerModule->StopAllListeners(); 35 | } 36 | 37 | void FWebServer::BindRouters(const TSharedPtr& HttpRouter) 38 | { 39 | // UE4 uses Map to store router bindings, so that bind different verbs to a same HTTP path is not supported 40 | 41 | /* ====================== Base Handler ==================== */ 42 | 43 | // health check 44 | FWebUtil::BindRoute(HttpRouter, TEXT("/health"), EHttpServerRequestVerbs::VERB_GET, FBaseHandler::HealthCheck); 45 | 46 | /* ====================== Player Handler ==================== */ 47 | 48 | // get player location 49 | FWebUtil::BindRoute(HttpRouter, TEXT("/player/get_location"), EHttpServerRequestVerbs::VERB_GET, &FPlayerHandler::GetPlayerLocation); 50 | 51 | // set player location 52 | FWebUtil::BindRoute(HttpRouter, TEXT("/player/set_location"), EHttpServerRequestVerbs::VERB_PUT, &FPlayerHandler::SetPlayerLocation); 53 | 54 | // get player rotation 55 | FWebUtil::BindRoute(HttpRouter, TEXT("/player/get_rotation"), EHttpServerRequestVerbs::VERB_GET, &FPlayerHandler::GetPlayerRotation); 56 | 57 | // set player rotation 58 | FWebUtil::BindRoute(HttpRouter, TEXT("/player/set_rotation"), EHttpServerRequestVerbs::VERB_PUT, &FPlayerHandler::SetPlayerRotation); 59 | } 60 | } -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Private/WebServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Runtime/Online/HTTPServer/Public/IHttpRouter.h" 4 | 5 | namespace UnrealHttpServer 6 | { 7 | class FWebServer 8 | { 9 | public: 10 | /** 11 | * Start server 12 | */ 13 | static void Start(uint32 Port); 14 | 15 | /** 16 | * Stop server 17 | */ 18 | static void Stop(); 19 | 20 | private: 21 | /** 22 | * Bind routers with handlers 23 | */ 24 | static void BindRouters(const TSharedPtr& HttpRouter); 25 | }; 26 | 27 | } -------------------------------------------------------------------------------- /Source/UnrealHttpServer/Public/UnrealHttpServer.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | 9 | class FUnrealHttpServerModule : public IModuleInterface 10 | { 11 | public: 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | 16 | uint32 Port; 17 | 18 | private: 19 | static const uint32 DEFAULT_PORT = 26016; 20 | }; 21 | -------------------------------------------------------------------------------- /Source/UnrealHttpServer/UnrealHttpServer.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class UnrealHttpServer : ModuleRules 6 | { 7 | public UnrealHttpServer(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here .. 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | "Slate", 40 | "SlateCore", 41 | // ... add private dependencies that you statically link with here ... 42 | "HTTP", 43 | "HTTPServer", 44 | "JsonUtilities", 45 | "Json", 46 | "UMG", 47 | } 48 | ); 49 | 50 | 51 | DynamicallyLoadedModuleNames.AddRange( 52 | new string[] 53 | { 54 | // ... add any modules that your module loads dynamically here ... 55 | } 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /UnrealHttpServer.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "UnrealHttpServer", 6 | "Description": "A UE4 plugin with an HTTP Web Server", 7 | "Category": "Test", 8 | "CreatedBy": "utmhikari", 9 | "CreatedByURL": "http://utmhikari.top/", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": true, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "UnrealHttpServer", 20 | "Type": "Runtime", 21 | "LoadingPhase": "PostEngineInit", 22 | "WhitelistPlatforms": [ 23 | "Android", 24 | "Win64" 25 | ] 26 | } 27 | ] 28 | } --------------------------------------------------------------------------------