Skip to content

Commit

Permalink
Split execute function apart (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
nima-ap-zz authored Jun 29, 2019
1 parent ca487c4 commit d614e78
Showing 1 changed file with 114 additions and 74 deletions.
188 changes: 114 additions & 74 deletions csharp/ExecuteFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

namespace PlayFab.AzureFunctions
{
public static class ExecuteFunction
public static class LocalExecuteFunction
{
private const string DEV_SECRET_KEY = "PLAYFAB_DEV_SECRET_KEY";
private const string TITLE_ID = "PLAYFAB_TITLE_ID";
Expand All @@ -36,7 +36,7 @@ public static class ExecuteFunction
/// <param name="log">A logger object</param>
/// <returns>The function execution result(s)</returns>
[FunctionName("ExecuteFunction")]
public static async Task<HttpResponseMessage> Run(
public static async Task<HttpResponseMessage> ExecuteFunction(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "CloudScript/ExecuteFunction")] HttpRequest request, ILogger log)
{
// Extract the caller's entity token
Expand All @@ -46,6 +46,77 @@ public static async Task<HttpResponseMessage> Run(
string body = await DecompressHttpBody(request);
var execRequest = PlayFabSimpleJson.DeserializeObject<ExecuteFunctionRequest>(body);

// Create a FunctionContextInternal as the payload to send to the target function
var functionContext = new FunctionContextInternal
{
CallerEntityProfile = await GetEntityProfile(callerEntityToken),
TitleAuthenticationContext = new TitleAuthenticationContext
{
Id = Environment.GetEnvironmentVariable(TITLE_ID, EnvironmentVariableTarget.Process),
EntityToken = await GetTitleEntityToken()
},
FunctionArgument = execRequest.FunctionParameter
};

// Serialize the request to the azure function and add headers
var functionRequestContent = new StringContent(PlayFabSimpleJson.SerializeObject(functionContext));
functionRequestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

var azureFunctionUri = ConstructLocalAzureFunctionUri(execRequest.FunctionName, request.Host);

var sw = new Stopwatch();
sw.Start();

// Execute the local azure function
using (var functionResponseMessage =
await httpClient.PostAsync(azureFunctionUri, functionRequestContent))
{
sw.Stop();
long executionTime = sw.ElapsedMilliseconds;

if (!functionResponseMessage.IsSuccessStatusCode)
{
throw new Exception($"An error occured while executing the target function locally: FunctionName: {execRequest.FunctionName}, HTTP Status Code: {functionResponseMessage.StatusCode}.");
}

// Extract the response content
using (var functionResponseContent = functionResponseMessage.Content)
{
// Prepare a response to reply back to client with and include function execution results
var functionResult = new ExecuteFunctionResult
{
FunctionName = execRequest.FunctionName,
FunctionResult = await ExtractFunctionResult(functionResponseContent),
ExecutionTimeMilliseconds = (int) executionTime,
FunctionResultTooLarge = false
};

// Reply back to client with final results
var output = new PlayFabJsonSuccess<ExecuteFunctionResult>
{
code = 200,
status = "OK",
data = functionResult
};
// Serialize the output and return it
var outputStr = PlayFabSimpleJson.SerializeObject(output);

return new HttpResponseMessage
{
Content = new ByteArrayContent(CompressResponseBody(output, request)),
StatusCode = HttpStatusCode.OK
};
}
}
}

/// <summary>
/// Fetch's an entity's profile from the PlayFab server
/// </summary>
/// <param name="callerEntityToken">The entity token of the entity profile being fetched</param>
/// <returns>The entity's profile</returns>
private static async Task<EntityProfileBody> GetEntityProfile(string callerEntityToken) {
// Construct the PlayFabAPI URL for GetEntityProfile
var getProfileUrl = GetServerApiUri("/Profile/GetProfile");

// Create the get entity profile request
Expand Down Expand Up @@ -82,21 +153,33 @@ await httpClient.PostAsync(getProfileUrl, profileRequestContent))
throw new Exception($"Failed to get Entity Profile: code: {getProfileResponseSuccess?.code}");
}

// Find the Title Entity Token and attach to outbound request to target function
string titleEntityToken = null;
return getProfileResponse.Profile;
}

/// <summary>
/// Grabs the developer secret key from the environment variable (expected to be set) and uses it to
/// ask the PlayFab server for a title entity token.
/// </summary>
/// <returns>The title's entity token</returns>
private static async Task<string> GetTitleEntityToken()
{
var titleEntityTokenRequest = new AuthenticationModels.GetEntityTokenRequest();

var getEntityTokenUrl = GetServerApiUri("/Authentication/GetEntityToken");

// Grab the developer secret key from the environment variables (app settings) to use as header for GetEntityToken
var secretKey = Environment.GetEnvironmentVariable(DEV_SECRET_KEY, EnvironmentVariableTarget.Process);

if (string.IsNullOrEmpty(secretKey))
{
// Environment variable was not set on the app
throw new Exception("Could not fetch the developer secret key from the environment. Please set \"PLAYFAB_DEV_SECRET_KEY\" in your app's local.settings.json file.");
}

var titleEntityTokenRequestContent = new StringContent(PlayFabSimpleJson.SerializeObject(titleEntityTokenRequest));
titleEntityTokenRequestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
titleEntityTokenRequestContent.Headers.Add("X-SecretKey", secretKey);

PlayFabJsonSuccess<AuthenticationModels.GetEntityTokenResponse> titleEntityTokenResponseSuccess = null;
AuthenticationModels.GetEntityTokenResponse titleEntityTokenResponse = null;

using (var titleEntityTokenResponseMessage =
await httpClient.PostAsync(getEntityTokenUrl, titleEntityTokenRequestContent))
{
Expand All @@ -105,88 +188,40 @@ await httpClient.PostAsync(getEntityTokenUrl, titleEntityTokenRequestContent))
string titleEntityTokenResponseString = await titleEntityTokenResponseContent.ReadAsStringAsync();

// Deserialize the http response
titleEntityTokenResponseSuccess =
var titleEntityTokenResponseSuccess =
PlayFabSimpleJson.DeserializeObject<PlayFabJsonSuccess<AuthenticationModels.GetEntityTokenResponse>>(titleEntityTokenResponseString);

// Extract the actual get title entity token header
titleEntityTokenResponse = titleEntityTokenResponseSuccess.data;
titleEntityToken = titleEntityTokenResponse.EntityToken;
var titleEntityTokenResponse = titleEntityTokenResponseSuccess.data;

return titleEntityTokenResponse.EntityToken;
}
}
}

// Extract the request for the next stage from the get arguments response
var functionContext = new FunctionContextInternal
{
CallerEntityProfile = getProfileResponse.Profile,
TitleAuthenticationContext = new TitleAuthenticationContext
{
Id = Environment.GetEnvironmentVariable(TITLE_ID, EnvironmentVariableTarget.Process),
EntityToken = titleEntityToken
},
FunctionArgument = execRequest.FunctionParameter
};

/// <summary>
/// Constructs a function's local url given its name and the current function app's host. Assumes that the
/// function is located in the same function app as the application host provided.
/// </summary>
/// <param name="functionName">The name of the function to construct a URL for</param>
/// <param name="appHost">The function's application host</param>
/// <returns>The function's URI</returns>
private static string ConstructLocalAzureFunctionUri(string functionName, HostString appHost)
{
// Assemble the target function's path in the current App
string routePrefix = GetHostRoutePrefix();
string functionPath = routePrefix != null ? routePrefix + "/" + execRequest.FunctionName
: execRequest.FunctionName;
string functionPath = routePrefix != null ? routePrefix + "/" + functionName
: functionName;

// Build URI of Azure Function based on current host
var uriBuilder = new UriBuilder
{
Host = request.Host.Host,
Port = request.Host.Port ?? 80,
Host = appHost.Host,
Port = appHost.Port ?? 80,
Path = functionPath
};

// Serialize the request to the azure function and add headers
var functionRequestContent = new StringContent(PlayFabSimpleJson.SerializeObject(functionContext));
functionRequestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

var sw = new Stopwatch();
sw.Start();

// Execute the local azure function
using (var functionResponseMessage =
await httpClient.PostAsync(uriBuilder.Uri.AbsoluteUri, functionRequestContent))
{
sw.Stop();
long executionTime = sw.ElapsedMilliseconds;

if (!functionResponseMessage.IsSuccessStatusCode)
{
throw new Exception($"An error occured while executing the target function locally: FunctionName: {execRequest.FunctionName}, HTTP Status Code: {functionResponseMessage.StatusCode}.");
}

// Extract the response content
using (var functionResponseContent = functionResponseMessage.Content)
{
// Prepare a response to reply back to client with and include function execution results
var functionResult = new ExecuteFunctionResult
{
FunctionName = execRequest.FunctionName,
FunctionResult = await ExtractFunctionResult(functionResponseContent),
ExecutionTimeMilliseconds = (int) executionTime,
FunctionResultTooLarge = false
};

// Reply back to client with final results
var output = new PlayFabJsonSuccess<ExecuteFunctionResult>
{
code = 200,
status = "OK",
data = functionResult
};
// Serialize the output and return it
var outputStr = PlayFabSimpleJson.SerializeObject(output);

return new HttpResponseMessage
{
Content = new ByteArrayContent(CompressResponseBody(output, request)),
StatusCode = HttpStatusCode.OK
};
}
}
return uriBuilder.Uri.AbsoluteUri;
}

private static async Task<object> ExtractFunctionResult(HttpContent content)
Expand Down Expand Up @@ -219,6 +254,7 @@ private static async Task<object> ExtractFunctionResult(HttpContent content)
return null;
}


private static string GetServerApiUri(string endpoint)
{
var sb = new StringBuilder();
Expand Down Expand Up @@ -248,6 +284,7 @@ private static string GetServerApiUri(string endpoint)
return uriBuilder.Uri.AbsoluteUri;
}

#region Utility Functions
private static string ReadAllFileText(string filename)
{
var sb = new StringBuilder();
Expand Down Expand Up @@ -387,8 +424,10 @@ private static byte[] StreamToBytes(Stream input)
return output.ToArray();
}
}
#endregion
}

#region Models
public class TitleAuthenticationContext
{
public string Id;
Expand Down Expand Up @@ -440,4 +479,5 @@ public class HostJsonHttpModel
public string routePrefix { get; set; }
}
}
#endregion
}

0 comments on commit d614e78

Please sign in to comment.