@@ -21,6 +21,8 @@ namespace Microsoft.Azure.WebJobs.Script.Binding
21
21
{
22
22
public class HttpBinding : FunctionBinding , IResultProcessingBinding
23
23
{
24
+ private static readonly DictionaryJsonConverter _dictionaryJsonConverter = new DictionaryJsonConverter ( ) ;
25
+
24
26
public HttpBinding ( ScriptHostConfiguration config , BindingMetadata metadata , FileAccess access ) :
25
27
base ( config , metadata , access )
26
28
{
@@ -31,88 +33,113 @@ public override Collection<CustomAttributeBuilder> GetCustomAttributes(Type para
31
33
return null ;
32
34
}
33
35
34
- public override async Task BindAsync ( BindingContext context )
36
+ public override Task BindAsync ( BindingContext context )
35
37
{
36
38
HttpRequestMessage request = ( HttpRequestMessage ) context . TriggerValue ;
37
39
38
40
object content = context . Value ;
39
41
if ( content is Stream )
40
42
{
41
43
// for script language functions (e.g. PowerShell, BAT, etc.) the value
42
- // will be a Stream which we need to convert
43
- using ( StreamReader streamReader = new StreamReader ( ( Stream ) content ) )
44
- {
45
- content = await streamReader . ReadToEndAsync ( ) ;
46
- }
44
+ // will be a Stream which we need to convert to string
45
+ ConvertStreamToValue ( ( Stream ) content , DataType . String , ref content ) ;
47
46
}
48
47
49
- HttpStatusCode statusCode = HttpStatusCode . OK ;
50
- JObject headers = null ;
51
- bool isRawResponse = false ;
52
- if ( content is string )
48
+ HttpResponseMessage response = CreateResponse ( request , content ) ;
49
+ request . Properties [ ScriptConstants . AzureFunctionsHttpResponseKey ] = response ;
50
+
51
+ return Task . CompletedTask ;
52
+ }
53
+
54
+ internal static HttpResponseMessage CreateResponse ( HttpRequestMessage request , object content )
55
+ {
56
+ string stringContent = content as string ;
57
+ if ( stringContent != null )
53
58
{
54
59
try
55
60
{
56
- // attempt to read the content as a JObject
57
- JObject jo = JObject . Parse ( ( string ) content ) ;
58
-
59
- // if the content is json we capture that so it will be
60
- // serialized as json by WebApi below
61
- content = jo ;
62
-
63
- // TODO: Improve this logic
64
- // Sniff the object to see if it looks like a response object
65
- // by convention
66
- JToken value = null ;
67
- if ( jo . TryGetValue ( "body" , StringComparison . OrdinalIgnoreCase , out value ) )
68
- {
69
- content = value ;
70
-
71
- if ( value is JValue && ( ( JValue ) value ) . Type == JTokenType . String )
72
- {
73
- // convert raw strings so they get serialized properly below
74
- content = ( string ) value ;
75
- }
76
-
77
- if ( jo . TryGetValue ( "headers" , StringComparison . OrdinalIgnoreCase , out value ) && value is JObject )
78
- {
79
- headers = ( JObject ) value ;
80
- }
81
-
82
- if ( ( jo . TryGetValue ( "status" , StringComparison . OrdinalIgnoreCase , out value ) && value is JValue ) ||
83
- ( jo . TryGetValue ( "statusCode" , StringComparison . OrdinalIgnoreCase , out value ) && value is JValue ) )
84
- {
85
- statusCode = ( HttpStatusCode ) ( int ) value ;
86
- }
87
-
88
- if ( ( jo . TryGetValue ( "isRaw" , StringComparison . OrdinalIgnoreCase , out value ) && value is JValue ) &&
89
- value . Type == JTokenType . Boolean )
90
- {
91
- isRawResponse = ( bool ) value ;
92
- }
93
- }
61
+ // attempt to read the content as json
62
+ content = JObject . Parse ( stringContent ) ;
94
63
}
95
64
catch ( JsonException )
96
65
{
97
66
// not a json response
98
67
}
99
68
}
100
69
101
- HttpResponseMessage response = CreateResponse ( request , statusCode , content , headers , isRawResponse ) ;
70
+ // see if the content is a response object, defining http response
71
+ // properties
72
+ IDictionary < string , object > responseObject = null ;
73
+ if ( content is JObject )
74
+ {
75
+ responseObject = JsonConvert . DeserializeObject < Dictionary < string , object > > ( stringContent , _dictionaryJsonConverter ) ;
76
+ }
77
+ else
78
+ {
79
+ // Handle ExpandoObjects
80
+ responseObject = content as IDictionary < string , object > ;
81
+ }
82
+
83
+ HttpStatusCode statusCode = HttpStatusCode . OK ;
84
+ IDictionary < string , object > responseHeaders = null ;
85
+ bool isRawResponse = false ;
86
+ if ( responseObject != null )
87
+ {
88
+ ParseResponseObject ( responseObject , ref content , out responseHeaders , out statusCode , out isRawResponse ) ;
89
+ }
90
+
91
+ HttpResponseMessage response = CreateResponse ( request , statusCode , content , responseHeaders , isRawResponse ) ;
102
92
103
- if ( headers != null )
93
+ if ( responseHeaders != null )
104
94
{
105
95
// apply any user specified headers
106
- foreach ( var header in headers )
96
+ foreach ( var header in responseHeaders )
107
97
{
108
98
AddResponseHeader ( response , header ) ;
109
99
}
110
100
}
111
101
112
- request . Properties [ ScriptConstants . AzureFunctionsHttpResponseKey ] = response ;
102
+ return response ;
103
+ }
104
+
105
+ internal static void ParseResponseObject ( IDictionary < string , object > responseObject , ref object content , out IDictionary < string , object > headers , out HttpStatusCode statusCode , out bool isRawResponse )
106
+ {
107
+ headers = null ;
108
+ statusCode = HttpStatusCode . OK ;
109
+ isRawResponse = false ;
110
+
111
+ // TODO: Improve this logic
112
+ // Sniff the object to see if it looks like a response object
113
+ // by convention
114
+ object bodyValue = null ;
115
+ if ( responseObject . TryGetValue ( "body" , out bodyValue , ignoreCase : true ) )
116
+ {
117
+ // the response content becomes the specified body value
118
+ content = bodyValue ;
119
+
120
+ IDictionary < string , object > headersValue = null ;
121
+ if ( responseObject . TryGetValue < IDictionary < string , object > > ( "headers" , out headersValue , ignoreCase : true ) )
122
+ {
123
+ headers = headersValue ;
124
+ }
125
+
126
+ object statusValue ;
127
+ if ( ( responseObject . TryGetValue ( "statusCode" , out statusValue , ignoreCase : true ) ||
128
+ responseObject . TryGetValue ( "status" , out statusValue , ignoreCase : true ) ) &&
129
+ ( statusValue is int || statusValue is string ) )
130
+ {
131
+ statusCode = ( HttpStatusCode ) Convert . ToInt32 ( statusValue ) ;
132
+ }
133
+
134
+ bool isRawValue ;
135
+ if ( responseObject . TryGetValue < bool > ( "isRaw" , out isRawValue , ignoreCase : true ) )
136
+ {
137
+ isRawResponse = isRawValue ;
138
+ }
139
+ }
113
140
}
114
141
115
- private static HttpResponseMessage CreateResponse ( HttpRequestMessage request , HttpStatusCode statusCode , object content , JObject headers , bool isRawResponse )
142
+ private static HttpResponseMessage CreateResponse ( HttpRequestMessage request , HttpStatusCode statusCode , object content , IDictionary < string , object > headers , bool isRawResponse )
116
143
{
117
144
if ( isRawResponse )
118
145
{
@@ -121,11 +148,11 @@ private static HttpResponseMessage CreateResponse(HttpRequestMessage request, Ht
121
148
return new HttpResponseMessage ( statusCode ) { Content = CreateResultContent ( content ) } ;
122
149
}
123
150
124
- JToken contentType = null ;
151
+ string contentType = null ;
125
152
MediaTypeHeaderValue mediaType = null ;
126
153
if ( content != null &&
127
- ( headers ? . TryGetValue ( "content-type" , StringComparison . OrdinalIgnoreCase , out contentType ) ?? false ) &&
128
- MediaTypeHeaderValue . TryParse ( contentType . Value < string > ( ) , out mediaType ) )
154
+ ( headers ? . TryGetValue < string > ( "content-type" , out contentType , ignoreCase : true ) ?? false ) &&
155
+ MediaTypeHeaderValue . TryParse ( ( string ) contentType , out mediaType ) )
129
156
{
130
157
MediaTypeFormatter writer = request . GetConfiguration ( )
131
158
. Formatters . FindWriter ( content . GetType ( ) , mediaType ) ;
@@ -209,7 +236,7 @@ public bool CanProcessResult(object result)
209
236
return result != null ;
210
237
}
211
238
212
- private static void AddResponseHeader ( HttpResponseMessage response , KeyValuePair < string , JToken > header )
239
+ internal static void AddResponseHeader ( HttpResponseMessage response , KeyValuePair < string , object > header )
213
240
{
214
241
if ( header . Value != null )
215
242
{
@@ -248,7 +275,16 @@ private static void AddResponseHeader(HttpResponseMessage response, KeyValuePair
248
275
}
249
276
break ;
250
277
case "content-md5" :
251
- response . Content . Headers . ContentMD5 = header . Value . Value < byte [ ] > ( ) ;
278
+ byte [ ] value ;
279
+ if ( header . Value is string )
280
+ {
281
+ value = Convert . FromBase64String ( ( string ) header . Value ) ;
282
+ }
283
+ else
284
+ {
285
+ value = header . Value as byte [ ] ;
286
+ }
287
+ response . Content . Headers . ContentMD5 = value ;
252
288
break ;
253
289
case "expires" :
254
290
if ( DateTimeOffset . TryParse ( header . Value . ToString ( ) , out dateTimeOffset ) )
0 commit comments