GithubHelp home page GithubHelp logo

cloudamqp / amqp-client.js Goto Github PK

View Code? Open in Web Editor NEW
200.0 10.0 19.0 3.42 MB

AMQP 0-9-1 TypeScript client both for Node.js and browsers (using WebSocket)

Home Page: https://cloudamqp.github.io/amqp-client.js/

License: Apache License 2.0

JavaScript 0.21% TypeScript 99.79%
amqp javascript typescript

amqp-client.js's Introduction

amqp-client.js

AMQP 0-9-1 TypeScript client both for Node.js and browsers (using WebSocket). This library is intended to replace all other Node.js AMQP libraries.

API documentation.

This library is Promise-based and hence works very well with async/await. It's secure by default, for instance, publishes aren't fulfilled until either the data has been sent on the wire (so that back propagation is respected), or if the channel has Publish Confirms enabled, it isn't fulfilled until the server has acknowledged that the message has been enqueued.

The library was developed so to make it easy for developers who already are familiar with AMQP to write browser apps that communicates directly with an AMQP server over WebSocket.

Support

The library is developed and supported by CloudAMQP, the largest hosted RabbitMQ provider in the world.

Install

npm install @cloudamqp/amqp-client --save

Start node with --enable-source-maps to get proper stacktraces as the library is transpiled from TypeScript.

Example usage

Using AMQP in Node.js:

import { AMQPClient } from '@cloudamqp/amqp-client'

async function run() {
  try {
    const amqp = new AMQPClient("amqp://localhost")
    const conn = await amqp.connect()
    const ch = await conn.channel()
    const q = await ch.queue()
    const consumer = await q.subscribe({noAck: true}, async (msg) => {
      console.log(msg.bodyToString())
      await consumer.cancel()
    })
    await q.publish("Hello World", {deliveryMode: 2})
    await consumer.wait() // will block until consumer is canceled or throw an error if server closed channel/connection
    await conn.close()
  } catch (e) {
    console.error("ERROR", e)
    e.connection.close()
    setTimeout(run, 1000) // will try to reconnect in 1s
  }
}

run()

WebSockets

This library can be used in the browser to access an AMQP server over WebSockets. For servers such as RabbitMQ that doesn't (yet?) support WebSockets natively a WebSocket TCP relay have to be used as a proxy. More information can be found in this blog post.

For web browsers a compiled and rolled up version is available at https://github.com/cloudamqp/amqp-client.js/releases.

Using AMQP over WebSockets in a browser:

<!DOCTYPE html>
<html>
  <head>
    <script type=module>
      import { AMQPWebSocketClient } from './js/amqp-websocket-client.mjs'

      const textarea = document.getElementById("textarea")
      const input = document.getElementById("message")

      const tls = window.location.protocol === "https:"
      const url = `${tls ? "wss" : "ws"}://${window.location.host}`
      const amqp = new AMQPWebSocketClient(url, "/", "guest", "guest")

      async function start() {
        try {
          const conn = await amqp.connect()
          const ch = await conn.channel()
          attachPublish(ch)
          const q = await ch.queue("")
          await q.bind("amq.fanout")
          const consumer = await q.subscribe({noAck: false}, (msg) => {
            console.log(msg)
            textarea.value += msg.bodyToString() + "\n"
            msg.ack()
          })
        } catch (err) {
          console.error("Error", err, "reconnecting in 1s")
          disablePublish()
          setTimeout(start, 1000)
        }
      }

      function attachPublish(ch) {
        document.forms[0].onsubmit = async (e) => {
          e.preventDefault()
          try {
            await ch.basicPublish("amq.fanout", "", input.value, { contentType: "text/plain" })
          } catch (err) {
            console.error("Error", err, "reconnecting in 1s")
            disablePublish()
            setTimeout(start, 1000)
          }
          input.value = ""
        }
      }

      function disablePublish() {
        document.forms[0].onsubmit = (e) => { alert("Disconnected, waiting to be reconnected") }
      }

      start()
    </script>
  </head>
  <body>
    <form>
      <textarea id="textarea" rows=10></textarea>
      <br/>
      <input id="message"/>
      <button type="submit">Send</button>
    </form>
  </body>
</html>

Performance

Messages with a 1-byte body, no properties:

Client Publish rate Consume rate
amqp-client.js 300.000 msgs/s 512.000 msgs/s
amqplib 172.000 msgs/s 519.000 msgs/s

Messages with a 1-byte body, and all properties, except headers:

Client Publish rate Consume rate
amqp-client.js 144.000 msgs/s 202.000 msgs/s
amqplib 110.000 msgs/s 251.000 msgs/s

Messages with a 1-byte body, and all properties, including headers:

Client Publish rate Consume rate
amqp-client.js 70.000 msgs/s 89.000 msgs/s
amqplib 60.000 msgs/s 99.000 msgs/s

The reason amqp-client is somewhat slower to consume is that to maintain browser compatibility for the websocket client, DataView are used for parsing the binary protocol instead of Buffer.

Module comparison

Client Runtime dependencies Lines of code
amqp-client.js 0 1743
amqplib 14 6720 (w/o dependencies)

amqp-client.js's People

Contributors

carlhoerberg avatar dentarg avatar ngbrown avatar baelter avatar kristjantammekivi avatar johanrhodin avatar walro avatar rockerboo avatar meyfa avatar kidel avatar fanmaomao avatar jonaskello avatar thomassarlin avatar maxalbert avatar mephju avatar

Stargazers

Alessandro avatar Emmanuel Gonzalez avatar Chiến Trần avatar Bilgenur Çelik avatar Dogan Kaya Berktas avatar idchlife avatar Wilmer H. Muñoz avatar Gabin Desserprit avatar 杜若甫 avatar Jurig Culun avatar Fernando Salazar avatar Daniel Lando avatar  avatar Jesse Johnson avatar Andrey avatar Nikita Savchenko avatar Hyesong Kim avatar  avatar Arc avatar  avatar  avatar Harold Sarmiento avatar Robin Monnier avatar Nriagu Chidubem avatar Ali Z. Emili avatar Whatk avatar Aras Güngöre avatar Justin Malonson avatar  avatar Mike Chabot avatar Sajan avatar Melih Acikgoz avatar Meet Ghelani avatar Alexander Cheprasov avatar Cory Crook avatar Christian Corrales avatar pigLoveRabbit avatar Matheus Berkenbrock Nedel avatar Moshe Mizrachi avatar Paweł Partyka avatar Vladislav Ponomarev avatar  avatar Luis Henrique avatar Josh Jarabek avatar Toni Villena avatar Meysam Hadeli avatar RAFAEL S GUIMARÃES avatar Michael Horák avatar  avatar trung avatar  avatar Mateusz Stepaniuk avatar Vinson Chuong avatar Seasonley avatar Ozabra avatar  avatar Bastien PELLETIER avatar Wilhelm Dewald avatar Megan avatar KevInZhao avatar Thomas Dondorf avatar Néstor avatar Dave Glendenning avatar Mark Vainomaa avatar Pavel Brylev avatar Ivan Portilla avatar NGloom avatar Fabricio Rateni avatar Egor Malkevich avatar Tim Schumacher avatar Afraz Hussain avatar Commology avatar Thanakij Pechprasarn avatar Pongsathon avatar  avatar  avatar KEMIO avatar Mahdiy avatar Mark Odayan avatar Teddy Martin avatar Valentin Agachi avatar  avatar Restu Wahyu Saputra avatar Rocky Assad avatar Christian Rotzoll avatar swellee avatar Shern Shiou Tan avatar crapthings avatar  avatar  avatar hung avatar  avatar Yves Riedener avatar sudofusion avatar Thinh Vu avatar  avatar Sam Mason de Caires avatar Alexander Niebuhr avatar Adrien avatar  avatar

Watchers

 avatar  avatar Stephen Cresswell avatar  avatar James Cloos avatar  avatar Sinan Argun avatar  avatar Daniel Byta avatar  avatar

amqp-client.js's Issues

Connect/Disconnect

Is there anyway to get notified on events at least connect and disconnect ?

amqp websockets on Little Lemur free plan

I signed up on the free plan to try the amqp over websockets protocol but I can't find the websocket url that should be used with this library. More so the docs page says that the amqp websockets plugin is pre-installed on dedicated instances, but the free plan is a shared instance. Does this mean I can't test the amqp websockets on the free plan?

[TS + Node + ESM] AMQPBaseClient can't be found

When using the library in TypeScript project configured to use ESM the imports from types are failing.

package.json has "type": "module"

tsconfig:

  "compilerOptions": {
    "module": "ES2022",
    "moduleResolution": "nodenext",
    "target": "ES2022",
  }

The AMQPBaseClient is then imported as

import { AMQPBaseClient } from '@cloudamqp/amqp-client/types/amqp-base-client.js';`

and during runtime (using ts-node-esm) or compilation (tsc) it fails with error

Cannot find module '@cloudamqp/amqp-client/types/amqp-base-client.js' or its corresponding type declarations.

The solution I came up with is to add export to node_modules/@cloudamqp/amqp-client/types/index.d.ts:

export { AMQPBaseClient } from '../types/amqp-base-client.js';

and then import in the project simply as

import { AMQPBaseClient } from '@cloudamqp/amqp-client';

Not sure whether this is the right solution. Could you please modify the library to work with ESM?

basic.cancel event?

Hello,

Does your Node.js client allow handling of basic.cancel event as described here: https://www.rabbitmq.com/consumer-cancel.html ?

If so, would you be so kind as to provide an example of how to do this? I am quite interested in using your client, but could not find such an example in the tests directory.

Thank you.

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module

const AMQPClient = require('@cloudamqp/amqp-client');

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: node_modules@cloudamqp\amqp-client\src\amqp-socket-client.mjs

"@cloudamqp/amqp-client": "^1.1.6"

$ node -v v14.18.1

Snake case fields are converted to camel case

I'm not sure if this is expected behaviour but:

Given this proto file

message Document {
  required string mime_type = 1;
}

and this object:

{ mime_type: "text/html" }

The field is ignored. However if I use this object it works, even if the proto file is unchanged

{ mimeType: "text/html" }

It seems like the library is converting snake case fields to camel case?

I'm using snake case for field names as this is recommended in the style guide

amqp protocol potential bug

Hello! This is truly an amazing library!
I ran into a big memory leak with buffers when I was working with amqplib, but with your library the application runs very stable and consumes little volume of memory.
But, unfortunately, I cannot use your library in the enterprise, because I found some problems in the protocol itself, which I cannot solve myself.
With large read/write volumes, channels are very often closed with strange errors. I have found 2:

  • frame_too_large (it writes out of volume range and fail on sending overfilled frame to rabbit)
  • connection closed by server 501 FRAME_ERROR - type 3, first 16 octets = <<"{"type":"save_pr">>: {invalid_frame_end_marker, 0} 0 0 (seems there is some problems with encoding or smth like that)
  • telegram-cloud-photo-size-2-5217803838193515006-y

Error on webpack compile

If I use it w/ webpack, after I include the code, I got an error. I would like to joint to the RMQ from a client, like a browser.

Environment

Versions

node -v
v14.18.1yarn -v
3.0.2npm -v
8.1.4

package.json

{
  "license": "MIT",
  "private": true,
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router": "^5.2.1",
    "react-router-dom": "^5.3.0",
    "redux": "^4.1.1",

    ...

  },
  "devDependencies": {
    "@babel/preset-react": "^7.0.0",
    "@symfony/webpack-encore": "^1.6.1",
    "core-js": "^3.0.0",
    "eslint": "^7.0.0",
    "husky": "^7.0.2",
    "prettier-standard": "^16.4.1",
    "sass": "^1.42.1",
    "sass-loader": "^12.0.0",
    "webpack-notifier": "^1.6.0"

    ...

  },
  "scripts": {
    "dev-server": "encore dev-server",
    "dev-server:local": "yarn dev-server --host=itg-cloud.local --port=8000",
    "dev": "encore dev",
    "watch": "encore dev --watch",
    "build": "encore production --progress",
    "postinstall": "npx husky install",
    "format": "prettier-standard --lint"
  }
}

Js file

import AMQPClient from "@cloudamqp/amqp-client";
...

Output on terminal

Running webpack ...

 ERROR  Failed to compile with 4 errors          1:58:35 PM

Module build failed: Module not found:
"./node_modules/@cloudamqp/amqp-client/src/amqp-socket-client.mjs" contains a reference to the file "buffer".
This file can not be found, please check it for typos or update it if the file got moved.

"./node_modules/@cloudamqp/amqp-client/src/amqp-socket-client.mjs" contains a reference to the file "net".
This file can not be found, please check it for typos or update it if the file got moved.

"./node_modules/@cloudamqp/amqp-client/src/amqp-socket-client.mjs" contains a reference to the file "tls".
This file can not be found, please check it for typos or update it if the file got moved.

"./node_modules/@cloudamqp/amqp-client/src/amqp-socket-client.mjs" contains a reference to the file "process".
This file can not be found, please check it for typos or update it if the file got moved.

AMQP 1.0 support?

Hello, does this client support AMQP 1.0? I was trying to use it with ActiveMQ Artemis, but couldn't get a connection.

Make frameMax configurable

Sometimes headers don't fit into 4KB (the current default), allow the user to override the negotiated frameMax value.

Error connection crashes whole application

Even though I've put the connection code inside a try catch, and tried to only show a message, the whole application crashes when the RabbitMQ connection fails.

The message part via message brokker is only part of my application, and I what it to only show a message but the rest of the application should still work.

Is there a way to do that? What am I mossing?

Web Broswer client expects node libraries

Using the web browser example, converting it to typescript I get in ng serve:

ERROR in ./node_modules/@cloudamqp/amqp-client/lib/cjs/amqp-socket-client.js
Module not found: Error: Can't resolve 'net' in 'C:\Projects\Sandbox\test-amqp\node_modules@cloudamqp\amqp-client\lib\cjs'
ERROR in ./node_modules/@cloudamqp/amqp-client/lib/cjs/amqp-socket-client.js
Module not found: Error: Can't resolve 'tls' in 'C:\Projects\Sandbox\test-amqp\node_modules@cloudamqp\amqp-client\lib\cjs'

Support for parallel calls

After debugging sporadic "Consumer x not available on channel" errors for a long time, I have come to realise this package does not like parallel calls at all.

I would do something like

await Promise.all([
  queue.subscribe(consumeParams, callback),
  channel.exchangeBind(this.getTopic(), defaultExchange),
  queue.bind(this.getTopic(), this.routingKey),
])

This would cause AMQPChannel.sendRpc to resolve AMQPChannel.promises out of order, meaning sometimes consumerTag would come back undefined in AMQPChannel.basicConsume:

this.sendRpc(frame, j).then((consumerTag) =>  {
  const consumer = new AMQPConsumer(this, consumerTag, callback)
  this.consumers.set(consumerTag, consumer)
  resolve(consumer)
})

Some fundamental changes are required to make AMQPChannel support parallel calls. Would probably need to queue up one command at a time?

For now, I would suggest mentioning this pitfall in the README to prevent others running into the same issue.

Typescript support?

This package looks great! :-). Unfortunately my projects are all in typescript so I cannot use it in a good way as it is now. Are there any plans to support typescript? Like adding a typings file or rewriting it in typescript?

Compatibility with rabbitmq broker hosted on amazon mq

How can this package be used in the browser with a rabbitmq instance that is hosted on amazon mq? It seems that the tcp relay is needed in some way, but I'm not sure how that would work with the amazon mq. Thanks.

Creating non-admin users

Do dedicated plans allow creation of non-admin users? In other words, do dedicated plans give full admin privileges to the account owner? I'm currently testing out the free tier and I understand that shared instances rely on named vhosts to distinguish different users who are on shared plans, so each of those users can't be made an admin of the instance. I want to use a structure whereby I give different users non-admin access to the instance, configured to only have read/write access to specific queues. This way they can connect individually to the broker without being able to affect other users' queues. I can do this when I have full admin privileges to the broker, so I'd like to know if I would get that on the dedicated plans. Thanks.

Export AMQPBaseClient type

The connect method of the AMQPBaseClient class returns a Promise of type AMQPBaseClient, but it is not exported in the index.d.ts file, so the following code cannot be typed:

  • Connect to an AMQP server (returns a Promise of type AMQPBaseClient)
const connectToServer = async (url: string) => {
  const client = new AMQPClient(url);
  return client.connect();
};
  • Use the connection
const main = async () => {
  const client = await connectToServer('amqp://guest:guest@localhost:5672'); 
  // Here TypeScript can infer the type Promise<AMQPBaseClient> automatically
  await processOne(client);
}

// Here we cannot type it because the AMQPBaseClient type is not exported
const processOne = async (client) => {
  const channel = await client.channel();
 // ...
}

Demo on Stackblitz

Typescript nodenext moduleresolution does not work

The current package.json breaks TypeScript when used with nodenext (ESM) module resolution.
The types field in the package.json is no longer respected when using exports as this package does. See microsoft/TypeScript#48235 (comment).

The recommended approach is to provide multiple type definition files, that should reside alongside the js-files, i.e.:
./dist/esm/index.d.ts and ./dist/cjs/index.d.cts. The types field can be used as a fallback for older TS versions.

If you have to provide a single types file, it should say instead:


"exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js"
    }
  },

This is explained here:
https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-beta/#package-json-exports-imports-and-self-referencing

TS Compilation Error: Cannot find name `TextDecoder`

I'm running this with TypeScript 4.4.0 and I'm getting the error in the title.

I've edited amqp-message.d.ts to add the proper import as a temporary workaround, but I'm surprised no one complained about this, so I'm thinking maybe I'm doing something wrong.

I have not added "dom" to the list of libs in tsconfig.json, nor will I do that, since this is a backend server app.

Any suggestions would be much appreciated.

ArrayBuffer errors with large amounts of messages

Hey,

Great library so far, but I encountered an issue that I can't explain.
A bit of background; I'm sending simulated air traffic to a browser that is displaying it in Leaflet. I'm sending info about 6000 planes, an update each 10 seconds. So about 600 messages per second. After 'some time' (seemingly random, but quicker with higher volumes) I get a pair of errors out of amqp-websocket-client.js:

ERROR TypeError: First argument to DataView constructor must be an ArrayBuffer
    at new DataView (<anonymous>)
    at AMQPWebSocketClient.handleMessage (amqp-websocket-client.js:59:38)
    at WebSocket.wrapFn (zone.js:769:39)
    at _ZoneDelegate.invokeTask (zone.js:409:31)
    at core.mjs:25299:55
    at AsyncStackTaggingZoneSpec.onInvokeTask (core.mjs:25299:36)
    at _ZoneDelegate.invokeTask (zone.js:408:60)
    at Object.onInvokeTask (core.mjs:25607:33)
    at _ZoneDelegate.invokeTask (zone.js:408:60)
    at Zone.runTask (zone.js:178:47)
ERROR RangeError: offset is out of bounds
    at Uint8Array.set (<anonymous>)
    at AMQPWebSocketClient.handleMessage (amqp-websocket-client.js:58:38)
    at WebSocket.wrapFn (zone.js:769:39)
    at _ZoneDelegate.invokeTask (zone.js:409:31)
    at core.mjs:25299:55
    at AsyncStackTaggingZoneSpec.onInvokeTask (core.mjs:25299:36)
    at _ZoneDelegate.invokeTask (zone.js:408:60)
    at Object.onInvokeTask (core.mjs:25607:33)
    at _ZoneDelegate.invokeTask (zone.js:408:60)
    at Zone.runTask (zone.js:178:47)

As far as I can tell the data is intact. I have another listener on the same exchange (this one in Java) that parses the messages just fine.

Here is the code for the consumer running in the browser:

private queue_name = uuidv4()
private queue_options = { passive: false, durable: false, autoDelete: true, exclusive: true }
private exchange_name = "exchange_name"
private exchange_type = "fanout"
private exchange_options = { passive: false, durable: true, autoDelete: false, internal: false }
private routing_key = "gps"
private subscribe_options = { noAck: false, exclusive: false }

private connectRabbitMQ() {
    const amqp = new AMQPWebSocketClient("ws://localhost:15670", "/", "rabbituser", "rabbitpassword")
    amqp.connect().then(conn => {
      conn.channel().then(channel => {
        this.rabbitmq_channel = channel
        channel.exchangeDeclare(this.exchange_name, this.exchange_type, this.exchange_options)
        channel.queue(this.queue_name, this.queue_options).then(async queue => {
          await queue.bind(this.exchange_name, this.routing_key)
          await queue.subscribe(this.subscribe_options, msg => {
            if (msg.routingKey === "track") {
              let newTrack: TrackInfo = JSON.parse(msg.bodyToString() as string) as TrackInfo;

              // Some handling...

              msg.ack();
            } else if (msg.routingKey === "plot") {
              let newPlot: PlotInfo = JSON.parse(msg.bodyToString() as string) as PlotInfo;

              // Some handling...

              msg.ack();
            } else {
              console.log(msg);
              console.log(msg.bodyToString());
            }
          })
        })
      })
    })

I am using your latest version:

"@cloudamqp/amqp-client": "^2.1.1"

Am I missing a step, or is something actually going wrong?

Large messages causes channel to shut down

We've got a production setup running where we've encountered an issue where the responses causes the server to shut down with the following error:

connection closed by server 505 UNEXPECTED_FRAME - expected content body, got non content body frame instead 60 40
amqp-client connection closed connection closed: UNEXPECTED_FRAME - expected content body, got non content body frame instead (505)
ERROR AMQPError: connection closed: UNEXPECTED_FRAME - expected content body, got non content body frame instead (505)

I've made a repo that replicates this issue consistently, under my profile: https://github.com/2joocy/cloudamqp-frame-issue
The repo contains a very minimal example of sending a message from the same channel a couple times, causing the server to crash. You can edit the connection string to rabbitmq in app.ts. The only requirement is that you've run npm i and then run npm start with the correct connection string.

We've used an older library before and didn't have issues, I've set the same test up with https://www.npmjs.com/package/amqplib and the data passes through.

Is this an issue from the library side or should we handle messages differently? Or are there parameters to circumvent this?

Client sending invalid frame

Client can in some cases send invalid frames.

connection closed by server 501 FRAME_ERROR - type 1, first 16 octets = <<0,60,0,20,0,0,22,119,101,98,104,111,111,107,46,97>>: {invalid_frame_end_marker, 0} 0 0

Will update with a stacktrace if it happens again.

Consider adding support for Deno

Deno is a Javascript runtime similar to Nodejs: Deno.land

Deno has an NPM compatibility layer that allows modules written for NodeJs to be utilised.

So far I've found the amqp-client library to work in Deno unchanged, both with a localhost RabbitMQ and your AMQP cloud service. Very stable, full featured, great experience so far.

The only issue is that during the construction of the AMQPClient, there is a reference to process.release which is not supplied in Deno land. So if that could be made optional (e.g. process?.release), you'd basically be there.

const platform = `${process.release.name} ${process.version} ${process.platform} ${process.arch}`

In the mean-time, if anyone else stumbles across this, you can solve the problem with the following hack in your deps.ts file:

export { AMQPClient } from "npm:@cloudamqp/[email protected]";
import process from "node:process";
process.release = {}; // Fix for using AMQPClient in Deno.

tls support ?

Hello,
Does amqp-client support TLS connection to RabbitMQ? can you post an example,please?

Thank you.

Create a way to detect channel close to be able to recreate the channel

I had an issue when I ack some messages who come from a queue that was marked as non-ack. So calling basicAck or basicNack was throw an error.

This issue was solved and my system now is running as expected but was sad not found a way to detect channel close event to be able to recreate/reconnect that channel.

I have:

// Connect and create a channel to RabbitMQ
this.client = new AMQPClient(connectionUrl)
this.connection = await this.client.connect()
this.channel = await this.connection.channel()

this.client.onerror = (error) => {
    console.error(error)
    this.tryReconnect()
}

// Handle disconnection
this.connection.onerror = () => {
    console.error(
        `[RabbitMqClient] ${new Date().toISOString()} Upps! We have a connection error... Trying to reconnect with exponential backoff..`
    )
    this.tryReconnect()
}

But this only work when RabbitMQ server goes down not when the channel crash.

Where to find the browser file

In the documentation we are asked to go to dist/amqp-websocket-client.js for the browser file, Do we have to build it as i dont find it the repo.

Range Error

I get this error when I listen for events sometimes not sure what to do on my end.
Screenshot 2021-12-29 at 09 59 05

cannot connect browser websocket library to deployed instance of rabbitmq

i have deployed a free rabbitmq instance using cloudrabbit mq, with hostname "vulture.rmq.cloudamqp.com", i tried to connect to this domain.

image

const amqp = new AMQPWebSocketClient(
  "wss://vulture.rmq.cloudamqp.com/ws/amqp",
  "pyxvoxaf",
  "pyxvoxaf",
  "password"
);
const conn = await amqp.connect()
console.log(conn)

But the websocket connection always fails.

image

anything i am doing wrong here? .. I am connecting from localhost:3000

Be able to specify timeout

It takes a very long time before a connection attempt times out. Right now there's no way to configure a connect timeout in the AMQPClient constructor or the .connect() function, which would be great to have.

For now, I wrote the following workaround:

    const connectionPromise = amqp.connect();
    amqp.socket?.setTimeout(5000);
    connection = await new Promise((resolve, reject) => {
      amqp.socket?.on('timeout', () => {
        amqp.socket?.destroy();
        reject(new DestinationError('Timeout hit'));
      });
      connectionPromise.then(resolve).catch(reject);
    });

Method for easily responding to a channel being closed

The current documentation shows catching an error in order to reestablish a connection. This has a couple of problems:

  • It may just be the channel that needs to e reestablished if the server decided to close it. In that case I don't necessarily want to recreate the entire connection.
  • The entirety of the amqp code may not make sense to have inside one large try/catch block.

I see that an onerror callback has been added to AMQPChannel at https://github.com/cloudamqp/amqp-client.js/blob/ab0db59/src/amqp-channel.ts#L23 but this isn't packaged in the latest release yet. I'm assuming responding to this by recreating the channel would accomplish the server closing the channel? If so, would it be possible to create a new release with this callback included?

Handling of closed channels

I've come across a scenario where, due to a programming error on my side, RabbitMQ responded with a 406 and as a consequence my channel was closed and further publishes failed. I noticed I can see the channel status in the debugger (queue.channel.closed), but since it's a private field I can't read it directly.

Maybe I'm going about this the wrong way, so I'm open to any suggestions, but my feeling is that I should be able to read that field and try to re-declare the queue in order to have fault-tolerance.

Cheers!

I got 4.1k ERR_STREAM_DESTROYED error when trying to call basicPublish, but the connection never closed

I use the amqp-client and try to reconnect whenever the connection is lost. when I got an error from basicPublish I will try to retry. before that, I will check the AMQPBaseClient.closed property. but it never changed to true.

Although now I know this error can happen and I can handle them manually. but it's better to indicate in document or have some logic to mark it as a socket error.

Here is the error object and call stack.

{
    "type": "Error",
    "message": "Cannot call write after a stream was destroyed",
    "stack": "Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed\n    at __node_internal_captureLargerStackTrace (node:internal/errors:465:5)\n    at new NodeError (node:internal/errors:372:5)\n    at _write (node:internal/streams/writable:322:11)\n    at TLSSocket.Writable.write (node:internal/streams/writable:335:10)\n    at <anonymous> (/app/node_modules/@cloudamqp/amqp-client/src/amqp-socket-client.ts:127:21)\n    at new Promise (<anonymous>)\n    at AMQPClient.send (/app/node_modules/@cloudamqp/amqp-client/src/amqp-socket-client.ts:125:12)\n    at AMQPChannel.basicPublish (/app/node_modules/@cloudamqp/amqp-client/src/amqp-channel.ts:345:29)\n    at AmqpClientJSImpl.publish (/services/AmqpClientJSImpl.ts:212:37)\n    at AmqpClientJSImpl.resendMessage (/services/AmqpClientJSImpl.ts:247:28)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)",
    "code": "ERR_STREAM_DESTROYED"
}

Stack:

Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
    at __node_internal_captureLargerStackTrace (node:internal/errors:465:5)
    at new NodeError (node:internal/errors:372:5)
    at _write (node:internal/streams/writable:322:11)
    at TLSSocket.Writable.write (node:internal/streams/writable:335:10)
    at <anonymous> (/app/node_modules/@cloudamqp/amqp-client/src/amqp-socket-client.ts:127:21)
    at new Promise (<anonymous>)
    at AMQPClient.send (/app/node_modules/@cloudamqp/amqp-client/src/amqp-socket-client.ts:125:12)
    at AMQPChannel.basicPublish (/app/node_modules/@cloudamqp/amqp-client/src/amqp-channel.ts:345:29)
    at AmqpClientJSImpl.publish (/services/AmqpClientJSImpl.ts:212:37)
    at AmqpClientJSImpl.resendMessage (/services/AmqpClientJSImpl.ts:247:28)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

my code can be found here: https://github.com/irohalab/mira-shared/blob/c1a829540d6572b8b609216524205066e9d41d72/src/services/AmqpClientJSImpl.ts#L212

Cannot import into typescript browser application

I'm experimenting with using this package when building a browser application using typescript, react and webpack.

The problem I encounter is that I cannot import from this package because of the way the typings are exported.

Specifically this package has this in package.json:

  "name": "@cloudamqp/amqp-client",
  "version": "1.3.2",
  "description": "AMQP 0-9-1 client, both for browsers (WebSocket) and node (TCP Socket)",
  "type": "module",
  "main": "dist/amqp-client.cjs",
  "types": "dist/types/amqp-client.d.ts",
  "browser": "dist/amqp-websocket-client.mjs",

This seems to be correct usage of the browser field. The idea being that a browser application will get dist/amqp-websocket-client.mjs instead of dist/amqp-client.cjs. However it does not work with typescript because there is only a single types field that can export types. So even if webpack will pick the browser field typescript will still pick the types field when compiling. For more info see this issue in the typscript repo.

Could we somehow rework the structure of the package so it will be possible to import the websocket client from a typescript app? Perhaps so it can be imported with an explicit path like @cloudamqp/amqp-client/lib/amqp-websocket-client?

If it helps, my experimental app is here. It works but only becuase I copied all the typing files out from the node_modules folder and into the application.

Process Is not Defined error while running from angular component

I am new to this, I tried to connect one RabbitMQ server that is located in my LAN. but getting following error

ERROR ReferenceError: process is not defined
at new AMQPClient (amqp-socket-client.js:16:1)

Following is the code I am using in component file
`
ngOnInit(): void {
this.run()
}

async run() {
try {
const amqp = new AMQPClient("amqp://192.168.20.57")
const conn = await amqp.connect()
const ch = await conn.channel()
const q = await ch.queue()
const consumer = await q.subscribe({noAck: true}, async (msg) => {
console.log(msg.bodyToString())
await consumer.cancel()
})
await q.publish("Hello World", {deliveryMode: 2})
await consumer.wait() // will block until consumer is canceled or throw an error if server closed channel/connection
await conn.close()
} catch (e:any) {
console.error("ERROR", e)
e.connection.close()
setTimeout(this.run, 1000) // will try to reconnect in 1s
}
}`

BUG: Program stop working if RMQ restart

I'm trying to run the standard example from readme

async function run() {
  try {
  } catch (e) {
  }
}
  1. First stop RMQ
  2. Run code
  3. code wrote: "connect ECONNREFUSED 127.0.0.1:5672" - it's OK
  4. start RMQ
    program stop working: "Process finished with exit code 0" (no any working and output)

Normally I expected what program to continue running and show the output

Streams support?

Does this library support consuming from RabbitMQ 3.9 Streams over AMQP?

Trying to consume from a queue of type Stream I'm getting the following error:

connection closed by server 540 NOT_IMPLEMENTED - automatic acknowledgement not supported by stream queues queue 'stream_queue' in vhost '/'

I've set noAck: true when subscribing but it seems I need to set a QoS for the consumer (Channel-wide QoS does not seem to be supported in Streams), but I don't know how to do this. Is there an argument I'm missing when creating the consumer?

import { AMQPClient } from "@cloudamqp/amqp-client";

async function run() {
  try {
    const amqp = new AMQPClient(
      "amqp://localhost"
    );
    const conn = await amqp.connect();
    const ch = await conn.channel();
    ch.basicQos(10000);

    const q = await ch.queue("stream_queue", undefined, {
      "x-queue-type": "stream",
    });

    const count = 0;

    console.log("Subscribing to stream_queue...");

    const consumer = await q.subscribe({ noAck: true }, async (msg) => {
      console.log(msg.bodyToString());
      count++;

      if (count % 10000 === 0) {
        console.log(`Messages processed: ${count}`);
      }

      await msg.ack();
    });

    await consumer.wait(); // will block until consumer is canceled or throw an error if server closed channel/connection
    await conn.close();

    console.log("DONE!");
    console.log(`Messages processed: ${count}`);
  } catch (e) {
    console.error("ERROR", e);
    e.connection.close();
    setTimeout(run, 1000); // will try to reconnect in 1s
  }
}

run()
  .then(() => console.log("Done!"))
  .catch((err) => console.error(err));

Heartbeats support?

Does this library support heartbeats?

When I try to use heartbeat with this library (by appending heartbeat=60 in the URL), I get an error and the client disconnects.

Client throws

connection closed by server 501 FRAME_ERROR - type 1, all octets = <<>>: unknown_frame 0 0
ERROR AMQPError: connection closed: FRAME_ERROR - type 1, all octets = <<>>: unknown_frame (501)
    at AMQPClient.parseFrames (file:///C:/Projects/amqp-client/node_modules/@cloudamqp/amqp-client/lib/mjs/amqp-base-client.js:256:49)
    at AMQPClient.onRead (file:///C:/Projects/amqp-client/node_modules/@cloudamqp/amqp-client/lib/mjs/amqp-socket-client.js:73:26)
    at Socket.emit (node:events:520:28)
    at addChunk (node:internal/streams/readable:315:12)
    at readableAddChunk (node:internal/streams/readable:289:9)
    at Socket.Readable.push (node:internal/streams/readable:228:10)
    at TCP.onStreamRead (node:internal/stream_base_commons:190:23)

Server logs shows:

2022-03-07 09:40:51.987000+00:00 [erro] <0.16753.3> Error on AMQP connection <0.16753.3> (172.22.0.6:60980 -> 172.22.0.5:5672, vhost: '/', user: 'consumer', state: running), channel 0:
2022-03-07 09:40:51.987000+00:00 [erro] <0.16753.3>  operation none caused a connection exception frame_error: "type 1, all octets = <<>>: unknown_frame"
2022-03-07 09:40:51.987000+00:00 [info] <0.16753.3> closing AMQP connection <0.16753.3> (172.22.0.6:60980 -> 172.22.0.5:5672, vhost: '/', user: 'consumer')

This works just fine using either the amqp-node/amqplib or rascal libraries.

Reconnection of websocket client

I'm trying to implement reconnection logic for the AMQPWebSocketClient.

I have two scenarios I'm trying to cover:

  1. The server is down when the client starts up. Then the server comes up and the client should connect automatically.
  2. The server is up when the client starts up and the client connects. Later the server goes down for a while and then comes back up. The client should reconnect automatcially when the server is back up.

I have an example here to try out these scenarios.

Scenario (1) works but I'm having some trouble to make scenario (2) work.

Basically I do this to simulate scenario (2):

  1. Start rabbit and relay: docker compose up -d.
  2. Start the client: yarn start
  3. Open web browser. The client connects and the Send button works.
  4. Check id of the relay container and stop it so simulate server going down:
$ docker ps
CONTAINER ID   IMAGE                           COMMAND                  CREATED         STATUS         PORTS                                                                                                         NAMES
1b607d716888   rabbitmq:3.9.13-management      "docker-entrypoint.s…"   4 minutes ago   Up 4 minutes   4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp   amqp-example-ws-ts-react-webpack-rabbitmq-1
11c9052798d6   cloudamqp/websocket-tcp-relay   "/usr/local/bin/webs…"   4 minutes ago   Up 4 minutes   0.0.0.0:15670->15670/tcp                                                                                     amqp-example-ws-ts-react-webpack-relay-1
$ docker stop 11c9052798d6
11c9052798d6
  1. Ideally the client would just start reconnecting automatically at this point but I have not found an API in the library to listen to the socket state. Is there such an API? If not I think that would be a nice addition :-).

  2. Click the Send button to try to send new message. This code has an exception handler that calls the connection logic. However the problem is that even though all code in the package and in the example seem to have correct exception handling it is not called. Instead the web browser writes this in the console:

WebSocket is already in CLOSING or CLOSED state.
(anonymous)	                              @	amqp-websocket-client.js:30
send	                                      @	amqp-websocket-client.js:27
basicPublish                                  @	amqp-channel.js:345
(anonymous)                                   @	index.tsx:51
(anonymous)                                   @	tracing.js:7
__webpack_modules__../src/index.tsx.__awaiter @ tracing.js:7
publishMessage	                              @	index.tsx:48
onClick	                                      @	index.tsx:64
callCallback	                              @	react-dom.development.js:394

Even though the console message looks like an exception it is not caught by the handlers. I'm puzzled by this. Looking at the line that is the root in the stack trace is looks like this:

  send(bytes) {
        return new Promise((resolve, reject) => {
            if (this.socket) {
                try {
                    this.socket.send(bytes); // this line
                    resolve();
                }
                catch (err) {
                    reject(err);
                }
            }
            else {
                reject("Socket not connected");
            }
        });
    }

Obviously the socket state is "CLOSING or CLOSED" as the message says. Reading in the MDN docs for websocket.send() I found this:

If you call send() when the connection is in the CLOSING or CLOSED states, the browser will silently discard the data.

I'm not really sure what to make of this. Perhaps one possible explanation could be that a "real" exception is not thrown, instead just an error with stacktrace is logged an the browser silently discard the data.

The code in my example basically implements the code from the readme but in a typescript/react/webpack setting. However if I run the exact example code from the readme using just javascript with <script type="module"> it does seem to actually throw an exception that can be caught.

Perhaps I'm doing something wrong in my example that causes the exception not to propagate but I cannot see what it would be.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.