This is an automated email from the ASF dual-hosted git repository. lizhanhui pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/rocketmq-client-csharp.git
commit e6a6bb970e56cd89f8cc5c1c9771b4536d850d45 Author: ZhanhuiLi <[email protected]> AuthorDate: Fri Feb 11 21:57:45 2022 +0800 Initial commit --- .gitignore | 2 + README.md | 3 + examples/Program.cs | 12 + examples/examples.csproj | 12 + rocketmq-client-csharp/Class1.cs | 8 + .../Protos/apache/rocketmq/v1/admin.proto | 45 ++ .../Protos/apache/rocketmq/v1/definition.proto | 351 ++++++++++++++ .../Protos/apache/rocketmq/v1/service.proto | 522 +++++++++++++++++++++ .../Protos/google/rpc/error_details.proto | 249 ++++++++++ .../Protos/google/rpc/status.proto | 47 ++ .../rocketmq-client-csharp.csproj | 31 ++ rocketmq-client.sln | 62 +++ tests/UnitTest1.cs | 13 + tests/tests.csproj | 20 + 14 files changed, 1377 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d4a6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +obj \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..85a3d46 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +Introduction +-------------- +Project rocketmq-client-csharp is targeted to implement C# binding in native C# code. \ No newline at end of file diff --git a/examples/Program.cs b/examples/Program.cs new file mode 100644 index 0000000..9d460cf --- /dev/null +++ b/examples/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace examples +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/examples/examples.csproj b/examples/examples.csproj new file mode 100644 index 0000000..03cc409 --- /dev/null +++ b/examples/examples.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <ItemGroup> + <ProjectReference Include="..\rocketmq-client-csharp\rocketmq-client-csharp.csproj" /> + </ItemGroup> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net5.0</TargetFramework> + </PropertyGroup> + +</Project> diff --git a/rocketmq-client-csharp/Class1.cs b/rocketmq-client-csharp/Class1.cs new file mode 100644 index 0000000..b9cf30a --- /dev/null +++ b/rocketmq-client-csharp/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace rocketmq_client_csharp +{ + public class Class1 + { + } +} diff --git a/rocketmq-client-csharp/Protos/apache/rocketmq/v1/admin.proto b/rocketmq-client-csharp/Protos/apache/rocketmq/v1/admin.proto new file mode 100644 index 0000000..554207b --- /dev/null +++ b/rocketmq-client-csharp/Protos/apache/rocketmq/v1/admin.proto @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package apache.rocketmq.v1; + +option cc_enable_arenas = true; +option java_multiple_files = true; +option java_package = "apache.rocketmq.v1"; +option java_generate_equals_and_hash = true; +option java_string_check_utf8 = true; +option java_outer_classname = "MQAdmin"; + +message ChangeLogLevelRequest { + enum Level { + TRACE = 0; + DEBUG = 1; + INFO = 2; + WARN = 3; + ERROR = 4; + } + Level level = 1; +} + +message ChangeLogLevelResponse { + string remark = 1; +} + +service Admin { + rpc ChangeLogLevel(ChangeLogLevelRequest) returns (ChangeLogLevelResponse) { + } +} \ No newline at end of file diff --git a/rocketmq-client-csharp/Protos/apache/rocketmq/v1/definition.proto b/rocketmq-client-csharp/Protos/apache/rocketmq/v1/definition.proto new file mode 100644 index 0000000..33f4644 --- /dev/null +++ b/rocketmq-client-csharp/Protos/apache/rocketmq/v1/definition.proto @@ -0,0 +1,351 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +package apache.rocketmq.v1; + +option java_multiple_files = true; +option java_package = "apache.rocketmq.v1"; +option java_generate_equals_and_hash = true; +option java_string_check_utf8 = true; +option java_outer_classname = "MQDomain"; + +option csharp_namespace = "apache.rocketmq.v1"; + +enum Permission { + NONE = 0; + READ = 1; + WRITE = 2; + READ_WRITE = 3; + + reserved 4 to 64; +} + +enum FilterType { + TAG = 0; + SQL = 1; + + reserved 2 to 64; +} + +message FilterExpression { + FilterType type = 1; + string expression = 2; + + reserved 3 to 64; +} + +// Dead lettering is done on a best effort basis. The same message might be +// dead lettered multiple times. +// +// If validation on any of the fields fails at subscription creation/update, +// the create/update subscription request will fail. +message DeadLetterPolicy { + // The maximum number of delivery attempts for any message. + // + // This field will be honored on a best effort basis. + // + // If this parameter is 0, a default value of 16 is used. + int32 max_delivery_attempts = 1; + + reserved 2 to 64; +} + +message Resource { + string resource_namespace = 1; + + // Resource name identifier, which remains unique within the abstract resource + // namespace. + string name = 2; + + reserved 3 to 64; +} + +enum ConsumeModel { + CLUSTERING = 0; + BROADCASTING = 1; + + reserved 2 to 64; +} + +message ProducerData { + Resource group = 1; + + reserved 2 to 64; +} + +enum ConsumePolicy { + RESUME = 0; + PLAYBACK = 1; + DISCARD = 2; + TARGET_TIMESTAMP = 3; + + reserved 4 to 64; +} + +enum ConsumeMessageType { + ACTIVE = 0; + PASSIVE = 1; + + reserved 2 to 64; +} + +message ConsumerData { + Resource group = 1; + + repeated SubscriptionEntry subscriptions = 2; + + ConsumeModel consume_model = 3; + + ConsumePolicy consume_policy = 4; + + DeadLetterPolicy dead_letter_policy = 5; + + ConsumeMessageType consume_type = 6; + + reserved 7 to 64; +} + +message SubscriptionEntry { + Resource topic = 1; + FilterExpression expression = 2; + + reserved 3 to 64; +} + +enum AddressScheme { + IPv4 = 0; + IPv6 = 1; + DOMAIN_NAME = 2; + + reserved 3 to 64; +} + +message Address { + string host = 1; + int32 port = 2; + + reserved 3 to 64; +} + +message Endpoints { + AddressScheme scheme = 1; + repeated Address addresses = 2; + + reserved 3 to 64; +} + +message Broker { + // Name of the broker + string name = 1; + + // Broker index. Canonically, index = 0 implies that the broker is playing + // leader role while brokers with index > 0 play follower role. + int32 id = 2; + + // Address of the broker, complying with the following scheme + // 1. dns:[//authority/]host[:port] + // 2. ipv4:address[:port][,address[:port],...] – IPv4 addresses + // 3. ipv6:address[:port][,address[:port],...] – IPv6 addresses + Endpoints endpoints = 3; + + reserved 4 to 64; +} + +message Partition { + Resource topic = 1; + int32 id = 2; + Permission permission = 3; + Broker broker = 4; + + reserved 5 to 64; +} + +enum MessageType { + NORMAL = 0; + + // Sequenced message + FIFO = 1; + + // Messages that are delivered after the specified duration. + DELAY = 2; + + // Messages that are transactional. Only committed messages are delivered to + // subscribers. + TRANSACTION = 3; + + reserved 4 to 64; +} + +enum DigestType { + // CRC algorithm achieves goal of detecting random data error with lowest + // computation overhead. + CRC32 = 0; + + // MD5 algorithm achieves good balance between collision rate and computation + // overhead. + MD5 = 1; + + // SHA-family has substantially fewer collision with fair amount of + // computation. + SHA1 = 2; + + reserved 3 to 64; +} + +// When publishing messages to or subscribing messages from brokers, clients +// shall include or validate digests of message body to ensure data integrity. +// +// For message publishment, when an invalid digest were detected, brokers need +// respond client with BAD_REQUEST. +// +// For messags subscription, when an invalid digest were detected, consumers +// need to handle this case according to message type: +// 1) Standard messages should be negatively acknowledged instantly, causing +// immediate re-delivery; 2) FIFO messages require special RPC, to re-fetch +// previously acquired messages batch; +// +// Message consumption model also affects how invalid digest are handled. When +// messages are consumed in broadcasting way, +// TODO: define semantics of invalid-digest-when-broadcasting. +message Digest { + DigestType type = 1; + string checksum = 2; + + reserved 3 to 64; +} + +enum Encoding { + IDENTITY = 0; + GZIP = 1; + + reserved 2 to 64; +} + +message SystemAttribute { + // Tag + string tag = 1; + + // Message keys + repeated string keys = 2; + + // Message identifier, client-side generated, remains unique. + // if message_id is empty, the send message request will be aborted with + // status `INVALID_ARGUMENT` + string message_id = 3; + + // Message body digest + Digest body_digest = 4; + + // Message body encoding. Candidate options are identity, gzip, snappy etc. + Encoding body_encoding = 5; + + // Message type, normal, FIFO or transactional. + MessageType message_type = 6; + + // Message born time-point. + google.protobuf.Timestamp born_timestamp = 7; + + // Message born host. Valid options are IPv4, IPv6 or client host domain name. + string born_host = 8; + + // Time-point at which the message is stored in the broker. + google.protobuf.Timestamp store_timestamp = 9; + + // The broker that stores this message. It may be name, IP or arbitrary + // identifier that uniquely identify the broker. + string store_host = 10; + + oneof timed_delivery { + // Time-point at which broker delivers to clients. + google.protobuf.Timestamp delivery_timestamp = 11; + + // Level-based delay strategy. + int32 delay_level = 12; + } + + // If a message is acquired by way of POP, this field holds the receipt. + // Clients use the receipt to acknowledge or negatively acknowledge the + // message. + string receipt_handle = 13; + + // Partition identifier in which a message is physically stored. + int32 partition_id = 14; + + // Partition offset at which a message is stored. + int64 partition_offset = 15; + + // Period of time servers would remain invisible once a message is acquired. + google.protobuf.Duration invisible_period = 16; + + // Business code may failed to process messages for the moment. Hence, clients + // may request servers to deliver them again using certain back-off strategy, + // the attempt is 1 not 0 if message is delivered first time. + int32 delivery_attempt = 17; + + // Message producer load-balance group if applicable. + Resource producer_group = 18; + + string message_group = 19; + + // Trace context. + string trace_context = 20; + + // Delay time of first recover orphaned transaction request from server. + google.protobuf.Duration orphaned_transaction_recovery_period = 21; + + reserved 22 to 64; +} + +message Message { + + Resource topic = 1; + + // User defined key-value pairs. + // If user_attribute contains the reserved keys by RocketMQ, + // the send message request will be aborted with status `INVALID_ARGUMENT`. + // See below links for the reserved keys + // https://github.com/apache/rocketmq/blob/master/common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java#L58 + map<string, string> user_attribute = 2; + + SystemAttribute system_attribute = 3; + + bytes body = 4; + + reserved 5 to 64; +} + +message Assignment { + Partition Partition = 1; + + reserved 2 to 64; +} + +enum QueryOffsetPolicy { + // Use this option if client wishes to playback all existing messages. + BEGINNING = 0; + + // Use this option if client wishes to skip all existing messages. + END = 1; + + // Use this option if time-based seek is targeted. + TIME_POINT = 2; + + reserved 3 to 64; +} \ No newline at end of file diff --git a/rocketmq-client-csharp/Protos/apache/rocketmq/v1/service.proto b/rocketmq-client-csharp/Protos/apache/rocketmq/v1/service.proto new file mode 100644 index 0000000..2dacfa8 --- /dev/null +++ b/rocketmq-client-csharp/Protos/apache/rocketmq/v1/service.proto @@ -0,0 +1,522 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/rpc/error_details.proto"; +import "google/rpc/status.proto"; + +import "apache/rocketmq/v1/definition.proto"; + +package apache.rocketmq.v1; + +option java_multiple_files = true; +option java_package = "apache.rocketmq.v1"; +option java_generate_equals_and_hash = true; +option java_string_check_utf8 = true; +option java_outer_classname = "MQService"; + +option csharp_namespace = "apache.rocketmq.v1"; + +message ResponseCommon { + google.rpc.Status status = 1; + google.rpc.RequestInfo request_info = 2; + google.rpc.Help help = 3; + google.rpc.RetryInfo retry_info = 4; + google.rpc.DebugInfo debug_info = 5; + google.rpc.ErrorInfo error_info = 6; + + reserved 7 to 64; +} + +// Topics are destination of messages to publish to or subscribe from. Similar +// to domain names, they will be addressable after resolution through the +// provided access point. +// +// Access points are usually the addresses of name servers, which fulfill +// service discovery, load-balancing and other auxillary services. Name servers +// receive periodic heartbeats from affiliate brokers and erase those which +// failed to maintain alive status. +// +// Name servers answer queries of QueryRouteRequest, responding clients with +// addressable partitions, which they may directly publish messages to or +// subscribe messages from. +// +// QueryRouteRequest shall include source endpoints, aka, configured +// access-point, which annotates tenant-id, instance-id or other +// vendor-specific settings. Purpose-built name servers may respond customized +// results based on these particular requirements. +message QueryRouteRequest { + Resource topic = 1; + + Endpoints endpoints = 2; + + reserved 3 to 64; +} + +message QueryRouteResponse { + ResponseCommon common = 1; + + repeated Partition partitions = 2; + + reserved 3 to 64; +} + +message SendMessageRequest { + Message message = 1; + Partition partition = 2; + + reserved 3 to 64; +} + +message SendMessageResponse { + ResponseCommon common = 1; + string message_id = 2; + string transaction_id = 3; + + reserved 4 to 64; +} + +message QueryAssignmentRequest { + Resource topic = 1; + Resource group = 2; + string client_id = 3; + + // Service access point + Endpoints endpoints = 4; + + reserved 5 to 64; +} + +message QueryAssignmentResponse { + ResponseCommon common = 1; + repeated Assignment assignments = 2; + + reserved 3 to 64; +} + +message ReceiveMessageRequest { + Resource group = 1; + string client_id = 2; + Partition partition = 3; + FilterExpression filter_expression = 4; + ConsumePolicy consume_policy = 5; + google.protobuf.Timestamp initialization_timestamp = 6; + int32 batch_size = 7; + google.protobuf.Duration invisible_duration = 8; + google.protobuf.Duration await_time = 9; + bool fifo_flag = 10; + + reserved 11 to 64; +} + +message ReceiveMessageResponse { + ResponseCommon common = 1; + repeated Message messages = 2; + google.protobuf.Timestamp delivery_timestamp = 3; + google.protobuf.Duration invisible_duration = 4; + + reserved 5 to 64; +} + +message AckMessageRequest { + Resource group = 1; + Resource topic = 2; + string client_id = 3; + oneof handle { + string receipt_handle = 4; + int64 offset = 5; + } + string message_id = 6; + + reserved 7 to 64; +} + +message AckMessageResponse { + ResponseCommon common = 1; + + reserved 2 to 64; +} + +message NackMessageRequest { + Resource group = 1; + Resource topic = 2; + string client_id = 3; + string receipt_handle = 4; + string message_id = 5; + int32 delivery_attempt = 6; + int32 max_delivery_attempts = 7; + + reserved 8 to 64; +} + +message NackMessageResponse { + ResponseCommon common = 1; + + reserved 2 to 64; +} + +message ForwardMessageToDeadLetterQueueRequest { + Resource group = 1; + Resource topic = 2; + string client_id = 3; + string receipt_handle = 4; + string message_id = 5; + int32 delivery_attempt = 6; + int32 max_delivery_attempts = 7; + + reserved 8 to 64; +} + +message ForwardMessageToDeadLetterQueueResponse { + ResponseCommon common = 1; + + reserved 2 to 64; +} + +message HeartbeatRequest { + string client_id = 1; + oneof client_data { + ProducerData producer_data = 2; + ConsumerData consumer_data = 3; + } + bool fifo_flag = 4; + + reserved 5 to 64; +} + +message HeartbeatResponse { + ResponseCommon common = 1; + + reserved 2 to 64; +} + +message HealthCheckRequest { + Resource group = 1; + string client_host = 2; + + reserved 3 to 64; +} + +message HealthCheckResponse { + ResponseCommon common = 1; + + reserved 2 to 64; +} + +message EndTransactionRequest { + Resource group = 1; + string message_id = 2; + string transaction_id = 3; + enum TransactionResolution { + COMMIT = 0; + ROLLBACK = 1; + } + TransactionResolution resolution = 4; + enum Source { + CLIENT = 0; + SERVER_CHECK = 1; + } + Source source = 5; + string trace_context = 6; + + reserved 7 to 64; +} + +message EndTransactionResponse { + ResponseCommon common = 1; + + reserved 2 to 64; +} + +message QueryOffsetRequest { + Partition partition = 1; + QueryOffsetPolicy policy = 2; + google.protobuf.Timestamp time_point = 3; + + reserved 4 to 64; +} + +message QueryOffsetResponse { + ResponseCommon common = 1; + int64 offset = 2; + + reserved 3 to 64; +} + +message PullMessageRequest { + Resource group = 1; + Partition partition = 2; + int64 offset = 3; + int32 batch_size = 4; + google.protobuf.Duration await_time = 5; + FilterExpression filter_expression = 6; + string client_id = 7; + + reserved 8 to 64; +} + +message PullMessageResponse { + ResponseCommon common = 1; + int64 min_offset = 2; + int64 next_offset = 3; + int64 max_offset = 4; + repeated Message messages = 5; + + reserved 6 to 64; +} + +message NoopCommand { reserved 1 to 64; } + +message PrintThreadStackTraceCommand { + string command_id = 1; + + reserved 2 to 64; +} + +message ReportThreadStackTraceRequest { + string command_id = 1; + string thread_stack_trace = 2; + + reserved 3 to 64; +} + +message ReportThreadStackTraceResponse { + ResponseCommon common = 1; + + reserved 2 to 64; +} + +message VerifyMessageConsumptionCommand { + string command_id = 1; + Message message = 2; + + reserved 3 to 64; +} + +message ReportMessageConsumptionResultRequest { + string command_id = 1; + + // 1. Return `INVALID_ARGUMENT` if message is corrupted. + // 2. Return `INTERNAL` if failed to consume message. + // 3. Return `OK` if success. + google.rpc.Status status = 2; + + reserved 3 to 64; +} + +message ReportMessageConsumptionResultResponse { + ResponseCommon common = 1; + + reserved 2 to 64; +} + +message RecoverOrphanedTransactionCommand { + Message orphaned_transactional_message = 1; + string transaction_id = 2; + + reserved 3 to 64; +} + +message PollCommandRequest { + string client_id = 1; + repeated Resource topics = 2; + oneof group { + Resource producer_group = 3; + Resource consumer_group = 4; + } + + reserved 5 to 64; +} + +message PollCommandResponse { + oneof type { + // Default command when no new command need to be delivered. + NoopCommand noop_command = 1; + // Request client to print thread stack trace. + PrintThreadStackTraceCommand print_thread_stack_trace_command = 2; + // Request client to verify the consumption of the appointed message. + VerifyMessageConsumptionCommand verify_message_consumption_command = 3; + // Request client to recover the orphaned transaction message. + RecoverOrphanedTransactionCommand recover_orphaned_transaction_command = 4; + } + + reserved 5 to 64; +} + +message NotifyClientTerminationRequest { + oneof group { + Resource producer_group = 1; + Resource consumer_group = 2; + } + string client_id = 3; + + reserved 4 to 64; +} + +message NotifyClientTerminationResponse { + ResponseCommon common = 1; + + reserved 2 to 64; +} + +// For all the RPCs in MessagingService, the following error handling policies +// apply: +// +// If the request doesn't bear a valid authentication credential, return a +// response with common.status.code == `UNAUTHENTICATED`. If the authenticated +// user is not granted with sufficient permission to execute the requested +// operation, return a response with common.status.code == `PERMISSION_DENIED`. +// If the per-user-resource-based quota is exhausted, return a response with +// common.status.code == `RESOURCE_EXHAUSTED`. If any unexpected server-side +// errors raise, return a response with common.status.code == `INTERNAL`. +service MessagingService { + + // Querys the route entries of the requested topic in the perspective of the + // given endpoints. On success, servers should return a collection of + // addressable partitions. Note servers may return customized route entries + // based on endpoints provided. + // + // If the requested topic doesn't exist, returns `NOT_FOUND`. + // If the specific endpoints is emtpy, returns `INVALID_ARGUMENT`. + rpc QueryRoute(QueryRouteRequest) returns (QueryRouteResponse) {} + + // Producer or consumer sends HeartbeatRequest to servers periodically to + // keep-alive. Additionally, it also reports client-side configuration, + // including topic subscription, load-balancing group name, etc. + // + // Returns `OK` if success. + // + // If a client specifies a language that is not yet supported by servers, + // returns `INVALID_ARGUMENT` + rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse) {} + + // Checks the health status of message server, returns `OK` if services are + // online and serving. Clients may use this RPC to detect availability of + // messaging service, and take isolation actions when necessary. + rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse) {} + + // Delivers messages to brokers. + // Clients may further: + // 1. Refine a message destination to topic partition which fulfills parts of + // FIFO semantic; + // 2. Flag a message as transactional, which keeps it invisible to consumers + // until it commits; + // 3. Time a message, making it invisible to consumers till specified + // time-point; + // 4. And more... + // + // Returns message-id or transaction-id with status `OK` on success. + // + // If the destination topic doesn't exist, returns `NOT_FOUND`. + rpc SendMessage(SendMessageRequest) returns (SendMessageResponse) {} + + // Querys the assigned partition route info of a topic for current consumer, + // the returned assignment result is descided by server-side load balacner. + // + // If the corresponding topic doesn't exist, returns `NOT_FOUND`. + // If the specific endpoints is emtpy, returns `INVALID_ARGUMENT`. + rpc QueryAssignment(QueryAssignmentRequest) + returns (QueryAssignmentResponse) {} + + // Receives messages from the server in batch manner, returns a set of + // messages if success. The received messages should be acked or uacked after + // processed. + // + // If the pending concurrent receive requests exceed the quota of the given + // consumer group, returns `UNAVAILABLE`. If the upstream store server hangs, + // return `DEADLINE_EXCEEDED` in a timely manner. If the corresponding topic + // or consumer group doesn't exist, returns `NOT_FOUND`. If there is no new + // message in the specific topic, returns `OK` with an empty message set. + // Please note that client may suffer from false empty responses. + rpc ReceiveMessage(ReceiveMessageRequest) returns (ReceiveMessageResponse) {} + + // Acknowledges the message associated with the `receipt_handle` or `offset` + // in the `AckMessageRequest`, it means the message has been successfully + // processed. Returns `OK` if the message server remove the relevant message + // successfully. + // + // If the given receipt_handle is illegal or out of date, returns + // `INVALID_ARGUMENT`. + rpc AckMessage(AckMessageRequest) returns (AckMessageResponse) {} + + // Signals that the message has not been successfully processed. The message + // server should resend the message follow the retry policy defined at + // server-side. + // + // If the corresponding topic or consumer group doesn't exist, returns + // `NOT_FOUND`. + rpc NackMessage(NackMessageRequest) returns (NackMessageResponse) {} + + // Forwards one message to dead letter queue if the DeadLetterPolicy is + // triggered by this message at client-side, return `OK` if success. + rpc ForwardMessageToDeadLetterQueue(ForwardMessageToDeadLetterQueueRequest) + returns (ForwardMessageToDeadLetterQueueResponse) {} + + // Commits or rollback one transactional message. + rpc EndTransaction(EndTransactionRequest) returns (EndTransactionResponse) {} + + // Querys the offset of the specific partition, returns the offset with `OK` + // if success. The message server should maintain a numerical offset for each + // message in a parition. + rpc QueryOffset(QueryOffsetRequest) returns (QueryOffsetResponse) {} + + // Pulls messages from the specific partition, returns a set of messages with + // next pull offset. The pulled messages can't be acked or nacked, while the + // client is responsible for manage offesets for consumer, typically update + // consume offset to local memory or a third-party storage service. + // + // If the pending concurrent receive requests exceed the quota of the given + // consumer group, returns `UNAVAILABLE`. If the upstream store server hangs, + // return `DEADLINE_EXCEEDED` in a timely manner. If the corresponding topic + // or consumer group doesn't exist, returns `NOT_FOUND`. If there is no new + // message in the specific topic, returns `OK` with an empty message set. + // Please note that client may suffer from false empty responses. + rpc PullMessage(PullMessageRequest) returns (PullMessageResponse) {} + + // Multiplexing RPC(s) for various polling requests, which issue different + // commands to client. + // + // Sometimes client may need to receive and process the command from server. + // To prevent the complexity of streaming RPC(s), a unary RPC using + // long-polling is another solution. + // + // To mark the request-response of corresponding command, `command_id` in + // message is recorded in the subsequent RPC(s). For example, after receiving + // command of printing thread stack trace, client would send + // `ReportMessageConsumptionResultRequest` to server, which contain both of + // the stack trace and `command_id`. + // + // At same time, `NoopCommand` is delivered from server when no new command is + // needed, it is essential for client to maintain the ping-pong. + // + rpc PollCommand(PollCommandRequest) returns (PollCommandResponse) {} + + // After receiving the corresponding polling command, the thread stack trace + // is reported to the server. + rpc ReportThreadStackTrace(ReportThreadStackTraceRequest) + returns (ReportThreadStackTraceResponse) {} + + // After receiving the corresponding polling command, the consumption result + // of appointed message is reported to the server. + rpc ReportMessageConsumptionResult(ReportMessageConsumptionResultRequest) + returns (ReportMessageConsumptionResultResponse) {} + + // Notify the server that the client is terminated. + rpc NotifyClientTermination(NotifyClientTerminationRequest) + returns (NotifyClientTerminationResponse) {} +} \ No newline at end of file diff --git a/rocketmq-client-csharp/Protos/google/rpc/error_details.proto b/rocketmq-client-csharp/Protos/google/rpc/error_details.proto new file mode 100644 index 0000000..c4d6c4b --- /dev/null +++ b/rocketmq-client-csharp/Protos/google/rpc/error_details.proto @@ -0,0 +1,249 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.rpc; + +import "google/protobuf/duration.proto"; + +option go_package = "google.golang.org/genproto/googleapis/rpc/errdetails;errdetails"; +option java_multiple_files = true; +option java_outer_classname = "ErrorDetailsProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// Describes when the clients can retry a failed request. Clients could ignore +// the recommendation here or retry when this information is missing from error +// responses. +// +// It's always recommended that clients should use exponential backoff when +// retrying. +// +// Clients should wait until `retry_delay` amount of time has passed since +// receiving the error response before retrying. If retrying requests also +// fail, clients should use an exponential backoff scheme to gradually increase +// the delay between retries based on `retry_delay`, until either a maximum +// number of retries have been reached or a maximum retry delay cap has been +// reached. +message RetryInfo { + // Clients should wait at least this long between retrying the same request. + google.protobuf.Duration retry_delay = 1; +} + +// Describes additional debugging info. +message DebugInfo { + // The stack trace entries indicating where the error occurred. + repeated string stack_entries = 1; + + // Additional debugging information provided by the server. + string detail = 2; +} + +// Describes how a quota check failed. +// +// For example if a daily limit was exceeded for the calling project, +// a service could respond with a QuotaFailure detail containing the project +// id and the description of the quota limit that was exceeded. If the +// calling project hasn't enabled the service in the developer console, then +// a service could respond with the project id and set `service_disabled` +// to true. +// +// Also see RetryInfo and Help types for other details about handling a +// quota failure. +message QuotaFailure { + // A message type used to describe a single quota violation. For example, a + // daily quota or a custom quota that was exceeded. + message Violation { + // The subject on which the quota check failed. + // For example, "clientip:<ip address of client>" or "project:<Google + // developer project id>". + string subject = 1; + + // A description of how the quota check failed. Clients can use this + // description to find more about the quota configuration in the service's + // public documentation, or find the relevant quota limit to adjust through + // developer console. + // + // For example: "Service disabled" or "Daily Limit for read operations + // exceeded". + string description = 2; + } + + // Describes all quota violations. + repeated Violation violations = 1; +} + +// Describes the cause of the error with structured details. +// +// Example of an error when contacting the "pubsub.googleapis.com" API when it +// is not enabled: +// +// { "reason": "API_DISABLED" +// "domain": "googleapis.com" +// "metadata": { +// "resource": "projects/123", +// "service": "pubsub.googleapis.com" +// } +// } +// +// This response indicates that the pubsub.googleapis.com API is not enabled. +// +// Example of an error that is returned when attempting to create a Spanner +// instance in a region that is out of stock: +// +// { "reason": "STOCKOUT" +// "domain": "spanner.googleapis.com", +// "metadata": { +// "availableRegions": "us-central1,us-east2" +// } +// } +message ErrorInfo { + // The reason of the error. This is a constant value that identifies the + // proximate cause of the error. Error reasons are unique within a particular + // domain of errors. This should be at most 63 characters and match + // /[A-Z0-9_]+/. + string reason = 1; + + // The logical grouping to which the "reason" belongs. The error domain + // is typically the registered service name of the tool or product that + // generates the error. Example: "pubsub.googleapis.com". If the error is + // generated by some common infrastructure, the error domain must be a + // globally unique value that identifies the infrastructure. For Google API + // infrastructure, the error domain is "googleapis.com". + string domain = 2; + + // Additional structured details about this error. + // + // Keys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in + // length. When identifying the current value of an exceeded limit, the units + // should be contained in the key, not the value. For example, rather than + // {"instanceLimit": "100/request"}, should be returned as, + // {"instanceLimitPerRequest": "100"}, if the client exceeds the number of + // instances that can be created in a single (batch) request. + map<string, string> metadata = 3; +} + +// Describes what preconditions have failed. +// +// For example, if an RPC failed because it required the Terms of Service to be +// acknowledged, it could list the terms of service violation in the +// PreconditionFailure message. +message PreconditionFailure { + // A message type used to describe a single precondition failure. + message Violation { + // The type of PreconditionFailure. We recommend using a service-specific + // enum type to define the supported precondition violation subjects. For + // example, "TOS" for "Terms of Service violation". + string type = 1; + + // The subject, relative to the type, that failed. + // For example, "google.com/cloud" relative to the "TOS" type would indicate + // which terms of service is being referenced. + string subject = 2; + + // A description of how the precondition failed. Developers can use this + // description to understand how to fix the failure. + // + // For example: "Terms of service not accepted". + string description = 3; + } + + // Describes all precondition violations. + repeated Violation violations = 1; +} + +// Describes violations in a client request. This error type focuses on the +// syntactic aspects of the request. +message BadRequest { + // A message type used to describe a single bad request field. + message FieldViolation { + // A path leading to a field in the request body. The value will be a + // sequence of dot-separated identifiers that identify a protocol buffer + // field. E.g., "field_violations.field" would identify this field. + string field = 1; + + // A description of why the request element is bad. + string description = 2; + } + + // Describes all violations in a client request. + repeated FieldViolation field_violations = 1; +} + +// Contains metadata about the request that clients can attach when filing a bug +// or providing other forms of feedback. +message RequestInfo { + // An opaque string that should only be interpreted by the service generating + // it. For example, it can be used to identify requests in the service's logs. + string request_id = 1; + + // Any data that was used to serve this request. For example, an encrypted + // stack trace that can be sent back to the service provider for debugging. + string serving_data = 2; +} + +// Describes the resource that is being accessed. +message ResourceInfo { + // A name for the type of resource being accessed, e.g. "sql table", + // "cloud storage bucket", "file", "Google calendar"; or the type URL + // of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic". + string resource_type = 1; + + // The name of the resource being accessed. For example, a shared calendar + // name: "[email protected]", if the current + // error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED]. + string resource_name = 2; + + // The owner of the resource (optional). + // For example, "user:<owner email>" or "project:<Google developer project + // id>". + string owner = 3; + + // Describes what error is encountered when accessing this resource. + // For example, updating a cloud project may require the `writer` permission + // on the developer console project. + string description = 4; +} + +// Provides links to documentation or for performing an out of band action. +// +// For example, if a quota check failed with an error indicating the calling +// project hasn't enabled the accessed service, this can contain a URL pointing +// directly to the right place in the developer console to flip the bit. +message Help { + // Describes a URL link. + message Link { + // Describes what the link offers. + string description = 1; + + // The URL of the link. + string url = 2; + } + + // URL(s) pointing to additional information on handling the current error. + repeated Link links = 1; +} + +// Provides a localized error message that is safe to return to the user +// which can be attached to an RPC error. +message LocalizedMessage { + // The locale used following the specification defined at + // http://www.rfc-editor.org/rfc/bcp/bcp47.txt. + // Examples are: "en-US", "fr-CH", "es-MX" + string locale = 1; + + // The localized error message in the above locale. + string message = 2; +} diff --git a/rocketmq-client-csharp/Protos/google/rpc/status.proto b/rocketmq-client-csharp/Protos/google/rpc/status.proto new file mode 100644 index 0000000..3b1f7a9 --- /dev/null +++ b/rocketmq-client-csharp/Protos/google/rpc/status.proto @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.rpc; + +import "google/protobuf/any.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/rpc/status;status"; +option java_multiple_files = true; +option java_outer_classname = "StatusProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// The `Status` type defines a logical error model that is suitable for +// different programming environments, including REST APIs and RPC APIs. It is +// used by [gRPC](https://github.com/grpc). Each `Status` message contains +// three pieces of data: error code, error message, and error details. +// +// You can find out more about this error model and how to work with it in the +// [API Design Guide](https://cloud.google.com/apis/design/errors). +message Status { + // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + int32 code = 1; + + // A developer-facing error message, which should be in English. Any + // user-facing error message should be localized and sent in the + // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + string message = 2; + + // A list of messages that carry the error details. There is a common set of + // message types for APIs to use. + repeated google.protobuf.Any details = 3; +} diff --git a/rocketmq-client-csharp/rocketmq-client-csharp.csproj b/rocketmq-client-csharp/rocketmq-client-csharp.csproj new file mode 100644 index 0000000..0a1e2a6 --- /dev/null +++ b/rocketmq-client-csharp/rocketmq-client-csharp.csproj @@ -0,0 +1,31 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <PackageId>RocketMQ-Client-CSharp</PackageId> + <Version>1.0.0</Version> + <Authors>Zhanhui Li</Authors> + <Company>Apache Software Foundation</Company> + <TargetFramework>net5.0</TargetFramework> + <RootNamespace>org.apache.rocketmq</RootNamespace> + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Google.Protobuf" Version="3.19.4" /> + <PackageReference Include="Grpc.Net.Client" Version="2.42.0" /> + <PackageReference Include="Grpc.Tools" Version="2.43.0"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + + <Protobuf Include="Protos\apache\rocketmq\v1\definition.proto" ProtoRoot="Protos" GrpcServices="Client" /> + <Protobuf Include="Protos\google\rpc\status.proto" ProtoRoot="Protos" GrpcServices="Client" /> + <Protobuf Include="Protos\google\rpc\error_details.proto" ProtoRoot="Protos" GrpcServices="Client" /> + <Protobuf Include="Protos\apache\rocketmq\v1\service.proto" ProtoRoot="Protos" GrpcServices="Client"> + <Link>Protos\apache\rocketmq\v1\definition.proto</Link> + <Link>Protos\google\rpc\status.proto</Link> + <Link>Protos\google\rpc\error_details.proto</Link> + </Protobuf> + </ItemGroup> + +</Project> diff --git a/rocketmq-client.sln b/rocketmq-client.sln new file mode 100644 index 0000000..d3a68db --- /dev/null +++ b/rocketmq-client.sln @@ -0,0 +1,62 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rocketmq-client-csharp", "rocketmq-client-csharp\rocketmq-client-csharp.csproj", "{7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "examples", "examples\examples.csproj", "{9F749350-A3D0-423E-AFB6-79E521C777D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tests", "tests\tests.csproj", "{92248517-D1FD-4C65-A691-647C696B9F85}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Debug|x64.ActiveCfg = Debug|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Debug|x64.Build.0 = Debug|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Debug|x86.ActiveCfg = Debug|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Debug|x86.Build.0 = Debug|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Release|Any CPU.Build.0 = Release|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Release|x64.ActiveCfg = Release|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Release|x64.Build.0 = Release|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Release|x86.ActiveCfg = Release|Any CPU + {7BD84605-FDC5-4B47-8D5D-A9EEADA0EA99}.Release|x86.Build.0 = Release|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Debug|x64.ActiveCfg = Debug|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Debug|x64.Build.0 = Debug|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Debug|x86.ActiveCfg = Debug|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Debug|x86.Build.0 = Debug|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Release|Any CPU.Build.0 = Release|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Release|x64.ActiveCfg = Release|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Release|x64.Build.0 = Release|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Release|x86.ActiveCfg = Release|Any CPU + {9F749350-A3D0-423E-AFB6-79E521C777D0}.Release|x86.Build.0 = Release|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Debug|x64.ActiveCfg = Debug|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Debug|x64.Build.0 = Debug|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Debug|x86.ActiveCfg = Debug|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Debug|x86.Build.0 = Debug|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Release|Any CPU.Build.0 = Release|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Release|x64.ActiveCfg = Release|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Release|x64.Build.0 = Release|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Release|x86.ActiveCfg = Release|Any CPU + {92248517-D1FD-4C65-A691-647C696B9F85}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/tests/UnitTest1.cs b/tests/UnitTest1.cs new file mode 100644 index 0000000..bdc4aa3 --- /dev/null +++ b/tests/UnitTest1.cs @@ -0,0 +1,13 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace tests +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + } + } +} diff --git a/tests/tests.csproj b/tests/tests.csproj new file mode 100644 index 0000000..a70533f --- /dev/null +++ b/tests/tests.csproj @@ -0,0 +1,20 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.3" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.3" /> + <PackageReference Include="coverlet.collector" Version="3.0.2" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\rocketmq-client-csharp\rocketmq-client-csharp.csproj" /> + </ItemGroup> + +</Project>
