nats-io / nats.ts

TypeScript Node.js client for NATS, the cloud native messaging system
https://www.nats.io
Apache License 2.0
178 stars 13 forks source link

msg.subject contains the matching pattern rather than the subject #50

Closed ellis closed 5 years ago

ellis commented 5 years ago

As I understand it, msg.subject should contain the subject, but it contains the matching pattern instead.

Here's a toy example. I'm using ts-nats version 1.2.2. I subscribe to NATS using the following subscribe code:

nc.subscribe(`pipeline.superduperer.new.*`, (err, msg) => {
        console.log("msg:", JSON.stringify(msg));
}

I test it with telnet as follows:

% telnet localhost 4222
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
INFO {"server_id":"d36w05aOdEVZlPXkLkujLT","version":"1.4.1","proto":1,"git_commit":"3e64f0b","go":"go1.11.5","host":"0.0.0.0","port":4222,"max_payload":1048576,"client_id":15} 
pub pipeline.superduperer.new.one 0

+OK

The output from the typescript program is:

msg: {"sid":1,"size":0,"reply":"","subject":"pipeline.superduperer.new.*"}

Shouldn the subject be pipeline.superduperer.new.one?

aricart commented 5 years ago

@ellis not quite sure what is going on your side but:

ts-nats/examples  master [!] 
✘255 ➜ ts-node tsnats-sub.ts "pipeline.superduper.new.*"
listening to [pipeline.superduper.new.*]
[#1] received on [pipeline.superduper.new.foo]: 
➜ telnet localhost 4222
Trying 127.0.0.1...
Connected to malaga.lan.
Escape character is '^]'.
INFO {"server_id":"NCSCDUEYMLR6DVVVO5QKUF6S5MMQMOFIMP7RYSYD7DQSWN5PS5PHNIQ5","version":"2.0.0-RC5","proto":1,"go":"go1.11","host":"0.0.0.0","port":4222,"max_payload":1048576,"client_id":2} 
pub pipeline.superduper.new.foo 0

+OK

and also:

test('subscribe subject should be in subject ', async (t) => {
    t.plan(2);
    let sc = t.context as SC;
    let nc = await connect({url: sc.server.nats, payload: Payload.STRING} as NatsConnectionOptions);
    let prefix = createInbox();
    let subj = `${prefix}.*`;
    let sub = await nc.subscribe(subj, (err, msg) => {
        if (err) {
            t.fail("error on handler " + err.message);
        }
        t.log(msg.subject);
        t.log(msg);
        t.not(msg.subject, prefix);
        t.is(msg.subject, `${prefix}.foo`);
    });

    nc.publish(`${prefix}.foo`, 'bar');
    await nc.flush();
    nc.close();
});
✔ basics › subscribe subject should be in subject  (116ms)
    ℹ _INBOX.YYZ0I8Q5S7JS32X7ND3OLE.foo
    ℹ {
        data: 'bar',
        reply: undefined,
        sid: 1,
        size: 3,
        subject: '_INBOX.YYZ0I8Q5S7JS32X7ND3OLE.foo',
      }
aricart commented 5 years ago

Let me try with the npm library to make sure there's no build issue behind this

aricart commented 5 years ago

Did a simple example with the npm library, and it worked for me.

import {connect, NatsConnectionOptions} from 'ts-nats'
import * as minimist from 'minimist'

const argv = minimist(process.argv.slice(2));
const subject = argv._[0];
const url = argv.s || "";

let opts = {} as NatsConnectionOptions;
if (url) {
    opts.url = url;
}

if (!subject) {
    usage();
}

function usage() {
    console.log('tsnode-sub [-s <server>] <subject>');
    process.exit(-1);
}

async function main() {
    let nc = await connect(opts);

    nc.on('unsubscribe', () => {
        nc.close();
    });

    nc.on('permissionError', (err) => {
        nc.close();
        console.log(`${err}`);
    });

    // create the subscription
    let count = 0;
    let sub = await nc.subscribe(subject, (err, msg) => {
        count++;
        if (msg.reply) {
            console.log(`[#${count}] received request on [${msg.subject}]: ${msg.data} respond to ${msg.reply}`);
        } else {
            console.log(`[#${count}] received on [${msg.subject}]: ${msg.data}`);
        }
    });

    nc.flush(() => {
        console.log(`listening to [${subject}]`);
    });
}

main();
130 ➜ ts-node tsnats-sub.ts "foo.*"
listening to [foo.*]
[#1] received on [foo.bar]: 
aricart commented 5 years ago

Finally looking at the code, the subject the message came in, is extracted using a regular expression, from the protocol line that the server provided. So since the server doesn't return wildcards. You wouldn't happen by any chance to be mocking a server somewhere?

ellis commented 5 years ago

@aricart Thanks so much for looking into this! No, I'm not mocking a server - the server is running as a docker container. I'll test your example tomorrow report back.

ellis commented 5 years ago

Your example also worked on my system, so that helped narrow down the source of the problem: payload: Payload.JSON with an empty payload. Could you please try the following patch?:

-import { connect, NatsConnectionOptions } from "ts-nats";
+import { connect, NatsConnectionOptions, Payload } from "ts-nats";
 import * as minimist from "minimist";

 const argv = minimist(process.argv.slice(2));
 const subject = argv._[0];
 const url = argv.s || "";

 let opts = {} as NatsConnectionOptions;
+opts.payload = Payload.JSON;
 if (url) {

The result of receiving a foo.bar message is then:

 % npx ts-node src/tsnode-sub.ts "foo.*"
listening to [foo.*]
[#1] received on [foo.*]: undefined
[#2] received on [foo.bar]: 1

when the following telnet messages are sent:

 % telnet localhost 4222
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
INFO {"server_id":"d36w05aOdEVZlPXkLkujLT","version":"1.4.1","proto":1,"git_commit":"3e64f0b","go":"go1.11.5","host":"0.0.0.0","port":4222,"max_payload":1048576,"client_id":26} 
pub foo.bar 0

+OK
pub foo.bar 1
1
+OK

The gives me an easy workaround for now: make sure I never send an undefined payload. What do you think?

aricart commented 5 years ago

Ah - I understand now what is going on. JSON was a very important nugget.

The issue is that the telnet client is not providing a valid JSON payload, so the typescript client is actually providing an error argument - because there was a problem parsing the payload as a JSON, the message contains information about the subscription, rather than the bits collected (which clearly are enough because we are able to dispatch to the callback).

aricart commented 5 years ago

The main issue in your test is a very possible scenario, where your client is expecting JSON but the client publishing the information isn't. When publishing, it is acceptable to publish without a payload, this gets translated into a JSON null which is a valid JSON data type (undefined isn't) for clients that have a JSON option - other clients can send you garbage, and the error will catch that.

I am going to submit a small PR that provides whatever we know about the message, with the understanding that if there's an error the contents of the message may not be complete or correct. This will be better than the current 'debug' argument since you'll see the correct subject (rather than the subscription). In all cases, error arguments should be handled.

ellis commented 5 years ago

Thanks, @aricart, sounds good to me!