Messaging Service

The PubSub+ Messaging API for Python provides the MessagingService class, which handles all the functionality for interacting with a PubSub+ event broker. To create a MessagingService instance, you must first configure a properties dictionary with the information required to establish a connection to the event broker, including the host details and the authentication scheme.

Creating a Property Dictionary

To connect to an event broker, you need to configure service properties in a dictionary. A properties dictionary can have a number of properties, however it must contain the host and vpn-name keys. The broker properties dictionary is passed to the MessagingServiceClientBuilder to configure the connection to the event broker. There are five categories of properties that can be configured in a broker properties dictionary:

  • solace.messaging.config.solace_properties.service_properties (required for the vpn-name property)
  • solace.messaging.config.solace_properties.transport_layer_properties (required for host property )
  • solace.messaging.config.solace_properties.authentication_properties
  • solace.messaging.config.solace_properties.client_properties
  • solace.messaging.config.solace_properties.transport_layer_security_properties

For more information, see the PubSub+ Messaging API for Python reference.

The code below shows an example of a broker properties dictionary used for establishing a connection to an event broker using basic authentication:

from solace.messaging.config.solace_properties import transport_layer_properties, service_properties, authentication_properties
# ...
broker_props = {
    transport_layer_properties.HOST: "tcps://messaging.solace.cloud:55443",
    service_properties.VPN_NAME: "my_VPN",
    authentication_properties.SCHEME_BASIC_USER_NAME: "my_username",
    authentication_properties.SCHEME_BASIC_PASSWORD: "my_password",
    }

Alternatively if you're running samples, the configuration you want to use can be passed into the client application via a JSON file. The following sample code shows how you can use the json.loads() function to parse JSON data and use it to create a broker properties dictionary:

import json
# ...
# Read the JSON file
with open('/path/to/config.json') as file:
       json_data = file.read()

# Unmarshal JSON into a dictionary
configDict = json.loads(json_data)

# Use from_properties(configDict) to use JSON data when creating your MessagingService instance
messaging_service = MessagingService.builder().from_properties(configDict).build()

For a more detailed example, see the read_config() function in sampler_boot.py in the Solace GitHub repository.

End-to-End Payload Compression

The PubSub+ Python API can perform end-to-end payload compression to allow for:

  • faster message throughput

  • reduced bandwidth usage

  • improved performance in your applications

While end-to-end payload compression creates more work for individual PubSub+ APIs, it enables faster aggregate rates of message publishing and message receiving. The PubSub+ event broker offers transport message compression that compresses the entire message, see Streaming Compressed Connections. However, when you send and receive large messages, compressing the entire message creates a lot of work for the event broker, which can result in slower throughput. If your application needs to send and receive large messages, we recommend you use end-to-end payload compression to improve performance.

  1. Considerations When Compressing Message Payloads

  2. Compressing Message Payloads in the PubSub+ Python API

Considerations When Compressing Message Payloads

The following sections explain what you should be aware of when you compress message payloads with the PubSub+ Messaging APIs.

Use only one form of message compression

We recommend you only use one form of compression, either Streaming Compressed Connections through the event broker or end-to-end payload compression through the APIs. Compressing the same message multiple times can waste resources and usually does not result in smaller message sizes.

Do not use end-to-end payload compression with small messages

End-to-end Payload Compression works best with messages that are a few megabytes in size. Solace does not recommend using message payload compression with small messages, because end-to-end payload compression can actually increase the size of small messages.

Upgrade your publisher and receiver applications

Receiver applications automatically decompress any compressed message payloads only if the API supports message payload compression and is updated to the minimum supported version. If your receiver application does not support message payload compression, this can cause potential errors or exceptions. Make sure you update your publisher and receiver applications to the minimum supported versions for payload compression. For version support information, see Feature Support in PubSub+ Messaging APIs.

End-to-end payload compression limitations

End-to-end payload compression does not currently support PubSub+ Cache.

End-to-end payload compression is not compatible with:

  • SolCache

  • Non-SMF protocols, such as AMQP, HTTP, Kafka and MQTT

If your applications use any of the above, we recommend you do not use end-to-end payload compression.

Compressing Message Payloads in the PubSub+ Python API

Your publisher application can compress the payload of any message before you publish it. To compress a message payload, you must set PAYLOAD_COMPRESSION_LEVEL, which tells the API you want end-to-end payload compression enabled. The payload compression level property can be set to an integer from 0-9:

  • 0—Payload compression is disabled. This is the default setting.

  • 1 - 9—Payload compression is enabled. 1 is the lowest level of compression with the fastest data throughput, and 9 is the highest level of compression with the slowest data throughput.

Your payload compression level should be adjusted according to your network and performance requirements. The following code snippet shows how to set PAYLOAD_COMPRESSION_LEVEL in a broker properties dictionary:

from solace.messaging.config.solace_properties import service_properties
# ...
service_props = {
    # ...
    service_properties.PAYLOAD_COMPRESSION_LEVEL: 9 
}

Establishing a Connection to an Event Broker

A MessagingService object allows the API to establish a connection to the event broker. To create a MessagingService object, do the following:

  1. Call the MessagingService class' builder() function to return a MessagingServiceClientBuilder object.

  2. The MessagingServiceClientBuilder object gives you access to a number of functions that let you customize a MessagingService object. These include the following:

    • from_properties(configuration: dict)—Pass the necessary broker properties dictionary to the MessagingServiceClientBuilder.
    • with_authentication_strategy(authentication_strategy: AuthenticationStrategy)
    • with_transport_security_strategy(transport_layer_security_strategy: TransportSecurityStrategy)
    • with_reconnection_retry_strategy(strategy: RetryStrategy)
  3. Call the build() function on the MessagingServiceClientBuilder object to return a MessagingService object.

  4. Call the connect() function on your MessagingService object to connect to the event broker.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to create a simple MessagingService instance and connect it to an event broker:

# Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds
messaging_service = MessagingService.builder().from_properties(broker_props)\
    .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20, 3)) \
    .with_authentication_strategy(BasicUserNamePassword.of("my_username", "my_password")) \
    .build()

# Blocking connect thread
messaging_service.connect()	

To sever a MessagingService object's connection to the event broker, call disconnect() on it. This also makes the MessagingService object eligible for garbage collection.

Connecting to a Host Event Broker Through Proxies in the Python API

You can use HTTP or SOCKS5 proxies to connect to event brokers inside your private network. Instead of requiring you to give firewall access to each client, you only need to make one firewall exception for the external proxy server, which authenticates the clients to the broker. You may need to use proxies to meet security requirements. Solace supports proxies in most situations.

To set a host use:

transport_layer_properties.HOST

To connect to an event broker through an HTTP or SOCKS protocol version 5 proxy server, the configured host setting must include those parameters required for a standard, direct connection to an event broker, but it must also include a proxy service string:

[Protocol:]Host[:Port][%ProxyService]

Where:

ProxyService—The proxy server that is used to connect to event broker. The proxy service string format is specified as:

[ProxyProtocol]://[username:password@]proxyHost[:proxyPort]

Where:

  • ProxyProtocol—The protocol used to communication with the proxy server. The valid values are:
    • socks5—Connect to the server with the SOCKS Protocol Version 5, RFC 1928 (IETF Standards Track Document).
    • httpc—Connect to the server with the HTTP Connect Protocol, RFC 2817 (IETF Standards Track Document).
  • username:password@—If authentication is required for the proxy server, the username and password may be specified before the proxy host.
  • proxyHost—The IP address (or hostname) of the proxy server.
  • proxyPort—The port to connect to for a connection. If the port number is not specified, the default is port 1080 for SOCKS5 and port 3128 default for HTTP Connect.

Examples:

The following examples show how to connect to an event broker through a proxy server.

  • 192.168.160.28%socks5://192.168.1.1—Connects to an event broker at 192.168.160.28 through a SOCKS5 proxy server at 192.168.1.1.
  • 192.168.160.28%httpc://192.168.1.1—Connects to an event broker at 192.168.160.28 through an HTTP-Connect proxy server at 192.168.1.1.
  • tcps:solace.company.com%socks5://User:PassWord@proxy.company.com:13128—Connect to an event broker at solace.company.com using SSL over TCP through a SOCKS5 proxy server at proxy.company.com, port 13128. Authenticate with the proxy server using username User and password PassWord.
  • http://192.168.160.28:44444%httpc://proxy.company.com:11050—Connect to the event broker at 192.168.160.28, port 44444, using HTTP. Connect through the proxy server at proxy.company.com, port 11050.

Using Transport Layer Security

Transport Layer Security (TLS) allows for encrypted authentication and data transmission between the PubSub+ Python API and a PubSub+ event broker. The Python API supports Transport Layer Security versions. The versions available are TLS 1.1, and TLS 1.2 . The recommended version to use is the most recent version of TLS.

You can use the with_transport_security_strategy() function with the config package to configure the TLS connection properties to use, or whether or not to disable certificate validation entirely. When you use TLS, you must always use the secure TCP  protocol (tcpsor https) in setting the host property for your connection, for example:

broker_props = {
    solace.messaging.config.solace_properties.transport_layer_properties.HOST: "tcps://messaging.solace.cloud:55443",
    solace.messaging.config.solace_properties.service_properties.VPN_NAME: "myVPN",
}

We recommend using certificate validation when configuring your messaging service. The Python API's config module provides the following functions for configuring certificate validation:

  • with_certificate_validation(ignore_expiration: bool, validate_server_name: bool, trust_store_file_path: str, trusted_common_name_list: str)

    • ignore_expiration— When set to true, expired certificates are accepted. This is not recommended because it poses a security risk.
    • validate_server_name— When set to true, the default setting, certificates without the matching host are not accepted.
    • trust_store_file_path— The location of the trust store files. If an empty string is passed, no file path will be set.
    • trusted_common_name_list— A comma-separated list of acceptable common names for matching with server certificates. An empty string will match no names. Modern TLS implementations confirm the subject alternative name in the server certificate, so it is usually unnecessary to set this parameter. If your server certificate cannot be upgraded appropriately, you may use trusted_common_name_list for additional validation of the server certificate.

     

    The following sample code shows the recommended security setup for client applications when you use TLS:

    # Creates a transport security strategy with validation on certificates that excludes dated TLS/SSL protocols			
    transport_security = TLS.create().with_certificate_validation(True, False, "./trusted-store", "") \
                    .with_excluded_protocols(TLS.SecureProtocols.TLSv1,
                                             TLS.SecureProtocols.TLSv1_1,
                                             TLS.SecureProtocols.SSLv3) 
    			
    # Build A messaging service
    messaging_service = MessagingService.builder().from_properties(broker_props)\
                   .with_authentication_strategy(BasicUserNamePassword.of("username", "password")) \
                   .with_transport_security_strategy(transport_security) \
                   .build()	
    
  • without_certificate_validation()—This function configures your TLS connection not to validate server certificates.

    Only use without_certificate_validation() in development environments. We recommend that you never use this function in production environments because it creates a security vulnerability.

    The following sample code shows how to use the without_certificate_validation() function:

    # Creates a transport security strategy without validation on certificates			
    transport_security = TLS.create().without_certificate_validation()
    			
    # Build a messaging service
    messaging_service = MessagingService.builder().from_properties(broker_props)\
                   .with_authentication_strategy(BasicUserNamePassword.of("username", "password")) \
                   .with_transport_security_strategy(transport_security) \
                   .build()						

You can also configure the aspects of the TLS connection using transport_layer_security_properties in a properties dictionary. The TLS connection can be configured using various fields found in solace.messaging.config.solace_properties.transport_layer_security_properties. We recommend that you use the default settings (set to true and enabled) to ensure secure connections for the following properties:

  • solace.messaging.config.solace_properties.transport_layer_security_properties.CERT_REJECT_EXPIRED
  • solace.messaging.config.solace_properties.transport_layer_security_properties.CERT_VALIDATE_SERVERNAME
  • solace.messaging.config.solace_properties.transport_layer_security_properties.CERT_VALIDATED

For more information, see the PubSub+ Messaging API for Python reference.

Authentication

The PubSub+ Messing API for Python supports a number of authentication schemes (or strategies). that you can choose from. The scheme that you choose may depend on the credentials that the connecting client is required to provide. You can use one of the following authentication schemes:

Basic Authentication

Basic authentication is the default client authentication scheme which allows a client to authenticate with an event broker using a client username and password. To specify basic authentication, create an instance of a MessagingService and specify the following as the parameter for the with_authentication_strategy() function:

  • BasicUserNamePassword.of(username: str, password: str)
    • username— The user name to use to create a BasicUserNamePassword object.
    • password—The password to use to create a BasicUserNamePassword object.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to use basic authentication:

# Build a messaging service
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(BasicUserNamePassword.of("username", "password")) \
               .build()	

Kerberos Authentication

The Python API provides support for Kerberos Authentication. Connecting using this function requires you to load a Kerberos Keytab on the broker (see Managing Event Broker Files) and Kerberos authentication must be configured and enabled for any Message VPNs that Kerberos-authenticated clients connect to.

. Call the with_authentication_strategy() function and pass the following as the parameters:

  • Kerberos.of(service_name: str)
    • service_name—A valid Kerberos service name.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to use Kerberos for authentication:

# Build a messaging service
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(Kerberos.of("client_kerberos_service_name")) \
               .build()	

Client Certificate Authentication

To use the Client certificate authentication scheme, the following steps are required:

  1. Configure the host event broker to use TLS connections (see Using Transport Layer Security).

  2. Your application must connect to the broker using TLS.

  3. Enable Client certificate verification on the Message VPN that the application uses to connect.

  4. The client-side certificate must be present in a keystore file and configured using the following:

    • ClientCertificateAuthentication.of(certificate_file: str, key_file: str, key_password: str)

      • certificate_file—The file that contains the client certificate or the client-certificate chain.
      • key_file—The file contains the client private key.
      • key_password—The password if the private key (key_file) is password protected.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to configure client certificate authentication:

# Build a messaging service
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(ClientCertificateAuthentication.of(certificate_file,
                                                                                key_file, 
                                                                                key_password)) \
               .build()	

OAuth 2.0 Authentication

OAuth 2.0 is an open standard for access delegation and authorization. It is commonly used as a mechanism to grant websites or applications access to users' information on other websites without giving them access to sensitive credentials. The OAuth authentication scheme allows access through the use of tokens issued to third-party clients by an authorization server that provides access to Message VPNs on PubSub+ event brokers. To use OAuth 2.0 authentication, configure the host event broker to use TLS connections (see Using Transport Layer Security) and make sure your application connects to the event broker using TLS. For more information, see OAuth Authentication.

The Python API supports different fields that can be sent to the event broker:

  • access_token—A String for applications to make requests for data access.

    and/or

    oidc_id_token—A String for Open ID Connect (OIDC) connections.

  • issuer_identifier—(Optional) A String to identify the appropriate OAuth profile configuration.

OAuth authentication requires an access_token, an oidc_id_token, or both to be enabled using this function:

  • OAuth2.of(access_token: str, oidc_id_token: str, issuer_identifier: str)
    • At least one of access_token or oidc_id_token must be provided. Optionally, issuer_identifier can be provided. If any of the parameters is not required, None can be passed.

For more information, see the PubSub+ Messaging API for Python reference.

The following sample code shows how to use OAuth authentication with OpenID Connect (OIDC):

# Configure service access to use a Open ID connect authentication with an ID token and an optional access token.
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(OAuth2.of("my_access_token", 
                                                       "my_oidc_token",
                                                       None)) \
               .with_transport_security_strategy(transport_security) \
               .build()	
# Configure service access to use OAuth 2 authentication with an access token and an optional issuer identifier.
messaging_service = MessagingService.builder().from_properties(broker_props)\
               .with_authentication_strategy(OAuth2.of("my_access_token",
                                                       None, 
                                                       "my_issuer_identifier")) \
               .with_transport_security_strategy(transport_security) \
               .build()	

Required Event Broker Configurations

For a client application to use an OAuth authentication scheme, a Message VPN OAuth profile must be configured for the host event broker and OAuth authentication must be configured and enabled for any Message VPNs to which a client connects. For more information, see OAuth Authentication.

Refreshing Expired OAuth Tokens

By default, event brokers disconnect clients when their tokens expire (see Disconnect on Token Expiration). When a client session is disconnected, the client application tries to reconnect a number of times using the same OAuth token based on the RECONNECTION_ATTEMPTS property. If the reconnection attempts are exceeded, then the connection cannot be re-established and the client application must recreate the session with all its subscriptions.

To avoid needing to destroy and recreate the session, the client should update the OAuth token before it expires, or while reconnecting. To update the OAuth token, use the update_property(name: str, value: Any) function, which allows you to set a modifiable service property after the creation of the MessagingService object. The first parameter is one of the following strings and the second parameter is the token:

  • messaging_service.update_property(authentication_properties.SCHEME_OAUTH2_ACCESS_TOKEN, new_access_token) to update an expired access token
  • messaging_service.update_property(authentication_properties.SCHEME_OAUTH2_OIDC_ID_TOKEN, new_id_token) to update an expired ID token

Modifiable service properties may not update immediately and may require the next reconnection attempt to update.

Refreshing the expired token can happen while:

  • the client application is connected. In this case, the client contacts the authentication server to refresh the token and modifies the session to use the updated token the next time the API connects to the event broker.

  • the client application is reconnecting. The reconnecting event includes a diagnostic subCode. If this subCode is Login Failure, this may indicate that your token has expired. In this case, the API tries to reconnect (using the expired token). The client then contacts the authentication server to refresh the token and modifies the session to use the updated token the next time the API attempts to reconnect to the event broker.

In general, it is better if the client application is aware of potential token expiry and refreshes the token before it expires.

When the client application's session is reconnected, the Python API re-applies the client application's direct subscriptions. If there is a change in the ACLs as a result of the refreshed token, the subscriptions may be rejected by the event broker.