private static async Task <CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent content, CloudEventFormatter formatter, IEnumerable <CloudEventAttribute>?extensionAttributes, string paramName) { Validation.CheckNotNull(formatter, nameof(formatter)); if (HasCloudEventsContentType(content)) { var stream = await content.ReadAsStreamAsync().ConfigureAwait(false); return(await formatter.DecodeStructuredModeMessageAsync(stream, MimeUtilities.ToContentType(content.Headers.ContentType), extensionAttributes).ConfigureAwait(false)); } else { string?versionId = headers.Contains(HttpUtilities.SpecVersionHttpHeader) ? headers.GetValues(HttpUtilities.SpecVersionHttpHeader).First() : null; if (versionId is null) { throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(paramName)); } var version = CloudEventsSpecVersion.FromVersionId(versionId) ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", paramName); var cloudEvent = new CloudEvent(version, extensionAttributes); foreach (var header in headers) { string?attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key); if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name) { continue; } string attributeValue = HttpUtilities.DecodeHeaderValue(header.Value.First()); cloudEvent.SetAttributeFromString(attributeName, attributeValue); } if (content is object) { // TODO: Should this just be the media type? We probably need to take a full audit of this... cloudEvent.DataContentType = content.Headers?.ContentType?.ToString(); var data = await content.ReadAsByteArrayAsync().ConfigureAwait(false); formatter.DecodeBinaryModeEventData(data, cloudEvent); } return(Validation.CheckCloudEventArgument(cloudEvent, paramName)); } }
private async static Task <CloudEvent> ToCloudEventAsyncImpl(HttpListenerRequest httpListenerRequest, CloudEventFormatter formatter, IEnumerable <CloudEventAttribute> extensionAttributes, bool async) { Validation.CheckNotNull(httpListenerRequest, nameof(httpListenerRequest)); Validation.CheckNotNull(formatter, nameof(formatter)); var stream = httpListenerRequest.InputStream; if (HasCloudEventsContentType(httpListenerRequest)) { var contentType = MimeUtilities.CreateContentTypeOrNull(httpListenerRequest.ContentType); return(async ? await formatter.DecodeStructuredModeMessageAsync(stream, contentType, extensionAttributes).ConfigureAwait(false) : formatter.DecodeStructuredModeMessage(stream, contentType, extensionAttributes)); } else { string versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader]; var version = CloudEventsSpecVersion.FromVersionId(versionId) ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(httpListenerRequest)); var cloudEvent = new CloudEvent(version, extensionAttributes); var headers = httpListenerRequest.Headers; foreach (var key in headers.AllKeys) { string attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key); if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name) { continue; } string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]); cloudEvent.SetAttributeFromString(attributeName, attributeValue); } // The data content type should not have been set via a "ce-" header; instead, // it's in the regular content type. cloudEvent.DataContentType = httpListenerRequest.ContentType; byte[] data = async ? await BinaryDataUtilities.ToByteArrayAsync(stream).ConfigureAwait(false) : BinaryDataUtilities.ToByteArray(stream); formatter.DecodeBinaryModeEventData(data, cloudEvent); return(Validation.CheckCloudEventArgument(cloudEvent, nameof(httpListenerRequest))); } }
// TODO: Check the pattern here. I suspect that cloudEvent.CopyToAsync(response) would be more natural. /// <summary> /// Copies the CloudEvent into this HttpListenerResponse instance /// </summary> /// <param name="httpListenerResponse">this</param> /// <param name="cloudEvent">CloudEvent to copy</param> /// <param name="contentMode">Content mode (structured or binary)</param> /// <param name="formatter">Formatter</param> /// <returns>Task</returns> public static Task CopyFromAsync(this HttpListenerResponse httpListenerResponse, CloudEvent cloudEvent, ContentMode contentMode, ICloudEventFormatter formatter) { if (contentMode == ContentMode.Structured) { var buffer = formatter.EncodeStructuredEvent(cloudEvent, out var contentType); httpListenerResponse.ContentType = contentType.ToString(); MapAttributesToListenerResponse(cloudEvent, httpListenerResponse); return(httpListenerResponse.OutputStream.WriteAsync(buffer, 0, buffer.Length)); } Stream stream = HttpUtilities.MapDataAttributeToStream(cloudEvent, formatter); // TODO: Check the defaulting to JSON here... httpListenerResponse.ContentType = cloudEvent.DataContentType?.ToString() ?? "application/json"; MapAttributesToListenerResponse(cloudEvent, httpListenerResponse); return(stream.CopyToAsync(httpListenerResponse.OutputStream)); }
// TODO: HttpWebResponse as well? // TODO: Change to a CopyTo extension method on CloudEvent? /// <summary> /// Copies a CloudEvent into the specified HttpWebRequest instance. /// </summary> /// <param name="httpWebRequest">The request to populate.</param> /// <param name="cloudEvent">CloudEvent to copy</param> /// <param name="contentMode">Content mode (structured or binary)</param> /// <param name="formatter">Formatter</param> /// <returns>Task</returns> public static async Task CopyFromAsync(this HttpWebRequest httpWebRequest, CloudEvent cloudEvent, ContentMode contentMode, ICloudEventFormatter formatter) { if (contentMode == ContentMode.Structured) { var buffer = formatter.EncodeStructuredEvent(cloudEvent, out var contentType); httpWebRequest.ContentType = contentType.ToString(); await httpWebRequest.GetRequestStream().WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false); return; } Stream stream = HttpUtilities.MapDataAttributeToStream(cloudEvent, formatter); httpWebRequest.ContentType = cloudEvent.DataContentType?.ToString() ?? "application/json"; MapAttributesToWebRequest(cloudEvent, httpWebRequest); await stream.CopyToAsync(httpWebRequest.GetRequestStream()); }
/// <summary> /// Converts this listener request into a CloudEvent object, with the given extension attributes. /// </summary> /// <param name="httpListenerRequest">Listener request</param> /// <param name="formatter"></param> /// <param name="extensions">List of extension instances</param> /// <returns>A CloudEvent instance or 'null' if the request message doesn't hold a CloudEvent</returns> public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest, ICloudEventFormatter formatter, params CloudEventAttribute[] extensionAttributes) { if (HasCloudEventsContentType(httpListenerRequest)) { // FIXME: Handle no formatter being specified. return(formatter.DecodeStructuredEvent(httpListenerRequest.InputStream, extensionAttributes)); } else { CloudEventsSpecVersion version = CloudEventsSpecVersion.Default; if (httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader] is string versionId) { version = CloudEventsSpecVersion.FromVersionId(versionId); } var cloudEvent = new CloudEvent(version, extensionAttributes); var headers = httpListenerRequest.Headers; foreach (var key in headers.AllKeys) { string attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key); if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name) { continue; } string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]); cloudEvent.SetAttributeFromString(attributeName, attributeValue); } // TODO: Check that this doesn't come through as a header already cloudEvent.DataContentType = httpListenerRequest.ContentType; // TODO: This is a bit ugly. var memoryStream = new MemoryStream(); httpListenerRequest.InputStream.CopyTo(memoryStream); if (memoryStream.Length != 0) { cloudEvent.Data = formatter.DecodeData(memoryStream.ToArray(), cloudEvent.DataContentType); } return(cloudEvent); } }
private static async Task <CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent content, ICloudEventFormatter formatter, IEnumerable <CloudEventAttribute> extensionAttributes) { if (HasCloudEventsContentType(content)) { // FIXME: Handle no formatter being specified. var stream = await content.ReadAsStreamAsync().ConfigureAwait(false); return(await formatter.DecodeStructuredEventAsync(stream, extensionAttributes).ConfigureAwait(false)); } else { CloudEventsSpecVersion version = CloudEventsSpecVersion.Default; if (headers.Contains(HttpUtilities.SpecVersionHttpHeader)) { string versionId = headers.GetValues(HttpUtilities.SpecVersionHttpHeader).First(); version = CloudEventsSpecVersion.FromVersionId(versionId); } var cloudEvent = new CloudEvent(version, extensionAttributes); foreach (var header in headers) { string attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key); if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name) { continue; } string attributeValue = HttpUtilities.DecodeHeaderValue(header.Value.First()); cloudEvent.SetAttributeFromString(attributeName, attributeValue); } if (content is object) { // TODO: Should this just be the media type? We probably need to take a full audit of this... cloudEvent.DataContentType = content.Headers?.ContentType?.ToString(); var data = await content.ReadAsByteArrayAsync().ConfigureAwait(false); cloudEvent.Data = formatter.DecodeData(data, cloudEvent.DataContentType); } return(cloudEvent); } }
/// <summary> /// Converts this listener request into a CloudEvent object, with the given extension attributes. /// </summary> /// <param name="httpListenerRequest">Listener request</param> /// <param name="formatter"></param> /// <param name="extensions">List of extension instances</param> /// <returns>The CloudEvent corresponding to the given request.</returns> /// <exception cref="ArgumentException">The request does not represent a CloudEvent, /// or the event's specification version is not supported, /// or the event formatter cannot interpret it.</exception> public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest, CloudEventFormatter formatter, params CloudEventAttribute[] extensionAttributes) { if (HasCloudEventsContentType(httpListenerRequest)) { return(formatter.DecodeStructuredModeMessage(httpListenerRequest.InputStream, MimeUtilities.CreateContentTypeOrNull(httpListenerRequest.ContentType), extensionAttributes)); } else { string versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader]; if (versionId is null) { throw new ArgumentException("Request is not a CloudEvent"); } var version = CloudEventsSpecVersion.FromVersionId(versionId); if (version is null) { throw new ArgumentException($"Unsupported CloudEvents spec version '{versionId}'"); } var cloudEvent = new CloudEvent(version, extensionAttributes); var headers = httpListenerRequest.Headers; foreach (var key in headers.AllKeys) { string attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key); if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name) { continue; } string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]); cloudEvent.SetAttributeFromString(attributeName, attributeValue); } // The data content type should not have been set via a "ce-" header; instead, // it's in the regular content type. cloudEvent.DataContentType = httpListenerRequest.ContentType; formatter.DecodeBinaryModeEventData(BinaryDataUtilities.ToByteArray(httpListenerRequest.InputStream), cloudEvent); return(cloudEvent); } }
private void MapHeaders(CloudEvent cloudEvent, bool includeDataContentType) { Headers.Add(HttpUtilities.SpecVersionHttpHeader, HttpUtilities.EncodeHeaderValue(cloudEvent.SpecVersion.VersionId)); foreach (var attributeAndValue in cloudEvent.GetPopulatedAttributes()) { CloudEventAttribute attribute = attributeAndValue.Key; string headerName = HttpUtilities.HttpHeaderPrefix + attribute.Name; object value = attributeAndValue.Value; // Only map the data content type attribute to a header if we've been asked to if (attribute == cloudEvent.SpecVersion.DataContentTypeAttribute && !includeDataContentType) { continue; } else { string headerValue = HttpUtilities.EncodeHeaderValue(attribute.Format(value)); Headers.Add(headerName, headerValue); } } }
// TODO: Do we want this? It's not about CloudEvents... /// <summary> /// Handle the request as WebHook validation request /// </summary> /// <param name="context">Request context</param> /// <param name="validateOrigin">Callback that returns whether the given origin may push events. If 'null', all origins are acceptable.</param> /// <param name="validateRate">Callback that returns the acceptable request rate. If 'null', the rate is not limited.</param> /// <returns>Task</returns> public static async Task HandleAsWebHookValidationRequest(this HttpListenerContext context, Func <string, bool> validateOrigin, Func <string, string> validateRate) { if (!IsWebHookValidationRequest(context.Request)) { context.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; context.Response.Close(); } var(statusCode, allowedOrigin, allowedRate) = await HttpUtilities.HandleWebHookValidationAsync(context.Request, (request, headerName) => request.Headers.Get(headerName), validateOrigin, validateRate); context.Response.StatusCode = (int)statusCode; if (allowedOrigin is object) { context.Response.Headers.Add("Allow", "POST"); context.Response.Headers.Add("WebHook-Allowed-Origin", allowedOrigin); if (allowedRate is object) { context.Response.Headers.Add("WebHook-Allowed-Rate", allowedRate); } } context.Response.Close(); }