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

Possible QueryString Encoding Issue Causing NullReferenceException #470

Closed
garfbradaz opened this issue Oct 11, 2018 · 3 comments
Closed

Comments

@garfbradaz
Copy link

I first posted this Issue on stackoverflow here and @cgillum kindly answered and asked me to post here. I will though add that question here as well.

So I'm prototyping some Azure Durable Functions, to try and understand to see if they will fit within a proposed solution for our internal API system.

Based on examples, I've created a Orchestrator Client (HelloOrchestratorClient.cs), that responds to a HttpTrigger. This client extracts some information from the original request, then proceeds to fire off a Orchestrator Function (HelloOrchestrator.cs) passing in some of the information extracted:

Complex HelloOrchestratorClient.cs:

[FunctionName("HttpSyncStart")]
public static async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, methods: "get", Route = "orchestrators/{functionName}/wait")]
    HttpRequestMessage req,
    [OrchestrationClient] DurableOrchestrationClient starter,
    string functionName,
    ILogger log)
{       
    HttpReq originalRequest = new HttpReq() {
            DeveloperId = GetDevKey(req,apiHeaderKey),
            QueryString = req.RequestUri.Query,
            APIName = GetQueryStringValue(req,APIName),
            APIVersion = GetQueryStringValue(req,APIVersion)

    };
    string instanceId =   await starter.StartNewAsync(functionName, originalRequest);

    TimeSpan timeout = GetTimeSpan(req, Timeout) ?? TimeSpan.FromSeconds(30);
    TimeSpan retryInterval = GetTimeSpan(req, RetryInterval) ?? TimeSpan.FromSeconds(1);

    return  await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(
        req,
        instanceId,
        timeout,
        retryInterval);

}

The HelloOrchestrator.cs simply for now is just calling off to one of our internal API's and returning a JsonProduct payload (Simple POCO describing, you guessed it, a title), using a ActivityTigger named HelloOrchestrator.APICall to make the API call itself.

Complex HelloOrchestrator.cs:

[FunctionName("E1_JsonProduct")]
        public static async Task<List<JsonProduct>> Run(
            [OrchestrationTrigger] DurableOrchestrationContextBase context,
            ILogger log)
        {
            List<JsonProduct> output = new List<JsonProduct>();
            HttpReq r = context.GetInput<HttpReq>();
            if(r != null)
            {
                if(r.DeveloperId == null)
                {
                    return output;
                }
                output.Add(await context.CallActivityAsync<JsonProduct>("E1_CallAPI",r));
                return output;
            }
            return output;
        } 

[FunctionName("E1_CallAPI")]
public async static Task<JsonProduct> APICall([ActivityTrigger] HttpReq req,
    ILogger log)
{

    JsonProduct products  = null;
    string u = $"{baseAddress}{req.APIVersion}/{req.APIName}{req.QueryString}";  

    var request = new HttpRequestMessage(HttpMethod.Get, u);
    request.Headers.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json")
    );
    request.Headers.Add("x-apikey",req.DeveloperId);
     log.LogInformation($"URL calling = '{request.RequestUri.AbsoluteUri}'.");
    HttpResponseMessage response = await client.SendAsync(request);
    // return await response.Content.ReadAsStringAsync();
    if(response.IsSuccessStatusCode)
    {
        var formatter = new JsonMediaTypeFormatter
        {
            SerializerSettings = HelloProj.CosmosDB.Models.Products.Converter.Settings
        };

        products = await response.Content.ReadAsAsync<JsonProduct>(new [] {formatter});
    }
    return products;
}

Side Note: The plan is if I can get this to work, is to fan out a bunch of processes to different API's and fan back in again and merge the JSON payload and return it back to the originator.

Issue I'm experiencing

So, when my List<JsonProduct> is returned back from HelloOrchestrator.Run, I receive the following NullReferenceException found on this Gist (Big stack trace) and I receive a 500 response from the Orchestrator Client.

The following proves the output returned does actually have an object at runtime:

Image Posted on SO

Could it be due to the complexity of JsonProduct (Again find the model classes here)? I ask, because when I swap out my Orchestrator Function for a simpler model structure, I don't receive a 500, I receive my JSON Payload.

This example shows the Simple Orchestrator Function HelloOrchestrator.cs, returning a simple TestToDo (Gist for model) flat object that doesn't error:

Simple HelloOrchestrator.cs:

 [FunctionName("E1_Todo")]
    public static async Task<TestToDo> RunToDo(
    [OrchestrationTrigger] DurableOrchestrationContextBase context,
        ILogger log)
    {
        HttpReq r = context.GetInput<HttpReq>();
        TestToDo todo = new TestToDo();
        if(r != null)
        {
            todo = await context.CallActivityAsync<TestToDo>("E1_CallAPITodo",r);
        }
        return todo;
    }

[FunctionName("E1_CallAPITodo")]
public async static Task<TestToDo> APITodoCall([ActivityTrigger] HttpReq req,
    ILogger log)
{

    var request = new HttpRequestMessage(HttpMethod.Get, "https://jsonplaceholder.typicode.com/todos/1");
    request.Headers.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json")
    );
     log.LogInformation($"URL calling = '{request.RequestUri.AbsoluteUri}'. for {req.QueryString}");
    HttpResponseMessage response = await client.SendAsync(request);
    return await response.Content.ReadAsAsync<TestToDo>();

More Information

If you require my full prototype projects, you can find them here:

When you run it, use the following in something like Postman (After F5ing it):

http://localhost:7071/api/orchestrators/E1_JsonProduct/wait?timeout=20&retryInterval=0.25&api=products&apiVersion=v1&filterByImprints=w%26n&limit=20

Interesting (Question that @cgillum asked in the SO post, was the error could be due to an QueryString encoding. So I tested again using the following and it worked, so it seems @cgillum could be right:

http://localhost:7071/api/orchestrators/E1_JsonProduct/wait?timeout=20&retryInterval=0.25&api=products&apiVersion=v1&filterByImprints=WANDN&limit=20

FYI: Both W&N and WANDN are valid values for our filterByImprints parameter.

When you run it, use the following in something like Postman (after F5ing it):

http://localhost:7071/api/orchestrators/E1_Todo/wait?timeout=20&retryInterval=0.25

@cgillum cgillum added the bug label Oct 12, 2018
@garfbradaz
Copy link
Author

garfbradaz commented Oct 16, 2018

I will pull down the Azure Functions project and reference it in my project. and try and glean some more info.

@garfbradaz
Copy link
Author

@cgillum This is still on my list of todos to get you more info.

@cgillum
Copy link
Member

cgillum commented Nov 21, 2018

This will be fixed in the next release.

@cgillum cgillum closed this as completed Nov 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants