RabbitMQHTTPAPIClient

0.7.0

A Swift 6 client for the RabbitMQ HTTP API
michaelklishin/rabbitmq-http-api-client-swift

What's New

v0.7.0

2026-03-16T09:10:00Z

0.7.0 (Mar 15, 2026)

Initial Release

This library, heavily inspired by the one available to Rust users
and powering rabbitmqadmin v2, is now mature enough to be publicly released.

It tries to port the Rust client API and feature set as faithfully as possible
in a different language with different idioms and approaches.

It targets Swift 6.x.

A Swift Client for the RabbitMQ HTTP API

This is a Swift 6 client for the RabbitMQ HTTP API heavily inspired by its Rust counterpart that powers modern rabbitmqadmin.

Project Maturity

This library is reasonably mature.

Before 1.0.0, breaking API changes can and will be introduced.

Supported RabbitMQ Series

This library targets RabbitMQ 4.x and 3.13.x.

All older series have reached End of Life.

Swift Version

This library requires Swift 6 and uses strict concurrency checking (swift-6 language mode).

Installation

Add this to Package.swift:

.package(url: "https://github.com/rabbitmq/rabbitmq-http-api-client-swift.git", from: "0.1.0")

And to your target dependencies:

.product(name: "RabbitMQHTTPAPIClient", package: "rabbitmq-http-api-client-swift")

Async Client

The async client uses Swift's structured concurrency with async/await. All client methods are async throws and designed for concurrent use.

Instantiate a Client

import RabbitMQHTTPAPIClient

let client = Client(
  endpoint: "http://localhost:15672/api",
  username: "guest",
  password: "guest"
)

let nodes = try await client.listNodes()

The client uses Foundation.URLSession under the hood and is Sendable, safe to use from multiple concurrent tasks.

Default Endpoint and Credentials

The default endpoint is http://localhost:15672/api, username is guest, and password is guest.

let client = Client()
let overview = try await client.overview()

Configuring Retries and Timeouts

Pass RetrySettings to configure retry behavior:

import RabbitMQHTTPAPIClient

let retrySettings = RetrySettings(maxAttempts: 3, delayMs: 500)
let client = Client(
  endpoint: "http://localhost:15672/api",
  username: "guest",
  password: "guest",
  retrySettings: retrySettings
)

Default retry behavior is maxAttempts: 3 with delayMs: 0.

Custom URLSession

Pass a custom URLSession for advanced configuration:

var config = URLSessionConfiguration.default
// Timeout for individual request/response (not including connection time)
config.timeoutIntervalForRequest = 30
// Timeout for total resource (connection + request + response)
config.timeoutIntervalForResource = 60
config.waitsForConnectivity = true

let session = URLSession(configuration: config)
let client = Client(
  endpoint: "http://localhost:15672/api",
  username: "guest",
  password: "guest",
  session: session
)

Cluster and Node Operations

Get Cluster Overview

let overview = try await client.overview()

Returns cluster overview including node name, version, statistics, and listeners.

Get RabbitMQ Version

let version = try await client.serverVersion()

List Cluster Nodes

let nodes = try await client.listNodes()

Get Node Information

let node = try await client.getNodeInfo("rabbit@hostname")

Node Memory Footprint

let footprint = try await client.getNodeMemoryFootprint("rabbit@hostname")

Returns a per-category memory footprint breakdown, in bytes and as percentages.

Get Cluster Name

let identity = try await client.getClusterName()

Set Cluster Name

try await client.setClusterName("my-cluster")

Cluster Tags

Cluster tags are arbitrary key-value pairs for the cluster:

let tags = try await client.getClusterTags()

let newTags = ["environment": "production", "region": "us-east-1"]
try await client.setClusterTags(newTags)

try await client.clearClusterTags()

Virtual Host Operations

Virtual hosts group and isolate resources.

List Virtual Hosts

let vhosts = try await client.listVirtualHosts()

Get Virtual Host

let vhost = try await client.getVirtualHost("/")

Create Virtual Host

let params = VirtualHostParams(
  name: "my-vhost",
  description: "Production vhost",
  tags: ["production", "critical"],
  defaultQueueType: "quorum"
)
try await client.createVirtualHost(params)

Delete Virtual Host

try await client.deleteVirtualHost("my-vhost", idempotently: false)

Set idempotently: true to ignore 404 errors if the vhost doesn't exist.

Virtual Host Deletion Protection

try await client.enableVirtualHostDeletionProtection("my-vhost")
try await client.disableVirtualHostDeletionProtection("my-vhost")

User Operations

List Users

let users = try await client.listUsers()

Get User

let user = try await client.getUser("my-user")

Get Current User

let currentUser = try await client.whoami()

Create User

let params = UserParams(
  name: "new-user",
  password: "s3cRe7"
)
try await client.createUser(params)

Users can be created with a plaintext password or a password hash. To use a hash:

let params = UserParams(
  name: "new-user",
  passwordHash: "base64EncodedHash",
  hashingAlgorithm: "SHA-256"
)
try await client.createUser(params)

Delete User

try await client.deleteUser("new-user", idempotently: false)

Delete Multiple Users

try await client.deleteUsers(["user1", "user2", "user3"])

List Users Without Permissions

let unusedUsers = try await client.listUsersWithoutPermissions()

Connection Operations

List Connections

let connections = try await client.listConnections()

List Connections in a Virtual Host

let vhostConnections = try await client.listConnections(in: "/")

Get Connection

let connection = try await client.getConnectionInfo("connection-name")

Close Connection

try await client.closeConnection(
  "connection-name",
  reason: "closing for maintenance",
  idempotently: false
)

List Stream Connections

let streamConnections = try await client.listStreamConnections()
let vhostStreamConnections = try await client.listStreamConnections(in: "/")

User Connections

let userConnections = try await client.listUserConnections("username")
try await client.closeUserConnections("username", reason: "session expired")

Queue Operations

List Queues

let allQueues = try await client.listQueues()
let vhostQueues = try await client.listQueues(in: "/")

List Queues by Type

let classicQueues = try await client.listClassicQueues()
let classicVhostQueues = try await client.listClassicQueues(in: "/")

let quorumQueues = try await client.listQuorumQueues()
let quorumVhostQueues = try await client.listQuorumQueues(in: "/")

let streams = try await client.listStreams()
let vhostStreams = try await client.listStreams(in: "/")

Get Queue Properties and Metrics

let queueInfo = try await client.getQueueInfo("my-queue", in: "/")

Declare a Classic Queue

let params = QueueParams.classicQueue("my-queue", in: "/")
try await client.declareQueue(params)

Pass custom arguments:

var args: [String: JSONValue] = [:]
args["x-message-ttl"] = .int(3600000)
args["x-max-length"] = .int(10000)

let params = QueueParams.classicQueue("my-queue", in: "/", arguments: args)
try await client.declareQueue(params)

Declare a Quorum Queue

Quorum queues are replicated, data safety-oriented queues based on the Raft consensus algorithm:

let params = QueueParams.quorumQueue("my-qq", in: "/")
try await client.declareQueue(params)

With custom arguments:

var args: [String: JSONValue] = [:]
args["x-max-length"] = .int(10000)
args["x-single-active-consumer"] = .bool(true)

let params = QueueParams.quorumQueue("my-qq", in: "/", arguments: args)
try await client.declareQueue(params)

Declare a Stream

Streams are persistent, replicated append-only logs with non-destructive consumer semantics:

let params = QueueParams.stream("my-stream", in: "/")
try await client.declareQueue(params)

With retention settings:

var args: [String: JSONValue] = [:]
args["x-max-age"] = .string("7D")
args["x-max-length-bytes"] = .int(10_000_000_000)

let params = QueueParams.stream("my-stream", in: "/", arguments: args)
try await client.declareQueue(params)

Purge Queue

try await client.purgeQueue("my-queue", in: "/")

Delete Queue

try await client.deleteQueue("my-queue", in: "/", idempotently: false)

Batch Queue Deletion

try await client.deleteQueue("queue-1", in: "/")
try await client.deleteQueue("queue-2", in: "/")
try await client.deleteQueue("queue-3", in: "/")

Exchange Operations

List Exchanges

let exchanges = try await client.listExchanges()
let vhostExchanges = try await client.listExchanges(in: "/")

Get Exchange Properties and Metrics

let exchangeInfo = try await client.getExchangeInfo("my-exchange", in: "/")

Declare an Exchange

Use the type-safe factory methods:

let params = ExchangeParams.direct("my-exchange", in: "/")
try await client.declareExchange(params)

let params = ExchangeParams.topic("my-topic", in: "/")
try await client.declareExchange(params)

let params = ExchangeParams.fanout("my-fanout", in: "/")
try await client.declareExchange(params)

let params = ExchangeParams.headers("my-headers", in: "/")
try await client.declareExchange(params)

With arguments:

var args: [String: JSONValue] = [:]
args["x-message-ttl"] = .int(3600000)

let params = ExchangeParams.topic("my-topic", in: "/", arguments: args)
try await client.declareExchange(params)

Delete Exchange

try await client.deleteExchange("my-exchange", in: "/", idempotently: false)

Binding Operations

Bindings connect exchanges to queues or other exchanges.

List Bindings

let bindings = try await client.listBindings()
let vhostBindings = try await client.listBindings(in: "/")

List Queue Bindings

let queueBindings = try await client.listQueueBindings("my-queue", in: "/")

List Exchange Bindings

let asSource = try await client.listExchangeBindingsAsSource("my-exchange", in: "/")
let asDestination = try await client.listExchangeBindingsAsDestination("my-exchange", in: "/")

Bind Queue to Exchange

try await client.bindQueue(
  "my-queue",
  to: "my-exchange",
  in: "/",
  routingKey: "routing.key"
)

With arguments:

try await client.bindQueue(
  "my-queue",
  to: "my-exchange",
  in: "/",
  routingKey: "routing.key",
  arguments: ["x-match": .string("all")]
)

Bind Exchange to Exchange

try await client.bindExchange(
  "destination-exchange",
  to: "source-exchange",
  in: "/",
  routingKey: "routing.key"
)

Delete Binding

try await client.deleteQueueBinding(
  "my-queue",
  from: "my-exchange",
  in: "/",
  propertiesKey: "routing.key",
  idempotently: false
)

Channel Operations

List Channels

let channels = try await client.listChannels()
let vhostChannels = try await client.listChannels(in: "/")

Get Channel

let channelInfo = try await client.getChannelInfo("channel-name")

List Channels on Connection

let connectionChannels = try await client.listChannels(on: "connection-name")

Consumer Operations

List Consumers

let consumers = try await client.listConsumers()
let vhostConsumers = try await client.listConsumers(in: "/")

Permission Operations

List Permissions

let allPermissions = try await client.listPermissions()
let vhostPermissions = try await client.listPermissions(in: "/")
let userPermissions = try await client.listPermissions(of: "username")

Get Permission

let permissions = try await client.getPermissions(of: "username", in: "/")

Grant Permissions

let params = PermissionParams(
  vhost: "/",
  username: "my-user",
  configure: ".*",
  read: ".*",
  write: ".*"
)
try await client.grantPermissions(params)

Clear Permissions

try await client.clearPermissions(of: "username", in: "/", idempotently: false)

Topic Permission Operations

Topic permissions control access to topics in exchanges.

List Topic Permissions

let allTopicPerms = try await client.listTopicPermissions()
let vhostTopicPerms = try await client.listTopicPermissions(in: "/")
let userTopicPerms = try await client.listTopicPermissions(of: "username")

Get Topic Permission

let topicPerms = try await client.getTopicPermissions(of: "username", in: "/", exchange: "my-exchange")

Grant Topic Permissions

let params = TopicPermissionParams(
  vhost: "/",
  username: "my-user",
  exchange: "my-exchange",
  write: ".*",
  read: ".*"
)
try await client.grantTopicPermissions(params)

Clear Topic Permissions

Clears all topic permissions for a user in a virtual host:

try await client.clearTopicPermissions(
  of: "username",
  in: "/",
  idempotently: false
)

Policy Operations

Policies dynamically configure queue and exchange properties using pattern matching.

List Policies

let policies = try await client.listPolicies()
let vhostPolicies = try await client.listPolicies(in: "/")

Get Policy

let policy = try await client.getPolicy("my-policy", in: "/")

Declare a Policy

var definition: [String: JSONValue] = [:]
definition["max-length"] = .int(10000)
definition["overflow"] = .string("reject-publish")

let params = PolicyParams(
  vhost: "/",
  name: "my-policy",
  pattern: "^my-.*",
  definition: definition,
  priority: 0,
  applyTo: .queues
)
try await client.declarePolicy(params)

Delete Policy

try await client.deletePolicy("my-policy", in: "/", idempotently: false)

Operator Policy Operations

Operator policies are system-wide policies for operators to apply without input from users.

List Operator Policies

let opPolicies = try await client.listOperatorPolicies()
let vhostOpPolicies = try await client.listOperatorPolicies(in: "/")

Declare Operator Policy

var definition: [String: JSONValue] = [:]
definition["delivery-limit"] = .int(5)

let params = PolicyParams(
  vhost: "/",
  name: "op-policy",
  pattern: "^.*",
  definition: definition,
  priority: 100,
  applyTo: .queues
)
try await client.declareOperatorPolicy(params)

Delete Operator Policy

try await client.deleteOperatorPolicy("op-policy", in: "/", idempotently: false)

Pagination

Some list operations support pagination:

let page = PaginationParams(page: 1, pageSize: 100)
let queuesPage = try await client.listQueues(page: page)

let nextPage = PaginationParams(page: 2, pageSize: 100)
let moreQueues = try await client.listQueues(page: nextPage)

Iterate all pages:

var allQueues: [QueueInfo] = []
var page = 1

while true {
  let params = PaginationParams(page: page, pageSize: 100)
  let result = try await client.listQueues(page: params)
  allQueues.append(contentsOf: result.items)

  if !result.hasMore {
    break
  }
  page += 1
}

Paginated operations include: listQueues, listConnections, and listExchanges.

Health Checks

Cluster Alarms

try await client.healthCheckClusterAlarms()

Local Alarms

try await client.healthCheckLocalAlarms()

Quorum Critical

try await client.healthCheckNodeIsQuorumCritical()

Port Listener

try await client.healthCheckPortListener(5672)

Protocol Listener

try await client.healthCheckProtocolListener(.amqp)
try await client.healthCheckProtocolListener(.stream)
try await client.healthCheckProtocolListener(.mqtt)

Virtual Hosts

try await client.healthCheckVirtualHosts()

Feature Flags

List Feature Flags

let flags = try await client.listFeatureFlags()

Enable Feature Flag

try await client.enableFeatureFlag("feature-name")

Enable All Stable Feature Flags

try await client.enableAllStableFeatureFlags()

Definition Operations

Export Definitions

let json = try await client.exportDefinitions()

Export vhost-specific definitions:

let vhostJson = try await client.exportDefinitions(of: "/")

Import Definitions

try await client.importDefinitions(jsonString)

Import into a specific vhost:

try await client.importDefinitions(jsonString, into: "/")

Message Operations

Message operations are for testing only and not recommended for production use.

Publish Message

try await client.publishMessage(
  "test message",
  to: "my-exchange",
  routingKey: "test.key",
  in: "/"
)

Get Messages

let messages = try await client.getMessages(
  from: "my-queue",
  in: "/",
  count: 10,
  requeue: true
)

User Limits

List All User Limits

let limits = try await client.listAllUserLimits()

List User Limits

let userLimits = try await client.listUserLimits("username")

Set User Limit

try await client.setUserLimit("username", .maxConnections, value: 100)

Clear User Limit

try await client.clearUserLimit("username", .maxConnections)

Virtual Host Limits

List All Virtual Host Limits

let limits = try await client.listAllVirtualHostLimits()

List Virtual Host Limits

let vhostLimits = try await client.listVirtualHostLimits("/")

Set Virtual Host Limit

try await client.setVirtualHostLimit("/", .maxQueues, value: 10000)

Clear Virtual Host Limit

try await client.clearVirtualHostLimit("/", .maxQueues)

Deprecated Features

List Deprecated Features

let allDeprecated = try await client.listDeprecatedFeatures()

List Deprecated Features In Use

let inUse = try await client.listDeprecatedFeaturesInUse()

Rebalancing

Rebalance Queue Leaders

try await client.rebalanceQueueLeaders()

Plugin Operations

List Node Plugins

let plugins = try await client.listNodePlugins("rabbit@hostname")

List All Cluster Plugins

let clusterPlugins = try await client.listAllClusterPlugins()

Runtime Parameters

List Runtime Parameters

let allParams = try await client.listRuntimeParameters()

List by component:

let federationParams = try await client.listRuntimeParameters(component: "federation")

List for component in vhost:

let vhostFedParams = try await client.listRuntimeParameters(component: "federation", in: "/")

Get Runtime Parameter

let param = try await client.getRuntimeParameter(
  "upstream",
  of: "federation",
  in: "/"
)

Set Runtime Parameter

var value: [String: JSONValue] = [:]
value["uri"] = .string("amqp://upstream-host")
value["ack-mode"] = .string("on-confirm")

let params = RuntimeParameterParams(
  component: "federation",
  name: "my-upstream",
  vhost: "/",
  value: value
)
try await client.upsertRuntimeParameter(params)

Delete Runtime Parameter

try await client.deleteRuntimeParameter(
  "my-upstream",
  of: "federation",
  in: "/",
  idempotently: false
)

Global Parameters

List Global Parameters

let globals = try await client.listGlobalParameters()

Get Global Parameter

let param = try await client.getGlobalParameter("my-param")

Set Global Parameter

var value: [String: JSONValue] = [:]
value["key"] = .string("value")

let params = GlobalParameterParams(
  name: "my-param",
  value: value
)
try await client.upsertGlobalParameter(params)

Delete Global Parameter

try await client.deleteGlobalParameter("my-param", idempotently: false)

Federation

Federation links RabbitMQ brokers together to distribute messages.

List Federation Upstreams

let upstreams = try await client.listFederationUpstreams()
let vhostUpstreams = try await client.listFederationUpstreams(in: "/")

Get Federation Upstream

let upstream = try await client.getFederationUpstream("my-upstream", in: "/")

Declare Federation Upstream

let params = FederationUpstreamParams(
  name: "my-upstream",
  vhost: "/",
  definition: ["uri": .string("amqp://upstream-host")]
)
try await client.declareFederationUpstream(params)

Delete Federation Upstream

try await client.deleteFederationUpstream("my-upstream", in: "/", idempotently: false)

Federation Links

let links = try await client.listFederationLinks()
let vhostLinks = try await client.listFederationLinks(in: "/")

Shovels

Shovels move messages from a source to a destination.

List Shovels

let shovels = try await client.listShovels()
let vhostShovels = try await client.listShovels(in: "/")

Get Shovel

let shovel = try await client.getShovel("my-shovel", in: "/")

Declare Shovel

Type-safe factory methods for common shovel configurations:

let params = ShovelParams.amqp091QueueShovel(
  name: "my-shovel",
  vhost: "/",
  sourceUri: "amqp://source-host",
  destinationUri: "amqp://dest-host",
  sourceQueue: "src-queue",
  destinationQueue: "dst-queue"
)
try await client.declareShovel(params)

Or for shoveling from exchange to queue:

let params = ShovelParams.amqp091ExchangeShovel(
  name: "my-shovel",
  vhost: "/",
  sourceUri: "amqp://source-host",
  sourceExchange: "src-exchange",
  destinationUri: "amqp://dest-host",
  destinationQueue: "dst-queue"
)
try await client.declareShovel(params)

Delete Shovel

try await client.deleteShovel("my-shovel", in: "/", idempotently: false)

Stream Publishers and Consumers

List Stream Publishers

All cluster publishers:

let allPublishers = try await client.listStreamPublishers()

By virtual host:

let vhostPublishers = try await client.listStreamPublishers(in: "/")

By stream and virtual host:

let streamPublishers = try await client.listStreamPublishers(of: "my-stream", in: "/")

By connection and virtual host:

let connectionPublishers = try await client.listStreamPublishers(on: "connection-name", in: "/")

List Stream Consumers

All cluster consumers:

let allConsumers = try await client.listStreamConsumers()

By virtual host:

let vhostConsumers = try await client.listStreamConsumers(in: "/")

By connection and virtual host:

let connectionConsumers = try await client.listStreamConsumers(on: "connection-name", in: "/")

Get Stream Connection Info

let connInfo = try await client.getStreamConnectionInfo("connection-name")

Authentication and OAuth

OAuth Configuration

let oauthConfig = try await client.oauthConfiguration()

Auth Attempt Statistics

let stats = try await client.authAttemptStatistics(user: "username", in: "/")

Tanzu RabbitMQ Schema Definition Sync

These operations are specific to Tanzu RabbitMQ deployments.

Enable Schema Definition Sync

try await client.enableSchemaDefinitionSync()
try await client.enableSchemaDefinitionSync(on: "rabbit@hostname")

Disable Schema Definition Sync

try await client.disableSchemaDefinitionSync()
try await client.disableSchemaDefinitionSync(on: "rabbit@hostname")

Schema Definition Sync Status

let status = try await client.schemaDefinitionSyncStatus()
let nodeStatus = try await client.schemaDefinitionSyncStatus(on: "rabbit@hostname")

Warm Standby Replication Status

let replStatus = try await client.warmStandbyReplicationStatus()

Error Handling

The client throws errors from the ClientError enum which includes cases like badRequest, unauthorized, notFound, conflict, and serverError:

do {
  try await client.getQueueInfo("my-queue", in: "/")
} catch let error as ClientError {
  switch error {
  case .notFound:
    print("Queue not found")
  case .unauthorized:
    print("Authentication failed")
  case .conflict:
    print("Resource already exists")
  case .badRequest(let reason):
    print("Bad request: \(reason)")
  case .serverError(let statusCode, let body):
    print("Server error \(statusCode): \(body)")
  default:
    print("Client error: \(error)")
  }
} catch {
  print("Other error: \(error)")
}

Idempotent Deletes

Deletion operations accept an idempotently parameter. When true, the client returns success even if the resource doesn't exist (404):

try await client.deleteQueue("my-queue", in: "/", idempotently: true)
try await client.deleteUser("my-user", idempotently: true)

JSONValue

The JSONValue enum provides type-safe construction of JSON values for arguments and definitions:

var args: [String: JSONValue] = [:]
args["x-max-length"] = .int(10000)
args["x-overflow"] = .string("reject-publish")
args["x-single-active-consumer"] = .bool(true)
args["x-dead-letter-strategy"] = .object(["type": .string("at-most-once")])

let params = QueueParams.quorumQueue("my-queue", in: "/", arguments: args)
try await client.declareQueue(params)

Security & TLS Configuration

The client delegates TLS and certificate handling to URLSession, following Apple's design patterns. Peer certificate chain verification using system root CA certificates is enabled by default for HTTPS endpoints.

Default Peer Certificate Chain Verification

// Automatically verifies the server's certificate chain and rejects invalid/self-signed certs
let client = Client(
  endpoint: "https://rabbitmq.example.com:15671/api",
  username: "guest",
  password: "guest"
)

Certificate Pinning (Custom Peer Certificate Chain Verification)

Restrict connections to specific certificates for additional security:

import Foundation

class PinningDelegate: NSObject, URLSessionDelegate {
  let pinnedCertificates: [SecCertificate]

  init(certificates: [Data]) {
    self.pinnedCertificates = certificates.compactMap { data in
      SecCertificateCreateWithData(nil, data as CFData)
    }
  }

  func urlSession(
    _ session: URLSession,
    didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
  ) {
    guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
          let serverTrust = challenge.protectionSpace.serverTrust else {
      completionHandler(.cancelAuthenticationChallenge, nil)
      return
    }

    // Validate the server's trust chain against the system root CAs first
    var secResult = SecTrustResultType.invalid
    let status = SecTrustEvaluate(serverTrust, &secResult)
    guard status == errSecSuccess else {
      completionHandler(.cancelAuthenticationChallenge, nil)
      return
    }

    // Then verify that at least one certificate in the chain matches a pinned certificate
    for i in 0..<SecTrustGetCertificateCount(serverTrust) {
      if let cert = SecTrustGetCertificateAtIndex(serverTrust, i) {
        for pinnedCert in pinnedCertificates {
          if cert == pinnedCert {
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
            return
          }
        }
      }
    }

    // If no pinned certificate matched, reject the connection
    completionHandler(.cancelAuthenticationChallenge, nil)
  }
}

let certData = try Data(contentsOf: URL(fileURLWithPath: "/path/to/server.cer"))
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: PinningDelegate(certificates: [certData]), delegateQueue: nil)

let client = Client(
  endpoint: "https://rabbitmq.example.com:15671/api",
  username: "guest",
  password: "guest",
  session: session
)

Mutual TLS (Mutual Peer Certificate Chain Verification), Client Certificate Configuration

Use client certificates for mTLS (mutual peer certificate chain verification):

class ClientCertificateDelegate: NSObject, URLSessionDelegate {
  let identity: SecIdentity
  let certificate: SecCertificate

  init(pkcs12Data: Data, password: String) throws {
    // Extract client identity and certificate from a PKCS#12 file
    var importResult: CFArray?
    let status = SecPKCS12Import(
      pkcs12Data as CFData,
      [kSecImportExportPassphrase as String: password] as CFDictionary,
      &importResult
    )

    guard status == errSecSuccess,
          let result = importResult as? [[String: Any]],
          let firstItem = result.first,
          let identity = firstItem[kSecImportItemIdentity as String] as? SecIdentity,
          let cert = firstItem[kSecImportItemCertificate as String] as? SecCertificate
    else {
      throw NSError(domain: "ClientCert", code: Int(status))
    }

    self.identity = identity
    self.certificate = cert
  }

  func urlSession(
    _ session: URLSession,
    didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
  ) {
    // Respond only to client certificate requests; delegate all other challenges
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
      let credential = URLCredential(
        identity: identity,
        certificates: [certificate],
        persistence: .forSession
      )
      completionHandler(.useCredential, credential)
    } else {
      completionHandler(.performDefaultHandling, nil)
    }
  }
}

let pkcs12Data = try Data(contentsOf: URL(fileURLWithPath: "/path/to/client.p12"))
let delegate = try ClientCertificateDelegate(pkcs12Data: pkcs12Data, password: "password")
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)

let client = Client(
  endpoint: "https://rabbitmq.example.com:15671/api",
  username: "guest",
  password: "guest",
  session: session
)

Self-Signed Certificates (Testing Only)

⚠️ Self-signed certificate use should be limited to development and testing environments. Consider using tls-gen to generate a self-signed CA and certificate chains for local development.

class SelfSignedDelegate: NSObject, URLSessionDelegate {
  func urlSession(
    _ session: URLSession,
    didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
  ) {
    // IMPORTANT: This effectively disables peer certificate chain verification by accepting any and every server certificate
    // chain. Use this configuration in development and testing environments, NOT in production.
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
       let serverTrust = challenge.protectionSpace.serverTrust {
      completionHandler(.useCredential, URLCredential(trust: serverTrust))
    } else {
      completionHandler(.performDefaultHandling, nil)
    }
  }
}

let session = URLSession(
  configuration: .default,
  delegate: SelfSignedDelegate(),
  delegateQueue: nil
)

let client = Client(
  endpoint: "https://localhost:15671/api",
  username: "guest",
  password: "guest",
  session: session
)

License

Copyright (C) 2025-2026 Michael S. Klishin and Contributors

Licensed under the Apache License, Version 2.0. See LICENSE for details.

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Thu Apr 09 2026 09:55:30 GMT-0900 (Hawaii-Aleutian Daylight Time)