@@ -21,6 +21,8 @@ namespace Microsoft.Azure.WebJobs.Script.Binding
2121{
2222 public class HttpBinding : FunctionBinding , IResultProcessingBinding
2323 {
24+ private static readonly DictionaryJsonConverter _dictionaryJsonConverter = new DictionaryJsonConverter ( ) ;
25+
2426 public HttpBinding ( ScriptHostConfiguration config , BindingMetadata metadata , FileAccess access ) :
2527 base ( config , metadata , access )
2628 {
@@ -31,88 +33,113 @@ public override Collection<CustomAttributeBuilder> GetCustomAttributes(Type para
3133 return null ;
3234 }
3335
34- public override async Task BindAsync ( BindingContext context )
36+ public override Task BindAsync ( BindingContext context )
3537 {
3638 HttpRequestMessage request = ( HttpRequestMessage ) context . TriggerValue ;
3739
3840 object content = context . Value ;
3941 if ( content is Stream )
4042 {
4143 // 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 ) ;
4746 }
4847
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 )
5358 {
5459 try
5560 {
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 ) ;
9463 }
9564 catch ( JsonException )
9665 {
9766 // not a json response
9867 }
9968 }
10069
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 ) ;
10292
103- if ( headers != null )
93+ if ( responseHeaders != null )
10494 {
10595 // apply any user specified headers
106- foreach ( var header in headers )
96+ foreach ( var header in responseHeaders )
10797 {
10898 AddResponseHeader ( response , header ) ;
10999 }
110100 }
111101
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+ }
113140 }
114141
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 )
116143 {
117144 if ( isRawResponse )
118145 {
@@ -121,11 +148,11 @@ private static HttpResponseMessage CreateResponse(HttpRequestMessage request, Ht
121148 return new HttpResponseMessage ( statusCode ) { Content = CreateResultContent ( content ) } ;
122149 }
123150
124- JToken contentType = null ;
151+ string contentType = null ;
125152 MediaTypeHeaderValue mediaType = null ;
126153 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 ) )
129156 {
130157 MediaTypeFormatter writer = request . GetConfiguration ( )
131158 . Formatters . FindWriter ( content . GetType ( ) , mediaType ) ;
@@ -209,7 +236,7 @@ public bool CanProcessResult(object result)
209236 return result != null ;
210237 }
211238
212- private static void AddResponseHeader ( HttpResponseMessage response , KeyValuePair < string , JToken > header )
239+ internal static void AddResponseHeader ( HttpResponseMessage response , KeyValuePair < string , object > header )
213240 {
214241 if ( header . Value != null )
215242 {
@@ -248,7 +275,16 @@ private static void AddResponseHeader(HttpResponseMessage response, KeyValuePair
248275 }
249276 break ;
250277 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 ;
252288 break ;
253289 case "expires" :
254290 if ( DateTimeOffset . TryParse ( header . Value . ToString ( ) , out dateTimeOffset ) )
0 commit comments