pulsar-client 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/package.json +3 -3
- package/src/Consumer.cc +58 -21
- package/src/Consumer.h +7 -2
- package/src/ConsumerConfig.cc +91 -3
- package/src/ConsumerConfig.h +16 -1
- package/src/Message.cc +1 -3
- package/src/MessageId.cc +2 -2
- package/src/MessageId.h +1 -1
- package/tests/consumer.test.js +15 -0
- package/tests/end_to_end.test.js +173 -1
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ Compatibility between each version of the Node.js client and the C++ client is a
|
|
|
43
43
|
| Node.js client | C++ client |
|
|
44
44
|
|----------------|----------------|
|
|
45
45
|
| 1.0.0 | 2.3.0 or later |
|
|
46
|
+
| 1.1.0 | 2.4.0 or later |
|
|
46
47
|
|
|
47
48
|
If an incompatible version of the C++ client is installed, you may fail to build or run this library.
|
|
48
49
|
|
|
@@ -54,6 +55,12 @@ If an incompatible version of the C++ client is installed, you may fail to build
|
|
|
54
55
|
$ npm install pulsar-client
|
|
55
56
|
```
|
|
56
57
|
|
|
58
|
+
## Typescript Definitions
|
|
59
|
+
|
|
60
|
+
```shell
|
|
61
|
+
$ npm install @types/pulsar-client --save-dev
|
|
62
|
+
```
|
|
63
|
+
|
|
57
64
|
## Sample code
|
|
58
65
|
|
|
59
66
|
Please refer to [examples](https://github.com/apache/pulsar-client-node/tree/master/examples).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pulsar-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Pulsar Node.js client",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"license": "Apache-2.0",
|
|
28
28
|
"gypfile": true,
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=10.
|
|
30
|
+
"node": ">=10.16.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"clang-format": "^1.2.4",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"bindings": "^1.3.1",
|
|
48
|
-
"node-addon-api": "^
|
|
48
|
+
"node-addon-api": "^2.0.0",
|
|
49
49
|
"node-gyp": "^3.8.0",
|
|
50
50
|
"node-pre-gyp": "^0.12.0"
|
|
51
51
|
},
|
package/src/Consumer.cc
CHANGED
|
@@ -34,6 +34,8 @@ void Consumer::Init(Napi::Env env, Napi::Object exports) {
|
|
|
34
34
|
InstanceMethod("receive", &Consumer::Receive),
|
|
35
35
|
InstanceMethod("acknowledge", &Consumer::Acknowledge),
|
|
36
36
|
InstanceMethod("acknowledgeId", &Consumer::AcknowledgeId),
|
|
37
|
+
InstanceMethod("negativeAcknowledge", &Consumer::NegativeAcknowledge),
|
|
38
|
+
InstanceMethod("negativeAcknowledgeId", &Consumer::NegativeAcknowledgeId),
|
|
37
39
|
InstanceMethod("acknowledgeCumulative", &Consumer::AcknowledgeCumulative),
|
|
38
40
|
InstanceMethod("acknowledgeCumulativeId", &Consumer::AcknowledgeCumulativeId),
|
|
39
41
|
InstanceMethod("close", &Consumer::Close),
|
|
@@ -43,18 +45,20 @@ void Consumer::Init(Napi::Env env, Napi::Object exports) {
|
|
|
43
45
|
constructor.SuppressDestruct();
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
void Consumer::SetCConsumer(
|
|
48
|
+
void Consumer::SetCConsumer(std::shared_ptr<CConsumerWrapper> cConsumer) { this->wrapper = cConsumer; }
|
|
49
|
+
void Consumer::SetListenerCallback(ListenerCallback *listener) { this->listener = listener; }
|
|
47
50
|
|
|
48
|
-
Consumer::Consumer(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Consumer>(info) {}
|
|
51
|
+
Consumer::Consumer(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Consumer>(info), listener(nullptr) {}
|
|
49
52
|
|
|
50
53
|
class ConsumerNewInstanceWorker : public Napi::AsyncWorker {
|
|
51
54
|
public:
|
|
52
55
|
ConsumerNewInstanceWorker(const Napi::Promise::Deferred &deferred, pulsar_client_t *cClient,
|
|
53
|
-
ConsumerConfig *consumerConfig)
|
|
56
|
+
ConsumerConfig *consumerConfig, std::shared_ptr<CConsumerWrapper> consumerWrapper)
|
|
54
57
|
: AsyncWorker(Napi::Function::New(deferred.Promise().Env(), [](const Napi::CallbackInfo &info) {})),
|
|
55
58
|
deferred(deferred),
|
|
56
59
|
cClient(cClient),
|
|
57
|
-
consumerConfig(consumerConfig)
|
|
60
|
+
consumerConfig(consumerConfig),
|
|
61
|
+
consumerWrapper(consumerWrapper) {}
|
|
58
62
|
~ConsumerNewInstanceWorker() {}
|
|
59
63
|
void Execute() {
|
|
60
64
|
const std::string &topic = this->consumerConfig->GetTopic();
|
|
@@ -75,20 +79,29 @@ class ConsumerNewInstanceWorker : public Napi::AsyncWorker {
|
|
|
75
79
|
SetError(msg);
|
|
76
80
|
return;
|
|
77
81
|
}
|
|
82
|
+
int32_t nAckRedeliverTimeoutMs = this->consumerConfig->GetNAckRedeliverTimeoutMs();
|
|
83
|
+
if (nAckRedeliverTimeoutMs < 0) {
|
|
84
|
+
std::string msg("NAck timeout should be greater than or equal to zero");
|
|
85
|
+
SetError(msg);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
78
88
|
|
|
79
|
-
pulsar_result result =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
delete this->consumerConfig;
|
|
89
|
+
pulsar_result result = pulsar_client_subscribe(this->cClient, topic.c_str(), subscription.c_str(),
|
|
90
|
+
this->consumerConfig->GetCConsumerConfig(),
|
|
91
|
+
&this->consumerWrapper->cConsumer);
|
|
83
92
|
if (result != pulsar_result_Ok) {
|
|
84
93
|
SetError(std::string("Failed to create consumer: ") + pulsar_result_str(result));
|
|
85
|
-
|
|
94
|
+
} else {
|
|
95
|
+
this->listener = this->consumerConfig->GetListenerCallback();
|
|
86
96
|
}
|
|
97
|
+
|
|
98
|
+
delete this->consumerConfig;
|
|
87
99
|
}
|
|
88
100
|
void OnOK() {
|
|
89
101
|
Napi::Object obj = Consumer::constructor.New({});
|
|
90
102
|
Consumer *consumer = Consumer::Unwrap(obj);
|
|
91
|
-
consumer->SetCConsumer(this->
|
|
103
|
+
consumer->SetCConsumer(this->consumerWrapper);
|
|
104
|
+
consumer->SetListenerCallback(this->listener);
|
|
92
105
|
this->deferred.Resolve(obj);
|
|
93
106
|
}
|
|
94
107
|
void OnError(const Napi::Error &e) { this->deferred.Reject(Napi::Error::New(Env(), e.Message()).Value()); }
|
|
@@ -96,15 +109,19 @@ class ConsumerNewInstanceWorker : public Napi::AsyncWorker {
|
|
|
96
109
|
private:
|
|
97
110
|
Napi::Promise::Deferred deferred;
|
|
98
111
|
pulsar_client_t *cClient;
|
|
99
|
-
ConsumerConfig *consumerConfig;
|
|
100
112
|
pulsar_consumer_t *cConsumer;
|
|
113
|
+
ConsumerConfig *consumerConfig;
|
|
114
|
+
ListenerCallback *listener;
|
|
115
|
+
std::shared_ptr<CConsumerWrapper> consumerWrapper;
|
|
101
116
|
};
|
|
102
117
|
|
|
103
118
|
Napi::Value Consumer::NewInstance(const Napi::CallbackInfo &info, pulsar_client_t *cClient) {
|
|
104
119
|
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
|
|
105
120
|
Napi::Object config = info[0].As<Napi::Object>();
|
|
106
|
-
|
|
107
|
-
|
|
121
|
+
std::shared_ptr<CConsumerWrapper> consumerWrapper = std::make_shared<CConsumerWrapper>();
|
|
122
|
+
ConsumerConfig *consumerConfig = new ConsumerConfig(config, consumerWrapper);
|
|
123
|
+
ConsumerNewInstanceWorker *wk =
|
|
124
|
+
new ConsumerNewInstanceWorker(deferred, cClient, consumerConfig, consumerWrapper);
|
|
108
125
|
wk->Queue();
|
|
109
126
|
return deferred.Promise();
|
|
110
127
|
}
|
|
@@ -146,11 +163,12 @@ class ConsumerReceiveWorker : public Napi::AsyncWorker {
|
|
|
146
163
|
Napi::Value Consumer::Receive(const Napi::CallbackInfo &info) {
|
|
147
164
|
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
|
|
148
165
|
if (info[0].IsUndefined()) {
|
|
149
|
-
ConsumerReceiveWorker *wk = new ConsumerReceiveWorker(deferred, this->cConsumer);
|
|
166
|
+
ConsumerReceiveWorker *wk = new ConsumerReceiveWorker(deferred, this->wrapper->cConsumer);
|
|
150
167
|
wk->Queue();
|
|
151
168
|
} else {
|
|
152
169
|
Napi::Number timeout = info[0].As<Napi::Object>().ToNumber();
|
|
153
|
-
ConsumerReceiveWorker *wk =
|
|
170
|
+
ConsumerReceiveWorker *wk =
|
|
171
|
+
new ConsumerReceiveWorker(deferred, this->wrapper->cConsumer, timeout.Int64Value());
|
|
154
172
|
wk->Queue();
|
|
155
173
|
}
|
|
156
174
|
return deferred.Promise();
|
|
@@ -159,25 +177,38 @@ Napi::Value Consumer::Receive(const Napi::CallbackInfo &info) {
|
|
|
159
177
|
void Consumer::Acknowledge(const Napi::CallbackInfo &info) {
|
|
160
178
|
Napi::Object obj = info[0].As<Napi::Object>();
|
|
161
179
|
Message *msg = Message::Unwrap(obj);
|
|
162
|
-
pulsar_consumer_acknowledge_async(this->cConsumer, msg->GetCMessage(), NULL, NULL);
|
|
180
|
+
pulsar_consumer_acknowledge_async(this->wrapper->cConsumer, msg->GetCMessage(), NULL, NULL);
|
|
163
181
|
}
|
|
164
182
|
|
|
165
183
|
void Consumer::AcknowledgeId(const Napi::CallbackInfo &info) {
|
|
166
184
|
Napi::Object obj = info[0].As<Napi::Object>();
|
|
167
185
|
MessageId *msgId = MessageId::Unwrap(obj);
|
|
168
|
-
pulsar_consumer_acknowledge_async_id(this->cConsumer, msgId->GetCMessageId(), NULL, NULL);
|
|
186
|
+
pulsar_consumer_acknowledge_async_id(this->wrapper->cConsumer, msgId->GetCMessageId(), NULL, NULL);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
void Consumer::NegativeAcknowledge(const Napi::CallbackInfo &info) {
|
|
190
|
+
Napi::Object obj = info[0].As<Napi::Object>();
|
|
191
|
+
Message *msg = Message::Unwrap(obj);
|
|
192
|
+
pulsar_consumer_negative_acknowledge(this->wrapper->cConsumer, msg->GetCMessage());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
void Consumer::NegativeAcknowledgeId(const Napi::CallbackInfo &info) {
|
|
196
|
+
Napi::Object obj = info[0].As<Napi::Object>();
|
|
197
|
+
MessageId *msgId = MessageId::Unwrap(obj);
|
|
198
|
+
pulsar_consumer_negative_acknowledge_id(this->wrapper->cConsumer, msgId->GetCMessageId());
|
|
169
199
|
}
|
|
170
200
|
|
|
171
201
|
void Consumer::AcknowledgeCumulative(const Napi::CallbackInfo &info) {
|
|
172
202
|
Napi::Object obj = info[0].As<Napi::Object>();
|
|
173
203
|
Message *msg = Message::Unwrap(obj);
|
|
174
|
-
pulsar_consumer_acknowledge_cumulative_async(this->cConsumer, msg->GetCMessage(), NULL, NULL);
|
|
204
|
+
pulsar_consumer_acknowledge_cumulative_async(this->wrapper->cConsumer, msg->GetCMessage(), NULL, NULL);
|
|
175
205
|
}
|
|
176
206
|
|
|
177
207
|
void Consumer::AcknowledgeCumulativeId(const Napi::CallbackInfo &info) {
|
|
178
208
|
Napi::Object obj = info[0].As<Napi::Object>();
|
|
179
209
|
MessageId *msgId = MessageId::Unwrap(obj);
|
|
180
|
-
pulsar_consumer_acknowledge_cumulative_async_id(this->cConsumer, msgId->GetCMessageId(), NULL,
|
|
210
|
+
pulsar_consumer_acknowledge_cumulative_async_id(this->wrapper->cConsumer, msgId->GetCMessageId(), NULL,
|
|
211
|
+
NULL);
|
|
181
212
|
}
|
|
182
213
|
|
|
183
214
|
class ConsumerCloseWorker : public Napi::AsyncWorker {
|
|
@@ -188,6 +219,7 @@ class ConsumerCloseWorker : public Napi::AsyncWorker {
|
|
|
188
219
|
cConsumer(cConsumer) {}
|
|
189
220
|
~ConsumerCloseWorker() {}
|
|
190
221
|
void Execute() {
|
|
222
|
+
pulsar_consumer_pause_message_listener(this->cConsumer);
|
|
191
223
|
pulsar_result result = pulsar_consumer_close(this->cConsumer);
|
|
192
224
|
if (result != pulsar_result_Ok) SetError(pulsar_result_str(result));
|
|
193
225
|
}
|
|
@@ -204,9 +236,14 @@ class ConsumerCloseWorker : public Napi::AsyncWorker {
|
|
|
204
236
|
|
|
205
237
|
Napi::Value Consumer::Close(const Napi::CallbackInfo &info) {
|
|
206
238
|
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
|
|
207
|
-
ConsumerCloseWorker *wk = new ConsumerCloseWorker(deferred, this->cConsumer);
|
|
239
|
+
ConsumerCloseWorker *wk = new ConsumerCloseWorker(deferred, this->wrapper->cConsumer);
|
|
208
240
|
wk->Queue();
|
|
209
241
|
return deferred.Promise();
|
|
210
242
|
}
|
|
211
243
|
|
|
212
|
-
Consumer::~Consumer() {
|
|
244
|
+
Consumer::~Consumer() {
|
|
245
|
+
if (this->listener) {
|
|
246
|
+
pulsar_consumer_pause_message_listener(this->wrapper->cConsumer);
|
|
247
|
+
this->listener->callback.Release();
|
|
248
|
+
}
|
|
249
|
+
}
|
package/src/Consumer.h
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
#include <napi.h>
|
|
24
24
|
#include <pulsar/c/client.h>
|
|
25
|
+
#include "ConsumerConfig.h"
|
|
25
26
|
|
|
26
27
|
class Consumer : public Napi::ObjectWrap<Consumer> {
|
|
27
28
|
public:
|
|
@@ -30,14 +31,18 @@ class Consumer : public Napi::ObjectWrap<Consumer> {
|
|
|
30
31
|
static Napi::FunctionReference constructor;
|
|
31
32
|
Consumer(const Napi::CallbackInfo &info);
|
|
32
33
|
~Consumer();
|
|
33
|
-
void SetCConsumer(
|
|
34
|
+
void SetCConsumer(std::shared_ptr<CConsumerWrapper> cConsumer);
|
|
35
|
+
void SetListenerCallback(ListenerCallback *listener);
|
|
34
36
|
|
|
35
37
|
private:
|
|
36
|
-
|
|
38
|
+
std::shared_ptr<CConsumerWrapper> wrapper;
|
|
39
|
+
ListenerCallback *listener;
|
|
37
40
|
|
|
38
41
|
Napi::Value Receive(const Napi::CallbackInfo &info);
|
|
39
42
|
void Acknowledge(const Napi::CallbackInfo &info);
|
|
40
43
|
void AcknowledgeId(const Napi::CallbackInfo &info);
|
|
44
|
+
void NegativeAcknowledge(const Napi::CallbackInfo &info);
|
|
45
|
+
void NegativeAcknowledgeId(const Napi::CallbackInfo &info);
|
|
41
46
|
void AcknowledgeCumulative(const Napi::CallbackInfo &info);
|
|
42
47
|
void AcknowledgeCumulativeId(const Napi::CallbackInfo &info);
|
|
43
48
|
Napi::Value Close(const Napi::CallbackInfo &info);
|
package/src/ConsumerConfig.cc
CHANGED
|
@@ -18,25 +18,65 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
#include "ConsumerConfig.h"
|
|
21
|
+
#include "Consumer.h"
|
|
22
|
+
#include "Message.h"
|
|
21
23
|
#include <pulsar/c/consumer_configuration.h>
|
|
24
|
+
#include <pulsar/c/consumer.h>
|
|
22
25
|
#include <map>
|
|
23
26
|
|
|
24
27
|
static const std::string CFG_TOPIC = "topic";
|
|
25
28
|
static const std::string CFG_SUBSCRIPTION = "subscription";
|
|
26
29
|
static const std::string CFG_SUBSCRIPTION_TYPE = "subscriptionType";
|
|
30
|
+
static const std::string CFG_INIT_POSITION = "subscriptionInitialPosition";
|
|
27
31
|
static const std::string CFG_ACK_TIMEOUT = "ackTimeoutMs";
|
|
32
|
+
static const std::string CFG_NACK_REDELIVER_TIMEOUT = "nAckRedeliverTimeoutMs";
|
|
28
33
|
static const std::string CFG_RECV_QUEUE = "receiverQueueSize";
|
|
29
34
|
static const std::string CFG_RECV_QUEUE_ACROSS_PARTITIONS = "receiverQueueSizeAcrossPartitions";
|
|
30
35
|
static const std::string CFG_CONSUMER_NAME = "consumerName";
|
|
31
36
|
static const std::string CFG_PROPS = "properties";
|
|
37
|
+
static const std::string CFG_LISTENER = "listener";
|
|
32
38
|
|
|
33
39
|
static const std::map<std::string, pulsar_consumer_type> SUBSCRIPTION_TYPE = {
|
|
34
40
|
{"Exclusive", pulsar_ConsumerExclusive},
|
|
35
41
|
{"Shared", pulsar_ConsumerShared},
|
|
36
42
|
{"Failover", pulsar_ConsumerFailover}};
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
static const std::map<std::string, initial_position> INIT_POSITION = {
|
|
45
|
+
{"Latest", initial_position_latest}, {"Earliest", initial_position_earliest}};
|
|
46
|
+
|
|
47
|
+
struct MessageListenerProxyData {
|
|
48
|
+
std::shared_ptr<CConsumerWrapper> consumerWrapper;
|
|
49
|
+
pulsar_message_t *cMessage;
|
|
50
|
+
|
|
51
|
+
MessageListenerProxyData(std::shared_ptr<CConsumerWrapper> consumerWrapper, pulsar_message_t *cMessage)
|
|
52
|
+
: consumerWrapper(consumerWrapper), cMessage(cMessage) {}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
void MessageListenerProxy(Napi::Env env, Napi::Function jsCallback, MessageListenerProxyData *data) {
|
|
56
|
+
Napi::Object msg = Message::NewInstance({}, data->cMessage);
|
|
57
|
+
Napi::Object consumerObj = Consumer::constructor.New({});
|
|
58
|
+
Consumer *consumer = Consumer::Unwrap(consumerObj);
|
|
59
|
+
consumer->SetCConsumer(std::move(data->consumerWrapper));
|
|
60
|
+
delete data;
|
|
61
|
+
jsCallback.Call({msg, consumerObj});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void MessageListener(pulsar_consumer_t *cConsumer, pulsar_message_t *cMessage, void *ctx) {
|
|
65
|
+
ListenerCallback *listenerCallback = (ListenerCallback *)ctx;
|
|
66
|
+
if (listenerCallback->callback.Acquire() != napi_ok) {
|
|
67
|
+
return;
|
|
68
|
+
};
|
|
69
|
+
MessageListenerProxyData *dataPtr =
|
|
70
|
+
new MessageListenerProxyData(listenerCallback->consumerWrapper, cMessage);
|
|
71
|
+
listenerCallback->callback.BlockingCall(dataPtr, MessageListenerProxy);
|
|
72
|
+
listenerCallback->callback.Release();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
void FinalizeListenerCallback(Napi::Env env, ListenerCallback *cb, void *) { delete cb; }
|
|
76
|
+
|
|
77
|
+
ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig,
|
|
78
|
+
std::shared_ptr<CConsumerWrapper> consumerWrapper)
|
|
79
|
+
: topic(""), subscription(""), ackTimeoutMs(0), nAckRedeliverTimeoutMs(60000), listener(nullptr) {
|
|
40
80
|
this->cConsumerConfig = pulsar_consumer_configuration_create();
|
|
41
81
|
|
|
42
82
|
if (consumerConfig.Has(CFG_TOPIC) && consumerConfig.Get(CFG_TOPIC).IsString()) {
|
|
@@ -55,6 +95,14 @@ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig)
|
|
|
55
95
|
}
|
|
56
96
|
}
|
|
57
97
|
|
|
98
|
+
if (consumerConfig.Has(CFG_INIT_POSITION) && consumerConfig.Get(CFG_INIT_POSITION).IsString()) {
|
|
99
|
+
std::string initPosition = consumerConfig.Get(CFG_INIT_POSITION).ToString().Utf8Value();
|
|
100
|
+
if (INIT_POSITION.count(initPosition)) {
|
|
101
|
+
pulsar_consumer_set_subscription_initial_position(this->cConsumerConfig,
|
|
102
|
+
INIT_POSITION.at(initPosition));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
58
106
|
if (consumerConfig.Has(CFG_CONSUMER_NAME) && consumerConfig.Get(CFG_CONSUMER_NAME).IsString()) {
|
|
59
107
|
std::string consumerName = consumerConfig.Get(CFG_CONSUMER_NAME).ToString().Utf8Value();
|
|
60
108
|
if (!consumerName.empty()) pulsar_consumer_set_consumer_name(this->cConsumerConfig, consumerName.c_str());
|
|
@@ -67,6 +115,15 @@ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig)
|
|
|
67
115
|
}
|
|
68
116
|
}
|
|
69
117
|
|
|
118
|
+
if (consumerConfig.Has(CFG_NACK_REDELIVER_TIMEOUT) &&
|
|
119
|
+
consumerConfig.Get(CFG_NACK_REDELIVER_TIMEOUT).IsNumber()) {
|
|
120
|
+
this->nAckRedeliverTimeoutMs = consumerConfig.Get(CFG_NACK_REDELIVER_TIMEOUT).ToNumber().Int64Value();
|
|
121
|
+
if (this->nAckRedeliverTimeoutMs >= 0) {
|
|
122
|
+
pulsar_configure_set_negative_ack_redelivery_delay_ms(this->cConsumerConfig,
|
|
123
|
+
this->nAckRedeliverTimeoutMs);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
70
127
|
if (consumerConfig.Has(CFG_RECV_QUEUE) && consumerConfig.Get(CFG_RECV_QUEUE).IsNumber()) {
|
|
71
128
|
int32_t receiverQueueSize = consumerConfig.Get(CFG_RECV_QUEUE).ToNumber().Int32Value();
|
|
72
129
|
if (receiverQueueSize >= 0) {
|
|
@@ -94,12 +151,43 @@ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig)
|
|
|
94
151
|
pulsar_consumer_configuration_set_property(this->cConsumerConfig, key.c_str(), value.c_str());
|
|
95
152
|
}
|
|
96
153
|
}
|
|
154
|
+
|
|
155
|
+
if (consumerConfig.Has(CFG_LISTENER) && consumerConfig.Get(CFG_LISTENER).IsFunction()) {
|
|
156
|
+
this->listener = new ListenerCallback();
|
|
157
|
+
this->listener->consumerWrapper = consumerWrapper;
|
|
158
|
+
Napi::ThreadSafeFunction callback = Napi::ThreadSafeFunction::New(
|
|
159
|
+
consumerConfig.Env(), consumerConfig.Get(CFG_LISTENER).As<Napi::Function>(), "Listener Callback", 1,
|
|
160
|
+
1, (void *)NULL, FinalizeListenerCallback, listener);
|
|
161
|
+
this->listener->callback = std::move(callback);
|
|
162
|
+
pulsar_consumer_configuration_set_message_listener(this->cConsumerConfig, &MessageListener,
|
|
163
|
+
this->listener);
|
|
164
|
+
}
|
|
97
165
|
}
|
|
98
166
|
|
|
99
|
-
ConsumerConfig::~ConsumerConfig() {
|
|
167
|
+
ConsumerConfig::~ConsumerConfig() {
|
|
168
|
+
pulsar_consumer_configuration_free(this->cConsumerConfig);
|
|
169
|
+
if (this->listener) {
|
|
170
|
+
this->listener->callback.Release();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
100
173
|
|
|
101
174
|
pulsar_consumer_configuration_t *ConsumerConfig::GetCConsumerConfig() { return this->cConsumerConfig; }
|
|
102
175
|
|
|
103
176
|
std::string ConsumerConfig::GetTopic() { return this->topic; }
|
|
104
177
|
std::string ConsumerConfig::GetSubscription() { return this->subscription; }
|
|
178
|
+
ListenerCallback *ConsumerConfig::GetListenerCallback() {
|
|
179
|
+
ListenerCallback *cb = this->listener;
|
|
180
|
+
this->listener = nullptr;
|
|
181
|
+
return cb;
|
|
182
|
+
}
|
|
183
|
+
|
|
105
184
|
int64_t ConsumerConfig::GetAckTimeoutMs() { return this->ackTimeoutMs; }
|
|
185
|
+
int64_t ConsumerConfig::GetNAckRedeliverTimeoutMs() { return this->nAckRedeliverTimeoutMs; }
|
|
186
|
+
|
|
187
|
+
CConsumerWrapper::CConsumerWrapper() : cConsumer(nullptr) {}
|
|
188
|
+
|
|
189
|
+
CConsumerWrapper::~CConsumerWrapper() {
|
|
190
|
+
if (this->cConsumer) {
|
|
191
|
+
pulsar_consumer_free(this->cConsumer);
|
|
192
|
+
}
|
|
193
|
+
}
|
package/src/ConsumerConfig.h
CHANGED
|
@@ -25,20 +25,35 @@
|
|
|
25
25
|
|
|
26
26
|
#define MIN_ACK_TIMEOUT_MILLIS 10000
|
|
27
27
|
|
|
28
|
+
struct CConsumerWrapper {
|
|
29
|
+
pulsar_consumer_t *cConsumer;
|
|
30
|
+
CConsumerWrapper();
|
|
31
|
+
~CConsumerWrapper();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
struct ListenerCallback {
|
|
35
|
+
Napi::ThreadSafeFunction callback;
|
|
36
|
+
std::shared_ptr<CConsumerWrapper> consumerWrapper;
|
|
37
|
+
};
|
|
38
|
+
|
|
28
39
|
class ConsumerConfig {
|
|
29
40
|
public:
|
|
30
|
-
ConsumerConfig(const Napi::Object &consumerConfig);
|
|
41
|
+
ConsumerConfig(const Napi::Object &consumerConfig, std::shared_ptr<CConsumerWrapper> consumerWrapper);
|
|
31
42
|
~ConsumerConfig();
|
|
32
43
|
pulsar_consumer_configuration_t *GetCConsumerConfig();
|
|
33
44
|
std::string GetTopic();
|
|
34
45
|
std::string GetSubscription();
|
|
35
46
|
int64_t GetAckTimeoutMs();
|
|
47
|
+
int64_t GetNAckRedeliverTimeoutMs();
|
|
48
|
+
ListenerCallback *GetListenerCallback();
|
|
36
49
|
|
|
37
50
|
private:
|
|
38
51
|
pulsar_consumer_configuration_t *cConsumerConfig;
|
|
39
52
|
std::string topic;
|
|
40
53
|
std::string subscription;
|
|
41
54
|
int64_t ackTimeoutMs;
|
|
55
|
+
int64_t nAckRedeliverTimeoutMs;
|
|
56
|
+
ListenerCallback *listener;
|
|
42
57
|
};
|
|
43
58
|
|
|
44
59
|
#endif
|
package/src/Message.cc
CHANGED
|
@@ -181,9 +181,7 @@ pulsar_message_t *Message::BuildMessage(Napi::Object conf) {
|
|
|
181
181
|
for (int i = 0; i < length; i++) {
|
|
182
182
|
SetString(arr, clusters.Get(i).ToString().Utf8Value().c_str(), i);
|
|
183
183
|
}
|
|
184
|
-
|
|
185
|
-
// pulsar_message_set_replication_clusters (#3729) is released
|
|
186
|
-
// pulsar_message_set_replication_clusters(cMessage, (const char **)arr, length);
|
|
184
|
+
pulsar_message_set_replication_clusters(cMessage, (const char **)arr, length);
|
|
187
185
|
FreeStringArray(arr, length);
|
|
188
186
|
}
|
|
189
187
|
}
|
package/src/MessageId.cc
CHANGED
|
@@ -29,7 +29,7 @@ Napi::Object MessageId::Init(Napi::Env env, Napi::Object exports) {
|
|
|
29
29
|
Napi::Function func = DefineClass(env, "MessageId",
|
|
30
30
|
{StaticMethod("earliest", &MessageId::Earliest, napi_static),
|
|
31
31
|
StaticMethod("latest", &MessageId::Latest, napi_static),
|
|
32
|
-
StaticMethod("finalize", &MessageId::
|
|
32
|
+
StaticMethod("finalize", &MessageId::Free, napi_static),
|
|
33
33
|
InstanceMethod("serialize", &MessageId::Serialize),
|
|
34
34
|
StaticMethod("deserialize", &MessageId::Deserialize, napi_static),
|
|
35
35
|
InstanceMethod("toString", &MessageId::ToString)});
|
|
@@ -58,7 +58,7 @@ Napi::Object MessageId::NewInstance(Napi::Value arg) {
|
|
|
58
58
|
return obj;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
void MessageId::
|
|
61
|
+
void MessageId::Free(const Napi::CallbackInfo &info) {
|
|
62
62
|
Napi::Object obj = info[0].As<Napi::Object>();
|
|
63
63
|
MessageId *msgId = Unwrap(obj);
|
|
64
64
|
pulsar_message_id_free(msgId->cMessageId);
|
package/src/MessageId.h
CHANGED
|
@@ -33,7 +33,7 @@ class MessageId : public Napi::ObjectWrap<MessageId> {
|
|
|
33
33
|
static Napi::Value Latest(const Napi::CallbackInfo &info);
|
|
34
34
|
Napi::Value Serialize(const Napi::CallbackInfo &info);
|
|
35
35
|
static Napi::Value Deserialize(const Napi::CallbackInfo &info);
|
|
36
|
-
static void
|
|
36
|
+
static void Free(const Napi::CallbackInfo &info);
|
|
37
37
|
MessageId(const Napi::CallbackInfo &info);
|
|
38
38
|
~MessageId();
|
|
39
39
|
pulsar_message_id_t *GetCMessageId();
|
package/tests/consumer.test.js
CHANGED
|
@@ -30,6 +30,7 @@ const Pulsar = require('../index.js');
|
|
|
30
30
|
await expect(client.subscribe({
|
|
31
31
|
subscription: 'sub1',
|
|
32
32
|
ackTimeoutMs: 10000,
|
|
33
|
+
nAckRedeliverTimeoutMs: 60000,
|
|
33
34
|
})).rejects.toThrow('Topic is required and must be specified as a string when creating consumer');
|
|
34
35
|
});
|
|
35
36
|
|
|
@@ -38,6 +39,7 @@ const Pulsar = require('../index.js');
|
|
|
38
39
|
topic: 0,
|
|
39
40
|
subscription: 'sub1',
|
|
40
41
|
ackTimeoutMs: 10000,
|
|
42
|
+
nAckRedeliverTimeoutMs: 60000,
|
|
41
43
|
})).rejects.toThrow('Topic is required and must be specified as a string when creating consumer');
|
|
42
44
|
});
|
|
43
45
|
|
|
@@ -45,6 +47,7 @@ const Pulsar = require('../index.js');
|
|
|
45
47
|
await expect(client.subscribe({
|
|
46
48
|
topic: 'persistent://public/default/t1',
|
|
47
49
|
ackTimeoutMs: 10000,
|
|
50
|
+
nAckRedeliverTimeoutMs: 60000,
|
|
48
51
|
})).rejects.toThrow('Subscription is required and must be specified as a string when creating consumer');
|
|
49
52
|
});
|
|
50
53
|
|
|
@@ -53,6 +56,7 @@ const Pulsar = require('../index.js');
|
|
|
53
56
|
topic: 'persistent://public/default/t1',
|
|
54
57
|
subscription: 0,
|
|
55
58
|
ackTimeoutMs: 10000,
|
|
59
|
+
nAckRedeliverTimeoutMs: 60000,
|
|
56
60
|
})).rejects.toThrow('Subscription is required and must be specified as a string when creating consumer');
|
|
57
61
|
});
|
|
58
62
|
|
|
@@ -61,6 +65,7 @@ const Pulsar = require('../index.js');
|
|
|
61
65
|
topic: 'persistent://no-tenant/namespace/topic',
|
|
62
66
|
subscription: 'sub1',
|
|
63
67
|
ackTimeoutMs: 10000,
|
|
68
|
+
nAckRedeliverTimeoutMs: 60000,
|
|
64
69
|
})).rejects.toThrow('Failed to create consumer: ConnectError');
|
|
65
70
|
});
|
|
66
71
|
|
|
@@ -69,8 +74,18 @@ const Pulsar = require('../index.js');
|
|
|
69
74
|
topic: 'persistent://public/no-namespace/topic',
|
|
70
75
|
subscription: 'sub1',
|
|
71
76
|
ackTimeoutMs: 10000,
|
|
77
|
+
nAckRedeliverTimeoutMs: 60000,
|
|
72
78
|
})).rejects.toThrow('Failed to create consumer: ConnectError');
|
|
73
79
|
});
|
|
80
|
+
|
|
81
|
+
test('Not Positive NAckRedeliverTimeout', async () => {
|
|
82
|
+
await expect(client.subscribe({
|
|
83
|
+
topic: 'persistent://public/default/t1',
|
|
84
|
+
subscription: 'sub1',
|
|
85
|
+
ackTimeoutMs: 10000,
|
|
86
|
+
nAckRedeliverTimeoutMs: -12,
|
|
87
|
+
})).rejects.toThrow('NAck timeout should be greater than or equal to zero');
|
|
88
|
+
});
|
|
74
89
|
});
|
|
75
90
|
});
|
|
76
91
|
})();
|
package/tests/end_to_end.test.js
CHANGED
|
@@ -67,6 +67,107 @@ const Pulsar = require('../index.js');
|
|
|
67
67
|
await client.close();
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
test('negativeAcknowledge', async () => {
|
|
71
|
+
const client = new Pulsar.Client({
|
|
72
|
+
serviceUrl: 'pulsar://localhost:6650',
|
|
73
|
+
operationTimeoutSeconds: 30,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const topic = 'persistent://public/default/produce-consume';
|
|
77
|
+
const producer = await client.createProducer({
|
|
78
|
+
topic,
|
|
79
|
+
sendTimeoutMs: 30000,
|
|
80
|
+
batchingEnabled: true,
|
|
81
|
+
});
|
|
82
|
+
expect(producer).not.toBeNull();
|
|
83
|
+
|
|
84
|
+
const consumer = await client.subscribe({
|
|
85
|
+
topic,
|
|
86
|
+
subscription: 'sub1',
|
|
87
|
+
ackTimeoutMs: 10000,
|
|
88
|
+
nAckRedeliverTimeoutMs: 1000,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(consumer).not.toBeNull();
|
|
92
|
+
|
|
93
|
+
const message = 'my-message';
|
|
94
|
+
producer.send({
|
|
95
|
+
data: Buffer.from(message),
|
|
96
|
+
});
|
|
97
|
+
await producer.flush();
|
|
98
|
+
|
|
99
|
+
const results = [];
|
|
100
|
+
const msg = await consumer.receive();
|
|
101
|
+
results.push(msg.getData().toString());
|
|
102
|
+
consumer.negativeAcknowledge(msg);
|
|
103
|
+
|
|
104
|
+
const msg2 = await consumer.receive();
|
|
105
|
+
results.push(msg2.getData().toString());
|
|
106
|
+
consumer.acknowledge(msg2);
|
|
107
|
+
|
|
108
|
+
await expect(consumer.receive(1000)).rejects.toThrow(
|
|
109
|
+
'Failed to received message TimeOut',
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
expect(results).toEqual([message, message]);
|
|
113
|
+
|
|
114
|
+
await producer.close();
|
|
115
|
+
await consumer.close();
|
|
116
|
+
await client.close();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('Produce/Consume Listener', async () => {
|
|
120
|
+
const client = new Pulsar.Client({
|
|
121
|
+
serviceUrl: 'pulsar://localhost:6650',
|
|
122
|
+
operationTimeoutSeconds: 30,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const topic = 'persistent://public/default/produce-consume-listener';
|
|
126
|
+
const producer = await client.createProducer({
|
|
127
|
+
topic,
|
|
128
|
+
sendTimeoutMs: 30000,
|
|
129
|
+
batchingEnabled: true,
|
|
130
|
+
});
|
|
131
|
+
expect(producer).not.toBeNull();
|
|
132
|
+
|
|
133
|
+
let finish;
|
|
134
|
+
const results = [];
|
|
135
|
+
const finishPromise = new Promise((resolve) => {
|
|
136
|
+
finish = resolve;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const consumer = await client.subscribe({
|
|
140
|
+
topic,
|
|
141
|
+
subscription: 'sub1',
|
|
142
|
+
ackTimeoutMs: 10000,
|
|
143
|
+
listener: (message, messageConsumer) => {
|
|
144
|
+
const data = message.getData().toString();
|
|
145
|
+
results.push(data);
|
|
146
|
+
messageConsumer.acknowledge(message);
|
|
147
|
+
if (results.length === 10) finish();
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(consumer).not.toBeNull();
|
|
152
|
+
|
|
153
|
+
const messages = [];
|
|
154
|
+
for (let i = 0; i < 10; i += 1) {
|
|
155
|
+
const msg = `my-message-${i}`;
|
|
156
|
+
producer.send({
|
|
157
|
+
data: Buffer.from(msg),
|
|
158
|
+
});
|
|
159
|
+
messages.push(msg);
|
|
160
|
+
}
|
|
161
|
+
await producer.flush();
|
|
162
|
+
|
|
163
|
+
await finishPromise;
|
|
164
|
+
expect(lodash.difference(messages, results)).toEqual([]);
|
|
165
|
+
|
|
166
|
+
await producer.close();
|
|
167
|
+
await consumer.close();
|
|
168
|
+
await client.close();
|
|
169
|
+
});
|
|
170
|
+
|
|
70
171
|
test('acknowledgeCumulative', async () => {
|
|
71
172
|
const client = new Pulsar.Client({
|
|
72
173
|
serviceUrl: 'pulsar://localhost:6650',
|
|
@@ -105,13 +206,84 @@ const Pulsar = require('../index.js');
|
|
|
105
206
|
}
|
|
106
207
|
}
|
|
107
208
|
|
|
108
|
-
await expect(consumer.receive(1000)).rejects.toThrow(
|
|
209
|
+
await expect(consumer.receive(1000)).rejects.toThrow(
|
|
210
|
+
'Failed to received message TimeOut',
|
|
211
|
+
);
|
|
109
212
|
|
|
110
213
|
await producer.close();
|
|
111
214
|
await consumer.close();
|
|
112
215
|
await client.close();
|
|
113
216
|
});
|
|
114
217
|
|
|
218
|
+
test('subscriptionInitialPosition', async () => {
|
|
219
|
+
const client = new Pulsar.Client({
|
|
220
|
+
serviceUrl: 'pulsar://localhost:6650',
|
|
221
|
+
operationTimeoutSeconds: 30,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const topic = 'persistent://public/default/subscriptionInitialPosition';
|
|
225
|
+
const producer = await client.createProducer({
|
|
226
|
+
topic,
|
|
227
|
+
sendTimeoutMs: 30000,
|
|
228
|
+
batchingEnabled: false,
|
|
229
|
+
});
|
|
230
|
+
expect(producer).not.toBeNull();
|
|
231
|
+
|
|
232
|
+
const messages = [];
|
|
233
|
+
for (let i = 0; i < 2; i += 1) {
|
|
234
|
+
const msg = `my-message-${i}`;
|
|
235
|
+
producer.send({
|
|
236
|
+
data: Buffer.from(msg),
|
|
237
|
+
});
|
|
238
|
+
messages.push(msg);
|
|
239
|
+
}
|
|
240
|
+
await producer.flush();
|
|
241
|
+
|
|
242
|
+
const latestConsumer = await client.subscribe({
|
|
243
|
+
topic,
|
|
244
|
+
subscription: 'latestSub',
|
|
245
|
+
subscriptionInitialPosition: 'Latest',
|
|
246
|
+
});
|
|
247
|
+
expect(latestConsumer).not.toBeNull();
|
|
248
|
+
|
|
249
|
+
const earliestConsumer = await client.subscribe({
|
|
250
|
+
topic,
|
|
251
|
+
subscription: 'earliestSub',
|
|
252
|
+
subscriptionInitialPosition: 'Earliest',
|
|
253
|
+
});
|
|
254
|
+
expect(earliestConsumer).not.toBeNull();
|
|
255
|
+
|
|
256
|
+
for (let i = 2; i < 4; i += 1) {
|
|
257
|
+
const msg = `my-message-${i}`;
|
|
258
|
+
producer.send({
|
|
259
|
+
data: Buffer.from(msg),
|
|
260
|
+
});
|
|
261
|
+
messages.push(msg);
|
|
262
|
+
}
|
|
263
|
+
await producer.flush();
|
|
264
|
+
|
|
265
|
+
const latestResults = [];
|
|
266
|
+
const earliestResults = [];
|
|
267
|
+
for (let i = 0; i < 4; i += 1) {
|
|
268
|
+
if (i < 2) {
|
|
269
|
+
const latestMsg = await latestConsumer.receive(5000);
|
|
270
|
+
latestConsumer.acknowledge(latestMsg);
|
|
271
|
+
latestResults.push(latestMsg.getData().toString());
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const earliestMsg = await earliestConsumer.receive(5000);
|
|
275
|
+
earliestConsumer.acknowledge(earliestMsg);
|
|
276
|
+
earliestResults.push(earliestMsg.getData().toString());
|
|
277
|
+
}
|
|
278
|
+
expect(lodash.difference(messages, latestResults)).toEqual(['my-message-0', 'my-message-1']);
|
|
279
|
+
expect(lodash.difference(messages, earliestResults)).toEqual([]);
|
|
280
|
+
|
|
281
|
+
await producer.close();
|
|
282
|
+
await latestConsumer.close();
|
|
283
|
+
await earliestConsumer.close();
|
|
284
|
+
await client.close();
|
|
285
|
+
});
|
|
286
|
+
|
|
115
287
|
test('Produce/Read', async () => {
|
|
116
288
|
const client = new Pulsar.Client({
|
|
117
289
|
serviceUrl: 'pulsar://localhost:6650',
|