3

I'm trying to connect to an Azure Service bus using amqp-10-jms-spring-boot starter (which I understand to use apache qpid jms under the hood.

When I set the connection string (amqphub.amqp10jms.remote-url) to use 'amqps://' everything works fine, a hello world message is sent to an Azure queue and then retreived & printed by the application.

However when I use an "amqpwss://[Endpoint]:443" connection string I receive an exception...

Caused by: io.netty.handler.codec.http.websocketx.WebSocketHandshakeException: Invalid handshake response getStatus: 400 This service does not support WebSocket connections.

I am skeptical that the Azure Service bus does not support WebSocket connections.

  1. The amqphub documentation claims this is possible: https://github.com/amqphub/amqp-10-jms-spring-boot#jms-connection-configuration
  2. As does the apache quid documentation: http://qpid.apache.org/releases/qpid-jms-0.52.0/docs/index.html
  3. As does the Service bus documentation "The AMQP WebSockets binding creates a tunnel over TCP port 443 that is then equivalent to AMQP 5671 connections.": https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-amqp-protocol-guide#connections-and-sessions

How can I establish an amqpwss connection to an Azure service bus from a Spring Boot Application? (I would like to use qpid but I'm not tied to it).

Sample code here: https://github.com/kevvvvyp/amqp-websocket-apache-qpid

Kevvvvyp
  • 1,704
  • 2
  • 18
  • 38
  • I guess this is what I'm looking to achieve: http://docs.oasis-open.org/amqp-bindmap/amqp-wsb/v1.0/csprd01/amqp-wsb-v1.0-csprd01.html#:~:text=The%20AMQP%20WebSocket%20Binding%20specification,for%20Web%20browser%20messaging%20scenarios.&text=%2Dv1.0%5D-,Advanced%20Message%20Queuing%20Protocol%20(AMQP)%20WebSocket,Binding%20(WSB)%20Version%201.0. – Kevvvvyp Jul 24 '20 at 13:30
  • Qpid JMS supports this out of the box, you'd probably need to contact MS for support – Tim Bish Jul 24 '20 at 20:33

2 Answers2

2

After a support attempt at getting upselled, and looking through the documentation again I've proven once again that microsoft is aggressively interested in embracing and vendor locking people into their garden.

Tirade aside, the URL to connect to websockets endpoint is different compared to regular AMQP endpoint. Suppose you have the following

amqps://[endpoint]

To connect using websockets, you'd replace the protocol with amqpwss and the port with 443

amqpwss://[endpoint]:443

But, what is not mentioned, is that you must also specify a path of /$servicebus/websocket. As a result, the final url is

amqpwss://[endpoint]:443/$servicebus/websocket

I have no clue how you're supposed to figure this out. Please prove me wrong and point to the bit in documentation that as of 2021-03-18 does contain this bit of information. Regardless, I found it out via this SO question regarding connecting from browser via websockets to azure service bus.

Dragas
  • 1,140
  • 13
  • 29
1

While I believe Dragas's answer to be the correct solution, I thought I would add the approach I went with if its useful to anyone.

I ended up using the Java azure-service-bus library as I spotted it has a dependency on qpid-proton-j-extensions , their own extension library for Apache Qpid. The repo description reads "Extends qpid-proton-j library to talk AMQP over WEBSOCKETS"....so I assumed this would do the job!

I created a subscription client using a non-descructive read...

 /**
 * Connect & start listening to the azure service bus topic.
 */
public void start() {
    listeningTask = taskExecutor.submit(() -> {
        try {
            SubscriptionClient subscriptionClient = new SubscriptionClient(connectionString, ReceiveMode.PEEKLOCK);
            ExecutorService receiveExecutor = Executors.newCachedThreadPool();
            registerMessageHandlerOnClient(subscriptionClient, receiveExecutor);
        } catch (Exception e) {
            log.error("Caught exception", e);
        }
    });
}

Subscribed to the reponse, sending any messages into my Spring Integration Flow via a gateway. I added some configurable properties to apply a back off between message consumption...

   /**
     * Azure service bus listener.
     *
     * @param receiveClient   client
     * @param executorService executorService
     * @throws Exception If we cannot poll queue.
     */
    private void registerMessageHandlerOnClient(SubscriptionClient receiveClient, ExecutorService executorService) throws Exception {
        // register the RegisterMessageHandler callback
        receiveClient.registerMessageHandler(
                new IMessageHandler() {
                    // callback invoked when the message handler loop has obtained a message
                    public CompletableFuture<Void> onMessageAsync(IMessage message) {

                        log.debug("Message received from azure, id: {}", message.getMessageId()); //TODO deprecation alternative
                        brokerGateway.send(message.getBody());

                        try {
                            Thread.sleep(backOff.toMillis());
                        } catch (InterruptedException e) {
                            log.error("Failed to apply azure backoff", e);
                        }
                        return CompletableFuture.completedFuture(null);
                    }

                    // callback invoked when the message handler has an exception to report
                    public void notifyException(Throwable throwable, ExceptionPhase exceptionPhase) {
                        log.error("Exception {}", exceptionPhase, throwable);
                    }
                },
                // 1 concurrent call, messages are auto-completed, auto-renew duration
                new MessageHandlerOptions(1, false, Duration.ofMinutes(1)),
                executorService);

    }
Kevvvvyp
  • 1,704
  • 2
  • 18
  • 38