Skip to content

Commit

Permalink
[core-amqp][event-hubs] support specifying a custom endpoint (#12909)
Browse files Browse the repository at this point in the history
Fixes #12901 

### Purpose of change

Some users run their applications in a private network that lacks access to public endpoints, such as <namespace>.servicebus.windows.net. Due to these restrictions, these applications are unable to create direct connections to the Azure Event Hubs service using the standard endpoint address and require the ability to specify a custom host name to ensure they route through the proper intermediary for the connection to be made.

### Testing

I've added tests that verify the connection has been configured with the custom endpoint correctly.

I also manually ran these changes by setting up an Application Gateway in Azure with:
- backend pool with a backend target that points to the FQDN of my Event Hubs Namespace (`<namespace>.servicebus.windows.net`)
- A front-end listener using a HTTPS and a nonstandard port (200).
- HTTP settings with the following:
   - backend port: 443
   - Override with new host name: set to 'Yes'
- A rule that maps the front-end listener to the back-end target and uses the HTTP settings above.

I then had a test script that sent events, received events, and called operations on the $management link setting the `customEndpointAddress` to the public IP address from my application gateway.

Here's a simplified version of that script:
```js
const { EventHubProducerClient } = require('@azure/event-hubs');
const ws = require('ws');
const connectionString = `Endpoint=sb://<namespace>.servicebus.windows.net/;SharedAccessKeyName=sakn;SharedAccessKey=key`;
const eventHubName = `my-hub`;

async function run() {
  const client = new EventHubProducerClient(connectionString, eventHubName, {
    customEndpointAddress: `https://my.ip.address:200`,
    webSocketOptions: {
      webSocket: ws
    }
  });

  const props = await client.getEventHubProperties();
  console.log(props);
  return client.close();
}
run().catch(console.error);
```

Note that in my application gateway I also used self-signed certs so needed to do some additional work so node.js would recognize my certificate, but that's separate from the customEndpointAddress work and doesn't require changes from our SDK.
  • Loading branch information
chradek authored Dec 18, 2020
1 parent 9651b09 commit 336c6cf
Show file tree
Hide file tree
Showing 20 changed files with 566 additions and 22 deletions.
50 changes: 46 additions & 4 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions sdk/core/core-amqp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# Release History

## 2.0.1 (Unreleased)
## 2.1.0 (Unreleased)

- Fixes the bug reported in issue [12610](https://github.com/Azure/azure-sdk-for-js/issues/12610).
- Adds the ability to configure the `amqpHostname` and `port` that a `ConnectionContextBase` will use when connecting to a service.
The `host` field refers to the DNS host or IP address of the service, whereas the `amqpHostname`
is the fully qualified host name of the service. Normally `host` and `amqpHostname` will be the same.
However if your network does not allow connecting to the service via the public host,
you can specify a custom host (e.g. an application gateway) via the `host` field and continue
using the public host as the `amqpHostname`.

* Fixes the bug reported in issue [12610](https://github.com/Azure/azure-sdk-for-js/issues/12610).
Previously, `retry` would still sleep one more time after all retry attempts were exhausted before returning.
Now, `retry` will return immediately after all retry attempts are completed as necessary.

Expand Down
2 changes: 1 addition & 1 deletion sdk/core/core-amqp/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@azure/core-amqp",
"sdk-type": "client",
"version": "2.0.1",
"version": "2.1.0",
"description": "Common library for amqp based azure sdks like @azure/event-hubs.",
"author": "Microsoft Corporation",
"license": "MIT",
Expand Down
2 changes: 2 additions & 0 deletions sdk/core/core-amqp/review/core-amqp.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,12 @@ export enum ConditionErrorNameMapper {

// @public
export interface ConnectionConfig {
amqpHostname?: string;
connectionString: string;
endpoint: string;
entityPath?: string;
host: string;
port?: number;
sharedAccessKey: string;
sharedAccessKeyName: string;
webSocket?: WebSocketImpl;
Expand Down
7 changes: 4 additions & 3 deletions sdk/core/core-amqp/src/ConnectionContextBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ export const ConnectionContextBase = {
const connectionOptions: ConnectionOptions = {
transport: Constants.TLS,
host: parameters.config.host,
hostname: parameters.config.host,
hostname: parameters.config.amqpHostname ?? parameters.config.host,
username: parameters.config.sharedAccessKeyName,
port: 5671,
port: parameters.config.port ?? 5671,
reconnect: false,
properties: {
product: parameters.connectionProperties.product,
Expand All @@ -147,10 +147,11 @@ export const ConnectionContextBase = {
const host = parameters.config.host;
const endpoint = parameters.config.webSocketEndpointPath || "";
const socketOptions = parameters.config.webSocketConstructorOptions || {};
const port = parameters.config.port ?? 443;

connectionOptions.webSocketOptions = {
webSocket: socket,
url: `wss://${host}:443/${endpoint}`,
url: `wss://${host}:${port}/${endpoint}`,
protocol: ["AMQPWSB10"],
options: socketOptions
};
Expand Down
17 changes: 15 additions & 2 deletions sdk/core/core-amqp/src/connectionConfig/connectionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,22 @@ export interface ConnectionConfig {
*/
endpoint: string;
/**
* @property {string} host - The host "<yournamespace>.servicebus.windows.net".
* The DNS hostname or IP address of the service.
* Typically of the form "<yournamespace>.servicebus.windows.net" unless connecting
* to the service through an intermediary.
*/
host: string;
/**
* The fully qualified name of the host to connect to.
* This field can be used by AMQP proxies to determine the correct back-end service to
* connect the client to.
* Typically of the form "<yournamespace>.servicebus.windows.net".
*/
amqpHostname?: string;
/**
* The port number.
*/
port?: number;
/**
* @property {string} connectionString - The connection string.
*/
Expand Down Expand Up @@ -135,7 +148,7 @@ export const ConnectionConfig = {
throw new TypeError("Missing 'entityPath' in configuration");
}
if (config.entityPath != undefined) {
config.entityPath = String(config.entityPath);
config.entityPath = String(config.entityPath);
}

if (!isSharedAccessSignature(config.connectionString)) {
Expand Down
Loading

0 comments on commit 336c6cf

Please sign in to comment.