11
11
// limitations under the License.
12
12
// ------------------------------------------------------------------------
13
13
14
+ using System . Collections . Generic ;
15
+ using System . Linq ;
16
+
14
17
namespace Dapr
15
18
{
16
19
using System ;
@@ -27,6 +30,15 @@ namespace Dapr
27
30
internal class CloudEventsMiddleware
28
31
{
29
32
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
+
30
42
private readonly RequestDelegate next ;
31
43
private readonly CloudEventsMiddlewareOptions options ;
32
44
@@ -52,7 +64,7 @@ public Task InvokeAsync(HttpContext httpContext)
52
64
// The philosophy here is that we don't report an error for things we don't support, because
53
65
// that would block someone from implementing their own support for it. We only report an error
54
66
// when something we do support isn't correct.
55
- if ( ! this . MatchesContentType ( httpContext , out var charSet ) )
67
+ if ( ! MatchesContentType ( httpContext , out var charSet ) )
56
68
{
57
69
return this . next ( httpContext ) ;
58
70
}
@@ -69,7 +81,8 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
69
81
}
70
82
else
71
83
{
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 ) ) )
73
86
{
74
87
var text = await reader . ReadToEndAsync ( ) ;
75
88
json = JsonSerializer . Deserialize < JsonElement > ( text ) ;
@@ -83,17 +96,43 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
83
96
string contentType ;
84
97
85
98
// 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
+ }
88
126
89
127
if ( isDataSet && isBinaryDataSet )
90
128
{
91
129
httpContext . Response . StatusCode = ( int ) HttpStatusCode . BadRequest ;
92
130
return ;
93
131
}
94
- else if ( isDataSet )
132
+
133
+ if ( isDataSet )
95
134
{
96
- contentType = this . GetDataContentType ( json , out var isJson ) ;
135
+ contentType = GetDataContentType ( json , out var isJson ) ;
97
136
98
137
// If the value is anything other than a JSON string, treat it as JSON. Cloud Events requires
99
138
// non-JSON text to be enclosed in a JSON string.
@@ -109,8 +148,8 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
109
148
{
110
149
// Rehydrate body from contents of the string
111
150
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 ) ;
114
153
}
115
154
116
155
body . Seek ( 0L , SeekOrigin . Begin ) ;
@@ -120,17 +159,19 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
120
159
// As per the spec, if the implementation determines that the type of data is Binary,
121
160
// the value MUST be represented as a JSON string expression containing the Base64 encoded
122
161
// 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 ( ) ;
124
163
body = new MemoryStream ( decodedBody ) ;
125
164
body . Seek ( 0L , SeekOrigin . Begin ) ;
126
- contentType = this . GetDataContentType ( json , out _ ) ;
165
+ contentType = GetDataContentType ( json , out _ ) ;
127
166
}
128
167
else
129
168
{
130
169
body = new MemoryStream ( ) ;
131
170
contentType = null ;
132
171
}
133
172
173
+ ForwardCloudEventPropertiesAsHeaders ( httpContext , jsonPropNames ) ;
174
+
134
175
originalBody = httpContext . Request . Body ;
135
176
originalContentType = httpContext . Request . ContentType ;
136
177
@@ -148,16 +189,57 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
148
189
}
149
190
}
150
191
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 )
152
225
{
226
+ var dataContentTypePropName = json
227
+ . EnumerateObject ( )
228
+ . Select ( d => d . Name )
229
+ . FirstOrDefault ( d =>
230
+ d . Equals ( CloudEventPropertyNames . DataContentType ,
231
+ StringComparison . OrdinalIgnoreCase ) ) ;
232
+
153
233
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 ) )
157
239
{
158
240
contentType = dataContentType . GetString ( ) ;
159
- isJson =
160
- parsed . MediaType . Equals ( "application/json" , StringComparison . Ordinal ) ||
241
+ isJson =
242
+ parsed . MediaType . Equals ( "application/json" , StringComparison . Ordinal ) ||
161
243
parsed . Suffix . EndsWith ( "+json" , StringComparison . Ordinal ) ;
162
244
163
245
// 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)
179
261
return contentType ;
180
262
}
181
263
182
- private bool MatchesContentType ( HttpContext httpContext , out string charSet )
264
+ private static bool MatchesContentType ( HttpContext httpContext , out string charSet )
183
265
{
184
266
if ( httpContext . Request . ContentType == null )
185
267
{
0 commit comments