public ActionResult <string> GenerateCloudEvent() { var evt = new CloudEvent { Type = "CloudNative.CloudEvents.AspNetCoreSample", Source = new Uri("https://github.com/cloudevents/sdk-csharp"), Time = DateTimeOffset.Now, DataContentType = "application/json", Id = Guid.NewGuid().ToString(), Data = new { Language = "C#", EnvironmentVersion = Environment.Version.ToString() } }; // Format the event as the body of the response. This is UTF-8 JSON because of // the CloudEventFormatter we're using, but EncodeStructuredModeMessage always // returns binary data. We could return the data directly, but for debugging // purposes it's useful to have the JSON string. var bytes = formatter.EncodeStructuredModeMessage(evt, out var contentType); string json = Encoding.UTF8.GetString(bytes.Span); var result = Ok(json); // Specify the content type of the response: this is what makes it a CloudEvent. // (In "binary mode", the content type is the content type of the data, and headers // indicate that it's a CloudEvent.) result.ContentTypes.Add(contentType.MediaType); return(result); }
public AmqpCloudEventMessage(CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter) { ApplicationProperties = new ApplicationProperties(); MapHeaders(cloudEvent); if (contentMode == ContentMode.Structured) { BodySection = new Data { Binary = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType) }; Properties = new Properties { ContentType = contentType.MediaType }; ApplicationProperties = new ApplicationProperties(); MapHeaders(cloudEvent); return; } else { BodySection = SerializeData(cloudEvent.Data); Properties = new Properties { ContentType = cloudEvent.DataContentType }; } }
/// <summary> /// Constructor /// </summary> /// <param name="cloudEvent">CloudEvent</param> /// <param name="contentMode">Content mode. Structured or binary.</param> /// <param name="formatter">Event formatter</param> public CloudEventHttpContent(CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter) { byte[] content; ContentType contentType; switch (contentMode) { case ContentMode.Structured: content = formatter.EncodeStructuredModeMessage(cloudEvent, out contentType); // This is optional in the specification, but can be useful. MapHeaders(cloudEvent, includeDataContentType: true); break; case ContentMode.Binary: content = formatter.EncodeBinaryModeEventData(cloudEvent); contentType = MimeUtilities.CreateContentTypeOrNull(cloudEvent.DataContentType); MapHeaders(cloudEvent, includeDataContentType: false); break; default: throw new ArgumentException($"Unsupported content mode: {contentMode}"); } inner = new InnerByteArrayContent(content); if (contentType is object) { Headers.ContentType = contentType.ToMediaTypeHeaderValue(); } else if (content.Length != 0) { throw new ArgumentException(Strings.ErrorContentTypeUnspecified, nameof(cloudEvent)); } }
/// <summary> /// Converts a CloudEvent to <see cref="HttpContent"/>. /// </summary> /// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param> /// <param name="contentMode">Content mode. Structured or binary.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> public static HttpContent ToHttpContent(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(formatter, nameof(formatter)); ReadOnlyMemory <byte> content; // The content type to include in the ContentType header - may be the data content type, or the formatter's content type. ContentType?contentType; switch (contentMode) { case ContentMode.Structured: content = formatter.EncodeStructuredModeMessage(cloudEvent, out contentType); break; case ContentMode.Binary: content = formatter.EncodeBinaryModeEventData(cloudEvent); contentType = MimeUtilities.CreateContentTypeOrNull(formatter.GetOrInferDataContentType(cloudEvent)); break; default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } ByteArrayContent ret = ToByteArrayContent(content); if (contentType is object) { ret.Headers.ContentType = MimeUtilities.ToMediaTypeHeaderValue(contentType); } else if (content.Length != 0) { throw new ArgumentException(Strings.ErrorContentTypeUnspecified, nameof(cloudEvent)); } // Map headers in either mode. // Including the headers in structured mode is optional in the spec (as they're already within the body) but // can be useful. ret.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; // Skip the data content type attribute in binary mode, because it's already in the content type header. if (attribute == cloudEvent.SpecVersion.DataContentTypeAttribute && contentMode == ContentMode.Binary) { continue; } else { string headerValue = HttpUtilities.EncodeHeaderValue(attribute.Format(value)); ret.Headers.Add(headerName, headerValue); } } return(ret); }
/// <summary> /// Copies a <see cref="CloudEvent"/> into an <see cref="HttpListenerResponse" />. /// </summary> /// <param name="cloudEvent">The CloudEvent to copy. Must not be null, and must be a valid CloudEvent.</param> /// <param name="destination">The response to copy the CloudEvent to. Must not be null.</param> /// <param name="contentMode">Content mode (structured or binary)</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> /// <returns>A task representing the asynchronous operation.</returns> public static async Task CopyToHttpListenerResponseAsync(this CloudEvent cloudEvent, HttpListenerResponse destination, ContentMode contentMode, CloudEventFormatter formatter) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(destination, nameof(destination)); Validation.CheckNotNull(formatter, nameof(formatter)); byte[] content; ContentType contentType; switch (contentMode) { case ContentMode.Structured: content = formatter.EncodeStructuredModeMessage(cloudEvent, out contentType); break; case ContentMode.Binary: content = formatter.EncodeBinaryModeEventData(cloudEvent); contentType = MimeUtilities.CreateContentTypeOrNull(cloudEvent.DataContentType); destination.ContentType = cloudEvent.DataContentType?.ToString() ?? "application/json"; break; default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } if (contentType is object) { destination.ContentType = contentType.ToString(); } else if (content.Length != 0) { throw new ArgumentException(Strings.ErrorContentTypeUnspecified, nameof(cloudEvent)); } // Map headers in either mode. // Including the headers in structured mode is optional in the spec (as they're already within the body) but // can be useful. destination.Headers.Add(HttpUtilities.SpecVersionHttpHeader, HttpUtilities.EncodeHeaderValue(cloudEvent.SpecVersion.VersionId)); foreach (var attributeAndValue in cloudEvent.GetPopulatedAttributes()) { var attribute = attributeAndValue.Key; var value = attributeAndValue.Value; // The content type is already handled based on the content mode. if (attribute != cloudEvent.SpecVersion.DataContentTypeAttribute) { string headerValue = HttpUtilities.EncodeHeaderValue(attribute.Format(value)); destination.Headers.Add(HttpUtilities.HttpHeaderPrefix + attribute.Name, headerValue); } } await destination.OutputStream.WriteAsync(content, 0, content.Length).ConfigureAwait(false); }
public KafkaCloudEventMessage(CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter) { // TODO: Is this appropriate? Why can't we transport a CloudEvent without data in Kafka? if (cloudEvent.Data == null) { throw new ArgumentNullException(nameof(cloudEvent.Data)); } Headers = new Headers { { SpecVersionKafkaHeader, Encoding.UTF8.GetBytes(cloudEvent.SpecVersion.VersionId) } }; Key = (string)cloudEvent[Partitioning.PartitionKeyAttribute]; if (contentMode == ContentMode.Structured) { Value = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType); Headers.Add(KafkaContentTypeAttributeName, Encoding.UTF8.GetBytes(contentType.MediaType)); } else { if (cloudEvent.Data is byte[] byteData) { Value = byteData; } else if (cloudEvent.Data is Stream dataStream) { // TODO: Extract this common code somewhere if (dataStream is MemoryStream dataMemoryStream) { Value = dataMemoryStream.ToArray(); } else { var buffer = new MemoryStream(); dataStream.CopyTo(buffer); Value = buffer.ToArray(); } } else { throw new InvalidOperationException($"{cloudEvent.Data.GetType()} type is not supported for Cloud Event's Value."); } if (cloudEvent.DataContentType is string dataContentType) { Headers.Add(KafkaContentTypeAttributeName, Encoding.UTF8.GetBytes(dataContentType)); } } MapHeaders(cloudEvent, formatter); }
// TODO: HttpWebResponse as well? /// <summary> /// Copies a <see cref="CloudEvent"/> into the specified <see cref="HttpWebRequest"/>. /// </summary> /// <param name="cloudEvent">CloudEvent to copy. Must not be null, and must be a valid CloudEvent.</param> /// <param name="destination">The request to populate. Must not be null.</param> /// <param name="contentMode">Content mode (structured or binary)</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> /// <returns>A task representing the asynchronous operation.</returns> public static async Task CopyToHttpWebRequestAsync(this CloudEvent cloudEvent, HttpWebRequest destination, ContentMode contentMode, CloudEventFormatter formatter) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(destination, nameof(destination)); Validation.CheckNotNull(formatter, nameof(formatter)); ReadOnlyMemory <byte> content; // The content type to include in the ContentType header - may be the data content type, or the formatter's content type. ContentType?contentType; switch (contentMode) { case ContentMode.Structured: content = formatter.EncodeStructuredModeMessage(cloudEvent, out contentType); break; case ContentMode.Binary: content = formatter.EncodeBinaryModeEventData(cloudEvent); contentType = MimeUtilities.CreateContentTypeOrNull(formatter.GetOrInferDataContentType(cloudEvent)); break; default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } if (contentType is object) { destination.ContentType = contentType.ToString(); } else if (content.Length != 0) { throw new ArgumentException(Strings.ErrorContentTypeUnspecified, nameof(cloudEvent)); } // Map headers in either mode. // Including the headers in structured mode is optional in the spec (as they're already within the body) but // can be useful. destination.Headers.Add(HttpUtilities.SpecVersionHttpHeader, HttpUtilities.EncodeHeaderValue(cloudEvent.SpecVersion.VersionId)); foreach (var attributeAndValue in cloudEvent.GetPopulatedAttributes()) { var attribute = attributeAndValue.Key; var value = attributeAndValue.Value; if (attribute != cloudEvent.SpecVersion.DataContentTypeAttribute) { string headerValue = HttpUtilities.EncodeHeaderValue(attribute.Format(value)); destination.Headers.Add(HttpUtilities.HttpHeaderPrefix + attribute.Name, headerValue); } } await BinaryDataUtilities.CopyToStreamAsync(content, destination.GetRequestStream()).ConfigureAwait(false); }
// 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, CloudEventFormatter formatter) { if (contentMode == ContentMode.Structured) { var buffer = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType); httpListenerResponse.ContentType = contentType.ToString(); MapAttributesToListenerResponse(cloudEvent, httpListenerResponse); return(httpListenerResponse.OutputStream.WriteAsync(buffer, 0, buffer.Length)); } // TODO: Check the defaulting to JSON here... httpListenerResponse.ContentType = cloudEvent.DataContentType?.ToString() ?? "application/json"; MapAttributesToListenerResponse(cloudEvent, httpListenerResponse); byte[] content = formatter.EncodeBinaryModeEventData(cloudEvent); return(httpListenerResponse.OutputStream.WriteAsync(content, 0, content.Length)); }
// 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, CloudEventFormatter formatter) { if (contentMode == ContentMode.Structured) { var buffer = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType); httpWebRequest.ContentType = contentType.ToString(); await httpWebRequest.GetRequestStream().WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false); return; } httpWebRequest.ContentType = cloudEvent.DataContentType?.ToString() ?? "application/json"; MapAttributesToWebRequest(cloudEvent, httpWebRequest); byte[] content = formatter.EncodeBinaryModeEventData(cloudEvent); await httpWebRequest.GetRequestStream().WriteAsync(content, 0, content.Length).ConfigureAwait(false); }
// TODO: Update to a newer version of MQTTNet and support both binary and structured mode? /// <summary> /// Converts a CloudEvent to <see cref="MqttApplicationMessage"/>. /// </summary> /// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param> /// <param name="contentMode">Content mode. Currently only structured mode is supported.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> /// <param name="topic">The MQTT topic for the message. May be null.</param> public static MqttApplicationMessage ToMqttApplicationMessage(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter, string topic) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(formatter, nameof(formatter)); switch (contentMode) { case ContentMode.Structured: return new MqttApplicationMessage { Topic = topic, Payload = formatter.EncodeStructuredModeMessage(cloudEvent, out _) }; default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } }
public Task PublishMessageAsync(MotorCloudEvent <byte[]> motorCloudEvent, CancellationToken token = default) { switch (_publisherOptions.CloudEventFormat) { case CloudEventFormat.Protocol: _client.Publish(_options.Topic, motorCloudEvent.TypedData); break; case CloudEventFormat.Json: var value = _cloudEventFormatter.EncodeStructuredModeMessage(motorCloudEvent.ConvertToCloudEvent(), out _); _client.Publish(_options.Topic, value.ToArray()); break; default: throw new UnhandledCloudEventFormatException(_publisherOptions.CloudEventFormat); } return(Task.CompletedTask); }
/// <summary> /// Converts a CloudEvent to <see cref="Message"/>. /// </summary> /// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param> /// <param name="contentMode">Content mode. Structured or binary.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> public static Message ToAmqpMessage(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(formatter, nameof(formatter)); var applicationProperties = MapHeaders(cloudEvent); RestrictedDescribed bodySection; Properties properties; switch (contentMode) { case ContentMode.Structured: bodySection = new Data { Binary = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType) }; // TODO: What about the other parts of the content type? properties = new Properties { ContentType = contentType.MediaType }; break; case ContentMode.Binary: bodySection = new Data { Binary = formatter.EncodeBinaryModeEventData(cloudEvent) }; properties = new Properties { ContentType = cloudEvent.DataContentType }; break; default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } return(new Message { ApplicationProperties = applicationProperties, BodySection = bodySection, Properties = properties }); }
public async Task PublishMessageAsync(MotorCloudEvent <byte[]> motorCloudEvent, CancellationToken token = default) { if (!_connected) { await StartAsync().ConfigureAwait(false); } if (_channel is null) { throw new InvalidOperationException("Channel is not created."); } var properties = _channel.CreateBasicProperties(); properties.DeliveryMode = 2; properties.SetPriority(motorCloudEvent, _options); var exchange = motorCloudEvent.GetRabbitMQExchange() ?? _options.PublishingTarget.Exchange; var routingKey = motorCloudEvent.GetRabbitMQRoutingKey() ?? _options.PublishingTarget.RoutingKey; if (_options.OverwriteExchange) { exchange = _options.PublishingTarget.Exchange; } switch (_publisherOptions.CloudEventFormat) { case CloudEventFormat.Protocol: properties.WriteCloudEventIntoHeader(motorCloudEvent); _channel.BasicPublish(exchange, routingKey, true, properties, motorCloudEvent.TypedData); break; case CloudEventFormat.Json: var data = _cloudEventFormatter.EncodeStructuredModeMessage(motorCloudEvent.ConvertToCloudEvent(), out _); _channel.BasicPublish(exchange, routingKey, true, properties, data); break; default: throw new UnhandledCloudEventFormatException(_publisherOptions.CloudEventFormat); } }
/// <summary> /// Converts a CloudEvent to a Kafka message. /// </summary> /// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param> /// <param name="contentMode">Content mode. Structured or binary.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> public static Message <string, byte[]> ToKafkaMessage(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(formatter, nameof(formatter)); // TODO: Is this appropriate? Why can't we transport a CloudEvent without data in Kafka? Validation.CheckArgument(cloudEvent.Data is object, nameof(cloudEvent), "Only CloudEvents with data can be converted to Kafka messages"); var headers = MapHeaders(cloudEvent); string key = (string)cloudEvent[Partitioning.PartitionKeyAttribute]; byte[] value; string contentTypeHeaderValue; switch (contentMode) { case ContentMode.Structured: value = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType); // TODO: What about the non-media type parts? contentTypeHeaderValue = contentType.MediaType; break; case ContentMode.Binary: value = formatter.EncodeBinaryModeEventData(cloudEvent); contentTypeHeaderValue = cloudEvent.DataContentType; break; default: throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}"); } if (contentTypeHeaderValue is object) { headers.Add(KafkaContentTypeAttributeName, Encoding.UTF8.GetBytes(contentTypeHeaderValue)); } return(new Message <string, byte[]> { Headers = headers, Value = value, Key = key }); }
public Message <string?, byte[]> CloudEventToKafkaMessage(MotorCloudEvent <byte[]> motorCloudEvent) { var cloudEvent = motorCloudEvent.ConvertToCloudEvent(); switch (_publisherOptions.CloudEventFormat) { case CloudEventFormat.Protocol: return(cloudEvent.ToKafkaMessage(ContentMode.Binary, _cloudEventFormatter)); case CloudEventFormat.Json: var value = _cloudEventFormatter.EncodeStructuredModeMessage(cloudEvent, out _); var key = cloudEvent[Partitioning.PartitionKeyAttribute] as string; return(new Message <string?, byte[]> { Value = value.ToArray(), Key = key }); default: throw new UnhandledCloudEventFormatException(_publisherOptions.CloudEventFormat); } }
/// <summary> /// Converts a CloudEvent to <see cref="Message"/>. /// </summary> /// <param name="cloudEvent">The CloudEvent to convert. Must not be null, and must be a valid CloudEvent.</param> /// <param name="contentMode">Content mode. Structured or binary.</param> /// <param name="formatter">The formatter to use within the conversion. Must not be null.</param> /// <returns>A <see cref="Message"/>.</returns> public static Message ToServiceBusMessage( this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter ) { Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); Validation.CheckNotNull(formatter, nameof(formatter)); Message message; switch (contentMode) { case ContentMode.Structured: message = new Message( BinaryDataUtilities.AsArray(formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType)) ) { ContentType = contentType.MediaType, }; break; case ContentMode.Binary: message = new Message( BinaryDataUtilities.AsArray(formatter.EncodeBinaryModeEventData(cloudEvent)) ) { ContentType = cloudEvent.DataContentType, }; break; default: throw new ArgumentException($"Unsupported content mode: {contentMode}", nameof(contentMode)); } MapHeaders(cloudEvent, message); return(message); }