import vibe.vibe; import std.base64 : Base64; import std.conv : to; import botan.filters.pipe : Pipe; import botan.filters.b64_filt : Base64Encoder, Base64Decoder; import botan.filters.transform_filter : TransformationFilter; import botan.compression.zlib: ZlibDecompression; import botan.libstate.lookup : getCipher; import botan.algo_base.symkey : SymmetricKey, InitializationVector; import botan.utils.types; import config; void main() { auto router = new URLRouter; router.get("*", reverseProxyRequest(serverHost, serverPort)); router.post("/api/v1/*", &onAPIRequest); auto settings = new HTTPServerSettings; settings.port = proxyPortNumber; settings.options = HTTPServerOption.parseURL | HTTPServerOption.errorStackTraces; listenHTTP(settings, router); runApplication(); } void onAPIRequest(HTTPServerRequest request, HTTPServerResponse response) { auto apiEndpoint = request.path.split("/api/v1")[1]; auto nonce = request.headers["auth_nonce"]; auto requestBody = request.bodyReader.readAll(); auto requestJson = getRequestJson(requestBody, apiEndpoint, nonce); handleRequest(apiEndpoint, requestJson); //TODO: Allow request editing (requires recalculation of signature) auto apiResponse = sendRequest(apiEndpoint, request.headers.dup, requestBody); auto responseBody = apiResponse.bodyReader.readAll(); auto responseJson = getResponseJson(responseBody, apiEndpoint, nonce); auto finalResponseJson = handleResponse(apiEndpoint, responseJson); auto finalResponseBody = constructResponseBody(apiEndpoint, finalResponseJson, nonce); sendResponse(response, apiResponse, finalResponseBody); import std.stdio : writeln; writeln(apiEndpoint); } Json getRequestJson(ubyte[] input, string endpoint, string nonce) { auto originalRequestPipe = Pipe ( new Base64Decoder, getCipher ( cipherName, SymmetricKey((cast(ubyte[]) keyString).ptr, keyString.length), InitializationVector((cast(ubyte[]) nonce).ptr, nonce.length), DECRYPTION ) ); originalRequestPipe.processMsg(input.ptr, input.length); auto requestJsonString = originalRequestPipe.toString; return requestJsonString.parseJson; } Json getResponseJson(ubyte[] input, string endpoint, string nonce) { import std.zlib : uncompress; switch(endpoint) { case "/config/getGameConfig": return getRequestJson(input, endpoint, nonce); case "/master/getMaster": auto decompressedInput = cast(ubyte[])uncompress(input); //TODO: Use pipe instead (how?) auto responseJsonString = cast(string) decompressedInput; return responseJsonString.parseJson; default: auto decompressedInput = cast(ubyte[])uncompress(input); //TODO: Use pipe instead (how?) auto responsePipe = Pipe ( new Base64Decoder ); responsePipe.processMsg(decompressedInput.ptr, decompressedInput.length); auto responseJsonString = responsePipe.toString; return responseJsonString.parseJson; } } void handleRequest(string endpoint, Json json) { } Json handleResponse(string endpoint, Json json) { if(endpoint == "/master/getMaster") { import models.master; auto getMasterResponse = json.deserializeJson!GetMasterResponse; foreach(character; getMasterResponse.masterCharacter) { auto characterId = character[0].to!uint; import translations.character; if(characterId in characterNames) { character[5] = characterNames[characterId]; } } foreach(characterSkill; getMasterResponse.masterCharacterSkill) { auto skillId = characterSkill[0].to!uint; import translations.characterSkill; if(skillId in skillNames) { characterSkill[1] = skillNames[skillId]; characterSkill[6] = skillDescriptions[skillId]; } } foreach(stage; getMasterResponse.masterStage) { auto stageId = stage[0].to!uint; import translations.stage; if(stageId in stageNames) { stage[1] = stageNames[stageId]; } } foreach(mission; getMasterResponse.masterMission) { //TODO: Figure out a way to find the wrong ones auto missionId = mission[0].to!uint; import translations.mission; if(missionId in missionNames) { mission[1] = missionNames[missionId]; mission[2] = missionDescriptions[missionId]; mission[3] = missionRewards[missionId]; } } foreach(dungeon; getMasterResponse.masterDungeon) { auto dungeonId = dungeon[10].to!uint; import translations.dungeon; if(dungeonId in dungeonNames) { dungeon[12] = dungeonNames[dungeonId]; } } foreach(guest; getMasterResponse.masterGuest) { auto guestId = guest[0].to!uint; import translations.guest; if(guestId in guestPartyNames) { guest[1] = guestPartyNames[guestId]; } } foreach(item; getMasterResponse.masterItem) { auto itemId = item[0].to!uint; import translations.item; if(itemId in itemNames) { item[1] = itemNames[itemId]; } } foreach(location; getMasterResponse.masterWorldMap) { auto locationId = location[0].to!uint; import translations.worldMap; if(locationId in worldMapLocationNames) { location[3] = worldMapLocationNames[locationId]; location[4] = worldMapLocationCaptions[locationId]; location[5] = worldMapLocationDescriptions[locationId]; } } foreach(resource; getMasterResponse.masterCharacterTextResource) { auto resourceId = resource[0].to!uint; import translations.characterTextResource; if(resourceId in characterTextResource) { resource[3] = characterTextResource[resourceId]; } } foreach(leaderSkill; getMasterResponse.masterCharacterLeaderSkill) { auto leaderSkillId = leaderSkill[0].to!uint; import translations.characterLeaderSkill; if(leaderSkillId in characterLeaderSkillNames) { leaderSkill[1] = characterLeaderSkillNames[leaderSkillId]; leaderSkill[14] = characterLeaderSkillDescriptions[leaderSkillId]; } } foreach(characterBookEntry; getMasterResponse.masterCharacterBook) { auto characterId = characterBookEntry[0].to!uint; import translations.characterBook; if(characterId in characterBookFlowerLanguage) { characterBookEntry[5] = characterBookFlowerLanguage[characterId]; } } foreach(characterCategory; getMasterResponse.masterCharacterCategory) { auto categoryId = characterCategory[0].to!uint; import translations.characterCategory; if(categoryId in characterCategories) { characterCategory[1] = characterCategories[categoryId]; } } return getMasterResponse.serializeToJson; } return json; } ubyte[] constructResponseBody(string endpoint, Json json, string nonce) { import std.zlib : compress; auto jsonStringBytes = cast(ubyte[]) json.toString; switch(endpoint) { case "/config/getGameConfig": auto responsePipe = Pipe ( getCipher ( cipherName, SymmetricKey((cast(ubyte[]) keyString).ptr, keyString.length), InitializationVector((cast(ubyte[]) nonce).ptr, nonce.length), ENCRYPTION ), new Base64Encoder ); //TODO: Refactor this if(jsonStringBytes.length % 16 != 0) { jsonStringBytes.length += 16 - jsonStringBytes.length % 16; } responsePipe.processMsg(jsonStringBytes.ptr, jsonStringBytes.length); return cast(ubyte[]) responsePipe.toString; case "/master/getMaster": return compress(jsonStringBytes); //TODO: Use pipe instead (how?) default: auto responsePipe = Pipe ( new Base64Encoder ); responsePipe.processMsg(jsonStringBytes.ptr, jsonStringBytes.length); return compress(responsePipe.toString); } } HTTPClientResponse sendRequest(string endpoint, InetHeaderMap headers, ubyte[] requestBody) { return requestHTTP("http://" ~ serverHost ~ "/api/v1" ~ endpoint, (scope HTTPClientRequest r) { r.method = HTTPMethod.POST; r.headers = headers.dup; r.writeBody(requestBody, r.headers["Content-Type"]); } ); } void sendResponse(HTTPServerResponse response, HTTPClientResponse apiResponse, ubyte[] responseBody) { if("Transfer-Encoding" in apiResponse.headers) { apiResponse.headers.remove("Transfer-Encoding"); } response.statusCode = apiResponse.statusCode; response.statusPhrase = apiResponse.statusPhrase; response.headers = apiResponse.headers.dup; response.writeBody(responseBody); }