Skip to content

Commit cec05cd

Browse files
committed
HttpBinding improvements + fixes
1 parent 12df0cd commit cec05cd

File tree

14 files changed

+207
-79
lines changed

14 files changed

+207
-79
lines changed

sample/HttpTrigger-Powershell/run.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ else
77
$message = "Please pass a name on the query string"
88
}
99

10-
Out-File -Encoding Ascii $res -inputObject $message;
10+
[io.file]::WriteAllText($res, $message)

sample/HttpTrigger/index.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99
if (typeof req.query.name == 'undefined') {
1010
context.res = {
1111
status: 400,
12-
body: "Please pass a name on the query string"
12+
body: "Please pass a name on the query string",
13+
headers: {
14+
'Content-Type': 'text/plain'
15+
}
1316
};
1417
}
1518
else {
1619
context.res = {
1720
status: 200,
18-
body: "Hello " + req.query.name
19-
};
20-
context.res.headers = {
21-
'Content-Type': 'text/plain'
21+
body: "Hello " + req.query.name,
22+
headers: {
23+
'Content-Type': 'text/plain'
24+
}
2225
};
2326
}
2427

src/WebJobs.Script.WebHost/WebScriptHostManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public async Task<HttpResponseMessage> HandleRequestAsync(FunctionDescriptor fun
103103

104104
// Get the response
105105
HttpResponseMessage response = null;
106-
if (!request.Properties.TryGetValue<HttpResponseMessage>("MS_AzureFunctionsHttpResponse", out response))
106+
if (!request.Properties.TryGetValue<HttpResponseMessage>(ScriptConstants.AzureFunctionsHttpResponseKey, out response))
107107
{
108108
// the function was successful but did not write an explicit response
109109
response = new HttpResponseMessage(HttpStatusCode.OK);

src/WebJobs.Script/Binding/HttpBinding.cs

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -34,77 +34,77 @@ public override async Task BindAsync(BindingContext context)
3434
{
3535
HttpRequestMessage request = (HttpRequestMessage)context.TriggerValue;
3636

37-
// TODO: Find a better place for this code
38-
string content = string.Empty;
39-
if (context.Value is Stream)
37+
object content = context.Value;
38+
if (content is Stream)
4039
{
41-
using (StreamReader streamReader = new StreamReader((Stream)context.Value))
40+
using (StreamReader streamReader = new StreamReader((Stream)content))
4241
{
4342
content = await streamReader.ReadToEndAsync();
4443
}
4544
}
46-
else if (context.Value is string)
47-
{
48-
content = (string)context.Value;
49-
}
50-
51-
HttpResponseMessage response = null;
52-
try
53-
{
54-
// attempt to read the content as a JObject
55-
JObject jsonObject = JObject.Parse(content);
5645

57-
// TODO: This logic needs to be made more robust
58-
// E.g. we might decide to use a Regex to determine if
59-
// the json is a response body or not
60-
if (jsonObject["body"] != null)
46+
HttpStatusCode statusCode = HttpStatusCode.OK;
47+
JObject headers = null;
48+
if (content is string)
49+
{
50+
try
6151
{
62-
HttpStatusCode statusCode = HttpStatusCode.OK;
63-
if (jsonObject["status"] != null)
64-
{
65-
statusCode = (HttpStatusCode)jsonObject.Value<int>("status");
66-
}
67-
68-
string body = jsonObject["body"].ToString();
52+
// attempt to read the content as a JObject
53+
JObject jo = JObject.Parse((string)content);
6954

70-
response = new HttpResponseMessage(statusCode);
71-
response.Content = new StringContent(body);
55+
// if the content is json we capture that so it will be
56+
// serialized as json by WebApi below
57+
content = jo;
7258

73-
// we default the Content-Type here, but we override below with any
74-
// Content-Type header the user might have set themselves
75-
// TODO: rather than newing up an HttpResponseMessage investigate using
76-
// request.CreateResponse, which should allow WebApi Content negotiation to
77-
// take place.
78-
if (Utility.IsJson(body))
59+
// Sniff the object to see if it looks like a response object
60+
// by convention
61+
JToken value = null;
62+
if (jo.TryGetValue("body", out value))
7963
{
80-
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
81-
}
64+
content = value;
65+
headers = jo["headers"] as JObject;
8266

83-
// apply any user specified headers
84-
JObject headers = (JObject)jsonObject["headers"];
85-
if (headers != null)
86-
{
87-
foreach (var header in headers)
67+
if (value is JValue && ((JValue)value).Type == JTokenType.String)
8868
{
89-
AddResponseHeader(response, header);
69+
// convert raw strings so they get serialized properly below
70+
content = (string)value;
9071
}
72+
73+
if (jo.TryGetValue("status", out value) && value is JValue)
74+
{
75+
statusCode = (HttpStatusCode)(int)value;
76+
}
9177
}
9278
}
79+
catch (JsonException)
80+
{
81+
// not a json response
82+
}
9383
}
94-
catch (JsonException)
84+
85+
HttpResponseMessage response = null;
86+
if (content is string)
9587
{
96-
// not a json response
88+
// for raw strings, we compose the content ourselves, otherwise WebApi
89+
// will serialize it as JSON and add quotes/double quotes to the string
90+
response = new HttpResponseMessage(statusCode)
91+
{
92+
Content = new StringContent((string)content)
93+
};
94+
}
95+
else
96+
{
97+
// let WebApi do its default serialization and content negotiation
98+
response = request.CreateResponse(statusCode, content);
9799
}
98100

99-
if (response == null)
101+
if (headers != null)
100102
{
101-
// if unable to parse a json response just send
102-
// the raw content
103-
response = new HttpResponseMessage
103+
// apply any user specified headers
104+
foreach (var header in headers)
104105
{
105-
StatusCode = HttpStatusCode.OK,
106-
Content = new StringContent(content)
107-
};
106+
AddResponseHeader(response, header);
107+
}
108108
}
109109

110110
request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey] = response;

src/WebJobs.Script/Description/Node/NodeFunctionInvoker.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,17 @@ private static async Task ProcessOutputBindingsAsync(Collection<FunctionBinding>
211211
{
212212
// get the output value from the script
213213
object value = null;
214-
if (bindings.TryGetValue(binding.Metadata.Name, out value) && value != null)
214+
bool haveValue = bindings.TryGetValue(binding.Metadata.Name, out value);
215+
if (!haveValue && binding.Metadata.Type == "http")
216+
{
217+
// http bindings support a special context.req/context.res programming
218+
// model, so we must map that back to the actual binding name if a value
219+
// wasn't provided using the binding name itself
220+
haveValue = bindings.TryGetValue("res", out value);
221+
}
222+
223+
// apply the value to the binding
224+
if (haveValue && value != null)
215225
{
216226
value = ConvertBindingValue(value);
217227

test/WebJobs.Script.Tests/CSharpEndToEndTests.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
using Microsoft.CodeAnalysis;
1212
using Microsoft.CodeAnalysis.CSharp;
1313
using Microsoft.WindowsAzure.Storage.Blob;
14-
using Microsoft.WindowsAzure.Storage.Queue;
1514
using Newtonsoft.Json;
16-
using Newtonsoft.Json.Linq;
1715
using Xunit;
1816

1917
namespace Microsoft.Azure.WebJobs.Script.Tests

test/WebJobs.Script.Tests/NodeEndToEndTests.cs

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Net.Http;
1111
using System.Net.Http.Headers;
1212
using System.Threading.Tasks;
13+
using System.Web.Http;
1314
using Microsoft.Azure.WebJobs.Host;
1415
using Microsoft.Azure.WebJobs.Script.Tests.ApiHub;
1516
using Microsoft.WindowsAzure.Storage.Blob;
@@ -97,6 +98,7 @@ public async Task HttpTrigger_Post_ByteArray()
9798
Method = HttpMethod.Post,
9899
Content = new ByteArrayContent(inputBytes)
99100
};
101+
request.SetConfiguration(new HttpConfiguration());
100102
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
101103

102104
Dictionary<string, object> arguments = new Dictionary<string, object>
@@ -105,7 +107,7 @@ public async Task HttpTrigger_Post_ByteArray()
105107
};
106108
await Fixture.Host.CallAsync("HttpTriggerByteArray", arguments);
107109

108-
HttpResponseMessage response = (HttpResponseMessage)request.Properties["MS_AzureFunctionsHttpResponse"];
110+
HttpResponseMessage response = (HttpResponseMessage)request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey];
109111
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
110112

111113
JObject testResult = await GetFunctionTestResult("HttpTriggerByteArray");
@@ -345,15 +347,16 @@ public async Task HttpTrigger_Get()
345347
RequestUri = new Uri(string.Format("http://localhost/api/httptrigger?name=Mathew%20Charles&location=Seattle")),
346348
Method = HttpMethod.Get,
347349
};
350+
request.SetConfiguration(new HttpConfiguration());
348351
request.Headers.Add("test-header", "Test Request Header");
349352

350353
Dictionary<string, object> arguments = new Dictionary<string, object>
351354
{
352-
{ "req", request }
355+
{ "request", request }
353356
};
354357
await Fixture.Host.CallAsync("HttpTrigger", arguments);
355358

356-
HttpResponseMessage response = (HttpResponseMessage)request.Properties["MS_AzureFunctionsHttpResponse"];
359+
HttpResponseMessage response = (HttpResponseMessage)request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey];
357360
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
358361

359362
Assert.Equal("Test Response Header", response.Headers.GetValues("test-header").SingleOrDefault());
@@ -374,6 +377,71 @@ public async Task HttpTrigger_Get()
374377
Assert.Equal("Test Request Header", reqHeaders["test-header"]);
375378
}
376379

380+
[Fact]
381+
public async Task HttpTrigger_Scenarios_ScalarReturn_InBody()
382+
{
383+
HttpRequestMessage request = new HttpRequestMessage
384+
{
385+
RequestUri = new Uri(string.Format("http://localhost/api/httptrigger-scenarios")),
386+
Method = HttpMethod.Post,
387+
};
388+
request.SetConfiguration(new HttpConfiguration());
389+
390+
JObject value = new JObject()
391+
{
392+
{ "status", "200" },
393+
{ "body", 123 }
394+
};
395+
JObject input = new JObject()
396+
{
397+
{ "scenario", "echo" },
398+
{ "value", value }
399+
};
400+
request.Content = new StringContent(input.ToString());
401+
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
402+
403+
Dictionary<string, object> arguments = new Dictionary<string, object>
404+
{
405+
{ "req", request }
406+
};
407+
await Fixture.Host.CallAsync("HttpTrigger-Scenarios", arguments);
408+
409+
HttpResponseMessage response = (HttpResponseMessage)request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey];
410+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
411+
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
412+
Assert.Equal(123, await response.Content.ReadAsAsync<int>());
413+
}
414+
415+
[Fact]
416+
public async Task HttpTrigger_Scenarios_ScalarReturn()
417+
{
418+
HttpRequestMessage request = new HttpRequestMessage
419+
{
420+
RequestUri = new Uri(string.Format("http://localhost/api/httptrigger-scenarios")),
421+
Method = HttpMethod.Post,
422+
};
423+
request.SetConfiguration(new HttpConfiguration());
424+
425+
JObject input = new JObject()
426+
{
427+
{ "scenario", "echo" },
428+
{ "value", 123 }
429+
};
430+
request.Content = new StringContent(input.ToString());
431+
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
432+
433+
Dictionary<string, object> arguments = new Dictionary<string, object>
434+
{
435+
{ "req", request }
436+
};
437+
await Fixture.Host.CallAsync("HttpTrigger-Scenarios", arguments);
438+
439+
HttpResponseMessage response = (HttpResponseMessage)request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey];
440+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
441+
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
442+
Assert.Equal(123, await response.Content.ReadAsAsync<int>());
443+
}
444+
377445
[Fact]
378446
public async Task HttpTrigger_Post_PlainText()
379447
{
@@ -384,14 +452,15 @@ public async Task HttpTrigger_Post_PlainText()
384452
Method = HttpMethod.Post,
385453
Content = new StringContent(testData)
386454
};
455+
request.SetConfiguration(new HttpConfiguration());
387456

388457
Dictionary<string, object> arguments = new Dictionary<string, object>
389458
{
390-
{ "req", request }
459+
{ "request", request }
391460
};
392461
await Fixture.Host.CallAsync("HttpTrigger", arguments);
393462

394-
HttpResponseMessage response = (HttpResponseMessage)request.Properties["MS_AzureFunctionsHttpResponse"];
463+
HttpResponseMessage response = (HttpResponseMessage)request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey];
395464
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
396465

397466
string body = await response.Content.ReadAsStringAsync();
@@ -417,15 +486,16 @@ public async Task HttpTrigger_Post_JsonObject()
417486
Method = HttpMethod.Post,
418487
Content = new StringContent(rawBody)
419488
};
489+
request.SetConfiguration(new HttpConfiguration());
420490
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
421491

422492
Dictionary<string, object> arguments = new Dictionary<string, object>
423493
{
424-
{ "req", request }
494+
{ "request", request }
425495
};
426496
await Fixture.Host.CallAsync("HttpTrigger", arguments);
427497

428-
HttpResponseMessage response = (HttpResponseMessage)request.Properties["MS_AzureFunctionsHttpResponse"];
498+
HttpResponseMessage response = (HttpResponseMessage)request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey];
429499
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
430500

431501
string body = await response.Content.ReadAsStringAsync();
@@ -487,15 +557,16 @@ public async Task HttpTrigger_Post_JsonArray()
487557
Method = HttpMethod.Post,
488558
Content = new StringContent(rawBody)
489559
};
560+
request.SetConfiguration(new HttpConfiguration());
490561
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
491562

492563
Dictionary<string, object> arguments = new Dictionary<string, object>
493564
{
494-
{ "req", request }
565+
{ "request", request }
495566
};
496567
await Fixture.Host.CallAsync("HttpTrigger", arguments);
497568

498-
HttpResponseMessage response = (HttpResponseMessage)request.Properties["MS_AzureFunctionsHttpResponse"];
569+
HttpResponseMessage response = (HttpResponseMessage)request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey];
499570
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
500571

501572
string body = await response.Content.ReadAsStringAsync();
@@ -528,6 +599,7 @@ public async Task WebHookTrigger_GenericJson()
528599
Method = HttpMethod.Post,
529600
Content = new StringContent(testObject.ToString())
530601
};
602+
request.SetConfiguration(new HttpConfiguration());
531603
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
532604

533605
Dictionary<string, object> arguments = new Dictionary<string, object>
@@ -536,7 +608,7 @@ public async Task WebHookTrigger_GenericJson()
536608
};
537609
await Fixture.Host.CallAsync("WebHookTrigger", arguments);
538610

539-
HttpResponseMessage response = (HttpResponseMessage)request.Properties["MS_AzureFunctionsHttpResponse"];
611+
HttpResponseMessage response = (HttpResponseMessage)request.Properties[ScriptConstants.AzureFunctionsHttpResponseKey];
540612
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
541613

542614
string body = await response.Content.ReadAsStringAsync();

0 commit comments

Comments
 (0)