Skip to content

Commit 847cd31

Browse files
vinayada1rynowakamolenk
authored
Rc6 (#584)
Merging the following two commits from master into release branch * Add missing cancellation token * read binary data Co-authored-by: Ryan Nowak <nowakra@gmail.com> Co-authored-by: Sander Molenkamp <a.molenkamp@gmail.com> Co-authored-by: Vinaya Damle <vinayadamle@gmail.com>
1 parent 55e0dba commit 847cd31

File tree

6 files changed

+119
-29
lines changed

6 files changed

+119
-29
lines changed

src/Dapr.AspNetCore/CloudEventsMiddleware.cs

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace Dapr
77
{
88
using System;
99
using System.IO;
10+
using System.Net;
1011
using System.Net.Http.Headers;
1112
using System.Text;
1213
using System.Text.Json;
@@ -70,32 +71,32 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
7071
string originalContentType;
7172
string contentType;
7273

73-
// Data is optional.
74-
if (json.TryGetProperty("data", out var data))
74+
// 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
75+
var isDataSet = json.TryGetProperty("data", out var data);
76+
var isBinaryDataSet = json.TryGetProperty("data_base64", out var binaryData);
77+
78+
if (isDataSet && isBinaryDataSet)
79+
{
80+
httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
81+
return;
82+
}
83+
else if (isDataSet)
7584
{
7685
body = new MemoryStream();
7786
await JsonSerializer.SerializeAsync<JsonElement>(body, data);
7887
body.Seek(0L, SeekOrigin.Begin);
7988

80-
if (json.TryGetProperty("datacontenttype", out var dataContentType) &&
81-
dataContentType.ValueKind == JsonValueKind.String)
82-
{
83-
contentType = dataContentType.GetString();
84-
85-
// Since S.T.Json always outputs utf-8, we may need to normalize the data content type
86-
// to remove any charset information. We generally just assume utf-8 everywhere, so omitting
87-
// a charset is a safe bet.
88-
if (contentType.Contains("charset") && MediaTypeHeaderValue.TryParse(contentType, out var parsed))
89-
{
90-
parsed.CharSet = null;
91-
contentType = parsed.ToString();
92-
}
93-
}
94-
else
95-
{
96-
// assume JSON is not specified.
97-
contentType = "application/json";
98-
}
89+
contentType = this.GetDataContentType(json);
90+
}
91+
else if (isBinaryDataSet)
92+
{
93+
// As per the spec, if the implementation determines that the type of data is Binary,
94+
// the value MUST be represented as a JSON string expression containing the Base64 encoded
95+
// binary value, and use the member name data_base64 to store it inside the JSON object.
96+
var decodedBody = binaryData.GetBytesFromBase64();
97+
body = new MemoryStream(decodedBody);
98+
body.Seek(0L, SeekOrigin.Begin);
99+
contentType = this.GetDataContentType(json);
99100
}
100101
else
101102
{
@@ -120,6 +121,32 @@ private async Task ProcessBodyAsync(HttpContext httpContext, string charSet)
120121
}
121122
}
122123

124+
private string GetDataContentType(JsonElement json)
125+
{
126+
string contentType;
127+
if (json.TryGetProperty("datacontenttype", out var dataContentType) &&
128+
dataContentType.ValueKind == JsonValueKind.String)
129+
{
130+
contentType = dataContentType.GetString();
131+
132+
// Since S.T.Json always outputs utf-8, we may need to normalize the data content type
133+
// to remove any charset information. We generally just assume utf-8 everywhere, so omitting
134+
// a charset is a safe bet.
135+
if (contentType.Contains("charset") && MediaTypeHeaderValue.TryParse(contentType, out var parsed))
136+
{
137+
parsed.CharSet = null;
138+
contentType = parsed.ToString();
139+
}
140+
}
141+
else
142+
{
143+
// assume JSON is not specified.
144+
contentType = "application/json";
145+
}
146+
147+
return contentType;
148+
}
149+
123150
private bool MatchesContentType(HttpContext httpContext, out string charSet)
124151
{
125152
if (httpContext.Request.ContentType == null)

src/Dapr.Client/DaprClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ public HttpRequestMessage CreateInvokeMethodRequest<TRequest>(string appId, stri
272272
/// </param>
273273
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
274274
/// <returns>A <see cref="Task{T}" /> that will return the value when the operation has completed.</returns>
275-
public abstract Task<HttpResponseMessage> InvokeMethodWithResponseAsync(HttpRequestMessage request, CancellationToken cancellationToken);
275+
public abstract Task<HttpResponseMessage> InvokeMethodWithResponseAsync(HttpRequestMessage request, CancellationToken cancellationToken = default);
276276

277277
/// <summary>
278278
/// Perform service invocation using the request provided by <paramref name="request" />. If the response has a non-success

src/Dapr.Client/Protos/dapr/proto/common/v1/common.proto

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ------------------------------------------------------------
2-
// Copyright (c) Microsoft Corporation.
2+
// Copyright (c) Microsoft Corporation and Dapr Contributors.
33
// Licensed under the MIT License.
44
// ------------------------------------------------------------
55

@@ -39,8 +39,8 @@ message HTTPExtension {
3939
// Required. HTTP verb.
4040
Verb verb = 1;
4141

42-
// querystring includes HTTP querystring.
43-
map<string, string> querystring = 2;
42+
// Optional. querystring represents an encoded HTTP url query string in the following format: name=value&name2=value2
43+
string querystring = 2;
4444
}
4545

4646
// InvokeRequest is the message to invoke a method with the data.

src/Dapr.Client/Protos/dapr/proto/dapr/v1/appcallback.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ------------------------------------------------------------
2-
// Copyright (c) Microsoft Corporation.
2+
// Copyright (c) Microsoft Corporation and Dapr Contributors.
33
// Licensed under the MIT License.
44
// ------------------------------------------------------------
55

src/Dapr.Client/Protos/dapr/proto/dapr/v1/dapr.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// ------------------------------------------------------------
2-
// Copyright (c) Microsoft Corporation.
2+
// Copyright (c) Microsoft Corporation and Dapr Contributors.
33
// Licensed under the MIT License.
44
// ------------------------------------------------------------
55

test/Dapr.AspNetCore.Test/CloudEventsMiddlewareTest.cs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace Dapr.AspNetCore.Test
77
{
88
using System.IO;
9+
using System.Net;
910
using System.Text;
1011
using System.Threading.Tasks;
1112
using FluentAssertions;
@@ -104,12 +105,73 @@ public async Task InvokeAsync_ReplacesBodyJson_NormalizesPayloadCharset()
104105
await pipeline.Invoke(context);
105106
}
106107

108+
[Fact]
109+
public async Task InvokeAsync_ReadsBinaryData()
110+
{
111+
var dataContentType = "application/octet-stream";
112+
var app = new ApplicationBuilder(null);
113+
app.UseCloudEvents();
114+
var data = new byte[] { 1, 2, 3 };
115+
116+
// Do verification in the scope of the middleware
117+
app.Run(httpContext =>
118+
{
119+
httpContext.Request.ContentType.Should().Be(dataContentType);
120+
var bytes = new byte[httpContext.Request.Body.Length];
121+
httpContext.Request.Body.Read(bytes, 0, bytes.Length);
122+
bytes.Should().Equal(data);
123+
return Task.CompletedTask;
124+
});
125+
126+
var pipeline = app.Build();
127+
128+
var context = new DefaultHttpContext();
129+
context.Request.ContentType = "application/cloudevents+json";
130+
var base64Str = System.Convert.ToBase64String(data);
131+
132+
context.Request.Body =
133+
MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"data_base64\": \"{base64Str}\"}}");
134+
135+
await pipeline.Invoke(context);
136+
}
137+
138+
[Fact]
139+
public async Task InvokeAsync_DataAndData64Set_ReturnsBadRequest()
140+
{
141+
var dataContentType = "application/octet-stream";
142+
var app = new ApplicationBuilder(null);
143+
app.UseCloudEvents();
144+
var data = "{\"id\": \"1\"}";
145+
146+
// Do verification in the scope of the middleware
147+
app.Run(httpContext =>
148+
{
149+
httpContext.Request.ContentType.Should().Be("application/json");
150+
var body = ReadBody(httpContext.Request.Body);
151+
body.Should().Equals(data);
152+
return Task.CompletedTask;
153+
});
154+
155+
var pipeline = app.Build();
156+
157+
var context = new DefaultHttpContext();
158+
context.Request.ContentType = "application/cloudevents+json";
159+
var bytes = Encoding.UTF8.GetBytes(data);
160+
var base64Str = System.Convert.ToBase64String(bytes);
161+
context.Request.Body =
162+
MakeBody($"{{ \"datacontenttype\": \"{dataContentType}\", \"data_base64\": \"{base64Str}\", \"data\": {data} }}");
163+
164+
await pipeline.Invoke(context);
165+
context.Response.StatusCode.Should().Be((int)HttpStatusCode.BadRequest);
166+
}
167+
107168
private static Stream MakeBody(string text, Encoding encoding = null)
108169
{
109170
encoding ??= Encoding.UTF8;
110171

111172
var stream = new MemoryStream();
112-
stream.Write(encoding.GetBytes(text));
173+
var bytes = encoding.GetBytes(text);
174+
stream.Write(bytes);
113175
stream.Seek(0L, SeekOrigin.Begin);
114176
return stream;
115177
}
@@ -120,7 +182,8 @@ private static string ReadBody(Stream stream, Encoding encoding = null)
120182

121183
var bytes = new byte[stream.Length];
122184
stream.Read(bytes, 0, bytes.Length);
123-
return encoding.GetString(bytes);
185+
var str = encoding.GetString(bytes);
186+
return str;
124187
}
125188
}
126189
}

0 commit comments

Comments
 (0)