1111// limitations under the License.
1212// ------------------------------------------------------------------------
1313
14+ using System . Collections . Generic ;
15+ using System . Linq ;
16+
1417namespace Dapr
1518{
1619 using System ;
@@ -27,6 +30,15 @@ namespace Dapr
2730 internal class CloudEventsMiddleware
2831 {
2932 private const string ContentType = "application/cloudevents+json" ;
33+
34+ // These cloudevent properties are either containing the body of the message or
35+ // are included in the headers by other components of Dapr earlier in the pipeline
36+ private static readonly string [ ] ExcludedPropertiesFromHeaders =
37+ {
38+ CloudEventPropertyNames . DataContentType , CloudEventPropertyNames . Data ,
39+ CloudEventPropertyNames . DataBase64 , "pubsubname" , "traceparent"
40+ } ;
41+
3042 private readonly RequestDelegate next ;
3143 private readonly CloudEventsMiddlewareOptions options ;
3244
@@ -52,7 +64,7 @@ public Task InvokeAsync(HttpContext httpContext)
5264 // The philosophy here is that we don't report an error for things we don't support, because
5365 // that would block someone from implementing their own support for it. We only report an error
5466 // when something we do support isn't correct.
55- if ( ! this . MatchesContentType ( httpContext , out var charSet ) )
67+ if ( ! MatchesContentType ( httpContext , out var charSet ) )
5668 {
5769 return this . next ( httpContext ) ;
5870 }
@@ -69,7 +81,8 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
6981 }
7082 else
7183 {
72- using ( var reader = new HttpRequestStreamReader ( httpContext . Request . Body , Encoding . GetEncoding ( charSet ) ) )
84+ using ( var reader =
85+ new HttpRequestStreamReader ( httpContext . Request . Body , Encoding . GetEncoding ( charSet ) ) )
7386 {
7487 var text = await reader . ReadToEndAsync ( ) ;
7588 json = JsonSerializer . Deserialize < JsonElement > ( text ) ;
@@ -83,17 +96,43 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
8396 string contentType ;
8497
8598 // Check whether to use data or data_base64 as per https://github.com/cloudevents/spec/blob/v1.0.1/json-format.md#31-handling-of-data
86- var isDataSet = json . TryGetProperty ( "data" , out var data ) ;
87- var isBinaryDataSet = json . TryGetProperty ( "data_base64" , out var binaryData ) ;
99+ // Get the property names by OrdinalIgnoreCase comparison to support case insensitive JSON as the Json Serializer for AspCore already supports it by default.
100+ var jsonPropNames = json . EnumerateObject ( ) . ToArray ( ) ;
101+
102+ var dataPropName = jsonPropNames
103+ . Select ( d => d . Name )
104+ . FirstOrDefault ( d => d . Equals ( CloudEventPropertyNames . Data , StringComparison . OrdinalIgnoreCase ) ) ;
105+
106+ var dataBase64PropName = jsonPropNames
107+ . Select ( d => d . Name )
108+ . FirstOrDefault ( d =>
109+ d . Equals ( CloudEventPropertyNames . DataBase64 , StringComparison . OrdinalIgnoreCase ) ) ;
110+
111+ var isDataSet = false ;
112+ var isBinaryDataSet = false ;
113+ JsonElement data = default ;
114+
115+ if ( dataPropName != null )
116+ {
117+ isDataSet = true ;
118+ data = json . TryGetProperty ( dataPropName , out var dataJsonElement ) ? dataJsonElement : data ;
119+ }
120+
121+ if ( dataBase64PropName != null )
122+ {
123+ isBinaryDataSet = true ;
124+ data = json . TryGetProperty ( dataBase64PropName , out var dataJsonElement ) ? dataJsonElement : data ;
125+ }
88126
89127 if ( isDataSet && isBinaryDataSet )
90128 {
91129 httpContext . Response . StatusCode = ( int ) HttpStatusCode . BadRequest ;
92130 return ;
93131 }
94- else if ( isDataSet )
132+
133+ if ( isDataSet )
95134 {
96- contentType = this . GetDataContentType ( json , out var isJson ) ;
135+ contentType = GetDataContentType ( json , out var isJson ) ;
97136
98137 // If the value is anything other than a JSON string, treat it as JSON. Cloud Events requires
99138 // non-JSON text to be enclosed in a JSON string.
@@ -109,8 +148,8 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
109148 {
110149 // Rehydrate body from contents of the string
111150 var text = data . GetString ( ) ;
112- using var writer = new HttpResponseStreamWriter ( body , Encoding . UTF8 ) ;
113- writer . Write ( text ) ;
151+ await using var writer = new HttpResponseStreamWriter ( body , Encoding . UTF8 ) ;
152+ await writer . WriteAsync ( text ) ;
114153 }
115154
116155 body . Seek ( 0L , SeekOrigin . Begin ) ;
@@ -120,17 +159,19 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
120159 // As per the spec, if the implementation determines that the type of data is Binary,
121160 // the value MUST be represented as a JSON string expression containing the Base64 encoded
122161 // binary value, and use the member name data_base64 to store it inside the JSON object.
123- var decodedBody = binaryData . GetBytesFromBase64 ( ) ;
162+ var decodedBody = data . GetBytesFromBase64 ( ) ;
124163 body = new MemoryStream ( decodedBody ) ;
125164 body . Seek ( 0L , SeekOrigin . Begin ) ;
126- contentType = this . GetDataContentType ( json , out _ ) ;
165+ contentType = GetDataContentType ( json , out _ ) ;
127166 }
128167 else
129168 {
130169 body = new MemoryStream ( ) ;
131170 contentType = null ;
132171 }
133172
173+ ForwardCloudEventPropertiesAsHeaders ( httpContext , jsonPropNames ) ;
174+
134175 originalBody = httpContext . Request . Body ;
135176 originalContentType = httpContext . Request . ContentType ;
136177
@@ -148,16 +189,57 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
148189 }
149190 }
150191
151- private string GetDataContentType ( JsonElement json , out bool isJson )
192+ private void ForwardCloudEventPropertiesAsHeaders (
193+ HttpContext httpContext ,
194+ IEnumerable < JsonProperty > jsonPropNames )
195+ {
196+ if ( ! options . ForwardCloudEventPropertiesAsHeaders )
197+ {
198+ return ;
199+ }
200+
201+ var filteredPropertyNames = jsonPropNames
202+ . Where ( d => ! ExcludedPropertiesFromHeaders . Contains ( d . Name , StringComparer . OrdinalIgnoreCase ) ) ;
203+
204+ if ( options . IncludedCloudEventPropertiesAsHeaders != null )
205+ {
206+ filteredPropertyNames = filteredPropertyNames
207+ . Where ( d => options . IncludedCloudEventPropertiesAsHeaders
208+ . Contains ( d . Name , StringComparer . OrdinalIgnoreCase ) ) ;
209+ }
210+ else if ( options . ExcludedCloudEventPropertiesFromHeaders != null )
211+ {
212+ filteredPropertyNames = filteredPropertyNames
213+ . Where ( d => ! options . ExcludedCloudEventPropertiesFromHeaders
214+ . Contains ( d . Name , StringComparer . OrdinalIgnoreCase ) ) ;
215+ }
216+
217+ foreach ( var jsonProperty in filteredPropertyNames )
218+ {
219+ httpContext . Request . Headers . TryAdd ( $ "Cloudevent.{ jsonProperty . Name . ToLowerInvariant ( ) } ",
220+ jsonProperty . Value . GetRawText ( ) . Trim ( '\" ' ) ) ;
221+ }
222+ }
223+
224+ private static string GetDataContentType ( JsonElement json , out bool isJson )
152225 {
226+ var dataContentTypePropName = json
227+ . EnumerateObject ( )
228+ . Select ( d => d . Name )
229+ . FirstOrDefault ( d =>
230+ d . Equals ( CloudEventPropertyNames . DataContentType ,
231+ StringComparison . OrdinalIgnoreCase ) ) ;
232+
153233 string contentType ;
154- if ( json . TryGetProperty ( "datacontenttype" , out var dataContentType ) &&
155- dataContentType . ValueKind == JsonValueKind . String &&
156- MediaTypeHeaderValue . TryParse ( dataContentType . GetString ( ) , out var parsed ) )
234+
235+ if ( dataContentTypePropName != null
236+ && json . TryGetProperty ( dataContentTypePropName , out var dataContentType )
237+ && dataContentType . ValueKind == JsonValueKind . String
238+ && MediaTypeHeaderValue . TryParse ( dataContentType . GetString ( ) , out var parsed ) )
157239 {
158240 contentType = dataContentType . GetString ( ) ;
159- isJson =
160- parsed . MediaType . Equals ( "application/json" , StringComparison . Ordinal ) ||
241+ isJson =
242+ parsed . MediaType . Equals ( "application/json" , StringComparison . Ordinal ) ||
161243 parsed . Suffix . EndsWith ( "+json" , StringComparison . Ordinal ) ;
162244
163245 // Since S.T.Json always outputs utf-8, we may need to normalize the data content type
@@ -179,7 +261,7 @@ private string GetDataContentType(JsonElement json, out bool isJson)
179261 return contentType ;
180262 }
181263
182- private bool MatchesContentType ( HttpContext httpContext , out string charSet )
264+ private static bool MatchesContentType ( HttpContext httpContext , out string charSet )
183265 {
184266 if ( httpContext . Request . ContentType == null )
185267 {
0 commit comments