diff --git a/src/CloudNative.CloudEvents/HttpClientExtension.cs b/src/CloudNative.CloudEvents/HttpClientExtension.cs
index 9864e35..ad52ad8 100644
--- a/src/CloudNative.CloudEvents/HttpClientExtension.cs
+++ b/src/CloudNative.CloudEvents/HttpClientExtension.cs
@@ -77,6 +77,127 @@ namespace CloudNative.CloudEvents
await stream.CopyToAsync(httpWebRequest.GetRequestStream());
}
+ ///
+ /// Handle the request as WebHook validation request
+ ///
+ /// Request
+ /// Callback that returns whether the given origin may push events. If 'null', all origins are acceptable.
+ /// Callback that returns the acceptable request rate. If 'null', the rate is not limited.
+ /// Response
+ public static async Task HandleAsWebHookValidationRequest(
+ this HttpRequestMessage httpRequestMessage, Func validateOrigin,
+ Func validateRate)
+ {
+ if (IsWebHookValidationRequest(httpRequestMessage))
+ {
+ var origin = httpRequestMessage.Headers.GetValues("WebHook-Request-Origin").FirstOrDefault();
+ var rate = httpRequestMessage.Headers.GetValues("WebHook-Request-Rate").FirstOrDefault();
+
+ if (origin != null && (validateOrigin == null || validateOrigin(origin)))
+ {
+ if (rate != null)
+ {
+ if (validateRate != null)
+ {
+ rate = validateRate(rate);
+ }
+ else
+ {
+ rate = "*";
+ }
+ }
+
+ if (httpRequestMessage.Headers.Contains("WebHook-Request-Callback"))
+ {
+ var uri = httpRequestMessage.Headers.GetValues("WebHook-Request-Callback").FirstOrDefault();
+ try
+ {
+ HttpClient client = new HttpClient();
+ var response = await client.GetAsync(new Uri(uri));
+ return new HttpResponseMessage(response.StatusCode);
+ }
+ catch (Exception e)
+ {
+ return new HttpResponseMessage(HttpStatusCode.InternalServerError);
+ }
+ }
+ else
+ {
+ var response = new HttpResponseMessage(HttpStatusCode.OK);
+ response.Headers.Add("Allow", "POST");
+ response.Headers.Add("WebHook-Allowed-Origin", origin);
+ response.Headers.Add("WebHook-Allowed-Rate", rate);
+ return response;
+ }
+ }
+ }
+
+ return new HttpResponseMessage(HttpStatusCode.MethodNotAllowed);
+ }
+
+ ///
+ /// Handle the request as WebHook validation request
+ ///
+ /// Request context
+ /// Callback that returns whether the given origin may push events. If 'null', all origins are acceptable.
+ /// Callback that returns the acceptable request rate. If 'null', the rate is not limited.
+ /// Task
+ public static async Task HandleAsWebHookValidationRequest(this HttpListenerContext context,
+ Func validateOrigin, Func validateRate)
+ {
+ if (IsWebHookValidationRequest(context.Request))
+ {
+ var origin = context.Request.Headers.Get("WebHook-Request-Origin");
+ var rate = context.Request.Headers.Get("WebHook-Request-Rate");
+
+ if (origin != null && (validateOrigin == null || validateOrigin(origin)))
+ {
+ if (rate != null)
+ {
+ if (validateRate != null)
+ {
+ rate = validateRate(rate);
+ }
+ else
+ {
+ rate = "*";
+ }
+ }
+
+ if (context.Request.Headers["WebHook-Request-Callback"] != null)
+ {
+ var uri = context.Request.Headers.Get("WebHook-Request-Callback");
+ try
+ {
+ HttpClient client = new HttpClient();
+ var response = await client.GetAsync(new Uri(uri));
+ context.Response.StatusCode = (int)response.StatusCode;
+ context.Response.Close();
+ return;
+ }
+ catch (Exception e)
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+ context.Response.Close();
+ return;
+ }
+ }
+ else
+ {
+ context.Response.StatusCode = (int)HttpStatusCode.OK;
+ context.Response.Headers.Add("Allow", "POST");
+ context.Response.Headers.Add("WebHook-Allowed-Origin", origin);
+ context.Response.Headers.Add("WebHook-Allowed-Rate", rate);
+ context.Response.Close();
+ return;
+ }
+ }
+ }
+
+ context.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
+ context.Response.Close();
+ }
+
///
/// Indicates whether this HttpResponseMessage holds a CloudEvent
///
@@ -101,6 +222,24 @@ namespace CloudNative.CloudEvents
httpListenerRequest.Headers[SpecVersionHttpHeader2] != null;
}
+ ///
+ /// Indicates whether this HttpListenerRequest is a web hook validation request
+ ///
+ public static bool IsWebHookValidationRequest(this HttpRequestMessage httpRequestMessage)
+ {
+ return (httpRequestMessage.Method.Method.Equals("options", StringComparison.InvariantCultureIgnoreCase) &&
+ httpRequestMessage.Headers.Contains("WebHook-Request-Origin"));
+ }
+
+ ///
+ /// Indicates whether this HttpListenerRequest is a web hook validation request
+ ///
+ public static bool IsWebHookValidationRequest(this HttpListenerRequest httpRequestMessage)
+ {
+ return (httpRequestMessage.HttpMethod.Equals("options", StringComparison.InvariantCultureIgnoreCase) &&
+ httpRequestMessage.Headers["WebHook-Request-Origin"] != null);
+ }
+
///
/// Converts this response message into a CloudEvent object, with the given extensions.
///
@@ -382,7 +521,7 @@ namespace CloudNative.CloudEvents
else
{
attributes[name] = headerValue;
- }
+ }
}
}
diff --git a/test/CloudNative.CloudEvents.UnitTests/HttpTest.cs b/test/CloudNative.CloudEvents.UnitTests/HttpTest.cs
index 12d5bc0..d20cde6 100644
--- a/test/CloudNative.CloudEvents.UnitTests/HttpTest.cs
+++ b/test/CloudNative.CloudEvents.UnitTests/HttpTest.cs
@@ -7,6 +7,7 @@ namespace CloudNative.CloudEvents.UnitTests
using System;
using System.Collections.Concurrent;
using System.IO;
+ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
@@ -49,6 +50,13 @@ namespace CloudNative.CloudEvents.UnitTests
async Task HandleContext(HttpListenerContext requestContext)
{
var ctxHeaderValue = requestContext.Request.Headers[testContextHeader];
+
+ if (requestContext.Request.IsWebHookValidationRequest())
+ {
+ await requestContext.HandleAsWebHookValidationRequest(null, null);
+ return;
+ }
+
if (pendingRequests.TryRemove(ctxHeaderValue, out var pending))
{
await pending(requestContext);
@@ -64,6 +72,18 @@ namespace CloudNative.CloudEvents.UnitTests
#pragma warning restore 4014
}
+ [Fact]
+ async Task HttpWebHookValidation()
+ {
+ var httpClient = new HttpClient();
+ var req = new HttpRequestMessage(HttpMethod.Options, new Uri(listenerAddress + "ep"));
+ req.Headers.Add("WebHook-Request-Origin", "example.com");
+ req.Headers.Add("WebHook-Request-Rate", "120");
+ var result = await httpClient.SendAsync( req );
+ Assert.Equal("example.com", result.Headers.GetValues("WebHook-Allowed-Origin").First());
+ Assert.Equal(HttpStatusCode.OK, result.StatusCode);
+ }
+
[Fact]
async Task HttpBinaryClientReceiveTest()
{