Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split execute function apart #5

Merged
merged 1 commit into from
Jun 29, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}