Skip to content

Commit e94fb49

Browse files
authored
[HttpWorker]Parse JSON response for HttpTrigger (#5573)
1 parent adc21d8 commit e94fb49

File tree

6 files changed

+131
-36
lines changed

6 files changed

+131
-36
lines changed

src/WebJobs.Script/Binding/Http/HttpBinding.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
using Microsoft.AspNetCore.Mvc.WebApiCompatShim;
1818
using Microsoft.Azure.WebJobs.Script.Config;
1919
using Microsoft.Azure.WebJobs.Script.Description;
20-
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
20+
using Microsoft.Azure.WebJobs.Script.Workers;
2121
using Newtonsoft.Json;
2222
using Newtonsoft.Json.Linq;
2323

@@ -115,12 +115,12 @@ internal static void ParseResponseObject(IDictionary<string, object> responseObj
115115
// Sniff the object to see if it looks like a response object
116116
// by convention
117117
object bodyValue = null;
118-
if (responseObject.TryGetValue(RpcWorkerConstants.RpcHttpBody, out bodyValue, ignoreCase: true))
118+
if (responseObject.TryGetValue(WorkerConstants.HttpBody, out bodyValue, ignoreCase: true))
119119
{
120120
// the response content becomes the specified body value
121121
content = bodyValue;
122122

123-
if (responseObject.TryGetValue(RpcWorkerConstants.RpcHttpHeaders, out IDictionary<string, object> headersValue, ignoreCase: true))
123+
if (responseObject.TryGetValue(WorkerConstants.HttpHeaders, out IDictionary<string, object> headersValue, ignoreCase: true))
124124
{
125125
headers = headersValue;
126126
}
@@ -130,12 +130,12 @@ internal static void ParseResponseObject(IDictionary<string, object> responseObj
130130
statusCode = responseStatusCode.Value;
131131
}
132132

133-
if (responseObject.TryGetValue<bool>(RpcWorkerConstants.RpcHttpEnableContentNegotiation, out bool enableContentNegotiationValue, ignoreCase: true))
133+
if (responseObject.TryGetValue<bool>(WorkerConstants.HttpEnableContentNegotiation, out bool enableContentNegotiationValue, ignoreCase: true))
134134
{
135135
enableContentNegotiation = enableContentNegotiationValue;
136136
}
137137

138-
if (responseObject.TryGetValue(RpcWorkerConstants.RpcHttpCookies, out List<Tuple<string, string, CookieOptions>> cookiesValue, ignoreCase: true))
138+
if (responseObject.TryGetValue(WorkerConstants.HttpCookies, out List<Tuple<string, string, CookieOptions>> cookiesValue, ignoreCase: true))
139139
{
140140
cookies = cookiesValue;
141141
}
@@ -146,8 +146,8 @@ internal static bool TryParseStatusCode(IDictionary<string, object> responseObje
146146
{
147147
statusCode = StatusCodes.Status200OK;
148148

149-
if (!responseObject.TryGetValue(RpcWorkerConstants.RpcHttpStatusCode, out object statusValue, ignoreCase: true) &&
150-
!responseObject.TryGetValue(RpcWorkerConstants.RpcHttpStatus, out statusValue, ignoreCase: true))
149+
if (!responseObject.TryGetValue(WorkerConstants.HttpStatusCode, out object statusValue, ignoreCase: true) &&
150+
!responseObject.TryGetValue(WorkerConstants.HttpStatus, out statusValue, ignoreCase: true))
151151
{
152152
return false;
153153
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.Workers.Http
7+
{
8+
internal class HttpOutputBindingResponse
9+
{
10+
public string StatusCode { get; set; }
11+
12+
public string Status { get; set; }
13+
14+
public object Body { get; set; }
15+
16+
public IDictionary<string, object> Headers { get; set; }
17+
}
18+
}

src/WebJobs.Script/Workers/Http/HttpScriptInvocationResultExtensions.cs

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Dynamic;
67
using System.Linq;
8+
using Microsoft.Azure.WebJobs.Script.Binding;
79
using Microsoft.Azure.WebJobs.Script.Description;
10+
using Newtonsoft.Json;
11+
using Newtonsoft.Json.Linq;
812

913
namespace Microsoft.Azure.WebJobs.Script.Workers.Http
1014
{
@@ -16,49 +20,82 @@ public static ScriptInvocationResult ToScriptInvocationResult(this HttpScriptInv
1620
{
1721
Outputs = new Dictionary<string, object>()
1822
};
19-
if (httpScriptInvocationResult.Outputs != null && httpScriptInvocationResult.Outputs.Any())
23+
24+
foreach (var outputBindingMetadata in scriptInvocationContext.FunctionMetadata.OutputBindings)
2025
{
21-
foreach (var outputFromHttpWorker in httpScriptInvocationResult.Outputs)
26+
object outputValue = GetOutputValue(outputBindingMetadata.Name, outputBindingMetadata.Type, outputBindingMetadata.DataType, httpScriptInvocationResult.Outputs);
27+
if (outputValue != null)
2228
{
23-
BindingMetadata outputBindingMetadata = GetBindingMetadata(outputFromHttpWorker.Key, scriptInvocationContext);
24-
scriptInvocationResult.Outputs[outputFromHttpWorker.Key] = GetOutputValue(outputBindingMetadata, outputFromHttpWorker.Value);
29+
scriptInvocationResult.Outputs[outputBindingMetadata.Name] = outputValue;
2530
}
2631
}
32+
2733
if (httpScriptInvocationResult.ReturnValue != null)
2834
{
2935
BindingMetadata returnParameterBindingMetadata = GetBindingMetadata(ScriptConstants.SystemReturnParameterBindingName, scriptInvocationContext);
30-
scriptInvocationResult.Return = GetOutputValue(returnParameterBindingMetadata, httpScriptInvocationResult.ReturnValue);
36+
scriptInvocationResult.Return = GetBindingValue(returnParameterBindingMetadata.DataType, httpScriptInvocationResult.ReturnValue);
3137
}
3238
return scriptInvocationResult;
3339
}
3440

35-
private static object GetOutputValue(BindingMetadata bindingMetadata, object outputBindingValue)
41+
internal static object GetOutputValue(string outputBindingName, string bindingType, DataType? bindingDataType, IDictionary<string, object> outputsFromWorker)
42+
{
43+
if (outputsFromWorker == null)
44+
{
45+
return null;
46+
}
47+
object outputBindingValue;
48+
if (bindingType == "http" && !outputBindingName.Equals(ScriptConstants.SystemReturnParameterBindingName))
49+
{
50+
return GetHttpOutputBindingResponse(outputBindingName, outputsFromWorker);
51+
}
52+
if (outputsFromWorker.TryGetValue(outputBindingName, out outputBindingValue))
53+
{
54+
return GetBindingValue(bindingDataType, outputBindingValue);
55+
}
56+
return null;
57+
}
58+
59+
private static object GetBindingValue(DataType? bindingDataType, object outputBindingValue)
60+
{
61+
if (bindingDataType == DataType.Binary)
62+
{
63+
return outputBindingValue;
64+
}
65+
else
66+
{
67+
try
68+
{
69+
return Convert.FromBase64String((string)outputBindingValue);
70+
}
71+
catch
72+
{
73+
//ignore
74+
}
75+
}
76+
return outputBindingValue;
77+
}
78+
79+
internal static object GetHttpOutputBindingResponse(string bindingName, IDictionary<string, object> outputsFromWorker)
3680
{
37-
if (bindingMetadata != null && outputBindingValue != null)
81+
HttpOutputBindingResponse httpOut = new HttpOutputBindingResponse();
82+
if (outputsFromWorker.TryGetValue(bindingName, out object outputBindingValue))
3883
{
39-
if (bindingMetadata.DataType == DataType.Binary)
84+
try
4085
{
41-
return outputBindingValue;
86+
httpOut = JsonConvert.DeserializeObject<HttpOutputBindingResponse>(outputBindingValue.ToString());
4287
}
43-
else
88+
catch
4489
{
45-
try
46-
{
47-
return Convert.FromBase64String((string)outputBindingValue);
48-
}
49-
catch
50-
{
51-
//ignore
52-
}
90+
//ignore
5391
}
54-
return outputBindingValue;
5592
}
56-
return null;
93+
return JsonConvert.SerializeObject(httpOut);
5794
}
5895

5996
private static BindingMetadata GetBindingMetadata(string outputBidingName, ScriptInvocationContext scriptInvocationContext)
6097
{
61-
// Find dataType for outputbinding if exists
98+
// Find BindingMetadata that matches output form http response
6299
return scriptInvocationContext.FunctionMetadata.OutputBindings.FirstOrDefault(outputBindingMetadata => outputBindingMetadata.Name == outputBidingName);
63100
}
64101
}

src/WebJobs.Script/Workers/Rpc/RpcWorkerConstants.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,6 @@ public static class RpcWorkerConstants
3030
// Rpc message length
3131
public const int DefaultMaxMessageLengthBytes = 128 * 1024 * 1024;
3232

33-
// Rpc Http Constants
34-
public const string RpcHttpBody = "body";
35-
public const string RpcHttpHeaders = "headers";
36-
public const string RpcHttpEnableContentNegotiation = "enableContentNegotiation";
37-
public const string RpcHttpCookies = "cookies";
38-
public const string RpcHttpStatusCode = "statusCode";
39-
public const string RpcHttpStatus = "status";
40-
4133
// Capabilites
4234
public const string RawHttpBodyBytes = "RawHttpBodyBytes";
4335
public const string TypedDataCollection = "TypedDataCollection";

src/WebJobs.Script/Workers/WorkerConstants.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,13 @@ public static class WorkerConstants
4040
// Language Worker process exit codes
4141
public const int SuccessExitCode = 0;
4242
public const int IntentionalRestartExitCode = 200;
43+
44+
// Http Constants
45+
public const string HttpBody = "body";
46+
public const string HttpHeaders = "headers";
47+
public const string HttpEnableContentNegotiation = "enableContentNegotiation";
48+
public const string HttpCookies = "cookies";
49+
public const string HttpStatusCode = "statusCode";
50+
public const string HttpStatus = "status";
4351
}
4452
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Net;
8+
using System.Net.Http;
9+
using System.Net.Sockets;
10+
using System.Text;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
using Microsoft.Azure.WebJobs.Script.Workers;
14+
using Microsoft.Azure.WebJobs.Script.Workers.Http;
15+
using Microsoft.Extensions.Logging;
16+
using Microsoft.Extensions.Options;
17+
using Microsoft.Net.Http.Headers;
18+
using Moq;
19+
using Moq.Protected;
20+
using Newtonsoft.Json;
21+
using Xunit;
22+
23+
namespace Microsoft.Azure.WebJobs.Script.Tests.HttpWorker
24+
{
25+
public class HttpScriptInvocationResultExtensionsTests
26+
{
27+
[Theory]
28+
[InlineData("{\"statusCode\": \"204\"}", "{\"StatusCode\":\"204\",\"Status\":null,\"Body\":null,\"Headers\":null}")]
29+
[InlineData("{\"body\": \"hello\"}", "{\"StatusCode\":null,\"Status\":null,\"Body\":\"hello\",\"Headers\":null}")]
30+
[InlineData("{\"body\": \"hello\", \"statusCode\":\"300\"}", "{\"StatusCode\":\"300\",\"Status\":null,\"Body\":\"hello\",\"Headers\":null}")]
31+
[InlineData("{\"body\":\"foobar\",\"statusCode\":\"301\",\"headers\":{\"header1\":\"header1Value\",\"header2\":\"header2Value\"}}", "{\"StatusCode\":\"301\",\"Status\":null,\"Body\":\"foobar\",\"Headers\":{\"header1\":\"header1Value\",\"header2\":\"header2Value\"}}")]
32+
public void GetHttpOutputBindingResponse_ReturnsExpected(string inputString, string expectedOutput)
33+
{
34+
Dictionary<string, object> outputsFromWorker = new Dictionary<string, object>();
35+
outputsFromWorker["httpOutput1"] = inputString;
36+
var actualResult = HttpScriptInvocationResultExtensions.GetHttpOutputBindingResponse("httpOutput1", outputsFromWorker);
37+
Assert.Equal(expectedOutput, actualResult);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)