pulsar-client 1.1.0 → 1.2.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/.asf.yaml +46 -0
- package/.eslintrc.json +8 -0
- package/README.md +1 -0
- package/examples/consumer_listener.js +40 -0
- package/index.js +8 -0
- package/package.json +5 -4
- package/pulsar-version.txt +1 -1
- package/run-unit-tests.sh +1 -1
- package/src/Client.cc +45 -2
- package/src/Client.h +18 -0
- package/src/Consumer.cc +74 -9
- package/src/Consumer.h +3 -0
- package/src/ConsumerConfig.cc +12 -31
- package/src/ConsumerConfig.h +4 -13
- package/src/Message.cc +9 -0
- package/src/Message.h +1 -0
- package/src/MessageListener.h +39 -0
- package/src/ReaderConfig.cc +8 -0
- package/tests/conf/standalone.conf +2 -0
- package/tests/consumer.test.js +17 -0
- package/tests/end_to_end.test.js +46 -0
package/.asf.yaml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
|
4
|
+
# distributed with this work for additional information
|
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
|
7
|
+
# "License"); you may not use this file except in compliance
|
|
8
|
+
# with the License. You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
|
13
|
+
# software distributed under the License is distributed on an
|
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
# KIND, either express or implied. See the License for the
|
|
16
|
+
# specific language governing permissions and limitations
|
|
17
|
+
# under the License.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
github:
|
|
21
|
+
description: "Apache Pulsar NodeJS Client"
|
|
22
|
+
homepage: https://pulsar.apache.org/
|
|
23
|
+
labels:
|
|
24
|
+
- pulsar
|
|
25
|
+
- pubsub
|
|
26
|
+
- messaging
|
|
27
|
+
- streaming
|
|
28
|
+
- queuing
|
|
29
|
+
- event-streaming
|
|
30
|
+
- node
|
|
31
|
+
- javascript
|
|
32
|
+
- nodejs
|
|
33
|
+
features:
|
|
34
|
+
# Enable wiki for documentation
|
|
35
|
+
wiki: true
|
|
36
|
+
# Enable issues management
|
|
37
|
+
issues: true
|
|
38
|
+
# Enable projects for project management boards
|
|
39
|
+
projects: true
|
|
40
|
+
enabled_merge_buttons:
|
|
41
|
+
# enable squash button:
|
|
42
|
+
squash: true
|
|
43
|
+
# disable merge button:
|
|
44
|
+
merge: false
|
|
45
|
+
# disable rebase button:
|
|
46
|
+
rebase: false
|
package/.eslintrc.json
CHANGED
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ Compatibility between each version of the Node.js client and the C++ client is a
|
|
|
44
44
|
|----------------|----------------|
|
|
45
45
|
| 1.0.0 | 2.3.0 or later |
|
|
46
46
|
| 1.1.0 | 2.4.0 or later |
|
|
47
|
+
| 1.2.0 | 2.5.0 or later |
|
|
47
48
|
|
|
48
49
|
If an incompatible version of the C++ client is installed, you may fail to build or run this library.
|
|
49
50
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const Pulsar = require('pulsar-client');
|
|
21
|
+
|
|
22
|
+
(async () => {
|
|
23
|
+
// Create a client
|
|
24
|
+
const client = new Pulsar.Client({
|
|
25
|
+
serviceUrl: 'pulsar://localhost:6650',
|
|
26
|
+
operationTimeoutSeconds: 30,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Create a consumer with listener
|
|
30
|
+
const consumer = await client.subscribe({
|
|
31
|
+
topic: 'persistent://public/default/my-topic',
|
|
32
|
+
subscription: 'sub1',
|
|
33
|
+
subscriptionType: 'Shared',
|
|
34
|
+
ackTimeoutMs: 10000,
|
|
35
|
+
listener: (msg, msgConsumer) => {
|
|
36
|
+
console.log(msg.getData().toString());
|
|
37
|
+
msgConsumer.acknowledge(msg);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
})();
|
package/index.js
CHANGED
|
@@ -22,6 +22,13 @@ const AuthenticationTls = require('./src/AuthenticationTls.js');
|
|
|
22
22
|
const AuthenticationAthenz = require('./src/AuthenticationAthenz.js');
|
|
23
23
|
const AuthenticationToken = require('./src/AuthenticationToken.js');
|
|
24
24
|
|
|
25
|
+
const LogLevel = {
|
|
26
|
+
DEBUG: 0,
|
|
27
|
+
INFO: 1,
|
|
28
|
+
WARN: 2,
|
|
29
|
+
ERROR: 3,
|
|
30
|
+
};
|
|
31
|
+
|
|
25
32
|
const Pulsar = {
|
|
26
33
|
Client: PulsarBinding.Client,
|
|
27
34
|
Message: PulsarBinding.Message,
|
|
@@ -29,6 +36,7 @@ const Pulsar = {
|
|
|
29
36
|
AuthenticationTls,
|
|
30
37
|
AuthenticationAthenz,
|
|
31
38
|
AuthenticationToken,
|
|
39
|
+
LogLevel,
|
|
32
40
|
};
|
|
33
41
|
|
|
34
42
|
module.exports = Pulsar;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pulsar-client",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Pulsar Node.js client",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"install": "node-pre-gyp install --fallback-to-build",
|
|
12
12
|
"clean": "node-gyp clean",
|
|
13
13
|
"configure": "node-gyp configure",
|
|
14
|
-
"lint": "eslint --ext .js .",
|
|
15
|
-
"format": "clang-format
|
|
14
|
+
"lint": "clang-format-lint src/*.cc src/*.h && eslint --ext .js .",
|
|
15
|
+
"format": "clang-format-lint --fix src/*.cc src/*.h && eslint --fix --ext .js .",
|
|
16
16
|
"build": "npm run format && node-gyp rebuild",
|
|
17
17
|
"build:debug": "npm run format && node-gyp rebuild --debug",
|
|
18
18
|
"license:report": "mkdir -p report && grunt grunt-license-report",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"node": ">=10.16.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
+
"@seadub/clang-format-lint": "0.0.2",
|
|
33
34
|
"clang-format": "^1.2.4",
|
|
34
35
|
"commander": "^2.20.0",
|
|
35
36
|
"delay": "^4.3.0",
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"hdr-histogram-js": "^1.1.4",
|
|
42
43
|
"jest": "^24.8.0",
|
|
43
44
|
"license-check-and-add": "^2.3.6",
|
|
44
|
-
"lodash": "^4.17.
|
|
45
|
+
"lodash": "^4.17.19"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
48
|
"bindings": "^1.3.1",
|
package/pulsar-version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.5.0
|
package/run-unit-tests.sh
CHANGED
package/src/Client.cc
CHANGED
|
@@ -38,6 +38,7 @@ static const std::string CFG_TLS_TRUST_CERT = "tlsTrustCertsFilePath";
|
|
|
38
38
|
static const std::string CFG_TLS_VALIDATE_HOSTNAME = "tlsValidateHostname";
|
|
39
39
|
static const std::string CFG_TLS_ALLOW_INSECURE = "tlsAllowInsecureConnection";
|
|
40
40
|
static const std::string CFG_STATS_INTERVAL = "statsIntervalInSeconds";
|
|
41
|
+
static const std::string CFG_LOG = "log";
|
|
41
42
|
|
|
42
43
|
Napi::FunctionReference Client::constructor;
|
|
43
44
|
|
|
@@ -73,6 +74,17 @@ Client::Client(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Client>(info)
|
|
|
73
74
|
|
|
74
75
|
pulsar_client_configuration_t *cClientConfig = pulsar_client_configuration_create();
|
|
75
76
|
|
|
77
|
+
if (clientConfig.Has(CFG_LOG) && clientConfig.Get(CFG_LOG).IsFunction()) {
|
|
78
|
+
Napi::ThreadSafeFunction logFunction = Napi::ThreadSafeFunction::New(
|
|
79
|
+
env, clientConfig.Get(CFG_LOG).As<Napi::Function>(), "Pulsar Logging", 0, 1);
|
|
80
|
+
this->logCallback = new LogCallback();
|
|
81
|
+
this->logCallback->callback = logFunction;
|
|
82
|
+
|
|
83
|
+
pulsar_client_configuration_set_logger(cClientConfig, &LogMessage, this->logCallback);
|
|
84
|
+
} else {
|
|
85
|
+
this->logCallback = nullptr;
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
if (clientConfig.Has(CFG_AUTH) && clientConfig.Get(CFG_AUTH).IsObject()) {
|
|
77
89
|
Napi::Object obj = clientConfig.Get(CFG_AUTH).ToObject();
|
|
78
90
|
if (obj.Has(CFG_AUTH_PROP) && obj.Get(CFG_AUTH_PROP).IsObject()) {
|
|
@@ -139,14 +151,20 @@ Client::Client(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Client>(info)
|
|
|
139
151
|
|
|
140
152
|
this->cClient = pulsar_client_create(serviceUrl.Utf8Value().c_str(), cClientConfig);
|
|
141
153
|
pulsar_client_configuration_free(cClientConfig);
|
|
154
|
+
this->Ref();
|
|
142
155
|
}
|
|
143
156
|
|
|
144
|
-
Client::~Client() {
|
|
157
|
+
Client::~Client() {
|
|
158
|
+
pulsar_client_free(this->cClient);
|
|
159
|
+
if (this->logCallback != nullptr) {
|
|
160
|
+
this->logCallback->callback.Release();
|
|
161
|
+
this->logCallback = nullptr;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
145
164
|
|
|
146
165
|
Napi::Value Client::CreateProducer(const Napi::CallbackInfo &info) {
|
|
147
166
|
return Producer::NewInstance(info, this->cClient);
|
|
148
167
|
}
|
|
149
|
-
|
|
150
168
|
Napi::Value Client::Subscribe(const Napi::CallbackInfo &info) {
|
|
151
169
|
return Consumer::NewInstance(info, this->cClient);
|
|
152
170
|
}
|
|
@@ -155,6 +173,29 @@ Napi::Value Client::CreateReader(const Napi::CallbackInfo &info) {
|
|
|
155
173
|
return Reader::NewInstance(info, this->cClient);
|
|
156
174
|
}
|
|
157
175
|
|
|
176
|
+
void LogMessageProxy(Napi::Env env, Napi::Function jsCallback, struct LogMessage *logMessage) {
|
|
177
|
+
Napi::Number logLevel = Napi::Number::New(env, static_cast<double>(logMessage->level));
|
|
178
|
+
Napi::String file = Napi::String::New(env, logMessage->file);
|
|
179
|
+
Napi::Number line = Napi::Number::New(env, static_cast<double>(logMessage->line));
|
|
180
|
+
Napi::String message = Napi::String::New(env, logMessage->message);
|
|
181
|
+
|
|
182
|
+
delete logMessage;
|
|
183
|
+
jsCallback.Call({logLevel, file, line, message});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
void LogMessage(pulsar_logger_level_t level, const char *file, int line, const char *message, void *ctx) {
|
|
187
|
+
LogCallback *logCallback = (LogCallback *)ctx;
|
|
188
|
+
|
|
189
|
+
if (logCallback->callback.Acquire() != napi_ok) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
struct LogMessage *logMessage = new struct LogMessage(level, std::string(file), line, std::string(message));
|
|
194
|
+
|
|
195
|
+
logCallback->callback.BlockingCall(logMessage, LogMessageProxy);
|
|
196
|
+
logCallback->callback.Release();
|
|
197
|
+
}
|
|
198
|
+
|
|
158
199
|
class ClientCloseWorker : public Napi::AsyncWorker {
|
|
159
200
|
public:
|
|
160
201
|
ClientCloseWorker(const Napi::Promise::Deferred &deferred, pulsar_client_t *cClient)
|
|
@@ -178,6 +219,8 @@ class ClientCloseWorker : public Napi::AsyncWorker {
|
|
|
178
219
|
};
|
|
179
220
|
|
|
180
221
|
Napi::Value Client::Close(const Napi::CallbackInfo &info) {
|
|
222
|
+
this->Unref();
|
|
223
|
+
|
|
181
224
|
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
|
|
182
225
|
ClientCloseWorker *wk = new ClientCloseWorker(deferred, this->cClient);
|
|
183
226
|
wk->Queue();
|
package/src/Client.h
CHANGED
|
@@ -23,15 +23,33 @@
|
|
|
23
23
|
#include <napi.h>
|
|
24
24
|
#include <pulsar/c/client.h>
|
|
25
25
|
|
|
26
|
+
struct LogMessage {
|
|
27
|
+
pulsar_logger_level_t level;
|
|
28
|
+
std::string file;
|
|
29
|
+
int line;
|
|
30
|
+
std::string message;
|
|
31
|
+
|
|
32
|
+
LogMessage(pulsar_logger_level_t level, std::string file, int line, std::string message)
|
|
33
|
+
: level(level), file(file), line(line), message(message) {}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
struct LogCallback {
|
|
37
|
+
Napi::ThreadSafeFunction callback;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
void LogMessage(pulsar_logger_level_t level, const char *file, int line, const char *message, void *ctx);
|
|
41
|
+
|
|
26
42
|
class Client : public Napi::ObjectWrap<Client> {
|
|
27
43
|
public:
|
|
28
44
|
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
45
|
+
|
|
29
46
|
Client(const Napi::CallbackInfo &info);
|
|
30
47
|
~Client();
|
|
31
48
|
|
|
32
49
|
private:
|
|
33
50
|
static Napi::FunctionReference constructor;
|
|
34
51
|
pulsar_client_t *cClient;
|
|
52
|
+
LogCallback *logCallback;
|
|
35
53
|
|
|
36
54
|
Napi::Value CreateProducer(const Napi::CallbackInfo &info);
|
|
37
55
|
Napi::Value Subscribe(const Napi::CallbackInfo &info);
|
package/src/Consumer.cc
CHANGED
|
@@ -45,8 +45,49 @@ void Consumer::Init(Napi::Env env, Napi::Object exports) {
|
|
|
45
45
|
constructor.SuppressDestruct();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
struct MessageListenerProxyData {
|
|
49
|
+
pulsar_message_t *cMessage;
|
|
50
|
+
Consumer *consumer;
|
|
51
|
+
|
|
52
|
+
MessageListenerProxyData(pulsar_message_t *cMessage, Consumer *consumer)
|
|
53
|
+
: cMessage(cMessage), consumer(consumer) {}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
void MessageListenerProxy(Napi::Env env, Napi::Function jsCallback, MessageListenerProxyData *data) {
|
|
57
|
+
Napi::Object msg = Message::NewInstance({}, data->cMessage);
|
|
58
|
+
Consumer *consumer = data->consumer;
|
|
59
|
+
delete data;
|
|
60
|
+
|
|
61
|
+
jsCallback.Call({msg, consumer->Value()});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void MessageListener(pulsar_consumer_t *cConsumer, pulsar_message_t *cMessage, void *ctx) {
|
|
65
|
+
ListenerCallback *listenerCallback = (ListenerCallback *)ctx;
|
|
66
|
+
|
|
67
|
+
Consumer *consumer = (Consumer *)listenerCallback->consumer;
|
|
68
|
+
|
|
69
|
+
if (listenerCallback->callback.Acquire() != napi_ok) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
MessageListenerProxyData *dataPtr = new MessageListenerProxyData(cMessage, consumer);
|
|
74
|
+
listenerCallback->callback.BlockingCall(dataPtr, MessageListenerProxy);
|
|
75
|
+
listenerCallback->callback.Release();
|
|
76
|
+
}
|
|
77
|
+
|
|
48
78
|
void Consumer::SetCConsumer(std::shared_ptr<CConsumerWrapper> cConsumer) { this->wrapper = cConsumer; }
|
|
49
|
-
void Consumer::SetListenerCallback(ListenerCallback *listener) {
|
|
79
|
+
void Consumer::SetListenerCallback(ListenerCallback *listener) {
|
|
80
|
+
if (listener) {
|
|
81
|
+
// Maintain reference to consumer, so it won't get garbage collected
|
|
82
|
+
// since, when we have a listener, we don't have to maintain reference to consumer (in js code)
|
|
83
|
+
this->Ref();
|
|
84
|
+
|
|
85
|
+
// Pass consumer as argument
|
|
86
|
+
listener->consumer = this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this->listener = listener;
|
|
90
|
+
}
|
|
50
91
|
|
|
51
92
|
Consumer::Consumer(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Consumer>(info), listener(nullptr) {}
|
|
52
93
|
|
|
@@ -100,6 +141,7 @@ class ConsumerNewInstanceWorker : public Napi::AsyncWorker {
|
|
|
100
141
|
void OnOK() {
|
|
101
142
|
Napi::Object obj = Consumer::constructor.New({});
|
|
102
143
|
Consumer *consumer = Consumer::Unwrap(obj);
|
|
144
|
+
|
|
103
145
|
consumer->SetCConsumer(this->consumerWrapper);
|
|
104
146
|
consumer->SetListenerCallback(this->listener);
|
|
105
147
|
this->deferred.Resolve(obj);
|
|
@@ -118,8 +160,10 @@ class ConsumerNewInstanceWorker : public Napi::AsyncWorker {
|
|
|
118
160
|
Napi::Value Consumer::NewInstance(const Napi::CallbackInfo &info, pulsar_client_t *cClient) {
|
|
119
161
|
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
|
|
120
162
|
Napi::Object config = info[0].As<Napi::Object>();
|
|
163
|
+
|
|
121
164
|
std::shared_ptr<CConsumerWrapper> consumerWrapper = std::make_shared<CConsumerWrapper>();
|
|
122
|
-
|
|
165
|
+
|
|
166
|
+
ConsumerConfig *consumerConfig = new ConsumerConfig(config, consumerWrapper, &MessageListener);
|
|
123
167
|
ConsumerNewInstanceWorker *wk =
|
|
124
168
|
new ConsumerNewInstanceWorker(deferred, cClient, consumerConfig, consumerWrapper);
|
|
125
169
|
wk->Queue();
|
|
@@ -213,17 +257,25 @@ void Consumer::AcknowledgeCumulativeId(const Napi::CallbackInfo &info) {
|
|
|
213
257
|
|
|
214
258
|
class ConsumerCloseWorker : public Napi::AsyncWorker {
|
|
215
259
|
public:
|
|
216
|
-
ConsumerCloseWorker(const Napi::Promise::Deferred &deferred, pulsar_consumer_t *cConsumer
|
|
260
|
+
ConsumerCloseWorker(const Napi::Promise::Deferred &deferred, pulsar_consumer_t *cConsumer,
|
|
261
|
+
Consumer *consumer)
|
|
217
262
|
: AsyncWorker(Napi::Function::New(deferred.Promise().Env(), [](const Napi::CallbackInfo &info) {})),
|
|
218
263
|
deferred(deferred),
|
|
219
|
-
cConsumer(cConsumer)
|
|
264
|
+
cConsumer(cConsumer),
|
|
265
|
+
consumer(consumer) {}
|
|
266
|
+
|
|
220
267
|
~ConsumerCloseWorker() {}
|
|
221
268
|
void Execute() {
|
|
222
269
|
pulsar_consumer_pause_message_listener(this->cConsumer);
|
|
223
270
|
pulsar_result result = pulsar_consumer_close(this->cConsumer);
|
|
224
|
-
if (result != pulsar_result_Ok)
|
|
271
|
+
if (result != pulsar_result_Ok) {
|
|
272
|
+
SetError(pulsar_result_str(result));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
void OnOK() {
|
|
276
|
+
this->consumer->Cleanup();
|
|
277
|
+
this->deferred.Resolve(Env().Null());
|
|
225
278
|
}
|
|
226
|
-
void OnOK() { this->deferred.Resolve(Env().Null()); }
|
|
227
279
|
void OnError(const Napi::Error &e) {
|
|
228
280
|
this->deferred.Reject(
|
|
229
281
|
Napi::Error::New(Env(), std::string("Failed to close consumer: ") + e.Message()).Value());
|
|
@@ -232,18 +284,31 @@ class ConsumerCloseWorker : public Napi::AsyncWorker {
|
|
|
232
284
|
private:
|
|
233
285
|
Napi::Promise::Deferred deferred;
|
|
234
286
|
pulsar_consumer_t *cConsumer;
|
|
287
|
+
Consumer *consumer;
|
|
235
288
|
};
|
|
236
289
|
|
|
290
|
+
void Consumer::Cleanup() {
|
|
291
|
+
if (this->listener) {
|
|
292
|
+
this->CleanupListener();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
void Consumer::CleanupListener() {
|
|
297
|
+
pulsar_consumer_pause_message_listener(this->wrapper->cConsumer);
|
|
298
|
+
this->Unref();
|
|
299
|
+
this->listener->callback.Release();
|
|
300
|
+
this->listener = nullptr;
|
|
301
|
+
}
|
|
302
|
+
|
|
237
303
|
Napi::Value Consumer::Close(const Napi::CallbackInfo &info) {
|
|
238
304
|
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env());
|
|
239
|
-
ConsumerCloseWorker *wk = new ConsumerCloseWorker(deferred, this->wrapper->cConsumer);
|
|
305
|
+
ConsumerCloseWorker *wk = new ConsumerCloseWorker(deferred, this->wrapper->cConsumer, this);
|
|
240
306
|
wk->Queue();
|
|
241
307
|
return deferred.Promise();
|
|
242
308
|
}
|
|
243
309
|
|
|
244
310
|
Consumer::~Consumer() {
|
|
245
311
|
if (this->listener) {
|
|
246
|
-
|
|
247
|
-
this->listener->callback.Release();
|
|
312
|
+
this->CleanupListener();
|
|
248
313
|
}
|
|
249
314
|
}
|
package/src/Consumer.h
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
#include <napi.h>
|
|
24
24
|
#include <pulsar/c/client.h>
|
|
25
25
|
#include "ConsumerConfig.h"
|
|
26
|
+
#include "MessageListener.h"
|
|
26
27
|
|
|
27
28
|
class Consumer : public Napi::ObjectWrap<Consumer> {
|
|
28
29
|
public:
|
|
@@ -33,6 +34,8 @@ class Consumer : public Napi::ObjectWrap<Consumer> {
|
|
|
33
34
|
~Consumer();
|
|
34
35
|
void SetCConsumer(std::shared_ptr<CConsumerWrapper> cConsumer);
|
|
35
36
|
void SetListenerCallback(ListenerCallback *listener);
|
|
37
|
+
void Cleanup();
|
|
38
|
+
void CleanupListener();
|
|
36
39
|
|
|
37
40
|
private:
|
|
38
41
|
std::shared_ptr<CConsumerWrapper> wrapper;
|
package/src/ConsumerConfig.cc
CHANGED
|
@@ -35,47 +35,22 @@ static const std::string CFG_RECV_QUEUE_ACROSS_PARTITIONS = "receiverQueueSizeAc
|
|
|
35
35
|
static const std::string CFG_CONSUMER_NAME = "consumerName";
|
|
36
36
|
static const std::string CFG_PROPS = "properties";
|
|
37
37
|
static const std::string CFG_LISTENER = "listener";
|
|
38
|
+
static const std::string CFG_READ_COMPACTED = "readCompacted";
|
|
38
39
|
|
|
39
40
|
static const std::map<std::string, pulsar_consumer_type> SUBSCRIPTION_TYPE = {
|
|
40
41
|
{"Exclusive", pulsar_ConsumerExclusive},
|
|
41
42
|
{"Shared", pulsar_ConsumerShared},
|
|
43
|
+
{"KeyShared", pulsar_ConsumerKeyShared},
|
|
42
44
|
{"Failover", pulsar_ConsumerFailover}};
|
|
43
45
|
|
|
44
46
|
static const std::map<std::string, initial_position> INIT_POSITION = {
|
|
45
47
|
{"Latest", initial_position_latest}, {"Earliest", initial_position_earliest}};
|
|
46
48
|
|
|
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
49
|
void FinalizeListenerCallback(Napi::Env env, ListenerCallback *cb, void *) { delete cb; }
|
|
76
50
|
|
|
77
51
|
ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig,
|
|
78
|
-
std::shared_ptr<CConsumerWrapper> consumerWrapper
|
|
52
|
+
std::shared_ptr<CConsumerWrapper> consumerWrapper,
|
|
53
|
+
pulsar_message_listener messageListener)
|
|
79
54
|
: topic(""), subscription(""), ackTimeoutMs(0), nAckRedeliverTimeoutMs(60000), listener(nullptr) {
|
|
80
55
|
this->cConsumerConfig = pulsar_consumer_configuration_create();
|
|
81
56
|
|
|
@@ -154,14 +129,20 @@ ConsumerConfig::ConsumerConfig(const Napi::Object &consumerConfig,
|
|
|
154
129
|
|
|
155
130
|
if (consumerConfig.Has(CFG_LISTENER) && consumerConfig.Get(CFG_LISTENER).IsFunction()) {
|
|
156
131
|
this->listener = new ListenerCallback();
|
|
157
|
-
this->listener->consumerWrapper = consumerWrapper;
|
|
158
132
|
Napi::ThreadSafeFunction callback = Napi::ThreadSafeFunction::New(
|
|
159
133
|
consumerConfig.Env(), consumerConfig.Get(CFG_LISTENER).As<Napi::Function>(), "Listener Callback", 1,
|
|
160
134
|
1, (void *)NULL, FinalizeListenerCallback, listener);
|
|
161
135
|
this->listener->callback = std::move(callback);
|
|
162
|
-
pulsar_consumer_configuration_set_message_listener(this->cConsumerConfig,
|
|
136
|
+
pulsar_consumer_configuration_set_message_listener(this->cConsumerConfig, messageListener,
|
|
163
137
|
this->listener);
|
|
164
138
|
}
|
|
139
|
+
|
|
140
|
+
if (consumerConfig.Has(CFG_READ_COMPACTED) && consumerConfig.Get(CFG_READ_COMPACTED).IsBoolean()) {
|
|
141
|
+
bool readCompacted = consumerConfig.Get(CFG_READ_COMPACTED).ToBoolean();
|
|
142
|
+
if (readCompacted) {
|
|
143
|
+
pulsar_consumer_set_read_compacted(this->cConsumerConfig, 1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
165
146
|
}
|
|
166
147
|
|
|
167
148
|
ConsumerConfig::~ConsumerConfig() {
|
package/src/ConsumerConfig.h
CHANGED
|
@@ -20,31 +20,22 @@
|
|
|
20
20
|
#ifndef CONSUMER_CONFIG_H
|
|
21
21
|
#define CONSUMER_CONFIG_H
|
|
22
22
|
|
|
23
|
-
#include <napi.h>
|
|
24
23
|
#include <pulsar/c/consumer_configuration.h>
|
|
24
|
+
#include "MessageListener.h"
|
|
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
|
-
|
|
39
28
|
class ConsumerConfig {
|
|
40
29
|
public:
|
|
41
|
-
ConsumerConfig(const Napi::Object &consumerConfig, std::shared_ptr<CConsumerWrapper> consumerWrapper
|
|
30
|
+
ConsumerConfig(const Napi::Object &consumerConfig, std::shared_ptr<CConsumerWrapper> consumerWrapper,
|
|
31
|
+
pulsar_message_listener messageListener);
|
|
42
32
|
~ConsumerConfig();
|
|
43
33
|
pulsar_consumer_configuration_t *GetCConsumerConfig();
|
|
44
34
|
std::string GetTopic();
|
|
45
35
|
std::string GetSubscription();
|
|
46
36
|
int64_t GetAckTimeoutMs();
|
|
47
37
|
int64_t GetNAckRedeliverTimeoutMs();
|
|
38
|
+
|
|
48
39
|
ListenerCallback *GetListenerCallback();
|
|
49
40
|
|
|
50
41
|
private:
|
package/src/Message.cc
CHANGED
|
@@ -40,6 +40,7 @@ Napi::Object Message::Init(Napi::Env env, Napi::Object exports) {
|
|
|
40
40
|
InstanceMethod("getMessageId", &Message::GetMessageId),
|
|
41
41
|
InstanceMethod("getPublishTimestamp", &Message::GetPublishTimestamp),
|
|
42
42
|
InstanceMethod("getEventTimestamp", &Message::GetEventTimestamp),
|
|
43
|
+
InstanceMethod("getRedeliveryCount", &Message::GetRedeliveryCount),
|
|
43
44
|
InstanceMethod("getPartitionKey", &Message::GetPartitionKey)});
|
|
44
45
|
|
|
45
46
|
constructor = Napi::Persistent(func);
|
|
@@ -68,6 +69,14 @@ Napi::Value Message::GetTopicName(const Napi::CallbackInfo &info) {
|
|
|
68
69
|
return Napi::String::New(env, pulsar_message_get_topic_name(this->cMessage));
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
Napi::Value Message::GetRedeliveryCount(const Napi::CallbackInfo &info) {
|
|
73
|
+
Napi::Env env = info.Env();
|
|
74
|
+
if (!ValidateCMessage(env)) {
|
|
75
|
+
return env.Null();
|
|
76
|
+
}
|
|
77
|
+
return Napi::Number::New(env, pulsar_message_get_redelivery_count(this->cMessage));
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
Napi::Value Message::GetProperties(const Napi::CallbackInfo &info) {
|
|
72
81
|
Napi::Env env = info.Env();
|
|
73
82
|
if (!ValidateCMessage(env)) {
|
package/src/Message.h
CHANGED
|
@@ -44,6 +44,7 @@ class Message : public Napi::ObjectWrap<Message> {
|
|
|
44
44
|
Napi::Value GetPublishTimestamp(const Napi::CallbackInfo &info);
|
|
45
45
|
Napi::Value GetEventTimestamp(const Napi::CallbackInfo &info);
|
|
46
46
|
Napi::Value GetPartitionKey(const Napi::CallbackInfo &info);
|
|
47
|
+
Napi::Value GetRedeliveryCount(const Napi::CallbackInfo &info);
|
|
47
48
|
bool ValidateCMessage(Napi::Env env);
|
|
48
49
|
|
|
49
50
|
static char **NewStringArray(int size) { return (char **)calloc(sizeof(char *), size); }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
#ifndef MESSAGELISTENER_H
|
|
21
|
+
#define MESSAGELISTENER_H
|
|
22
|
+
|
|
23
|
+
#include <napi.h>
|
|
24
|
+
#include <pulsar/c/client.h>
|
|
25
|
+
|
|
26
|
+
struct CConsumerWrapper {
|
|
27
|
+
pulsar_consumer_t *cConsumer;
|
|
28
|
+
CConsumerWrapper();
|
|
29
|
+
~CConsumerWrapper();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
struct ListenerCallback {
|
|
33
|
+
Napi::ThreadSafeFunction callback;
|
|
34
|
+
|
|
35
|
+
// Using consumer as void* since the ListenerCallback is shared between Config and Consumer.
|
|
36
|
+
void *consumer;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
#endif
|
package/src/ReaderConfig.cc
CHANGED
|
@@ -26,6 +26,7 @@ static const std::string CFG_START_MESSAGE_ID = "startMessageId";
|
|
|
26
26
|
static const std::string CFG_RECV_QUEUE = "receiverQueueSize";
|
|
27
27
|
static const std::string CFG_READER_NAME = "readerName";
|
|
28
28
|
static const std::string CFG_SUBSCRIPTION_ROLE_PREFIX = "subscriptionRolePrefix";
|
|
29
|
+
static const std::string CFG_READ_COMPACTED = "readCompacted";
|
|
29
30
|
|
|
30
31
|
ReaderConfig::ReaderConfig(const Napi::Object &readerConfig) : topic(""), cStartMessageId(NULL) {
|
|
31
32
|
this->cReaderConfig = pulsar_reader_configuration_create();
|
|
@@ -59,6 +60,13 @@ ReaderConfig::ReaderConfig(const Napi::Object &readerConfig) : topic(""), cStart
|
|
|
59
60
|
if (!subscriptionRolePrefix.empty())
|
|
60
61
|
pulsar_reader_configuration_set_reader_name(this->cReaderConfig, subscriptionRolePrefix.c_str());
|
|
61
62
|
}
|
|
63
|
+
|
|
64
|
+
if (readerConfig.Has(CFG_READ_COMPACTED) && readerConfig.Get(CFG_READ_COMPACTED).IsBoolean()) {
|
|
65
|
+
bool readCompacted = readerConfig.Get(CFG_READ_COMPACTED).ToBoolean();
|
|
66
|
+
if (readCompacted) {
|
|
67
|
+
pulsar_reader_configuration_set_read_compacted(this->cReaderConfig, 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
ReaderConfig::~ReaderConfig() { pulsar_reader_configuration_free(this->cReaderConfig); }
|
|
@@ -78,6 +78,8 @@ statusFilePath=/usr/local/apache/htdocs
|
|
|
78
78
|
# Using a value of 0, is disabling unackeMessage limit check and consumer can receive messages without any restriction
|
|
79
79
|
maxUnackedMessagesPerConsumer=50000
|
|
80
80
|
|
|
81
|
+
subscriptionRedeliveryTrackerEnabled=true
|
|
82
|
+
|
|
81
83
|
### --- Authentication --- ###
|
|
82
84
|
|
|
83
85
|
# Enable authentication
|
package/tests/consumer.test.js
CHANGED
|
@@ -87,5 +87,22 @@ const Pulsar = require('../index.js');
|
|
|
87
87
|
})).rejects.toThrow('NAck timeout should be greater than or equal to zero');
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
|
+
|
|
91
|
+
describe('Close', () => {
|
|
92
|
+
test('throws error on subsequent calls to close', async () => {
|
|
93
|
+
const consumer = await client.subscribe({
|
|
94
|
+
topic: 'persistent://public/default/my-topic',
|
|
95
|
+
subscription: 'sub1',
|
|
96
|
+
subscriptionType: 'Shared',
|
|
97
|
+
// Test with listener since it changes the flow of close
|
|
98
|
+
// and reproduces an issue
|
|
99
|
+
listener() {},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await expect(consumer.close()).resolves.toEqual(null);
|
|
103
|
+
|
|
104
|
+
await expect(consumer.close()).rejects.toThrow('Failed to close consumer: AlreadyClosed');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
90
107
|
});
|
|
91
108
|
})();
|
package/tests/end_to_end.test.js
CHANGED
|
@@ -116,6 +116,52 @@ const Pulsar = require('../index.js');
|
|
|
116
116
|
await client.close();
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
+
test('getRedeliveryCount', 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';
|
|
126
|
+
const producer = await client.createProducer({
|
|
127
|
+
topic,
|
|
128
|
+
sendTimeoutMs: 30000,
|
|
129
|
+
batchingEnabled: true,
|
|
130
|
+
});
|
|
131
|
+
expect(producer).not.toBeNull();
|
|
132
|
+
|
|
133
|
+
const consumer = await client.subscribe({
|
|
134
|
+
topic,
|
|
135
|
+
subscriptionType: 'Shared',
|
|
136
|
+
subscription: 'sub1',
|
|
137
|
+
ackTimeoutMs: 10000,
|
|
138
|
+
nAckRedeliverTimeoutMs: 100,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(consumer).not.toBeNull();
|
|
142
|
+
|
|
143
|
+
const message = 'my-message';
|
|
144
|
+
producer.send({
|
|
145
|
+
data: Buffer.from(message),
|
|
146
|
+
});
|
|
147
|
+
await producer.flush();
|
|
148
|
+
|
|
149
|
+
let redeliveryCount;
|
|
150
|
+
let msg;
|
|
151
|
+
for (let index = 0; index < 3; index += 1) {
|
|
152
|
+
msg = await consumer.receive();
|
|
153
|
+
redeliveryCount = msg.getRedeliveryCount();
|
|
154
|
+
consumer.negativeAcknowledge(msg);
|
|
155
|
+
}
|
|
156
|
+
expect(redeliveryCount).toBe(2);
|
|
157
|
+
consumer.acknowledge(msg);
|
|
158
|
+
|
|
159
|
+
await producer.close();
|
|
160
|
+
await consumer.close();
|
|
161
|
+
await client.close();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
|
|
119
165
|
test('Produce/Consume Listener', async () => {
|
|
120
166
|
const client = new Pulsar.Client({
|
|
121
167
|
serviceUrl: 'pulsar://localhost:6650',
|