├── howtos ├── __init__.py ├── pubsub │ ├── __init__.py │ ├── how_to_direct_publish_business_obj.py │ ├── how_to_direct_publish_consume_business_obj.py │ ├── how_to_consume_persistent_message_with_auto_acknowledgement.py │ ├── how_to_publish_health_check.py │ ├── how_to_direct_consume_with_share_name.py │ ├── how_to_add_and_remove_subscription.py │ ├── how_to_use_share_name_on_request_reply.py │ ├── how_to_consume_persistent_message_with_negative_acknowledgement.py │ ├── how_to_use_request_reply_pattern.py │ ├── how_to_use_SolaceSDTTypes_and_messages.py │ └── how_to_use_publish_with_back_pressure.py ├── solbroker_properties.json ├── how_to_set_core_api_log_level.py ├── how_to_configure_payload_compression.py ├── fixtures │ ├── public_root_ca.crt │ └── api-client.pem ├── how_to_access_api_metrics.py ├── how_to_set_partition_key_on_message.py ├── how_to_for_unusual_situtations.py ├── sampler_master.py ├── how_to_update_service_properties.py ├── SEMPv2 │ ├── semp_client.py │ └── semp_endpoint.py ├── how_to_connect_messaging_service.py ├── how_to_trigger_replay_persistent_message.py ├── how_to_configure_authentication.py ├── how_to_configure_transport_layer_security.py ├── how_to_handle_service_interruption_and_failures.py ├── how_to_configure_service_connection_reconnection_retries.py └── how_to_request_cached_data.py ├── .gitignore ├── requirements.txt ├── README.md └── patterns ├── direct_publisher.py ├── direct_receiver.py ├── guaranteed_publisher.py ├── TLS_connection.py ├── direct_requestor.py ├── guaranteed_receiver.py ├── direct_requestor_blocking.py ├── direct_replier.py ├── hello_world_pubsub.py ├── direct_processor.py └── guaranteed_processor.py /howtos/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /howtos/pubsub/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .vscode 3 | .DS_Store 4 | __pycache__ 5 | .python-version 6 | trusted-store 7 | pubsubplus-python-client/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | autopep8==1.6.0 2 | certifi==2020.12.5 3 | chardet==4.0.0 4 | idna==2.10 5 | pycodestyle==2.8.0 6 | requests==2.25.1 7 | solace-pubsubplus==1.9.0 8 | toml==0.10.2 9 | urllib3==1.26.4 10 | grpcio==1.60.0 11 | opentelemetry-exporter-otlp-proto-grpc==1.22.0 12 | opentelemetry-sdk==1.22.0 13 | opentelemetry-api==1.22.0 14 | -------------------------------------------------------------------------------- /howtos/solbroker_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "solbrokerProperties": { 3 | "solace.messaging.transport.host": "tcp://localhost:55555", 4 | "solace.messaging.transport.host.secured": "tcps://localhost:55443", 5 | "solace.messaging.transport.host.compressed": "tcp://localhost:55003", 6 | "solace.messaging.service.vpn-name": "default", 7 | "solace.messaging.authentication.basic.username": "default", 8 | "solace.messaging.authentication.basic.password": "default" 9 | }, 10 | "semp": { 11 | "semp.hostname": "https://localhost:1943", 12 | "semp.username": "admin", 13 | "semp.password": "admin" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /howtos/how_to_set_core_api_log_level.py: -------------------------------------------------------------------------------- 1 | """sampler module on how to set the core api log level""" 2 | 3 | from solace.messaging.messaging_service import MessagingService 4 | 5 | 6 | class HowToSetCoreApiLogLevel: 7 | """class to set the core api log level""" 8 | 9 | @staticmethod 10 | def set_core_log_level_by_user(level): 11 | """method to set the core api log level by the user 12 | 13 | Args: 14 | level: user defined log level 15 | """ 16 | MessagingService.set_core_messaging_log_level(level) 17 | 18 | @staticmethod 19 | def run(): 20 | """method to run the sampler""" 21 | HowToSetCoreApiLogLevel.set_core_log_level_by_user(level="DEBUG") 22 | 23 | 24 | if __name__ == '__main__': 25 | HowToSetCoreApiLogLevel.run() 26 | -------------------------------------------------------------------------------- /howtos/how_to_configure_payload_compression.py: -------------------------------------------------------------------------------- 1 | from solace.messaging.messaging_service import MessagingService 2 | from solace.messaging.config.solace_properties import service_properties 3 | from howtos.sampler_boot import SamplerBoot 4 | 5 | boot = SamplerBoot() 6 | 7 | class HowToConfigurePayloadCompression: 8 | 9 | @staticmethod 10 | def run(): 11 | """method for configuring payload compression. 12 | 13 | This value is configured using a dictionary with the range of 0-9. 14 | Default value: 0 (disabled) 15 | """ 16 | try: 17 | # Set broker properties 18 | props = boot.broker_properties() 19 | 20 | # Set payload compression to 9 (maximum compression) 21 | props[service_properties.PAYLOAD_COMPRESSION_LEVEL] = 9 22 | 23 | messaging_service = MessagingService.builder().from_properties(props) \ 24 | .build() 25 | return messaging_service.connect() 26 | 27 | except Exception as exception: 28 | print(exception) 29 | 30 | if ('__name__' == '__main__'): 31 | HowToConfigurePayloadCompression.run() 32 | 33 | """ 34 | Now that payload compression is enabled on the session, any message 35 | published on the session with a non-empty binary attachment will be 36 | automatically compressed. Any receiver that supports payload compression 37 | will automatically decompress the message if it is compressed. 38 | """ 39 | -------------------------------------------------------------------------------- /howtos/fixtures/public_root_ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID1zCCAr+gAwIBAgIJAMuIdFAJxdFHMA0GCSqGSIb3DQEBCwUAMIGBMQswCQYD 3 | VQQGEwJDQTELMAkGA1UECAwCT04xDzANBgNVBAcMBkthbmF0YTEPMA0GA1UECgwG 4 | U29sYWNlMQwwCgYDVQQLDANBUEkxEjAQBgNVBAMMCXB1YmxpY2FmdzEhMB8GCSqG 5 | SIb3DQEJARYSc3VwcG9ydEBzb2xhY2UuY29tMB4XDTIxMDgxMDE1NTQ0MVoXDTMx 6 | MDgwODE1NTQ0MVowgYExCzAJBgNVBAYTAkNBMQswCQYDVQQIDAJPTjEPMA0GA1UE 7 | BwwGS2FuYXRhMQ8wDQYDVQQKDAZTb2xhY2UxDDAKBgNVBAsMA0FQSTESMBAGA1UE 8 | AwwJcHVibGljYWZ3MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QHNvbGFjZS5jb20w 9 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1q8sYWnHBBz0+VDIsmfkl 10 | e9HQUS1gP6eyWGXpZGFDf1Kpzpf4NzHiwt+ZcwtxV/NJhmU4Bwg6+OMagPsiF662 11 | lxmzfuIp9QQ+VHRNMFPDsYl4sIs7aoWI4jJJn9ow8AEwuG6dd/7yNTveyhzivVLk 12 | SQ6pXVu/MsIQUQOjKedIi1eK60ZDVmiy+fkeMDqWAier3SKbi/wqRHypXvhySJ5y 13 | LZgPHHEuP1EXTKP9BmRCSE2hK9IBDWXMuqsZordddeu3lG936GFnnIx10PUHarBf 14 | O8C8lAEyu+3zrVxb6zVGwjjPOP2Xt0uNJlMfzVi2HavwoEna4w/N0Roilf+64pFv 15 | AgMBAAGjUDBOMB0GA1UdDgQWBBS/Kh6mNI8nA4PD1G/mkqFb1yqkgzAfBgNVHSME 16 | GDAWgBS/Kh6mNI8nA4PD1G/mkqFb1yqkgzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 17 | DQEBCwUAA4IBAQAZObRu2FOoYSvfvDDDBe0/GEWU5CytSlueplxXRnaFqUHdi6TU 18 | guYw8b9G3IP4oZv6xYrh+zjrtgc1TXv/Zy+ZkgjATMMSTf+t9Nz5aVhDUH4KECkr 19 | sDcfrxl59jCWu+5M4m7O1HysOJffgF+7fwGyi0qEuwMD46bE3hZrDcnSd+Nuai2+ 20 | SxCYaWtreme+BgaKDe91GpTPoyQiT8rBU84dAT9h8nI/ZI0+SvsxbvlS+NnVGvaN 21 | GBm8DX58PCL/6IaQ1Ni7c2Rs+tt6hrITYfzSEVoyKow5c/v3OXpXcnI9JvUJP8mU 22 | g+fZgr4Uf1cm1nNxsX5O1Q9fbDx0WMEd3k3o 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /howtos/how_to_access_api_metrics.py: -------------------------------------------------------------------------------- 1 | """sampler module for accessing the api metrics""" 2 | 3 | from solace.messaging.messaging_service import MessagingService 4 | from solace.messaging.utils.manageable import ApiMetrics, Metric 5 | from howtos.sampler_boot import SamplerBoot 6 | 7 | boot = SamplerBoot() 8 | 9 | 10 | class HowToAccessApiMetrics: 11 | """class containing the methods for accessing the Api metrics""" 12 | 13 | @staticmethod 14 | def access_individual_api_metrics(service: "MessagingService", metric_of: Metric): 15 | """method implies on how to access individual API metrics using messaging service instance 16 | Args: 17 | metric_of: 18 | service: service connected instance of a messaging service, ready to be used 19 | 20 | """ 21 | metrics: "ApiMetrics" = service.metrics() 22 | metrics_result = metrics.get_value(metric_of) 23 | print(f'API metric [{metric_of}]: {metrics_result}\n') 24 | 25 | @staticmethod 26 | def reset_api_metrics(service: "MessagingService"): 27 | """ 28 | method to reset API metrics using messaging service instance 29 | 30 | Args: 31 | service: service connected instance of a messaging service, ready to be used 32 | 33 | """ 34 | metrics: "ApiMetrics" = service.metrics() 35 | metrics.reset() 36 | print('Reset API METRICS - Done') 37 | 38 | @staticmethod 39 | def to_string_api_metrics(service: "MessagingService"): 40 | """method implies on how to get String representation of all current API metrics using 41 | messaging service instance 42 | 43 | Args: 44 | service: service connected instance of a messaging service, ready to be used 45 | 46 | """ 47 | metrics: "ApiMetrics" = service.metrics() 48 | print(f'API metrics[ALL]: {metrics}\n') 49 | 50 | @staticmethod 51 | def run(): 52 | try: 53 | """method to run the sampler""" 54 | service = MessagingService.builder().from_properties(boot.broker_properties()).build() 55 | service.connect() 56 | finally: 57 | service.disconnect() 58 | 59 | 60 | if __name__ == '__main__': 61 | HowToAccessApiMetrics().run() 62 | -------------------------------------------------------------------------------- /howtos/how_to_set_partition_key_on_message.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module describes only some of the ways that the queue partition key can be set. 3 | It can also be set using the key - value pattern used for other properties 4 | elsewhere in the API. 5 | """ 6 | 7 | from solace.messaging.messaging_service import MessagingService 8 | from solace.messaging.config.solace_constants import message_user_property_constants 9 | from howtos.sampler_boot import SamplerBoot 10 | 11 | boot = SamplerBoot() 12 | 13 | class HowToSetPartitionKey: 14 | """Class containing methods for setting partition keys on outbound messages""" 15 | 16 | @staticmethod 17 | def set_queue_partition_key_using_with_property(queue_partition_key_value: str, messaging_service: "MessagingService"): 18 | """ 19 | Set the queue partition key on the outbound message using the `with_property()` builder method. 20 | """ 21 | 22 | payload = "my_payload" 23 | outbound_message = messaging_service \ 24 | .message_builder() \ 25 | .with_property(message_user_property_constants.QUEUE_PARTITION_KEY, queue_partition_key_value) \ 26 | .build(payload) 27 | return outbound_message 28 | # This message can later be published 29 | 30 | @staticmethod 31 | def set_queue_partition_key_using_from_properties(queue_partition_key_value: str, messaging_service: "MessagingService"): 32 | """ 33 | Set the queue partition key on the outbound message using the `from_properties()` builder method. 34 | """ 35 | 36 | payload = "my_payload" 37 | additional_properties = {message_user_property_constants.QUEUE_PARTITION_KEY, queue_partition_key_value} 38 | outbound_message = messaging_service \ 39 | .message_builder() \ 40 | .from_properties(additional_properties) \ 41 | .build(payload) 42 | return outbound_message 43 | # This message can later be published 44 | 45 | @staticmethod 46 | def run(): 47 | """Method to run the sampler""" 48 | try: 49 | service = MessagingService.builder().from_properties(boot.broker_properties()).build() 50 | service.connect() 51 | partition_key = "key" 52 | 53 | with_property_message_partition_key = HowToSetPartitionKey().set_queue_partition_key_using_with_property(partition_key, service) 54 | from_properties_message_partition_key = HowToSetPartitionKey().set_queue_partition_key_using_from_properties(partition_key, service) 55 | finally: 56 | service.disconnect() 57 | 58 | 59 | if __name__ == '__main__': 60 | HowToSetPartitionKey().run() 61 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_direct_publish_business_obj.py: -------------------------------------------------------------------------------- 1 | """ Run this file to publish a business object using direct message publisher""" 2 | import pickle 3 | 4 | from solace.messaging.messaging_service import MessagingService 5 | from solace.messaging.resources.topic import Topic 6 | from solace.messaging.utils.converter import ObjectToBytes 7 | from howtos.sampler_boot import SamplerBoot, SolaceConstants, MyData 8 | 9 | constants = SolaceConstants 10 | boot = SamplerBoot() 11 | 12 | 13 | class PopoConverter(ObjectToBytes): # plain old python object - popo 14 | """sample converter class""" 15 | 16 | def to_bytes(self, src) -> bytes: 17 | """This Method converts the given business object to bytes""" 18 | 19 | object_to_byte = pickle.dumps(src) 20 | return object_to_byte 21 | 22 | 23 | class HowToDirectMessagePublishBusinessObject: 24 | """this class contains methods to publish a business object""" 25 | 26 | @staticmethod 27 | def direct_message_publish_outbound_business_obj(messaging_service: MessagingService, destination, message_obj, 28 | converter): 29 | """ to publish outbound message from a custom object supplied with its own converter""" 30 | try: 31 | direct_publish_service = messaging_service.create_direct_message_publisher_builder().\ 32 | on_back_pressure_reject(buffer_capacity=0).build() 33 | publish_start = direct_publish_service.start_async() 34 | publish_start.result() 35 | outbound_msg = messaging_service.message_builder() \ 36 | .with_application_message_id(constants.APPLICATION_MESSAGE_ID) \ 37 | .build(message_obj, converter=converter) 38 | direct_publish_service.publish(destination=destination, message=outbound_msg) 39 | finally: 40 | direct_publish_service.terminate() 41 | 42 | @staticmethod 43 | def run(): 44 | """ 45 | this method creates a messaging service connection and publishes the message 46 | """ 47 | service = MessagingService.builder().from_properties(boot.broker_properties()).build() 48 | try: 49 | service.connect() 50 | destination_name = Topic.of(constants.TOPIC_ENDPOINT_DEFAULT) 51 | 52 | print("Execute Direct Publish - Generics Outbound Message") 53 | HowToDirectMessagePublishBusinessObject() \ 54 | .direct_message_publish_outbound_business_obj(service, destination_name, 55 | message_obj=MyData('some value'), 56 | converter=PopoConverter()) 57 | finally: 58 | service.disconnect() 59 | 60 | 61 | if __name__ == '__main__': 62 | HowToDirectMessagePublishBusinessObject().run() 63 | -------------------------------------------------------------------------------- /howtos/how_to_for_unusual_situtations.py: -------------------------------------------------------------------------------- 1 | """sampler module for reconnection strategy""" 2 | 3 | from solace.messaging.config.retry_strategy import RetryStrategy 4 | from solace.messaging.messaging_service import MessagingService 5 | from solace.messaging.messaging_service import ReconnectionListener, ServiceEvent, ReconnectionAttemptListener 6 | from howtos.sampler_boot import SolaceConstants, SamplerBoot 7 | 8 | constants = SolaceConstants 9 | boot = SamplerBoot() 10 | 11 | 12 | class ReconnectionListenerImpl(ReconnectionListener): 13 | """reconnection listener impl class""" 14 | 15 | def on_reconnected(self, e: ServiceEvent): 16 | """callback executed in situation after connection goes down and reconnection is attempted""" 17 | print("\nIn ReconnectionListenerImpl callback ") 18 | print(f"Timestamp: {e.get_time_stamp()}") 19 | print(f"Uri: {e.get_broker_uri()}") 20 | print(f"Error cause: {e.get_cause()}") 21 | print(f"Message: {e.get_message()}") 22 | 23 | 24 | class ReconnectionAttemptListenerImpl(ReconnectionAttemptListener): 25 | """reconnection attempt listener impl class""" 26 | 27 | def on_reconnecting(self, event: ServiceEvent): 28 | """ callback executed in situation after connection goes down and reconnection is attempted""" 29 | print("\n ************************************") 30 | print("in ReconnectionAttemptListenerImpl callback ") 31 | 32 | print("time stamp : ", event.get_time_stamp()) 33 | print("uri : ", event.get_broker_uri()) 34 | print("error cause : ", event.get_cause()) 35 | print("message : ", event.get_message()) 36 | print("\n ************************************") 37 | 38 | 39 | class HowToConnectMessagingServiceWithReConnectionStrategy: 40 | """class for reconnection strategy sampler""" 41 | @staticmethod 42 | def when_tcp_reconnection_happens(): 43 | """method to test the listener service event""" 44 | attempt_listener = ReconnectionAttemptListenerImpl() 45 | listener = ReconnectionListenerImpl() 46 | messaging_service = MessagingService.builder().from_properties(boot.broker_properties()) \ 47 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(3, 3)).build(). \ 48 | add_reconnection_attempt_listener(attempt_listener). \ 49 | add_reconnection_listener(listener) 50 | messaging_service.connect() 51 | # If message disconnect occurs due to any network issue, service will automatically get connects again 52 | # Apply some logic to simulate network issue, upon message service disconnect, 53 | # this listener will notify once it is automatically connect/disconnect 54 | messaging_service.remove_reconnection_listener(listener) 55 | 56 | @staticmethod 57 | def run(): 58 | """ 59 | :return: Success or Failed according to connection established 60 | """ 61 | HowToConnectMessagingServiceWithReConnectionStrategy.when_tcp_reconnection_happens() 62 | 63 | 64 | if __name__ == '__main__': 65 | HowToConnectMessagingServiceWithReConnectionStrategy().run() 66 | -------------------------------------------------------------------------------- /howtos/fixtures/api-client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDtzCCAp+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCQ0Ex 3 | CzAJBgNVBAgMAk9OMQ8wDQYDVQQHDAZLYW5hdGExDzANBgNVBAoMBlNvbGFjZTEM 4 | MAoGA1UECwwDQVBJMRIwEAYDVQQDDAlwdWJsaWNhZncxITAfBgkqhkiG9w0BCQEW 5 | EnN1cHBvcnRAc29sYWNlLmNvbTAeFw0yMTA4MTAxNjE3NTJaFw0zMTA4MDgxNjE3 6 | NTJaMH8xCzAJBgNVBAYTAkNBMQswCQYDVQQIDAJPTjEPMA0GA1UEBwwGS2FuYXRh 7 | MQ8wDQYDVQQKDAZTb2xhY2UxDDAKBgNVBAsMA0FQSTEQMA4GA1UEAwwHZGVmYXVs 8 | dDEhMB8GCSqGSIb3DQEJARYSc3VwcG9ydEBzb2xhY2UuY29tMIIBIjANBgkqhkiG 9 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx0APNaw1lBcY8AMg0k2zzipMva4AQzHbKzGN 10 | Db6I1mGqvSMq31Z6KK/6Q6CGPtpiz8htwIWXo0TV/XADJLk0f9h1en6prspLhZAw 11 | bHqmVLvBqz9t07JsceuavY34wCnQviuKatH38G/3kbonJp8zdecRH1LpOQIuKrdc 12 | ilDEpzMlgAzKc/tylSYbl7FDstd8xDA7NFM++YWTpmnussJErPzkKQKV+7njKd4i 13 | OPsntLN7J5SOlgcyGBbzTU+EnEuNxoTyYWW3Cu6dLVmINmv0LufoFobz2n+OPKJr 14 | 0lZX2wcPFZO2XIQbgCOIyhblj9TbdRFTwUr6x9msNEUKoqY8rwIDAQABozswOTA3 15 | BgNVHREEMDAugglsb2NhbGhvc3SCCXNvbGJyb2tlcocEfwAAAYcQAAAAAAAAAAAA 16 | AAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAX/jKTwnhAGgLW/Pz7zlpsLIs7Ihj 17 | UCOnvtGeTiyzskonFRfXPBbiQXg/vP7MHr20dlHChd/94ItCJYnipQeYH6lHJK0i 18 | qZOrxrOq8MxeVhTHtjT7ldO7m8xQ1sHBwZqemnNFWhnBbsskBnTmOWwmR+u6yrGJ 19 | 3DNFjYQKg4q4QCVcRLhq1N0l1CTzcGWak6nu8qtIPd9PfyEvBU6jEOtbhoYtob+n 20 | d7vV0WWPnfO/Ct50Cu0kJC/6hWfjPHAPmfbmMP9WMn8DrNkL5Y1VEkYftn68JjFZ 21 | DCVBVHDqPi+aRu5AMeKLcxsL6wMtAfqyv5/tqaqldtqvev4mmLDXelA8fQ== 22 | -----END CERTIFICATE----- 23 | -----BEGIN RSA PRIVATE KEY----- 24 | Proc-Type: 4,ENCRYPTED 25 | DEK-Info: AES-256-CBC,FF77C642D870F3B696C8E1F7779047F3 26 | 27 | hOTNrs4FXQ9AUsXKocqumqPC38Zdyc5x0KfpgV4bnXmCmnTj0NehXfOUf0dXFT9M 28 | trIOE3BI4XBFMs0//+3I2/+x5mmbhH1wdQwQ++7incQyeem/0F2r8Qy6DdFnmx4h 29 | fLqaosS+MdOyoIha9rixpLEnMfbMdAm7/ldK4GKUcVv3JEXwGL8bm7BNZ4Pu1DkP 30 | MXLvZi/NW2Qyf90831STVtSZUkNHQVFfVkM5sIHCDf8x+B5M3GPcHY/gUzP+wEN0 31 | JaXdFQnC6bgXHULkhpSPCxpcZUG97rZXpQUi17ypql2l8cJklD7MlLL50HTwak9v 32 | DtPGXTjrD3XRHK2DMvzQ9TgxtTx84JTYj4wBXGCEMJA848KX72xvkHzXXz+PYLiu 33 | bGyVdtqvJka61VZLBzSUFvmOpgk5XHYD5QC0a7FT60nxxVEM2+wNnCdTZO/vyFDu 34 | QoqY3LKqQui/qYHhPibu+IFGRk4sIEHqTmn08/9OYTzjEYQGHtmO3ilNBfNsQJ0J 35 | 0QY40MFPYBnKbZJi/NMJCtAqiQPE9aoOI4Zij9o8+96F/1QnR2hMn3oOSwt50Wuq 36 | qqhzXO81Jg6/8m4YmhX8QlVQwV3QBN3VmM/J3X6YMkv8MwF3U3icURY3iqzgHbmz 37 | Hwik1yL/M9GcC9e+BVFNKuwI+uhTqlOu65rjBdevqOH7fXwcWlLU85r/WhFPdiLw 38 | vsMrZL+ZgbwYjD2x4wKK8NQRmIy228XnyK6i3J5/I9CmcCJ5Syl+rNWmuNJVIdeS 39 | JQ8H+DaDJ/Eqej/uLj1Vzf/tDj7PLNO1S3+EdGXgQ3Mn6oMy55Plh4BUMuFuqmSk 40 | maC6UERNqVHMf2RQVayVA8qsZrMbpdUu40XnAam6IJbtvDA/O1EYJf2b1Beeus4g 41 | 3d4+3OYysLt4mJzPLat22pZJCPKnGJ1IPkycYDpPuY78aszNDjrpqwtH3LB+u5vn 42 | WeEzG5W63a0B84Xb5QNrEVlkUzSaD4suNOGct3xGfGyuLRdRHWWPw0Ld+t78bJvR 43 | cwFP1ZfdSHCteWBg9LGRfF5fAlaM+oGt8oJYkB3g0Pa/jFGW23brpKtGKB/sKm1a 44 | oXKIgJ88IKONPhumtx2xgQCeX/YsZOVe3259yAHbGhIP4qZPdrUitw3q87/7q7Bv 45 | Iqgo/rQQAD2R0pQN75H0yw/6h3VY27D9TnpC8X03xy/qJKrh4a4stQMnZ7lViQVH 46 | o+1Ttvmr2joG6wXAzvD5cI78QEHha4f7edMMH8NFaak24YgU3WtzYOjm21qF8iuO 47 | 5uBEa3XIUeyqhOr08V/j9jEAYq946luEsm8vjhUsotQp7+6e0PugaNCdNg9BFxKr 48 | Q835E5wO+Wxo9109EILB3bIfryAGeuwdMVZVAOc8WenMM+NLqRw1RyttYcDYqQ0T 49 | ASzS+PMp6UHsisSL5mRYisxYVJaJLBgBw46+fEc1kW0GzS7lNZY1OCFnR1ssoVDg 50 | LmFWnVHdGR1GRJgoF1I+EZ3DG5GoIIEThUpVIlK/YpEEittT9X0AhR5Eyvil2CtI 51 | RzGINvGQyPRB2ZiyBBdwe2PhP9G4ZM8KBn12FFBxhisDr4/Fy2B4uP887iFGiwCX 52 | -----END RSA PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /howtos/sampler_master.py: -------------------------------------------------------------------------------- 1 | """this module is used for running the samplers connect messaging service and direct message publisher""" 2 | import sys 3 | import os 4 | sys.path.append(os.path.split(os.getcwd())[0]) 5 | 6 | import urllib3 7 | 8 | from solace.messaging.messaging_service import MessagingService 9 | from howtos.how_to_access_api_metrics import HowToAccessApiMetrics 10 | from howtos.how_to_configure_authentication import HowToConfigureAuthentication 11 | from howtos.how_to_configure_service_connection_reconnection_retries import HowToConnectWithDifferentStrategy 12 | from howtos.how_to_configure_transport_layer_security import HowToConnectWithTls 13 | from howtos.how_to_connect_messaging_service import HowToConnectMessagingService 14 | from howtos.how_to_for_unusual_situtations import HowToConnectMessagingServiceWithReConnectionStrategy 15 | from howtos.how_to_set_core_api_log_level import HowToSetCoreApiLogLevel 16 | from howtos.pubsub.how_to_direct_consume_message import HowToDirectConsumeSampler 17 | from howtos.pubsub.how_to_direct_consume_with_share_name import \ 18 | HowToDirectConsumeShareNameSampler 19 | from howtos.pubsub.how_to_direct_publish_consume_business_obj import \ 20 | HowToDirectConsumeBusinessObjectSampler 21 | from howtos.pubsub.how_to_direct_publish_message import HowToDirectPublishMessage 22 | from howtos.pubsub.how_to_publish_health_check import \ 23 | HowToDirectMessagingHealthCheckSampler 24 | from howtos.pubsub.how_to_use_SolaceSDTTypes_and_messages import HowToWorkWithSolaceSDTTypesAndMessages 25 | from howtos.pubsub.how_to_use_publish_with_back_pressure import \ 26 | HowToDirectPublishWithBackPressureSampler 27 | from howtos.pubsub.how_to_use_request_reply_pattern import HowToUseRequestReplyPattern 28 | from howtos.pubsub.how_to_use_share_name_on_request_reply import HowToUseShareNameWithRequestReplyPattern 29 | from howtos.sampler_boot import SamplerUtil, SamplerBoot 30 | 31 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # we are suppressing explicitly about the warning 32 | 33 | 34 | class SamplerMaster: 35 | """this class is used to run the multiple samplers""" 36 | 37 | @staticmethod 38 | def connect_messaging_service(): 39 | messaging_service = MessagingService.builder().from_properties(SamplerBoot().broker_properties()).build() 40 | messaging_service.connect() 41 | return messaging_service 42 | 43 | @staticmethod 44 | def run_samplers(): 45 | """method to run all the samplers""" 46 | HowToUseShareNameWithRequestReplyPattern.run() 47 | HowToUseRequestReplyPattern.run() 48 | HowToWorkWithSolaceSDTTypesAndMessages().run() 49 | HowToConnectMessagingService().run() 50 | HowToConfigureAuthentication.run() 51 | HowToConnectWithDifferentStrategy().run() 52 | HowToConnectWithTls.run() 53 | HowToDirectPublishMessage().run() 54 | HowToDirectMessagingHealthCheckSampler().run() 55 | HowToDirectPublishWithBackPressureSampler().run() 56 | HowToDirectConsumeBusinessObjectSampler().publish_and_subscribe() 57 | HowToAccessApiMetrics().run() 58 | HowToDirectConsumeSampler.run() 59 | HowToDirectConsumeShareNameSampler().publish_and_subscribe() 60 | #HowToSetCoreApiLogLevel.run() 61 | 62 | 63 | if __name__ == '__main__': 64 | boot = SamplerBoot() 65 | broker_props = boot.broker_properties() 66 | semp_config = boot.read_semp_configuration() 67 | SamplerUtil.cert_feature(semp_props=semp_config, broker_props=broker_props) 68 | 69 | SamplerMaster.run_samplers() 70 | -------------------------------------------------------------------------------- /howtos/how_to_update_service_properties.py: -------------------------------------------------------------------------------- 1 | """module on how to update service properties on a messaging service""" 2 | 3 | from solace.messaging.config.solace_properties import authentication_properties 4 | from solace.messaging.config.authentication_strategy import OAuth2 5 | from solace.messaging.config.transport_security_strategy import TLS 6 | from solace.messaging.messaging_service import MessagingService 7 | 8 | from howtos.sampler_boot import SamplerBoot 9 | 10 | MY_TRUST_STORE = "" 11 | 12 | MY_FIRST_OIDC_ID_TOKEN = "" 13 | MY_FIRST_ACCESS_TOKEN = "" 14 | 15 | MY_NEW_OIDC_ID_TOKEN = "" 16 | MY_NEW_ACCESS_TOKEN = "" 17 | 18 | class HowToUpdateServiceProperties: 19 | """ 20 | This class contains methods used to update service properties, particularly for updating the 21 | OAuth2 tokens used for authentication. 22 | """ 23 | @staticmethod 24 | def update_oauth2_tokens(messaging_service: MessagingService, new_access_token: str, new_id_token: str): 25 | """ 26 | The new access token is going to be used for authentication to the broker after broker disconnects 27 | a client (i.e due to old token expiration). 28 | This token update happens during the next service reconnection attempt. 29 | There will be no way to signal to the user if new token is valid. When the new token is not valid, 30 | then reconnection will be retried for the remaining number of times or forever if configured so. 31 | Usage of ServiceInterruptionListener and accompanied exceptions if any can be used to determine 32 | if token update during next reconnection was successful. 33 | 34 | Raises: 35 | IllegalArgumentError: If the specified property cannot be modified. 36 | IllegalStateError: If the specified property cannot 37 | be modified in the current service state. 38 | PubSubPlusClientError: If other transport or communication related errors occur. 39 | """ 40 | messaging_service.update_property(authentication_properties.SCHEME_OAUTH2_ACCESS_TOKEN, new_access_token) 41 | messaging_service.update_property(authentication_properties.SCHEME_OAUTH2_OIDC_ID_TOKEN, new_id_token) 42 | 43 | @staticmethod 44 | def create_messaging_service_with_oauth_authentication() -> 'MessagingService': 45 | """ 46 | Create a messaging service configured for OAuth2 authentication 47 | """ 48 | tls = TLS.create() \ 49 | .with_certificate_validation(True, validate_server_name=False, trust_store_file_path=MY_TRUST_STORE) 50 | 51 | messaging_service = MessagingService \ 52 | .builder() \ 53 | .from_properties(SamplerBoot.secured_broker_properties()) \ 54 | .with_authentication_strategy(OAuth2.of(oidc_id_token=MY_FIRST_OIDC_ID_TOKEN, 55 | access_token=MY_FIRST_ACCESS_TOKEN)) \ 56 | .with_transport_security_strategy(tls) \ 57 | .build() 58 | 59 | return messaging_service 60 | 61 | @staticmethod 62 | def run(): 63 | """ 64 | Create a messaging service, connect that service using OAuth2 authentication, 65 | update the OAuth tokens, and disconnect the service. 66 | """ 67 | # Create the service with OAuth2 authentication, and establish the inital connection 68 | messaging_service = HowToUpdateServiceProperties.create_messaging_service_with_oauth_authentication() 69 | messaging_service.connect() 70 | 71 | # Update the service with the new OAuth tokens 72 | HowToUpdateServiceProperties.update_oauth2_tokens(messaging_service, MY_NEW_ACCESS_TOKEN, MY_NEW_OIDC_ID_TOKEN) 73 | 74 | # Once the original tokens expire, the broker will trigger a reconnection on the service, and the new 75 | # tokens will be used to re-authenticate the connection. 76 | 77 | # Once the application has finished, disconnect the messaging service 78 | messaging_service.disconnect() 79 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_direct_publish_consume_business_obj.py: -------------------------------------------------------------------------------- 1 | """ Run this file to publish and consume a business object using direct message publisher and receiver, 2 | also how to build the business object and retrieve using the MessageHandler""" 3 | import pickle 4 | import time 5 | from concurrent.futures.thread import ThreadPoolExecutor 6 | from typing import TypeVar 7 | 8 | from solace.messaging.messaging_service import MessagingService 9 | from solace.messaging.receiver.inbound_message import InboundMessage 10 | from solace.messaging.receiver.message_receiver import MessageHandler 11 | from solace.messaging.resources.topic_subscription import TopicSubscription 12 | from solace.messaging.utils.converter import BytesToObject 13 | from howtos.pubsub.how_to_direct_publish_business_obj import \ 14 | HowToDirectMessagePublishBusinessObject 15 | from howtos.sampler_boot import SamplerBoot, SolaceConstants, MyData 16 | 17 | X = TypeVar('X') 18 | constants = SolaceConstants 19 | boot = SamplerBoot() 20 | MAX_SLEEP = 10 21 | 22 | 23 | class ByteToObjectConverter(BytesToObject): 24 | """sample converter class to convert byte array to object""" 25 | 26 | def convert(self, src: bytearray) -> X: 27 | """This method converts the received byte array to an business object""" 28 | byte_to_object = pickle.loads(src) 29 | return byte_to_object 30 | 31 | 32 | class MessageHandlerImpl(MessageHandler): 33 | """this method is an call back handler to receive message""" 34 | 35 | def on_message(self, message: 'InboundMessage'): 36 | """ Message receive callback """ 37 | topic = message.get_destination_name() 38 | payload_as_bytes = message.get_and_convert_payload(converter=ByteToObjectConverter(), 39 | output_type=type(MyData(constants.MESSAGE_TO_SEND))) 40 | payload_as_string = message.get_payload_as_string() 41 | correlation_id = message.get_correlation_id() 42 | print("\n" + f"CALLBACK: Message Received on Topic: {topic}.\n" 43 | f"Message Bytes: {payload_as_bytes} \n" 44 | f"Message String: {payload_as_string} \n" 45 | f"Correlation id: {correlation_id}") 46 | 47 | 48 | class HowToDirectConsumeBusinessObjectSampler: 49 | """ 50 | class to show how to receive an business object 51 | """ 52 | 53 | @staticmethod 54 | def direct_message_consume_for_business_obj(messaging_service: MessagingService, consumer_subscription: str): 55 | """ to publish str or byte array type message""" 56 | try: 57 | global MAX_SLEEP 58 | topics = [TopicSubscription.of(consumer_subscription)] 59 | 60 | direct_receive_service = messaging_service.create_direct_message_receiver_builder() 61 | direct_receive_service = direct_receive_service.with_subscriptions(topics).build() 62 | direct_receive_service.start() 63 | direct_receive_service.receive_async(MessageHandlerImpl()) 64 | print(f"Subscribed to: {consumer_subscription}") 65 | time.sleep(MAX_SLEEP) 66 | finally: 67 | direct_receive_service.terminate() 68 | messaging_service.disconnect() 69 | 70 | @staticmethod 71 | def run(): 72 | service = MessagingService.builder().from_properties(boot.broker_properties()).build() 73 | service.connect() 74 | consumer_subscription = constants.TOPIC_ENDPOINT_DEFAULT 75 | 76 | print("Execute Direct Consume - String") 77 | HowToDirectConsumeBusinessObjectSampler(). \ 78 | direct_message_consume_for_business_obj(service, consumer_subscription) 79 | service.disconnect() 80 | @staticmethod 81 | def publish_and_subscribe(): 82 | """this method will run the subscriber instance and publisher instance in different threads""" 83 | with ThreadPoolExecutor(max_workers=2) as e: 84 | e.submit(HowToDirectConsumeBusinessObjectSampler.run) 85 | e.submit(HowToDirectMessagePublishBusinessObject.run) 86 | 87 | 88 | if __name__ == '__main__': 89 | HowToDirectConsumeBusinessObjectSampler().publish_and_subscribe() 90 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_consume_persistent_message_with_auto_acknowledgement.py: -------------------------------------------------------------------------------- 1 | """sampler to consume persistent message with auto acknowledgement""" 2 | 3 | import threading 4 | from typing import TypeVar 5 | 6 | from solace.messaging.messaging_service import MessagingService 7 | from solace.messaging.receiver.inbound_message import InboundMessage 8 | from solace.messaging.receiver.persistent_message_receiver import PersistentMessageReceiver 9 | from solace.messaging.resources.queue import Queue 10 | from solace.messaging.resources.topic import Topic 11 | from solace.messaging.resources.topic_subscription import TopicSubscription 12 | from howtos.pubsub.how_to_consume_persistent_message import HowToConsumeMessageExclusiveVsSharedMode 13 | from howtos.pubsub.how_to_publish_persistent_message import HowToPublishPersistentMessage 14 | from howtos.sampler_boot import SolaceConstants, SamplerBoot, BasicTestMessageHandler 15 | from howtos.sampler_master import SamplerMaster 16 | 17 | X = TypeVar('X') 18 | constants = SolaceConstants 19 | boot = SamplerBoot() 20 | lock = threading.Lock() 21 | 22 | topic_name = constants.TOPIC_ENDPOINT_DEFAULT 23 | topic = Topic.of(topic_name) 24 | 25 | 26 | class HowToConsumePersistentMessageWithAutoAcknowledgement: 27 | """class contains methods to consume message with auto acknowledgement""" 28 | 29 | @staticmethod 30 | def consume_full_message_and_do_ack(service: MessagingService, queue_to_consume: Queue, publisher, message): 31 | receiver: PersistentMessageReceiver = service.create_persistent_message_receiver_builder() \ 32 | .with_message_auto_acknowledgement().build(queue_to_consume) 33 | receiver.start() 34 | print(f'PERSISTENT receiver started... Listening to Queue [{queue_to_consume.get_name()}]') 35 | receiver.add_subscription(TopicSubscription.of(topic_name)) 36 | 37 | HowToPublishPersistentMessage.publish_string_message_non_blocking(publisher, topic, message) 38 | 39 | message: InboundMessage = receiver.receive_message() 40 | print(f"the message payload is {message.get_payload_as_string()}") 41 | 42 | @staticmethod 43 | def consume_full_message_using_callback_and_do_ack(service: MessagingService, queue_to_consume: Queue, 44 | publisher, message): 45 | try: 46 | receiver: PersistentMessageReceiver = service.create_persistent_message_receiver_builder() \ 47 | .with_message_auto_acknowledgement().build(queue_to_consume) 48 | receiver.start() 49 | print(f'PERSISTENT receiver started... Listening to Queue [{queue_to_consume.get_name()}]') 50 | receiver.add_subscription(TopicSubscription.of(topic_name)) 51 | message_handler = BasicTestMessageHandler() 52 | receiver.receive_async(message_handler) 53 | 54 | HowToPublishPersistentMessage.publish_string_message_non_blocking(publisher, topic, message) 55 | finally: 56 | receiver.terminate() 57 | HowToConsumeMessageExclusiveVsSharedMode.delete_queue(queue_to_consume.get_name()) 58 | 59 | @staticmethod 60 | def run(): 61 | try: 62 | message = constants.MESSAGE_TO_SEND 63 | messaging_service = SamplerMaster.connect_messaging_service() 64 | 65 | publisher = HowToPublishPersistentMessage.create_persistent_message_publisher(messaging_service) 66 | 67 | queue_name = constants.QUEUE_NAME_FORMAT.substitute(iteration=topic_name) 68 | HowToConsumeMessageExclusiveVsSharedMode.create_queue_and_add_topic(queue_name) 69 | durable_exclusive_queue = Queue.durable_exclusive_queue(queue_name) 70 | 71 | HowToConsumePersistentMessageWithAutoAcknowledgement \ 72 | .consume_full_message_and_do_ack(service=messaging_service, queue_to_consume=durable_exclusive_queue, 73 | publisher=publisher, message=message) 74 | 75 | HowToConsumePersistentMessageWithAutoAcknowledgement \ 76 | .consume_full_message_using_callback_and_do_ack(service=messaging_service, 77 | queue_to_consume=durable_exclusive_queue, 78 | publisher=publisher, message=message) 79 | finally: 80 | messaging_service.disconnect() 81 | 82 | 83 | if __name__ == '__main__': 84 | HowToConsumePersistentMessageWithAutoAcknowledgement.run() 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solace Samples Python 2 | 3 | This repo hosts sample code to showcase how the Solace Python API could be used. You can find: 4 | 5 | 1. `/patterns` --> runnable code showcasing different message exchange patters with the Python API 6 | 1. `/howtos` --> runnable code snippets showcasing how to use different features of the API. All howtos are named `how_to_*.py` with some sampler files under sub folders. 7 | 8 | ## Environment Setup 9 | 10 | 1. [Install Python3](https://www.python.org/downloads/) (See installed version using `python3 -V`) 11 | 1.1 Note: If you are installing python for the first time on your machine then you can just use `python` instead of `python3` for the commands 12 | 1. [Optional] Install virtualenv `python3 -m pip install --user virtualenv` 1.1 Note: on a Linux machine, depending on the distribution you might need to`apt-get install python3-venv`instead 1.2 Alternatively, you can use`pyenv` to manage multiple versions of python. Follow the instructions here for more information https://realpython.com/intro-to-pyenv/#virtual-environments-and-pyenv 13 | 1. Clone this repository 14 | 1. [Optional] Setup python virtual environment `python3 -m venv venv` 15 | 1. [Optional] Activate virtual environment: 16 | 1.1 MacOS/Linux: `source venv/bin/activate` 17 | 1.2 Windows: `venv/Scripts/activate` 18 | 1. After activating the virtual environment, make sure you have the latest pip installed `pip install --upgrade pip` 19 | 20 | ## Install the Solace Python API and other dependencies 21 | 22 | 1. Install the API `pip install -r requirements.txt` 23 | 24 | ## Run Samples 25 | 26 | Execute the script of choice as follows: 27 | 28 | - `python patterns/.py` 29 | 30 | Note: This assumes you have a [local docker](https://solace.com/products/event-broker/software/getting-started/) broker running on localhost 31 | 32 | To pass non default parameters, do so via the environment variables 33 | 34 | - `SOLACE_HOST= SOLACE_VPN= SOLACE_USERNAME= SOLACE_PASSWORD= python .py` 35 | 36 | ## Run Howtos 37 | 38 | To run any of the howtos samples, you will have to set the `PYTHONPATH` environment variable to the current directory since there are references to local modules. To do so you can run any of the howtos samples as follows 39 | 40 | `PYTHONPATH=.. python .py` 41 | 42 | ### Running all the samplers 43 | 44 | `PYTHONPATH=.. python sampler_master.py` 45 | 46 | ### Connection configuration for samplers 47 | 48 | All connection details are provided in the `solbroker_properties.json` file include the SEMPv2 connection details and client connection detail with default value for a localhost deployment of the PubSub+ software broker. Update the `solbroker_properties.json` file entries to point the connections to a different broker. 49 | 50 | ### Secure Transport connections 51 | 52 | The fixtures folder contains the sample client certificate api-client.pem and its CA public_root_ca.crt. Note the howtoes automatically adds a client certificate authority on the remote broker via SEMPv2. 53 | 54 | TLS downgrade requires some manual remote broker configuration as well as the message vpn on the remote broker does not enable the feature to tls downgrade to plain text by default and must be confirmed. In the future this might be automatically enabled via SEMPv2 for the duration of the sampler execution. 55 | 56 | Note the trusted certificate for the remote broker must be added to the fixtures folder as that directory is the trustore to establish TLS connections for the `how_to*.py` samplers. For details on how to add a server certifcate to the broker see [Managing Server Certs](https://docs.solace.com/Configuring-and-Managing/Managing-Server-Certs.htm). 57 | For example should the broker have a server certificate derived from the public_root_ca.crt then TLS connection can be established since the public_root_ca.crt is in the truststore located at fixtures. 58 | 59 | ## Notes: 60 | 61 | 1. [Python Virtual environment](https://docs.python.org/3/tutorial/venv.html) is recommended to keep your project dependencies within the project scope and avoid polluting global python packages 62 | 1. Solace hostname, username, message vpn, and password are obtained from your Solace cloud account 63 | 1. Make sure you have the latest pip version installed prior installation 64 | 65 | ## Resources 66 | 67 | - Solace Developer Portal is at [solace.dev](https://solace.dev) 68 | - Ask the [Solace Community](https://solace.community) for further discussions and questions. 69 | - Official python documentation on [https://docs.solace.com/Solace-PubSub-Messaging-APIs/Python-API/python-home.htm](https://docs.solace.com/Solace-PubSub-Messaging-APIs/Python-API/python-home.htm) 70 | -------------------------------------------------------------------------------- /patterns/direct_publisher.py: -------------------------------------------------------------------------------- 1 | ## Goal: Simple Publisher, event handling and message properties setting 2 | import os 3 | import platform 4 | import time 5 | 6 | # Import Solace Python API modules from the solace package 7 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener, RetryStrategy, ServiceEvent 8 | from solace.messaging.resources.topic import Topic 9 | from solace.messaging.publisher.direct_message_publisher import PublishFailureListener, FailedPublishEvent 10 | 11 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 12 | 13 | MSG_COUNT = 5 14 | TOPIC_PREFIX = "solace/samples/python" 15 | 16 | # Inner classes for error handling 17 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 18 | def on_reconnected(self, e: ServiceEvent): 19 | print("\non_reconnected") 20 | print(f"Error cause: {e.get_cause()}") 21 | print(f"Message: {e.get_message()}") 22 | 23 | def on_reconnecting(self, e: "ServiceEvent"): 24 | print("\non_reconnecting") 25 | print(f"Error cause: {e.get_cause()}") 26 | print(f"Message: {e.get_message()}") 27 | 28 | def on_service_interrupted(self, e: "ServiceEvent"): 29 | print("\non_service_interrupted") 30 | print(f"Error cause: {e.get_cause()}") 31 | print(f"Message: {e.get_message()}") 32 | 33 | 34 | class PublisherErrorHandling(PublishFailureListener): 35 | def on_failed_publish(self, e: "FailedPublishEvent"): 36 | print("on_failed_publish") 37 | 38 | # Broker Config. Note: Could pass other properties Look into 39 | broker_props = { 40 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 41 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 42 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 43 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "default" 44 | } 45 | 46 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 47 | # Note: The reconnections strategy could also be configured using the broker properties object 48 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 49 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 50 | .build() 51 | 52 | # Blocking connect thread 53 | messaging_service.connect() 54 | print(f'Messaging Service connected? {messaging_service.is_connected}') 55 | 56 | # Event Handling for the messaging service 57 | service_handler = ServiceEventHandler() 58 | messaging_service.add_reconnection_listener(service_handler) 59 | messaging_service.add_reconnection_attempt_listener(service_handler) 60 | messaging_service.add_service_interruption_listener(service_handler) 61 | 62 | # Create a direct message publisher and start it 63 | direct_publisher = messaging_service.create_direct_message_publisher_builder().build() 64 | direct_publisher.set_publish_failure_listener(PublisherErrorHandling()) 65 | 66 | # Blocking Start thread 67 | direct_publisher.start() 68 | print(f'Direct Publisher ready? {direct_publisher.is_ready()}') 69 | 70 | # Prepare outbound message payload and body 71 | message_body = "this is the body of the msg" 72 | outbound_msg_builder = messaging_service.message_builder() \ 73 | .with_application_message_id("sample_id") \ 74 | .with_property("application", "samples") \ 75 | .with_property("language", "Python") \ 76 | 77 | count = 1 78 | print("\nSend a KeyboardInterrupt to stop publishing\n") 79 | try: 80 | while True: 81 | while count <= MSG_COUNT: 82 | topic = Topic.of(TOPIC_PREFIX + f'/direct/pub/{count}') 83 | # Direct publish the message with dynamic headers and payload 84 | outbound_msg = outbound_msg_builder \ 85 | .with_application_message_id(f'NEW {count}')\ 86 | .build(f'{message_body} + {count}') 87 | direct_publisher.publish(destination=topic, message=outbound_msg) 88 | 89 | print(f'Published message on {topic}') 90 | count += 1 91 | time.sleep(0.1) 92 | print("\n") 93 | count = 1 94 | time.sleep(1) 95 | 96 | except KeyboardInterrupt: 97 | print('\nTerminating Publisher') 98 | direct_publisher.terminate() 99 | print('\nDisconnecting Messaging Service') 100 | messaging_service.disconnect() -------------------------------------------------------------------------------- /patterns/direct_receiver.py: -------------------------------------------------------------------------------- 1 | ## Goal: Simple Receiver and event handling 2 | import os 3 | import platform 4 | import time 5 | 6 | # Import Solace Python API modules from the solace package 7 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener, RetryStrategy, ServiceEvent 8 | from solace.messaging.resources.topic_subscription import TopicSubscription 9 | from solace.messaging.receiver.message_receiver import MessageHandler, InboundMessage 10 | 11 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 12 | 13 | TOPIC_PREFIX = "solace/samples/python" 14 | 15 | # Handle received messages 16 | class MessageHandlerImpl(MessageHandler): 17 | def on_message(self, message: InboundMessage): 18 | try: 19 | # Check if the payload is a String or Byte, decode if its the later 20 | payload = message.get_payload_as_string() if message.get_payload_as_string() is not None else message.get_payload_as_bytes() 21 | if isinstance(payload, bytearray): 22 | print(f"Received a message of type: {type(payload)}. Decoding to string") 23 | payload = payload.decode() 24 | 25 | topic = message.get_destination_name() 26 | print("\n" + f"Message Payload String: {payload} \n") 27 | print("\n" + f"Message Topic: {topic} \n") 28 | print("\n" + f"Message dump: {message} \n") 29 | except Exception as e: 30 | print(f"Error processing message: {e.__traceback__}") 31 | 32 | # Inner classes for error handling 33 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 34 | def on_reconnected(self, e: ServiceEvent): 35 | print("\non_reconnected") 36 | print(f"Error cause: {e.get_cause()}") 37 | print(f"Message: {e.get_message()}") 38 | 39 | def on_reconnecting(self, e: "ServiceEvent"): 40 | print("\non_reconnecting") 41 | print(f"Error cause: {e.get_cause()}") 42 | print(f"Message: {e.get_message()}") 43 | 44 | def on_service_interrupted(self, e: "ServiceEvent"): 45 | print("\non_service_interrupted") 46 | print(f"Error cause: {e.get_cause()}") 47 | print(f"Message: {e.get_message()}") 48 | 49 | # Broker Config 50 | broker_props = { 51 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 52 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 53 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 54 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "default" 55 | } 56 | 57 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 58 | # Note: The reconnections strategy could also be configured using the broker properties object 59 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 60 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 61 | .build() 62 | 63 | # Blocking connect thread 64 | messaging_service.connect() 65 | print(f'Messaging Service connected? {messaging_service.is_connected}') 66 | 67 | # Error Handeling for the messaging service 68 | service_handler = ServiceEventHandler() 69 | messaging_service.add_reconnection_listener(service_handler) 70 | messaging_service.add_reconnection_attempt_listener(service_handler) 71 | messaging_service.add_service_interruption_listener(service_handler) 72 | 73 | # Define a Topic subscriptions 74 | topics = [TOPIC_PREFIX + "/>"] 75 | topics_sub = [] 76 | for t in topics: 77 | topics_sub.append(TopicSubscription.of(t)) 78 | 79 | # Build a Receiver with the given topics and start it 80 | direct_receiver = messaging_service.create_direct_message_receiver_builder()\ 81 | .with_subscriptions(topics_sub)\ 82 | .build() 83 | 84 | direct_receiver.start() 85 | print(f'Direct Receiver is running? {direct_receiver.is_running()}') 86 | 87 | try: 88 | print(f"Subscribing to: {topics}") 89 | # Callback for received messages 90 | direct_receiver.receive_async(MessageHandlerImpl()) 91 | try: 92 | while True: 93 | time.sleep(1) 94 | except KeyboardInterrupt: 95 | print('\nDisconnecting Messaging Service') 96 | finally: 97 | print('\nTerminating receiver') 98 | direct_receiver.terminate() 99 | print('\nDisconnecting Messaging Service') 100 | messaging_service.disconnect() 101 | -------------------------------------------------------------------------------- /patterns/guaranteed_publisher.py: -------------------------------------------------------------------------------- 1 | # Guaranteed Publisher publishing persistent messages 2 | import os 3 | import platform 4 | import time 5 | import threading 6 | 7 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener, RetryStrategy, ServiceEvent 8 | from solace.messaging.publisher.persistent_message_publisher import PersistentMessagePublisher 9 | from solace.messaging.publisher.persistent_message_publisher import MessagePublishReceiptListener 10 | from solace.messaging.resources.topic import Topic 11 | 12 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 13 | 14 | lock = threading.Lock() # lock object that is not owned by any thread. Used for synchronization and counting the 15 | 16 | TOPIC_PREFIX = "solace/samples/python" 17 | 18 | # Inner classes for error handling 19 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 20 | def on_reconnected(self, e: ServiceEvent): 21 | print("\non_reconnected") 22 | print(f"Error cause: {e.get_cause()}") 23 | print(f"Message: {e.get_message()}") 24 | 25 | def on_reconnecting(self, e: "ServiceEvent"): 26 | print("\non_reconnecting") 27 | print(f"Error cause: {e.get_cause()}") 28 | print(f"Message: {e.get_message()}") 29 | 30 | def on_service_interrupted(self, e: "ServiceEvent"): 31 | print("\non_service_interrupted") 32 | print(f"Error cause: {e.get_cause()}") 33 | print(f"Message: {e.get_message()}") 34 | 35 | class MessageReceiptListener(MessagePublishReceiptListener): 36 | def __init__(self): 37 | self._receipt_count = 0 38 | 39 | @property 40 | def receipt_count(self): 41 | return self._receipt_count 42 | 43 | def on_publish_receipt(self, publish_receipt: 'PublishReceipt'): 44 | with lock: 45 | self._receipt_count += 1 46 | print(f"\npublish_receipt:\n {self.receipt_count}\n") 47 | 48 | # Broker Config. Note: Could pass other properties Look into 49 | broker_props = { 50 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 51 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 52 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 53 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "default" 54 | } 55 | 56 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 57 | # Note: The reconnections strategy could also be configured using the broker properties object 58 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 59 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 60 | .build() 61 | 62 | # Blocking connect thread 63 | messaging_service.connect() 64 | print(f'Messaging Service connected? {messaging_service.is_connected}') 65 | 66 | # Event Handling for the messaging service 67 | service_handler = ServiceEventHandler() 68 | messaging_service.add_reconnection_listener(service_handler) 69 | messaging_service.add_reconnection_attempt_listener(service_handler) 70 | messaging_service.add_service_interruption_listener(service_handler) 71 | 72 | # Create a persistent message publisher and start it 73 | publisher: PersistentMessagePublisher = messaging_service.create_persistent_message_publisher_builder().build() 74 | publisher.start() 75 | 76 | # set a message delivery listener to the publisher 77 | receipt_listener = MessageReceiptListener() 78 | publisher.set_message_publish_receipt_listener(receipt_listener) 79 | 80 | # Prepare the destination topic 81 | topic = Topic.of(TOPIC_PREFIX + f'/persistent/pub') 82 | 83 | # Prepare outbound message payload and body 84 | message_body = "this is the body of the msg" 85 | outbound_msg_builder = messaging_service.message_builder() \ 86 | .with_application_message_id("sample_id") \ 87 | .with_property("application", "samples") \ 88 | .with_property("language", "Python") 89 | count = 0 90 | try: 91 | while True: 92 | outbound_msg = outbound_msg_builder \ 93 | .with_application_message_id(f'NEW {count}')\ 94 | .build(f'{message_body} + {count}') 95 | 96 | publisher.publish(outbound_msg, topic) 97 | print(f'PERSISTENT publish message {count} is successful... Topic: [{topic.get_name()}]') 98 | count +=1 99 | time.sleep(0.1) 100 | 101 | except KeyboardInterrupt: 102 | print(f'\nDelivery receipt count: {receipt_listener.receipt_count}\n') 103 | print('\nTerminating Publisher') 104 | publisher.terminate() 105 | print('\nDisconnecting Messaging Service') 106 | messaging_service.disconnect() 107 | -------------------------------------------------------------------------------- /howtos/SEMPv2/semp_client.py: -------------------------------------------------------------------------------- 1 | """module for semp client""" 2 | import json 3 | 4 | import requests 5 | from requests.auth import HTTPBasicAuth 6 | from requests.exceptions import HTTPError 7 | 8 | 9 | class SempClient: 10 | """class holds semp client related methods""" 11 | 12 | def __init__(self, semp_base_url: str, user_name="admin", password="admin", verify_ssl=False): 13 | self.url_with_port = semp_base_url 14 | self.user_name = user_name 15 | self.password = password 16 | self.verify_ssl = verify_ssl 17 | 18 | self.authHeader = HTTPBasicAuth(self.user_name, self.password) 19 | self.json_content_type_header = {'Content-Type': 'application/json'} 20 | 21 | def http_get(self, endpoint: str): 22 | """method to get the http endpoint 23 | Args: 24 | endpoint: endpoint string 25 | 26 | Raises: 27 | HTTP GET request failed. with response status code or 28 | HTTP error occurred while HTTP GET exception 29 | """ 30 | url = f"{self.url_with_port}{endpoint}" 31 | try: 32 | req = requests.get(url, auth=self.authHeader, verify=self.verify_ssl) 33 | if req.status_code == 200: 34 | return req.json() 35 | else: 36 | raise Exception(f"HTTP GET request failed. Response status code: {req.status_code}. \n {req.json()}") 37 | except HTTPError as http_err: 38 | print(f'HTTP error occurred while HTTP GET - {url}. \n Exception: {http_err}') 39 | except Exception as err: 40 | print(f'Error occurred while HTTP GET - {url}. \n Exception: {err}') 41 | 42 | def http_patch(self, endpoint: str, payload): 43 | """method to update the http endpoint 44 | Args: 45 | endpoint: endpoint string 46 | payload: request payload 47 | 48 | Raises: 49 | HTTP PATCH request failed. with response status code or 50 | HTTP error occurred while HTTP PATCH exception 51 | """ 52 | url = f"{self.url_with_port}{endpoint}" 53 | try: 54 | req = requests.patch(url, auth=self.authHeader, data=json.dumps(payload), 55 | headers=self.json_content_type_header, verify=self.verify_ssl) 56 | 57 | if req.status_code == 200: 58 | return req.json() 59 | else: 60 | raise Exception(f"HTTP PATCH request failed. Response status code: {req.status_code}. \n {req.json()}") 61 | except HTTPError as http_err: 62 | print(f'HTTP error occurred while HTTP PATCH - {url}. \n Exception: {http_err}') 63 | except Exception as err: 64 | print(f'Error occurred while HTTP PATCH - {url}. \n Exception: {err}') 65 | 66 | def http_post(self, endpoint: str, payload, raise_exception=True): 67 | """method for http post 68 | Args: 69 | endpoint: endpoint string 70 | payload: request payload 71 | 72 | Raises: 73 | HTTP POST request failed. with response status code or 74 | HTTP error occurred while HTTP POST exception 75 | """ 76 | 77 | url = f"{self.url_with_port}{endpoint}" 78 | try: 79 | 80 | req = requests.post(url, auth=self.authHeader, data=json.dumps(payload), 81 | headers=self.json_content_type_header, verify=self.verify_ssl) 82 | if req.status_code == 200: 83 | return req.json() 84 | else: 85 | if raise_exception: 86 | raise Exception(f"HTTP POST [%s] request failed. Response status code: %s.\n%s", 87 | url, req.status_code, req.json()) 88 | else: 89 | return req.json() 90 | except HTTPError as http_err: 91 | print(f'HTTP error occurred while HTTP POST [%s].\nException: %s', url, http_err) 92 | except Exception as err: 93 | print(f'HTTP error occurred while HTTP POST [%s].\nException: %s', url, err) 94 | 95 | def http_delete(self, endpoint: str): 96 | """method for http delete 97 | Args: 98 | endpoint: endpoint string 99 | 100 | Raises: 101 | HTTP DELETE request failed. with response status code or 102 | HTTP error occurred while HTTP DELETE exception 103 | """ 104 | url = f"{self.url_with_port}{endpoint}" 105 | try: 106 | req = requests.delete(url, auth=self.authHeader, headers=self.json_content_type_header, 107 | verify=self.verify_ssl) 108 | if req.status_code == 200: 109 | return req.json() 110 | else: 111 | raise Exception(f'HTTP DELETE request failed. Response status code: {req.status_code}. \n {req.json()}') 112 | except HTTPError as http_err: 113 | print(f'HTTP error occurred while HTTP DELETE - {url}. \n Exception: {http_err}') 114 | except Exception as err: 115 | print(f'Error occurred while HTTP DELETE - {url}. \n Exception: {err}') 116 | -------------------------------------------------------------------------------- /patterns/TLS_connection.py: -------------------------------------------------------------------------------- 1 | 2 | ## Goal: Simple Subscriber and event handling 3 | import os 4 | import platform 5 | import time 6 | 7 | # Import Solace Python API modules from the solace package 8 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener, RetryStrategy, ServiceEvent 9 | from solace.messaging.resources.topic_subscription import TopicSubscription 10 | from solace.messaging.receiver.message_receiver import MessageHandler, InboundMessage 11 | from solace.messaging.config.transport_security_strategy import TLS 12 | # from solace.messaging.config.authentication_strategy import ClientCertificateAuthentication 13 | 14 | 15 | 16 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 17 | 18 | TOPIC_PREFIX = "solace/samples/python" 19 | 20 | # Handle received messages 21 | class MessageHandlerImpl(MessageHandler): 22 | def on_message(self, message: InboundMessage): 23 | try: 24 | topic = message.get_destination_name() 25 | payload_str = message.get_payload_as_string() 26 | print("\n" + f"Message Payload String: {payload_str} \n") 27 | print("\n" + f"Message Topic: {topic} \n") 28 | print("\n" + f"Message dump: {message} \n") 29 | except Exception as e: 30 | print(f"Error processing message: {e.__traceback__}") 31 | 32 | # Inner classes for error handling 33 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 34 | def on_reconnected(self, e: ServiceEvent): 35 | print("\non_reconnected") 36 | print(f"Error cause: {e.get_cause()}") 37 | print(f"Message: {e.get_message()}") 38 | 39 | def on_reconnecting(self, e: "ServiceEvent"): 40 | print("\non_reconnecting") 41 | print(f"Error cause: {e.get_cause()}") 42 | print(f"Message: {e.get_message()}") 43 | 44 | def on_service_interrupted(self, e: "ServiceEvent"): 45 | print("\non_service_interrupted") 46 | print(f"Error cause: {e.get_cause()}") 47 | print(f"Message: {e.get_message()}") 48 | 49 | # Broker Config 50 | broker_props = { 51 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 52 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 53 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 54 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "default" 55 | } 56 | 57 | # transport_security_strategy = TLS.create().without_certificate_validation() 58 | transport_security_strategy = TLS.create().with_certificate_validation(True, False, "./trusted-store") 59 | # authentication_strategy = ClientCertificateAuthentication.of("/path/to/certificate","/path/to/key","key_password")\ 60 | # .with_certificate_and_key_pem("/path/to/pem/file")\ 61 | # .with_private_key_password("private_key_password") 62 | 63 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 64 | # Note: The reconnections strategy could also be configured using the broker properties object 65 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 66 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 67 | .with_transport_security_strategy(transport_security_strategy)\ 68 | .build() 69 | # .with_authentication_strategy(authentication_strategy)\ 70 | 71 | # Blocking connect thread 72 | messaging_service.connect() 73 | print(f'Messaging Service connected? {messaging_service.is_connected}') 74 | 75 | # Error Handeling for the messaging service 76 | service_handler = ServiceEventHandler() 77 | messaging_service.add_reconnection_listener(service_handler) 78 | messaging_service.add_reconnection_attempt_listener(service_handler) 79 | messaging_service.add_service_interruption_listener(service_handler) 80 | 81 | # Define a Topic subscriptions 82 | topics = [TOPIC_PREFIX + "/dir/sub/>", TOPIC_PREFIX + "/dir/sub/v2/>", "solace/samples/>"] 83 | topics_sub = [] 84 | for t in topics: 85 | topics_sub.append(TopicSubscription.of(t)) 86 | 87 | # Build a Receiver with the given topics and start it 88 | direct_receiver = messaging_service.create_direct_message_receiver_builder()\ 89 | .with_subscriptions(topics_sub)\ 90 | .build() 91 | 92 | direct_receiver.start() 93 | print(f'Direct Receiver is running? {direct_receiver.is_running()}') 94 | 95 | try: 96 | print(f"Subscribing to: {topics}") 97 | # Callback for received messages 98 | direct_receiver.receive_async(MessageHandlerImpl()) 99 | try: 100 | while True: 101 | time.sleep(1) 102 | except KeyboardInterrupt: 103 | print('\nDisconnecting Messaging Service') 104 | finally: 105 | print('\nTerminating receiver') 106 | direct_receiver.terminate() 107 | print('\nDisconnecting Messaging Service') 108 | messaging_service.disconnect() 109 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_publish_health_check.py: -------------------------------------------------------------------------------- 1 | """sampler for publish health check""" 2 | from typing import TypeVar 3 | 4 | from solace.messaging.errors.pubsubplus_client_error import PublisherOverflowError 5 | from solace.messaging.messaging_service import MessagingService 6 | from solace.messaging.publisher.publisher_health_check import PublisherReadinessListener 7 | from solace.messaging.resources.topic import Topic 8 | from howtos.sampler_boot import SamplerBoot, SolaceConstants, SamplerUtil 9 | 10 | X = TypeVar('X') 11 | constants = SolaceConstants 12 | boot = SamplerBoot() 13 | 14 | 15 | class PublisherReadinessListenerImpl(PublisherReadinessListener): 16 | def __init__(self): 17 | self._invocation_counter = 0 18 | 19 | def ready(self): 20 | HowToDirectMessagingHealthCheckSampler.PUBLISHER_READINESS_RECEIVED_COUNTER += 1 21 | print(f"\nNOTIFIED. " 22 | f"counter #{HowToDirectMessagingHealthCheckSampler.PUBLISHER_READINESS_RECEIVED_COUNTER}") 23 | HowToDirectMessagingHealthCheckSampler.CAN_SEND_MESSAGE = True 24 | 25 | 26 | class HowToDirectMessagingHealthCheckSampler: 27 | """ 28 | class to show how to create a messaging service 29 | """ 30 | PUBLISHER_READINESS_SET_COUNTER = 0 31 | PUBLISHER_READINESS_RECEIVED_COUNTER = 0 32 | CAN_SEND_MESSAGE = True 33 | WOULD_BLOCK_RECEIVED = False 34 | 35 | @staticmethod 36 | def direct_message_publish_on_backpressure_reject(messaging_service: MessagingService, destination, message, 37 | buffer_capacity, message_count): 38 | """ to publish str or byte array type message using back pressure""" 39 | try: 40 | direct_publish_service = messaging_service.create_direct_message_publisher_builder() \ 41 | .on_back_pressure_reject(buffer_capacity=buffer_capacity) \ 42 | .build() 43 | direct_publish_service.start_async() 44 | 45 | HowToDirectMessagingHealthCheckSampler.PUBLISHER_READINESS_SET_COUNTER += 1 46 | direct_publish_service.set_publisher_readiness_listener(listener=PublisherReadinessListenerImpl()) 47 | 48 | for e in range(message_count): 49 | direct_publish_service.publish(destination=destination, message=message) 50 | print(f"{e} message(s) sent") 51 | except PublisherOverflowError: 52 | PublisherOverflowError("Queue maximum limit is reached") 53 | finally: 54 | direct_publish_service.terminate() 55 | 56 | @staticmethod 57 | def direct_message_publish_without_backpressure(messaging_service: MessagingService, destination, message, 58 | message_count): 59 | """ to publish str or byte array type message using back pressure""" 60 | try: 61 | direct_publish_service = messaging_service.create_direct_message_publisher_builder() \ 62 | .build() 63 | direct_publish_service.start_async() 64 | 65 | HowToDirectMessagingHealthCheckSampler.PUBLISHER_READINESS_SET_COUNTER += 1 66 | direct_publish_service.set_publisher_readiness_listener(listener=PublisherReadinessListenerImpl()) 67 | 68 | for e in range(message_count): 69 | direct_publish_service.publish(destination=destination, message=message) 70 | print(f"{e} message(s) sent") 71 | except PublisherOverflowError: 72 | PublisherOverflowError("Queue maximum limit is reached") 73 | finally: 74 | direct_publish_service.terminate() 75 | 76 | @staticmethod 77 | def run(): 78 | """ 79 | :return: 80 | """ 81 | service = None 82 | try: 83 | service = MessagingService.builder().from_properties(boot.broker_properties()).build() 84 | service.connect() 85 | print(f"Message service: {service.is_connected}") 86 | if service.is_connected: 87 | destination_name = Topic.of(constants.TOPIC_ENDPOINT_DEFAULT) 88 | message_count = 10 89 | 90 | print("Execute Direct Publish - String without using back pressure") 91 | HowToDirectMessagingHealthCheckSampler() \ 92 | .direct_message_publish_without_backpressure(service, destination_name, 93 | constants.MESSAGE_TO_SEND, 94 | message_count) 95 | 96 | buffer_capacity = 100 97 | print("Execute Direct Publish - String using back pressure") 98 | HowToDirectMessagingHealthCheckSampler() \ 99 | .direct_message_publish_on_backpressure_reject(service, destination_name, 100 | constants.MESSAGE_TO_SEND, 101 | buffer_capacity, message_count) 102 | else: 103 | print(f'Failed to connect service with properties: {boot.broker_properties()}') 104 | finally: 105 | if service: 106 | service.disconnect() 107 | 108 | 109 | if __name__ == '__main__': 110 | HowToDirectMessagingHealthCheckSampler().run() 111 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_direct_consume_with_share_name.py: -------------------------------------------------------------------------------- 1 | """ Run this file to consume messages using multiple receiver instances and with share name/group name""" 2 | import time 3 | from concurrent.futures.thread import ThreadPoolExecutor 4 | from typing import TypeVar 5 | 6 | from solace.messaging.messaging_service import MessagingService 7 | from solace.messaging.receiver.message_receiver import MessageHandler 8 | from solace.messaging.resources.share_name import ShareName 9 | from solace.messaging.resources.topic_subscription import TopicSubscription 10 | from howtos.pubsub.how_to_direct_publish_message import HowToDirectPublishMessage 11 | from howtos.sampler_boot import SamplerBoot, SolaceConstants 12 | 13 | X = TypeVar('X') 14 | constants = SolaceConstants 15 | boot = SamplerBoot() 16 | MAX_SLEEP = 10 17 | 18 | 19 | class MessageHandlerImpl(MessageHandler): 20 | """this method is an call back handler to receive message""" 21 | 22 | def on_message(self, message: 'InboundMessage'): 23 | """ Message receive callback """ 24 | topic = message.get_destination_name() 25 | payload_as_bytes = message.get_payload_as_bytes() 26 | payload_as_string = message.get_payload_as_string() 27 | correlation_id = message.get_correlation_id() 28 | print("\n" + f"Receiver A \n" 29 | f"CALLBACK: Message Received on Topic: {topic}.\n" 30 | f"Message Bytes: {payload_as_bytes} \n" 31 | f"Message String: {payload_as_string} \n" 32 | f"Correlation id: {correlation_id}") 33 | 34 | 35 | class MessageHandlerImpl2(MessageHandler): 36 | """this method is an call back handler to receive message""" 37 | 38 | def on_message(self, message: 'InboundMessage'): 39 | """ Message receive callback """ 40 | topic = message.get_destination_name() 41 | payload_as_bytes = message.get_payload_as_bytes() 42 | payload_as_string = message.get_payload_as_string() 43 | correlation_id = message.get_correlation_id() 44 | print("\n" + f"Receiver B \n" 45 | f"CALLBACK: Message Received on Topic: {topic}.\n" 46 | f"Message Bytes: {payload_as_bytes} \n" 47 | f"Message String: {payload_as_string} \n" 48 | f"Correlation id: {correlation_id}") 49 | 50 | 51 | class HowToDirectConsumeShareNameSampler: 52 | """ class to show how to create a messaging service """ 53 | 54 | @staticmethod 55 | def direct_message_consume(messaging_service: MessagingService, consumer_subscription: str): 56 | """This method will create an receiver instance to receive str or byte array type message""" 57 | try: 58 | global MAX_SLEEP 59 | topics = [TopicSubscription.of(consumer_subscription)] 60 | group_name = ShareName.of('test') 61 | direct_receive_service = messaging_service.create_direct_message_receiver_builder() 62 | direct_receive_service = direct_receive_service.with_subscriptions(topics).build( 63 | shared_subscription_group=group_name) 64 | direct_receive_service.start() 65 | direct_receive_service.receive_async(MessageHandlerImpl()) 66 | print(f"Subscribed to: {consumer_subscription}") 67 | time.sleep(MAX_SLEEP) 68 | finally: 69 | direct_receive_service.terminate() 70 | messaging_service.disconnect() 71 | 72 | @staticmethod 73 | def direct_message_consume2(messaging_service: MessagingService, consumer_subscription: str): 74 | """This method will create an receiver instance to receive str or byte array type message""" 75 | try: 76 | global MAX_SLEEP 77 | topics = [TopicSubscription.of(consumer_subscription)] 78 | group_name = ShareName.of('test') 79 | direct_receive_service = messaging_service.create_direct_message_receiver_builder() 80 | direct_receive_service = direct_receive_service.with_subscriptions(topics).build( 81 | shared_subscription_group=group_name) 82 | direct_receive_service.start() 83 | direct_receive_service.receive_async(MessageHandlerImpl2()) 84 | print(f"Subscribed to: {consumer_subscription}") 85 | time.sleep(MAX_SLEEP) 86 | finally: 87 | direct_receive_service.terminate() 88 | messaging_service.disconnect() 89 | 90 | @staticmethod 91 | def run(): 92 | """ 93 | :return: 94 | """ 95 | service = MessagingService.builder().from_properties(boot.broker_properties()).build() 96 | service.connect() 97 | consumer_subscription = constants.TOPIC_ENDPOINT_DEFAULT 98 | 99 | print("Execute Direct Consume - String") 100 | with ThreadPoolExecutor(max_workers=2) as e: 101 | e.submit(HowToDirectConsumeShareNameSampler.direct_message_consume, service, 102 | consumer_subscription) 103 | e.submit(HowToDirectConsumeShareNameSampler.direct_message_consume2, service, 104 | consumer_subscription) 105 | conn.disconnect() 106 | @staticmethod 107 | def publish_and_subscribe(): 108 | """""" 109 | with ThreadPoolExecutor(max_workers=2) as e: 110 | e.submit(HowToDirectConsumeShareNameSampler.run) 111 | e.submit(HowToDirectPublishMessage.run) 112 | 113 | 114 | if __name__ == '__main__': 115 | HowToDirectConsumeShareNameSampler().publish_and_subscribe() 116 | -------------------------------------------------------------------------------- /patterns/direct_requestor.py: -------------------------------------------------------------------------------- 1 | ## Goal is to demonstrate a requestor (a request-reply pattern) that will receive reply asynchronously. 2 | 3 | import os 4 | import platform 5 | import time 6 | import calendar 7 | 8 | # Import Solace Python API modules from the solace package 9 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, \ 10 | ServiceInterruptionListener, RetryStrategy, ServiceEvent 11 | from solace.messaging.resources.topic import Topic 12 | from solace.messaging.errors.pubsubplus_client_error import PubSubPlusClientError 13 | from solace.messaging.publisher.request_reply_message_publisher import RequestReplyMessagePublisher 14 | 15 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 16 | 17 | TOPIC_PREFIX = "solace/samples/python" 18 | 19 | # Inner classes for error handling 20 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 21 | def on_reconnected(self, e: ServiceEvent): 22 | print("\non_reconnected") 23 | print(f"Error cause: {e.get_cause()}") 24 | print(f"Message: {e.get_message()}") 25 | 26 | def on_reconnecting(self, e: "ServiceEvent"): 27 | print("\non_reconnecting") 28 | print(f"Error cause: {e.get_cause()}") 29 | print(f"Message: {e.get_message()}") 30 | 31 | def on_service_interrupted(self, e: "ServiceEvent"): 32 | print("\non_service_interrupted") 33 | print(f"Error cause: {e.get_cause()}") 34 | print(f"Message: {e.get_message()}") 35 | 36 | 37 | # Broker Config. Note: Could pass other properties Look into 38 | broker_props = { 39 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 40 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 41 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 42 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "" 43 | } 44 | 45 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 46 | # Note: The reconnections strategy could also be configured using the broker properties object 47 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 48 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 49 | .build() 50 | 51 | # Blocking connect thread 52 | messaging_service.connect() 53 | print(f'\nMessaging Service connected? {messaging_service.is_connected}') 54 | 55 | # Event Handling for the messaging service 56 | service_handler = ServiceEventHandler() 57 | messaging_service.add_reconnection_listener(service_handler) 58 | messaging_service.add_reconnection_attempt_listener(service_handler) 59 | messaging_service.add_service_interruption_listener(service_handler) 60 | 61 | # Create a direct message requestor and register the error handler 62 | direct_requestor: RequestReplyMessagePublisher = messaging_service.request_reply() \ 63 | .create_request_reply_message_publisher_builder() \ 64 | .build() 65 | 66 | # Blocking Start thread 67 | direct_requestor.start() 68 | print(f'\nDirect Requestor ready? {direct_requestor.is_ready()}') 69 | 70 | # Prepare outbound message payload and body 71 | topic = Topic.of(TOPIC_PREFIX + '/direct/request') 72 | message_body = "This is request message from direct requestor on " + f'{topic}' 73 | outbound_msg_builder = messaging_service.message_builder() \ 74 | .with_property("application", "samples") \ 75 | .with_property("language", "Python") 76 | 77 | # Capture the timestamp and use that as message-id 78 | gmt = time.gmtime() 79 | message_id = calendar.timegm(gmt) 80 | 81 | print('\nSend a KeyboardInterrupt to stop publishing') 82 | try: 83 | print(f'============================') 84 | print(f'Publishing to topic:\n{topic}') 85 | 86 | try: 87 | # Direct publish the message with dynamic headers and payload 88 | outbound_msg = outbound_msg_builder \ 89 | .with_application_message_id(f'NEW {message_id}')\ 90 | .build(f'\n{message_body}') 91 | print(f'>>>>>>>>>>>>>>>>>>>>>>>>>>>>') 92 | print(f'Publishing request (body):' + outbound_msg.get_payload_as_string()) 93 | print(f'----------------------------') 94 | print(f'Publishing message:\n{outbound_msg}') 95 | publish_async = direct_requestor.publish(request_message=outbound_msg, \ 96 | request_destination=topic, 97 | reply_timeout=3000) 98 | # we can get the reply in the future 99 | response = publish_async.result() 100 | print(f'<<<<<<<<<<<<<<<<<<<<<<<<<<<<') 101 | print(f'Received reply (body):\n' + response.get_payload_as_string()) 102 | print(f'----------------------------') 103 | print(f'Received reply:\n{response}') 104 | print(f'============================\n') 105 | except KeyboardInterrupt: 106 | print('\nInterrupted, disconnecting Messaging Service') 107 | except PubSubPlusClientError as exception: 108 | print(f'Received a PubSubPlusClientException: {exception}') 109 | finally: 110 | print('\nTerminating Requestor') 111 | direct_requestor.terminate() 112 | print('\nDisconnecting Messaging Service') 113 | messaging_service.disconnect() 114 | -------------------------------------------------------------------------------- /howtos/how_to_connect_messaging_service.py: -------------------------------------------------------------------------------- 1 | """ Sampler for direct message publisher """ 2 | from string import Template 3 | 4 | from solace.messaging.errors.pubsubplus_client_error import PubSubPlusClientError 5 | from solace.messaging.messaging_service import MessagingService 6 | from howtos.sampler_boot import SamplerBoot, SolaceConstants, SamplerUtil 7 | 8 | boot = SamplerBoot() 9 | constants = SolaceConstants() 10 | 11 | 12 | class HowToConnectMessagingService: 13 | """ 14 | class to show how to create a messaging service 15 | """ 16 | 17 | @staticmethod 18 | def create_from_system_properties(): 19 | """ 20 | creates a new instance of message service, that is used to configure 21 | direct message instances from system properties 22 | 23 | Returns:new connection for Direct messaging 24 | """ 25 | try: 26 | messaging_service = MessagingService.builder().from_system_properties().build() 27 | return messaging_service.connect() 28 | finally: 29 | messaging_service.disconnect() 30 | 31 | @staticmethod 32 | def create_from_environment_variables(): 33 | """ 34 | creates a new instance of message service, that is used to configure 35 | direct message instances from environment variables 36 | 37 | Returns:new connection for Direct messaging 38 | """ 39 | try: 40 | messaging_service = MessagingService.builder().from_environment_variables().build() 41 | return messaging_service.connect() 42 | finally: 43 | messaging_service.disconnect() 44 | 45 | @staticmethod 46 | def create_from_file(): 47 | """ 48 | creates a new instance of message service, that is used to configure 49 | direct message instances from properties file 50 | 51 | Returns:new connection for Direct messaging 52 | """ 53 | try: 54 | messaging_service = MessagingService.builder().from_file().build() 55 | return messaging_service.connect() 56 | finally: 57 | messaging_service.disconnect() 58 | 59 | @staticmethod 60 | def create_from_properties_async(config: dict): 61 | """ 62 | creates a new instance of message service, that is used to configure 63 | direct message instances from config 64 | 65 | Returns:new connection for Direct messaging 66 | Raises: 67 | PubSubPlusClientError 68 | """ 69 | try: 70 | messaging_service = MessagingService.builder().from_properties(config).build() 71 | future = messaging_service.connect_async() 72 | return future.result() 73 | except PubSubPlusClientError as exception: 74 | raise exception 75 | finally: 76 | messaging_service.disconnect() 77 | 78 | @staticmethod 79 | def create_from_properties(config: dict): 80 | """ 81 | creates a new instance of message service, that is used to configure 82 | direct message instances from config 83 | 84 | Returns: new connection for Direct messaging 85 | Raises: 86 | PubSubPlusClientError 87 | """ 88 | try: 89 | messaging_service = MessagingService.builder().from_properties(config).build() 90 | return messaging_service.connect() 91 | except PubSubPlusClientError as exception: 92 | raise exception 93 | finally: 94 | messaging_service.disconnect() 95 | 96 | @staticmethod 97 | def create_from_properties_async_application_id(config: dict, application_id: str): 98 | """ 99 | creates a new instance of message service, that is used to configure 100 | direct message instances from config 101 | 102 | Returns:new connection for Direct messaging 103 | Raises: 104 | PubSubPlusClientError: if we didn't receive future 105 | """ 106 | try: 107 | messaging_service = MessagingService.builder().from_properties(config).build(application_id) 108 | future = messaging_service.connect_async() 109 | return future.result() 110 | except PubSubPlusClientError as exception: 111 | raise exception 112 | finally: 113 | messaging_service.disconnect() 114 | 115 | @staticmethod 116 | def run(): 117 | """this method is used to run the connect messaging service sampler""" 118 | broker_props = boot.broker_properties() 119 | 120 | result = HowToConnectMessagingService().create_from_properties(broker_props) 121 | SamplerUtil.print_sampler_result(Template("Message Service[SYNC] connect $status") 122 | .substitute(status="SUCCESS" if result else "FAILED")) 123 | 124 | result = HowToConnectMessagingService().create_from_properties_async(broker_props) 125 | SamplerUtil.print_sampler_result(Template("Message Service[ASYNC] connect $status") 126 | .substitute(status="SUCCESS" if isinstance(result, MessagingService) else "FAILED")) 127 | 128 | result = HowToConnectMessagingService() \ 129 | .create_from_properties_async_application_id(broker_props, SamplerUtil.get_new_application_id()) 130 | SamplerUtil.print_sampler_result(Template("Message Service[ASYNC] connect with applicationId $status") 131 | .substitute(status="SUCCESS" if isinstance(result, MessagingService) else "FAILED")) 132 | 133 | 134 | if __name__ == '__main__': 135 | HowToConnectMessagingService().run() 136 | -------------------------------------------------------------------------------- /patterns/guaranteed_receiver.py: -------------------------------------------------------------------------------- 1 | # Receiver that binds to exclusive durable queue 2 | # Assumes existence of queue on broker holding messages. 3 | # Note: create queue with topic subscription 4 | # See https://docs.solace.com/Solace-PubSub-Messaging-APIs/API-Developer-Guide/Adding-Topic-Subscriptio.htm for more details 5 | 6 | import os 7 | import platform 8 | import time 9 | 10 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener, ServiceEvent 11 | from solace.messaging.resources.queue import Queue 12 | from solace.messaging.config.retry_strategy import RetryStrategy 13 | from solace.messaging.receiver.persistent_message_receiver import PersistentMessageReceiver 14 | from solace.messaging.receiver.message_receiver import MessageHandler, InboundMessage 15 | from solace.messaging.errors.pubsubplus_client_error import PubSubPlusClientError 16 | from solace.messaging.config.missing_resources_creation_configuration import MissingResourcesCreationStrategy 17 | 18 | 19 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 20 | 21 | # Handle received messages 22 | class MessageHandlerImpl(MessageHandler): 23 | def __init__(self, persistent_receiver: PersistentMessageReceiver): 24 | self.receiver: PersistentMessageReceiver = persistent_receiver 25 | 26 | def on_message(self, message: InboundMessage): 27 | try: 28 | # Check if the payload is a String or Byte, decode if its the later 29 | payload = message.get_payload_as_string() if message.get_payload_as_string() is not None else message.get_payload_as_bytes() 30 | if isinstance(payload, bytearray): 31 | print(f"Received a message of type: {type(payload)}. Decoding to string") 32 | payload = payload.decode() 33 | 34 | topic = message.get_destination_name() 35 | print("\n" + f"Received message on: {topic}") 36 | print("\n" + f"Message payload: {payload} \n") 37 | self.receiver.ack(message) 38 | # print("\n" + f"Message dump: {message} \n") 39 | except Exception as e: 40 | print(f"Error processing message: {e.__traceback__}") 41 | 42 | # Inner classes for error handling 43 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 44 | def on_reconnected(self, e: ServiceEvent): 45 | print("\non_reconnected") 46 | print(f"Error cause: {e.get_cause()}") 47 | print(f"Message: {e.get_message()}") 48 | 49 | def on_reconnecting(self, e: "ServiceEvent"): 50 | print("\non_reconnecting") 51 | print(f"Error cause: {e.get_cause()}") 52 | print(f"Message: {e.get_message()}") 53 | 54 | def on_service_interrupted(self, e: "ServiceEvent"): 55 | print("\non_service_interrupted") 56 | print(f"Error cause: {e.get_cause()}") 57 | print(f"Message: {e.get_message()}") 58 | 59 | # Broker Config. Note: Could pass other properties Look into 60 | broker_props = { 61 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 62 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 63 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 64 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "default" 65 | } 66 | 67 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 68 | # Note: The reconnections strategy could also be configured using the broker properties object 69 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 70 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3000))\ 71 | .build() 72 | 73 | # Blocking connect thread 74 | messaging_service.connect() 75 | print(f'Messaging Service connected? {messaging_service.is_connected}') 76 | 77 | # Event Handling for the messaging service 78 | service_handler = ServiceEventHandler() 79 | messaging_service.add_reconnection_listener(service_handler) 80 | messaging_service.add_reconnection_attempt_listener(service_handler) 81 | messaging_service.add_service_interruption_listener(service_handler) 82 | 83 | # Queue name. 84 | # NOTE: This assumes that a persistent queue already exists on the broker with the right topic subscription 85 | queue_name = "sample-queue" 86 | durable_exclusive_queue = Queue.durable_exclusive_queue(queue_name) 87 | 88 | try: 89 | # Build a receiver and bind it to the durable exclusive queue 90 | persistent_receiver: PersistentMessageReceiver = messaging_service.create_persistent_message_receiver_builder()\ 91 | .with_missing_resources_creation_strategy(MissingResourcesCreationStrategy.CREATE_ON_START)\ 92 | .build(durable_exclusive_queue) 93 | persistent_receiver.start() 94 | 95 | # Callback for received messages 96 | persistent_receiver.receive_async(MessageHandlerImpl(persistent_receiver)) 97 | print(f'PERSISTENT receiver started... Bound to Queue [{durable_exclusive_queue.get_name()}]') 98 | try: 99 | while True: 100 | time.sleep(1) 101 | except KeyboardInterrupt: 102 | print('\nKeyboardInterrupt received') 103 | # Handle API exception 104 | except PubSubPlusClientError as exception: 105 | print(f'\nMake sure queue {queue_name} exists on broker!') 106 | 107 | finally: 108 | if persistent_receiver and persistent_receiver.is_running(): 109 | print('\nTerminating receiver') 110 | persistent_receiver.terminate(grace_period = 0) 111 | print('\nDisconnecting Messaging Service') 112 | messaging_service.disconnect() 113 | 114 | -------------------------------------------------------------------------------- /howtos/how_to_trigger_replay_persistent_message.py: -------------------------------------------------------------------------------- 1 | """sampler to consume persistent message using message replay(with_message_replay)""" 2 | # Note to run this file replay feature needs to be enabled in broker 3 | 4 | import threading 5 | from datetime import datetime 6 | from typing import TypeVar 7 | 8 | from solace.messaging.config.replay_strategy import ReplayStrategy 9 | from solace.messaging.messaging_service import MessagingService 10 | from solace.messaging.receiver.inbound_message import InboundMessage 11 | from solace.messaging.receiver.persistent_message_receiver import PersistentMessageReceiver 12 | from solace.messaging.resources.queue import Queue 13 | from solace.messaging.resources.topic import Topic 14 | from howtos.sampler_boot import SolaceConstants, SamplerBoot 15 | from howtos.sampler_master import SamplerMaster 16 | 17 | X = TypeVar('X') 18 | constants = SolaceConstants 19 | boot = SamplerBoot() 20 | lock = threading.Lock() 21 | topic_name = constants.TOPIC_ENDPOINT_DEFAULT 22 | topic = Topic.of(topic_name) 23 | 24 | 25 | class HowToTriggerReplayPersistentMessage: 26 | """class contains methods to trigger All and Time Based message replays""" 27 | 28 | @staticmethod 29 | def all_messages_replay(service: MessagingService, queue_to_consume: Queue): 30 | receiver = None 31 | try: 32 | reply_strategy = ReplayStrategy.all_messages() 33 | receiver: PersistentMessageReceiver = service.create_persistent_message_receiver_builder() \ 34 | .with_message_replay(reply_strategy).build(queue_to_consume) 35 | receiver.start() 36 | message: InboundMessage = receiver.receive_message(timeout=5000) 37 | if message: 38 | print(f"the message payload is {message.get_payload_as_string()}") 39 | receiver.ack() 40 | finally: 41 | if receiver: 42 | receiver.terminate() 43 | 44 | @staticmethod 45 | def time_based_replay(service: MessagingService, queue_to_consume: Queue): 46 | receiver = None 47 | try: 48 | reply_strategy = ReplayStrategy.time_based(datetime.now()) # replay_date can be earlier/equal to the date of the message publish was done 49 | 50 | receiver: PersistentMessageReceiver = service.create_persistent_message_receiver_builder() \ 51 | .with_message_replay(reply_strategy).build(queue_to_consume) 52 | receiver.start() 53 | message: InboundMessage = receiver.receive_message(timeout=5000) 54 | if message: 55 | print(f"the message payload is {message.get_payload_as_string()}") 56 | receiver.ack() 57 | finally: 58 | if receiver: 59 | receiver.terminate() 60 | 61 | @staticmethod 62 | def id_based_replay(service: MessagingService, queue_to_consume: Queue, 63 | restored_replication_group_message_id: 'ReplicationGroupMessageId'): 64 | """Showcase for API to trigger message replay using string representation of a replication group message Id""" 65 | receiver = None 66 | try: 67 | reply_strategy = ReplayStrategy.replication_group_message_id_based(restored_replication_group_message_id) 68 | 69 | receiver: PersistentMessageReceiver = service.create_persistent_message_receiver_builder() \ 70 | .with_message_replay(reply_strategy).build(queue_to_consume) 71 | receiver.start() 72 | message: InboundMessage = receiver.receive_message(timeout=5000) 73 | if message: 74 | print(f"the message payload is {message.get_payload_as_string()}") 75 | receiver.ack() 76 | finally: 77 | if receiver: 78 | receiver.terminate() 79 | 80 | @staticmethod 81 | def get_replication_group_message_id_string_from_inbound_message(previously_received_message: InboundMessage): 82 | """ Showcase for API to retrieve ReplicationGroupMessageId in a string format. This string can be 83 | stored in between to be restored to the ReplicationGroupMessageId object later and used in the 84 | api to trigger message replay or it can also be used for administratively triggered message 85 | replay via SEMP or CLI interface""" 86 | original_replication_group_message_id = previously_received_message.get_replication_group_message_id() 87 | return str(original_replication_group_message_id) 88 | 89 | @staticmethod 90 | def compare(restored_replication_group_message_id: 'ReplicationGroupMessageId', second_message: InboundMessage): 91 | """ Attempts to compare a given instance of ReplicationGroupMessageId with an another 92 | instance for order.""" 93 | restored_replication_group_message_id.compare(second_message.get_replication_group_message_id()) 94 | 95 | @staticmethod 96 | def run(): 97 | messaging_service = None 98 | try: 99 | # we're assuming replay_log is already created and pub/sub is already done in the queue/topic 100 | queue_name = 'Q/test/replay/pub_sub' 101 | messaging_service = SamplerMaster.connect_messaging_service() 102 | durable_exclusive_queue = Queue.durable_exclusive_queue(queue_name) 103 | HowToTriggerReplayPersistentMessage \ 104 | .all_messages_replay(service=messaging_service, queue_to_consume=durable_exclusive_queue) 105 | HowToTriggerReplayPersistentMessage \ 106 | .time_based_replay(service=messaging_service, queue_to_consume=durable_exclusive_queue) 107 | finally: 108 | if messaging_service: 109 | messaging_service.disconnect() 110 | 111 | 112 | if __name__ == '__main__': 113 | HowToTriggerReplayPersistentMessage.run() 114 | -------------------------------------------------------------------------------- /patterns/direct_requestor_blocking.py: -------------------------------------------------------------------------------- 1 | ## Goal is to demonstrate a requestor (a request-reply pattern) that will publish a request and block until a reply is received or timed out. 2 | 3 | import os 4 | import platform 5 | import time 6 | import calendar 7 | 8 | # Import Solace Python API modules from the solace package 9 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, \ 10 | ServiceInterruptionListener, RetryStrategy, ServiceEvent 11 | from solace.messaging.resources.topic import Topic 12 | from solace.messaging.errors.pubsubplus_client_error import PubSubPlusClientError 13 | from solace.messaging.publisher.request_reply_message_publisher import RequestReplyMessagePublisher 14 | 15 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 16 | 17 | TOPIC_PREFIX = "solace/samples/python" 18 | 19 | # Inner classes for error handling 20 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 21 | def on_reconnected(self, e: ServiceEvent): 22 | print("\non_reconnected") 23 | print(f"Error cause: {e.get_cause()}") 24 | print(f"Message: {e.get_message()}") 25 | 26 | def on_reconnecting(self, e: "ServiceEvent"): 27 | print("\non_reconnecting") 28 | print(f"Error cause: {e.get_cause()}") 29 | print(f"Message: {e.get_message()}") 30 | 31 | def on_service_interrupted(self, e: "ServiceEvent"): 32 | print("\non_service_interrupted") 33 | print(f"Error cause: {e.get_cause()}") 34 | print(f"Message: {e.get_message()}") 35 | 36 | 37 | # Broker Config. Note: Could pass other properties Look into 38 | broker_props = { 39 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 40 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 41 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 42 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "" 43 | } 44 | 45 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 46 | # Note: The reconnections strategy could also be configured using the broker properties object 47 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 48 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 49 | .build() 50 | 51 | # Blocking connect thread 52 | messaging_service.connect() 53 | print(f'\nMessaging Service connected? {messaging_service.is_connected}') 54 | 55 | # Event Handling for the messaging service 56 | service_handler = ServiceEventHandler() 57 | messaging_service.add_reconnection_listener(service_handler) 58 | messaging_service.add_reconnection_attempt_listener(service_handler) 59 | messaging_service.add_service_interruption_listener(service_handler) 60 | 61 | # Create a direct message requestor and register the error handler 62 | direct_requestor_blocking: RequestReplyMessagePublisher = messaging_service.request_reply() \ 63 | .create_request_reply_message_publisher_builder() \ 64 | .build() 65 | 66 | # Blocking Start thread 67 | direct_requestor_blocking.start() 68 | print(f'\nDirect Requestor ready? {direct_requestor_blocking.is_ready()}') 69 | 70 | # Prepare outbound message payload and body 71 | topic = Topic.of(TOPIC_PREFIX + '/direct/request') 72 | message_body = "This is request message from direct requestor on " + f'{topic}' 73 | outbound_msg_builder = messaging_service.message_builder() \ 74 | .with_property("application", "samples") \ 75 | .with_property("language", "Python") 76 | 77 | # Capture the timestamp and use that as message-id 78 | gmt = time.gmtime() 79 | message_id = calendar.timegm(gmt) 80 | 81 | print('\nSend a KeyboardInterrupt to stop publishing') 82 | try: 83 | print(f'============================') 84 | print(f'Publishing to topic:\n{topic}') 85 | 86 | try: 87 | # Direct publish the message with dynamic headers and payload 88 | outbound_msg = outbound_msg_builder \ 89 | .with_application_message_id(f'NEW {message_id}')\ 90 | .build(f'\n{message_body}') 91 | print(f'>>>>>>>>>>>>>>>>>>>>>>>>>>>>') 92 | print(f'Publishing request (body):' + outbound_msg.get_payload_as_string()) 93 | print(f'----------------------------') 94 | print(f'Publishing message:\n{outbound_msg}') 95 | 96 | # Block the thread and wait for the response. Raise error after timeout 97 | response = direct_requestor_blocking.publish_await_response(request_message=outbound_msg, \ 98 | request_destination=topic, \ 99 | reply_timeout=3000) 100 | print(f'<<<<<<<<<<<<<<<<<<<<<<<<<<<<') 101 | print(f'Received reply (body):\n' + response.get_payload_as_string()) 102 | print(f'----------------------------') 103 | print(f'Received reply:\n{response}') 104 | print(f'============================\n') 105 | except KeyboardInterrupt: 106 | print('\nInterrupted, disconnecting Messaging Service') 107 | except PubSubPlusClientError as exception: 108 | print(f'Received a PubSubPlusClientException: {exception}') 109 | finally: 110 | print('\nTerminating Requestor') 111 | direct_requestor_blocking.terminate() 112 | print('\nDisconnecting Messaging Service') 113 | messaging_service.disconnect() -------------------------------------------------------------------------------- /howtos/how_to_configure_authentication.py: -------------------------------------------------------------------------------- 1 | """sampler module for configuring the authentication""" 2 | from string import Template 3 | 4 | from solace.messaging.config.authentication_strategy import BasicUserNamePassword, ClientCertificateAuthentication 5 | from solace.messaging.config.solace_properties import authentication_properties 6 | from solace.messaging.config.transport_security_strategy import TLS 7 | from solace.messaging.messaging_service import MessagingService 8 | from howtos.sampler_boot import SamplerBoot, SamplerUtil, SolaceConstants 9 | 10 | boot = SamplerBoot() 11 | 12 | 13 | class HowToConfigureAuthentication: 14 | """class contains methods for configuring the authentication""" 15 | 16 | @staticmethod 17 | def configure_basic_auth_credentials(props, user_name: str, password: str): 18 | """setup for basic auth using user name and password 19 | 20 | Args: 21 | props: 22 | user_name: user name 23 | password: password 24 | 25 | Returns: 26 | configured and connected instance of MessagingService ready to be used for messaging tasks 27 | """ 28 | try: 29 | messaging_service = MessagingService.builder().from_properties(props) \ 30 | .with_authentication_strategy(BasicUserNamePassword.of(user_name, password)).build() 31 | return messaging_service.connect() 32 | except Exception as exception: 33 | print(exception) 34 | finally: 35 | messaging_service.disconnect() 36 | 37 | @staticmethod 38 | def configure_client_certificate_authentication_customized_settings(props, key_file, 39 | key_store_password, key_store_url): 40 | """ 41 | For a client to use a client certificate authentication scheme, the host event broker must be 42 | properly configured for TLS/SSL connections, and Client Certificate Verification must be 43 | enabled for the particular Message VPN that the client is connecting to. On client side client 44 | certificate needs to be present in a keystore file. 45 | 46 | Args: 47 | props: 48 | key_store_password: password for the key store 49 | key_store_url: url to the key store file 50 | key_file: key file 51 | 52 | Returns: 53 | configured and connected instance of MessagingService ready to be used for messaging tasks 54 | """ 55 | try: 56 | transport_security = TLS.create() \ 57 | .with_certificate_validation(True, validate_server_name=False, 58 | trust_store_file_path=SamplerUtil.get_trusted_store_dir()) 59 | 60 | messaging_service = MessagingService.builder() \ 61 | .from_properties(props) \ 62 | .with_transport_security_strategy(transport_security) \ 63 | .with_authentication_strategy(ClientCertificateAuthentication.of(certificate_file=key_store_url, 64 | key_file=key_file, 65 | key_password=key_store_password)) \ 66 | .build(SamplerUtil.get_new_application_id()) 67 | return messaging_service.connect() 68 | except Exception as exception: 69 | print(exception) 70 | finally: 71 | messaging_service.disconnect() 72 | 73 | @staticmethod 74 | def basic_compression(props, compression_range): 75 | """method for applying compression to the messaging service 76 | 77 | Args: 78 | props: broker properties 79 | compression_range: int value the compression value 80 | """ 81 | try: 82 | messaging_service = MessagingService.builder().from_properties(props) \ 83 | .with_message_compression(compression_range).build() 84 | return messaging_service.connect() 85 | finally: 86 | messaging_service.disconnect() 87 | 88 | @staticmethod 89 | def run(): 90 | """method to run all the other authentication configuration methods""" 91 | props_unsecured = boot.broker_properties() 92 | props_secured = boot.secured_broker_properties() 93 | props_compressed = boot.compressed_broker_properties() 94 | user_name = props_unsecured[authentication_properties.SCHEME_BASIC_USER_NAME] 95 | password = props_unsecured[authentication_properties.SCHEME_BASIC_PASSWORD] 96 | key_store_url = SamplerUtil.get_valid_client_certificate() 97 | key_store_password = SolaceConstants.KEY_STORE_PASSWORD 98 | key_file = SamplerUtil.get_valid_client_key() 99 | 100 | result = HowToConfigureAuthentication.configure_basic_auth_credentials(props_unsecured, user_name, password) 101 | SamplerUtil.print_sampler_result(Template("Message Service[SYNC] connect with AUTH strategy $status") 102 | .substitute(status="SUCCESS" if result else "FAILED")) 103 | 104 | result = HowToConfigureAuthentication \ 105 | .configure_client_certificate_authentication_customized_settings(props_secured, key_file, 106 | key_store_password, key_store_url) 107 | SamplerUtil.print_sampler_result(Template("Message Service[SYNC] connect with TLS strategy $status") 108 | .substitute(status="SUCCESS" if result else "FAILED")) 109 | 110 | result = HowToConfigureAuthentication.basic_compression(props_compressed, compression_range=1) 111 | SamplerUtil.print_sampler_result(Template("Message Service[SYNC] connect with COMPRESSION $status") 112 | .substitute(status="SUCCESS" if result else "FAILED")) 113 | 114 | 115 | if __name__ == '__main__': 116 | HowToConfigureAuthentication.run() 117 | -------------------------------------------------------------------------------- /patterns/direct_replier.py: -------------------------------------------------------------------------------- 1 | ## Goal is to demonstrate a replier (a request-reply pattern) that receives a request asynchornously and responds with a reply 2 | 3 | import os 4 | import platform 5 | import time 6 | 7 | # Import Solace Python API modules from the solace package 8 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, \ 9 | ServiceInterruptionListener, RetryStrategy, ServiceEvent 10 | from solace.messaging.resources.topic import Topic 11 | from solace.messaging.resources.topic_subscription import TopicSubscription 12 | from solace.messaging.receiver.request_reply_message_receiver import RequestMessageHandler, InboundMessage, \ 13 | RequestReplyMessageReceiver, Replier 14 | 15 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 16 | 17 | TOPIC_PREFIX = "solace/samples/" 18 | 19 | # Handle received messages 20 | class RequestMessageHandlerImpl(RequestMessageHandler): 21 | def __init__(self, message_builder): 22 | self.message_builder = message_builder 23 | 24 | def on_message(self, request: InboundMessage, replier: Replier): 25 | try: 26 | # Check if the payload is a String or Byte, decode if its the later 27 | payload = request.get_payload_as_string() if request.get_payload_as_string() is not None else request.get_payload_as_bytes() 28 | if isinstance(payload, bytearray): 29 | print(f"Received a message of type: {type(payload)}. Decoding to string") 30 | payload = payload.decode() 31 | 32 | print(f'<<<<<<<<<<<<<<<<<<<<<<<<<<<<') 33 | print(f'Received request payload:' + payload) 34 | print(f'----------------------------') 35 | 36 | # Prepare response payload 37 | response = f'Greetings {payload.split("This is request message from ")[1]}!' if "This is request message from " in payload else 'Response body' 38 | 39 | message_id = request.get_application_message_id() 40 | if replier is not None: 41 | outbound_msg = outbound_msg_builder \ 42 | .with_application_message_id(f'{message_id}')\ 43 | .build(response) 44 | replier.reply(outbound_msg) 45 | print(f'>>>>>>>>>>>>>>>>>>>>>>>>>>>>') 46 | print(f'Replied with response payload: {outbound_msg.get_payload_as_string()}') 47 | print(f'----------------------------') 48 | else: 49 | print(f'Invalid request, reply_to not set') 50 | except Exception as e: 51 | print(f'Error processing request: {e.__traceback__}') 52 | 53 | # Inner classes for error handling 54 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 55 | def on_reconnected(self, e: ServiceEvent): 56 | print("\non_reconnected") 57 | print(f"Error cause: {e.get_cause()}") 58 | print(f"Message: {e.get_message()}") 59 | 60 | def on_reconnecting(self, e: "ServiceEvent"): 61 | print("\non_reconnecting") 62 | print(f"Error cause: {e.get_cause()}") 63 | print(f"Message: {e.get_message()}") 64 | 65 | def on_service_interrupted(self, e: "ServiceEvent"): 66 | print("\non_service_interrupted") 67 | print(f"Error cause: {e.get_cause()}") 68 | print(f"Message: {e.get_message()}") 69 | 70 | 71 | # Broker Config. Note: Could pass other properties Look into 72 | broker_props = { 73 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 74 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 75 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 76 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "" 77 | } 78 | 79 | request_topic = TOPIC_PREFIX + '*/direct/request' 80 | print(f'\nSubscribing to topic {request_topic}') 81 | 82 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 83 | # Note: The reconnections strategy could also be configured using the broker properties object 84 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 85 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 86 | .build() 87 | 88 | # Blocking connect thread 89 | messaging_service.connect() 90 | print(f'\nMessaging Service connected? {messaging_service.is_connected}') 91 | 92 | # Event Handling for the messaging service 93 | service_handler = ServiceEventHandler() 94 | messaging_service.add_reconnection_listener(service_handler) 95 | messaging_service.add_reconnection_attempt_listener(service_handler) 96 | messaging_service.add_service_interruption_listener(service_handler) 97 | 98 | # Create a direct message replier 99 | direct_replier: RequestReplyMessageReceiver = messaging_service.request_reply() \ 100 | .create_request_reply_message_receiver_builder() \ 101 | .build(TopicSubscription.of(request_topic)) 102 | 103 | # Blocking Start thread 104 | direct_replier.start() 105 | 106 | # Prepare outbound message payload and body 107 | message_body = "this is the reply body of the msg with count: " 108 | outbound_msg_builder = messaging_service.message_builder() \ 109 | .with_property("application", "samples") \ 110 | .with_property("language", "Python") 111 | 112 | print("\nSend a KeyboardInterrupt to stop receiving\n") 113 | 114 | try: 115 | # Callback for received messages 116 | direct_replier.receive_async(RequestMessageHandlerImpl(outbound_msg_builder)) 117 | try: 118 | while True: 119 | time.sleep(1) 120 | except KeyboardInterrupt: 121 | print('\nDisconnecting Messaging Service') 122 | finally: 123 | print('\nTerminating receiver') 124 | direct_replier.terminate() 125 | print('\nDisconnecting Messaging Service') 126 | messaging_service.disconnect() -------------------------------------------------------------------------------- /howtos/how_to_configure_transport_layer_security.py: -------------------------------------------------------------------------------- 1 | """sampler for configuring the transport layer security""" 2 | import configparser 3 | import os 4 | from os.path import dirname 5 | from solace.messaging.config.transport_security_strategy import TLS 6 | from solace.messaging.errors.pubsubplus_client_error import PubSubPlusClientError 7 | from solace.messaging.messaging_service import MessagingService 8 | from howtos.sampler_boot import SamplerUtil, SamplerBoot 9 | 10 | broker_properties_value = SamplerBoot.secured_broker_properties() 11 | 12 | 13 | class HowToConnectWithTls: 14 | """sampler class for validating transport layer security configuration""" 15 | 16 | @staticmethod 17 | def tls_with_certificate_validation_and_trusted_store_settings(props, ignore_expiration: bool, 18 | trust_store_path: str): 19 | """method for validating tls certificate along with trusted store by providing the file path 20 | Args: 21 | props: broker properties 22 | trust_store_path (str): trust store file path 23 | ignore_expiration (bool): holds a boolean flag whether to ignore expiration or not 24 | 25 | Returns: 26 | a new connection to messaging service 27 | """ 28 | try: 29 | transport_security = TLS.create() \ 30 | .with_certificate_validation(ignore_expiration=ignore_expiration, 31 | trust_store_file_path=trust_store_path) 32 | messaging_service = MessagingService.builder().from_properties(props)\ 33 | .with_transport_security_strategy(transport_security).build() 34 | messaging_service.connect() 35 | 36 | finally: 37 | messaging_service.disconnect() 38 | 39 | @staticmethod 40 | def tls_downgradable_to_plain_text(props, trust_store_path: str): 41 | """method for validating the tls downgradable to plain text 42 | Args: 43 | props: broker properties 44 | 45 | Returns: 46 | a new connection to messaging service 47 | """ 48 | try: 49 | transport_security = TLS.create().downgradable() \ 50 | .with_certificate_validation(ignore_expiration=True, 51 | trust_store_file_path=trust_store_path) 52 | print(props) 53 | messaging_service = MessagingService.builder().from_properties(props) \ 54 | .with_transport_security_strategy(transport_security).build() 55 | messaging_service.connect() 56 | finally: 57 | messaging_service.disconnect() 58 | 59 | @staticmethod 60 | def tls_with_excluded_protocols(props, excluded_protocol: TLS.SecureProtocols, trust_store_path: str): 61 | """method for validating excluding tls protocols 62 | Args: 63 | props: broker properties 64 | excluded_protocol (SecuredProtocols): contains a value or a list of values of protocols to be excluded 65 | 66 | Returns: 67 | a new connection to messaging service 68 | """ 69 | try: 70 | transport_security = TLS.create().with_excluded_protocols(excluded_protocol) \ 71 | .with_certificate_validation(ignore_expiration=True, 72 | trust_store_file_path=trust_store_path) 73 | 74 | messaging_service = MessagingService.builder().from_properties(props) \ 75 | .with_transport_security_strategy(transport_security).build() 76 | messaging_service.connect() 77 | finally: 78 | messaging_service.disconnect() 79 | 80 | @staticmethod 81 | def tls_with_cipher_suites(props, cipher_suite: str, trust_store_path: str): 82 | """method for validating the cipher suited with tls 83 | Args: 84 | props: broker properties 85 | cipher_suite (str): cipher suite list 86 | 87 | Returns: 88 | a new connection to messaging service 89 | """ 90 | try: 91 | transport_security = TLS.create().with_cipher_suites(cipher_suite) \ 92 | .with_certificate_validation(ignore_expiration=True, 93 | trust_store_file_path=trust_store_path) 94 | 95 | messaging_service = MessagingService.builder().from_properties(props) \ 96 | .with_transport_security_strategy(transport_security).build() 97 | messaging_service.connect() 98 | finally: 99 | messaging_service.disconnect() 100 | 101 | @staticmethod 102 | def run(): 103 | """method to run all the methods related to tls configuration """ 104 | props = broker_properties_value 105 | trust_store_path_name = SamplerUtil.get_trusted_store_dir() 106 | cipher_suite = "ECDHE-RSA-AES256-GCM-SHA384" 107 | 108 | HowToConnectWithTls.\ 109 | tls_with_certificate_validation_and_trusted_store_settings(props, ignore_expiration=True, 110 | trust_store_path=trust_store_path_name) 111 | try: 112 | HowToConnectWithTls.tls_downgradable_to_plain_text(props, trust_store_path=trust_store_path_name) 113 | except PubSubPlusClientError as err: 114 | # tls downgrade must be enabled on the connected broker: 115 | # - message vpn must have the TLS downgrade option enabled see, https://docs.solace.com/Configuring-and-Managing/Enabling-TLS-Downgrade.htm 116 | # - the client username provided must have profile with this enabled see, https://docs.solace.com/Configuring-and-Managing/Configuring-Client-Profiles.htm#Configur3 117 | print(f'Failed to connect with tls downgrade to plain text, is this enabled on the broker? Error: {err}') 118 | HowToConnectWithTls.tls_with_excluded_protocols(props, 119 | excluded_protocol=TLS.SecureProtocols.SSLv3, 120 | trust_store_path=trust_store_path_name) 121 | 122 | HowToConnectWithTls.tls_with_cipher_suites(props, cipher_suite=cipher_suite, 123 | trust_store_path=trust_store_path_name) 124 | 125 | 126 | if __name__ == '__main__': 127 | HowToConnectWithTls.run() 128 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_add_and_remove_subscription.py: -------------------------------------------------------------------------------- 1 | """sampler module to show how to add and remove the subscriptions""" 2 | 3 | import pickle 4 | import time 5 | from concurrent.futures.thread import ThreadPoolExecutor 6 | from typing import TypeVar, Generic 7 | 8 | from solace.messaging.messaging_service import MessagingService 9 | from solace.messaging.receiver.message_receiver import MessageHandler 10 | from solace.messaging.resources.topic_subscription import TopicSubscription 11 | from solace.messaging.utils.converter import BytesToObject 12 | from howtos.pubsub.how_to_direct_publish_message import HowToDirectPublishMessage 13 | from howtos.sampler_boot import SamplerBoot, SolaceConstants 14 | 15 | X = TypeVar('X') 16 | constants = SolaceConstants 17 | boot = SamplerBoot() 18 | MAX_SLEEP = 10 19 | 20 | 21 | class MessageHandlerImpl1(MessageHandler): 22 | """this method is an call back handler to receive message""" 23 | 24 | def on_message(self, message: 'InboundMessage'): 25 | """ Message receive callback """ 26 | topic = message.get_destination_name() 27 | payload_as_bytes = message.get_payload_as_bytes() 28 | payload_as_string = message.get_payload_as_string() 29 | correlation_id = message.get_correlation_id() 30 | print("\n" + f"CALLBACK: Message Received on Topic: {topic}.\n" 31 | f"Message Bytes: {payload_as_bytes} \n" 32 | f"Message String: {payload_as_string} \n" 33 | f"Correlation id: {correlation_id}" 34 | ) 35 | 36 | 37 | class MessageHandlerImpl2(MessageHandler): 38 | """this method is an call back handler to receive message""" 39 | 40 | def on_message(self, message: 'InboundMessage'): 41 | """ Message receive callback """ 42 | topic = message.get_destination_name() 43 | payload_as_bytes = message.get_payload_as_bytes() 44 | payload_as_string = message.get_payload_as_string() 45 | correlation_id = message.get_correlation_id() 46 | print("\n" + f"CALLBACK: Message Received on Topic: {topic}.\n" 47 | f"Message Bytes: {payload_as_bytes} \n" 48 | f"Message String: {payload_as_string} \n" 49 | f"Correlation id: {correlation_id}") 50 | 51 | 52 | class MyData(Generic[X]): 53 | """ sample class for business object""" 54 | name = 'some string' 55 | 56 | def __init__(self, name): 57 | self.name = name 58 | 59 | def get_name(self): 60 | """ return the name""" 61 | return self.name 62 | 63 | 64 | class ByteToObjectConverter(BytesToObject): 65 | """sample converter class to convert byte array to object""" 66 | 67 | def convert(self, src: bytearray) -> X: 68 | """This method converts the received byte array to an business object""" 69 | byte_to_object = pickle.loads(src) 70 | return byte_to_object 71 | 72 | 73 | class HowToAddAndRemoveSubscriptionSampler: 74 | """ 75 | class to show how to create a messaging service 76 | """ 77 | 78 | @staticmethod 79 | def direct_message_consume_adding_subscriptions(messaging_service: MessagingService, consumer_subscription: str, 80 | listener_topics: list): 81 | """ to publish str or byte array type message 82 | Args: 83 | messaging_service: connected messaging service 84 | consumer_subscription: Each topic subscribed 85 | listener_topics: list of topics subscribed to 86 | """ 87 | try: 88 | global MAX_SLEEP 89 | topics = [TopicSubscription.of(consumer_subscription)] 90 | 91 | direct_receive_service = messaging_service.create_direct_message_receiver_builder() 92 | direct_receive_service = direct_receive_service.with_subscriptions(topics).build() 93 | direct_receive_service.start() 94 | direct_receive_service.receive_async(MessageHandlerImpl1()) 95 | for topic in listener_topics: 96 | direct_receive_service.add_subscription(TopicSubscription.of(topic)) 97 | 98 | print(f"Subscribed to: {consumer_subscription}") 99 | time.sleep(MAX_SLEEP) 100 | finally: 101 | direct_receive_service.terminate() 102 | 103 | @staticmethod 104 | def direct_message_consume_removing_subscriptions(messaging_service: MessagingService, consumer_subscription: str, 105 | listener_topics: list): 106 | """ to publish str or byte array type message 107 | Args: 108 | messaging_service: connected messaging service 109 | consumer_subscription: Each topic subscribed 110 | listener_topics: list of topics subscribed to 111 | """ 112 | try: 113 | global MAX_SLEEP 114 | topics = [TopicSubscription.of(consumer_subscription)] 115 | 116 | direct_receive_service = messaging_service.create_direct_message_receiver_builder() 117 | direct_receive_service = direct_receive_service.with_subscriptions(topics).build() 118 | direct_receive_service.start() 119 | direct_receive_service.receive_async(MessageHandlerImpl2()) 120 | for topic in listener_topics: 121 | direct_receive_service.remove_subscription(TopicSubscription.of(topic)) 122 | 123 | print(f"Subscribed to: {consumer_subscription}") 124 | time.sleep(MAX_SLEEP) 125 | finally: 126 | direct_receive_service.terminate() 127 | 128 | @staticmethod 129 | def run(): 130 | service = MessagingService.builder().from_properties(boot.broker_properties()).build() 131 | service.connect() 132 | consumer_subscription = constants.TOPIC_ENDPOINT_DEFAULT 133 | listener_topics = ['try-me', 'try-me1', 134 | 'try-me2', 135 | 'try-me3'] 136 | 137 | HowToAddAndRemoveSubscriptionSampler() \ 138 | .direct_message_consume_adding_subscriptions(service, consumer_subscription, listener_topics) 139 | HowToAddAndRemoveSubscriptionSampler() \ 140 | .direct_message_consume_removing_subscriptions(service, consumer_subscription, listener_topics) 141 | 142 | @staticmethod 143 | def publish_and_subscribe(): 144 | """method for running the publisher and subscriber""" 145 | with ThreadPoolExecutor(max_workers=2) as e: 146 | e.submit(HowToAddAndRemoveSubscriptionSampler.run) 147 | e.submit(HowToDirectPublishMessage.run) 148 | 149 | 150 | if __name__ == '__main__': 151 | HowToAddAndRemoveSubscriptionSampler.publish_and_subscribe() 152 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_use_share_name_on_request_reply.py: -------------------------------------------------------------------------------- 1 | """sampler for using share_name on request reply message publishing and receiving""" 2 | import time 3 | from concurrent.futures.thread import ThreadPoolExecutor 4 | 5 | from solace.messaging.config.solace_properties.message_properties import SEQUENCE_NUMBER 6 | from solace.messaging.messaging_service import MessagingService 7 | from solace.messaging.publisher.outbound_message import OutboundMessage 8 | from solace.messaging.publisher.request_reply_message_publisher import RequestReplyMessagePublisher 9 | from solace.messaging.receiver.request_reply_message_receiver import RequestMessageHandler, Replier 10 | from solace.messaging.resources.share_name import ShareName 11 | from solace.messaging.resources.topic import Topic 12 | from solace.messaging.resources.topic_subscription import TopicSubscription 13 | from howtos.sampler_boot import SolaceConstants, SamplerBoot 14 | 15 | constants = SolaceConstants 16 | boot = SamplerBoot() 17 | MAX_SLEEP = 10 18 | 19 | 20 | class HowToUseShareNameWithRequestReplyPattern: 21 | """class contains methods on different ways to publish a request reply message""" 22 | 23 | @staticmethod 24 | def publish_request_and_process_response_message_async(service: MessagingService, request_destination: Topic, 25 | reply_timeout: int): 26 | """Mimics microservice that performs a async request 27 | Args: 28 | service: connected messaging service 29 | request_destination: where to send a request (it is same for requests and responses) 30 | reply_timeout: the reply timeout 31 | """ 32 | topic = Topic.of(request_destination) 33 | requester: RequestReplyMessagePublisher = service.request_reply() \ 34 | .create_request_reply_message_publisher_builder().build().start() 35 | try: 36 | ping_message = service.message_builder().build(payload='Ping', 37 | additional_message_properties={SEQUENCE_NUMBER: 123}) 38 | 39 | publish_request_async = requester.publish(request_message=ping_message, 40 | request_destination=topic, 41 | reply_timeout=reply_timeout) 42 | # we can get the reply from the future 43 | print(publish_request_async.result()) 44 | finally: 45 | requester.terminate() 46 | 47 | @staticmethod 48 | def request_reply_message_consume(messaging_service: MessagingService, consumer_subscription: str, 49 | reply_timeout: int): 50 | """This method will create an receiver instance to receive str or byte array type message""" 51 | try: 52 | global MAX_SLEEP 53 | topic_subscription = TopicSubscription.of(consumer_subscription) 54 | group_name = ShareName.of('test') 55 | 56 | receiver = messaging_service.request_reply(). \ 57 | create_request_reply_message_receiver_builder().build(request_topic_subscription=topic_subscription, 58 | share_name=group_name) 59 | receiver.start() 60 | msg, replier = receiver.receive_message(timeout=reply_timeout) 61 | print(f"incoming message is {msg.get_payload_as_string()}") 62 | if replier is not None: 63 | outbound_msg = messaging_service.message_builder().build("pong reply") 64 | replier.reply(outbound_msg) 65 | print(f"Subscribed to: {consumer_subscription}") 66 | time.sleep(MAX_SLEEP) 67 | finally: 68 | receiver.terminate() 69 | 70 | @staticmethod 71 | def request_reply_message_consume2(messaging_service: MessagingService, consumer_subscription: str, 72 | reply_timeout: int): 73 | """This method will create an receiver instance to receive str or byte array type message""" 74 | try: 75 | global MAX_SLEEP 76 | topic_subscription = TopicSubscription.of(consumer_subscription) 77 | group_name = ShareName.of('test') 78 | 79 | receiver = messaging_service.request_reply(). \ 80 | create_request_reply_message_receiver_builder().build(request_topic_subscription=topic_subscription, 81 | share_name=group_name) 82 | receiver.start() 83 | msg, replier = receiver.receive_message(timeout=reply_timeout) 84 | print(f"incoming message is {msg.get_payload_as_string()}") 85 | if replier is not None: 86 | outbound_msg = messaging_service.message_builder().build("pong reply") 87 | replier.reply(outbound_msg) 88 | print(f"Subscribed to: {consumer_subscription}") 89 | time.sleep(MAX_SLEEP) 90 | finally: 91 | receiver.terminate() 92 | 93 | @staticmethod 94 | def run_subscribers(service, consumer_subscription, reply_timeout): 95 | """ 96 | :return: 97 | """ 98 | 99 | print("Execute request reply consume - String") 100 | with ThreadPoolExecutor(max_workers=3) as e: 101 | e.submit(HowToUseShareNameWithRequestReplyPattern.request_reply_message_consume, messaging_service=service, 102 | consumer_subscription=consumer_subscription, reply_timeout=reply_timeout) 103 | e.submit(HowToUseShareNameWithRequestReplyPattern.request_reply_message_consume2, messaging_service=service, 104 | consumer_subscription=consumer_subscription, reply_timeout=reply_timeout) 105 | for counter in range(1, 3): 106 | e.submit(HowToUseShareNameWithRequestReplyPattern.publish_request_and_process_response_message_async, 107 | service=service, 108 | request_destination=consumer_subscription, reply_timeout=reply_timeout) 109 | 110 | @staticmethod 111 | def run(): 112 | service = MessagingService.builder().from_properties(boot.broker_properties()).build() 113 | service.connect() 114 | consumer_subscription = 'request_reply/pub_sub/sampler' 115 | reply_timeout = 5000 116 | 117 | HowToUseShareNameWithRequestReplyPattern.run_subscribers(service=service, 118 | consumer_subscription=consumer_subscription, 119 | reply_timeout=reply_timeout) 120 | service.disconnect() 121 | 122 | 123 | if __name__ == '__main__': 124 | HowToUseShareNameWithRequestReplyPattern.run() 125 | -------------------------------------------------------------------------------- /howtos/how_to_handle_service_interruption_and_failures.py: -------------------------------------------------------------------------------- 1 | """module to handle service interruption and failures""" 2 | 3 | from solace.messaging.messaging_service import MessagingService, ServiceInterruptionListener, ServiceEvent 4 | from solace.messaging.publisher.direct_message_publisher import PublishFailureListener 5 | from solace.messaging.receiver.inbound_message import InboundMessage 6 | from solace.messaging.receiver.persistent_message_receiver import PersistentMessageReceiver 7 | from solace.messaging.resources.queue import Queue 8 | from solace.messaging.resources.topic import Topic 9 | from solace.messaging.utils.life_cycle_control import TerminationNotificationListener 10 | from howtos.sampler_boot import SamplerBoot, SolaceConstants 11 | 12 | constants = SolaceConstants 13 | boot = SamplerBoot() 14 | 15 | 16 | class PublishFailureListenerImpl(PublishFailureListener): 17 | def on_failed_publish(self, failed_publish_event: 'FailedPublishEvent'): 18 | print(f"fail_destination name:{failed_publish_event.get_destination()}\n" 19 | f"fail_message:{failed_publish_event.get_message()}\n" 20 | f"fail_timestamp:{failed_publish_event.get_timestamp()}\n" 21 | f"fail_exception:{failed_publish_event.get_exception()}\n") 22 | 23 | 24 | class ServiceInterruptionListenerImpl(ServiceInterruptionListener): 25 | """implementation class for the service interruption listener""" 26 | 27 | def on_service_interrupted(self, event: ServiceEvent): 28 | print("Service Interruption Listener Callback") 29 | print(f"Timestamp: {event.get_time_stamp()}") 30 | print(f"Uri: {event.get_broker_uri()}") 31 | print(f"Error cause: {event.get_cause()}") 32 | print(f"Message: {event.get_message()}") 33 | 34 | 35 | class TerminationNotificationListenerImpl(TerminationNotificationListener): 36 | """implementation class for the termination notification listener""" 37 | 38 | def on_termination(self, termination_notification_event: 'TerminationNotificationEvent'): 39 | print("Termination Notification Listener Callback") 40 | print(f"Timestamp: {termination_notification_event.timestamp()}") 41 | print(f"Error cause: {termination_notification_event.cause()}") 42 | print(f"Message: {termination_notification_event.message()}") 43 | 44 | 45 | class HowToHandleServiceInterruptionAndFailures: 46 | 47 | @staticmethod 48 | def notify_about_service_access_unrecoverable_interruption(messaging_service, destination_name, message): 49 | """example how to configure service access to receive notifications about unrecoverable interruption 50 | 51 | Returns: 52 | configured instance of messaging service 53 | """ 54 | try: 55 | service_interruption_listener = ServiceInterruptionListenerImpl() 56 | messaging_service.add_service_interruption_listener(service_interruption_listener) 57 | 58 | direct_publish_service = messaging_service.create_direct_message_publisher_builder() \ 59 | .build() 60 | direct_publish_service.start() 61 | direct_publish_service.publish(destination=destination_name, message=message) 62 | 63 | return messaging_service 64 | finally: 65 | messaging_service.remove_service_interruption_listener(service_interruption_listener) 66 | 67 | @staticmethod 68 | def notify_on_direct_publisher_failures(messaging_service: MessagingService, destination_name, message, 69 | buffer_capacity, message_count): 70 | """ 71 | configure direct message publisher to receive notifications about publish 72 | failures if any occurred 73 | """ 74 | try: 75 | direct_publish_service = messaging_service.create_direct_message_publisher_builder() \ 76 | .on_back_pressure_reject(buffer_capacity=buffer_capacity) \ 77 | .build() 78 | direct_publish_service.start() 79 | publish_failure_listener = PublishFailureListenerImpl() 80 | direct_publish_service.set_publish_failure_listener(publish_failure_listener) 81 | outbound_msg = messaging_service.message_builder() \ 82 | .with_application_message_id(constants.APPLICATION_MESSAGE_ID) \ 83 | .build(message) 84 | direct_publish_service.publish(destination=destination_name, message=outbound_msg) 85 | 86 | finally: 87 | direct_publish_service.terminate_async() 88 | 89 | @staticmethod 90 | def notify_about_persistent_receiver_termination(messaging_service, queue): 91 | """example how to configure service access to receive notifications about persistent receiver terminations 92 | On termination of the persistent message receiver due to session or flow events, an 93 | asynchronous notification event is generated which explains the cause for the failure 94 | and the failure timestamp at which the exception was raised. 95 | """ 96 | receiver: PersistentMessageReceiver = messaging_service.create_persistent_message_receiver_builder() \ 97 | .build(queue) 98 | receiver_failure_listener = TerminationNotificationListenerImpl() 99 | receiver.set_termination_notification_listener(receiver_failure_listener) 100 | 101 | 102 | @staticmethod 103 | def run(): 104 | try: 105 | messaging_service = MessagingService.builder().from_properties(boot.broker_properties()).build() 106 | messaging_service.connect() 107 | destination_name = Topic.of(constants.TOPIC_ENDPOINT_DEFAULT) 108 | buffer_capacity = 20 109 | message_count = 50 110 | queue_name = 'Q/test/pub_sub' 111 | durable_exclusive_queue = Queue.durable_exclusive_queue(queue_name) 112 | 113 | HowToHandleServiceInterruptionAndFailures.notify_on_direct_publisher_failures(messaging_service, 114 | destination_name, 115 | constants.MESSAGE_TO_SEND, 116 | buffer_capacity, 117 | message_count) 118 | HowToHandleServiceInterruptionAndFailures. \ 119 | notify_about_service_access_unrecoverable_interruption(messaging_service, 120 | destination_name, constants.MESSAGE_TO_SEND) 121 | 122 | HowToHandleServiceInterruptionAndFailures. \ 123 | notify_about_persistent_receiver_termination(messaging_service, queue=durable_exclusive_queue) 124 | 125 | finally: 126 | messaging_service.disconnect() 127 | 128 | 129 | if __name__ == '__main__': 130 | HowToHandleServiceInterruptionAndFailures.run() 131 | -------------------------------------------------------------------------------- /patterns/hello_world_pubsub.py: -------------------------------------------------------------------------------- 1 | ## Goal: Publisher + Subscriber 2 | import os 3 | import time 4 | 5 | # Import Solace Python API modules 6 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener, RetryStrategy, ServiceEvent 7 | from solace.messaging.errors.pubsubplus_client_error import PubSubPlusClientError 8 | from solace.messaging.publisher.direct_message_publisher import PublishFailureListener, FailedPublishEvent 9 | from solace.messaging.resources.topic_subscription import TopicSubscription 10 | from solace.messaging.receiver.message_receiver import MessageHandler 11 | from solace.messaging.config.solace_properties.message_properties import APPLICATION_MESSAGE_ID 12 | from solace.messaging.resources.topic import Topic 13 | from solace.messaging.receiver.inbound_message import InboundMessage 14 | 15 | TOPIC_PREFIX = "solace/samples" 16 | SHUTDOWN = False 17 | 18 | # Handle received messages 19 | class MessageHandlerImpl(MessageHandler): 20 | def on_message(self, message: 'InboundMessage'): 21 | try: 22 | global SHUTDOWN 23 | if "quit" in message.get_destination_name(): 24 | print("QUIT message received, shutting down.") 25 | SHUTDOWN = True 26 | 27 | # Check if the payload is a String or Byte, decode if its the later 28 | payload = message.get_payload_as_string() if message.get_payload_as_string() is not None else message.get_payload_as_bytes() 29 | if isinstance(payload, bytearray): 30 | print(f"Received a message of type: {type(payload)}. Decoding to string") 31 | payload = payload.decode() 32 | 33 | print("\n" + f"Message payload: {payload} \n") 34 | print("\n" + f"Message dump: {message} \n") 35 | except Exception as e: 36 | print(f"Error processing message: {e.__traceback__}") 37 | 38 | # Inner classes for error handling 39 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 40 | def on_reconnected(self, e: ServiceEvent): 41 | print("\non_reconnected") 42 | print(f"Error cause: {e.get_cause()}") 43 | print(f"Message: {e.get_message()}") 44 | 45 | def on_reconnecting(self, e: "ServiceEvent"): 46 | print("\non_reconnecting") 47 | print(f"Error cause: {e.get_cause()}") 48 | print(f"Message: {e.get_message()}") 49 | 50 | def on_service_interrupted(self, e: "ServiceEvent"): 51 | print("\non_service_interrupted") 52 | print(f"Error cause: {e.get_cause()}") 53 | print(f"Message: {e.get_message()}") 54 | 55 | class PublisherErrorHandling(PublishFailureListener): 56 | def on_failed_publish(self, e: "FailedPublishEvent"): 57 | print("on_failed_publish") 58 | 59 | # Broker Config 60 | broker_props = { 61 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 62 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 63 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 64 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "default" 65 | } 66 | 67 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 68 | # Note: The reconnections strategy could also be configured using the broker properties object 69 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 70 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 71 | .build() 72 | 73 | # Blocking connect thread 74 | messaging_service.connect() 75 | # print(f'Messaging Service connected? {messaging_service.is_connected}') 76 | 77 | # Error Handeling for the messaging service 78 | service_handler = ServiceEventHandler() 79 | messaging_service.add_reconnection_listener(service_handler) 80 | messaging_service.add_reconnection_attempt_listener(service_handler) 81 | messaging_service.add_service_interruption_listener(service_handler) 82 | 83 | # Create a direct message publisher and start it 84 | direct_publisher = messaging_service.create_direct_message_publisher_builder().build() 85 | direct_publisher.set_publish_failure_listener(PublisherErrorHandling()) 86 | direct_publisher.set_publisher_readiness_listener 87 | 88 | # Blocking Start thread 89 | direct_publisher.start() 90 | # print(f'Direct Publisher ready? {direct_publisher.is_ready()}') 91 | 92 | unique_name = "" 93 | while not unique_name: 94 | unique_name = input("Enter your name: ").replace(" ", "") 95 | 96 | # Define a Topic subscriptions 97 | topics = [TOPIC_PREFIX + "/*/hello/>"] 98 | topics_sub = [] 99 | for t in topics: 100 | topics_sub.append(TopicSubscription.of(t)) 101 | 102 | msgSeqNum = 0 103 | # Prepare outbound message payload and body 104 | message_body = f'Hello from Python Hello World Sample!' 105 | message_builder = messaging_service.message_builder() \ 106 | .with_application_message_id("sample_id") \ 107 | .with_property("application", "samples") \ 108 | .with_property("language", "Python") \ 109 | 110 | try: 111 | print(f"Subscribed to: {topics}") 112 | # Build a Receiver 113 | direct_receiver = messaging_service.create_direct_message_receiver_builder().with_subscriptions(topics_sub).build() 114 | direct_receiver.start() 115 | # Callback for received messages 116 | direct_receiver.receive_async(MessageHandlerImpl()) 117 | if direct_receiver.is_running(): 118 | print("Connected and Subscribed! Ready to publish\n") 119 | try: 120 | while not SHUTDOWN: 121 | msgSeqNum += 1 122 | # Check https://docs.solace.com/API-Developer-Online-Ref-Documentation/python/source/rst/solace.messaging.config.solace_properties.html for additional message properties 123 | # Note: additional properties override what is set by the message_builder 124 | additional_properties = {APPLICATION_MESSAGE_ID: f'sample_id {msgSeqNum}'} 125 | # Creating a dynamic outbound message 126 | outbound_message = message_builder.build(f'{message_body} --> {msgSeqNum}', additional_message_properties=additional_properties) 127 | # Direct publish the message 128 | direct_publisher.publish(destination=Topic.of(TOPIC_PREFIX + f"/python/hello/{unique_name}/{msgSeqNum}"), message=outbound_message) 129 | # sleep are not necessary when dealing with the default back pressure elastic 130 | time.sleep(5) 131 | except KeyboardInterrupt: 132 | print('\nDisconnecting Messaging Service') 133 | except PubSubPlusClientError as exception: 134 | print(f'Received a PubSubPlusClientException: {exception}') 135 | finally: 136 | print('Terminating Publisher and Receiver') 137 | direct_publisher.terminate() 138 | direct_receiver.terminate() 139 | print('Disconnecting Messaging Service') 140 | messaging_service.disconnect() 141 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_consume_persistent_message_with_negative_acknowledgement.py: -------------------------------------------------------------------------------- 1 | """sampler to consume persistent message with negative acknowledgement""" 2 | 3 | import threading 4 | from typing import TypeVar 5 | 6 | from solace.messaging.messaging_service import MessagingService 7 | from solace.messaging.receiver.inbound_message import InboundMessage 8 | from solace.messaging.receiver.persistent_message_receiver import PersistentMessageReceiver 9 | from solace.messaging.resources.queue import Queue 10 | from solace.messaging.resources.topic import Topic 11 | from solace.messaging.resources.topic_subscription import TopicSubscription 12 | from solace.messaging.config.message_acknowledgement_configuration import Outcome 13 | from solace.messaging.utils.manageable import Metric 14 | from howtos.pubsub.how_to_consume_persistent_message import HowToConsumeMessageExclusiveVsSharedMode 15 | from howtos.pubsub.how_to_publish_persistent_message import HowToPublishPersistentMessage 16 | from howtos.sampler_boot import SolaceConstants, SamplerBoot, BasicTestMessageHandler 17 | from howtos.sampler_master import SamplerMaster 18 | 19 | constants = SolaceConstants 20 | boot = SamplerBoot() 21 | lock = threading.Lock() 22 | 23 | topic_name = constants.TOPIC_ENDPOINT_DEFAULT 24 | topic = Topic.of(topic_name) 25 | 26 | 27 | class HowToSettlePersistentMessageWithNegativeAcknowledgement: 28 | """class contains methods to settle message with negative acknowledgement""" 29 | 30 | @staticmethod 31 | def settle_message_sync(service: MessagingService, publisher, message, required_outcomes, outcome_to_settle): 32 | try: 33 | queue_name = constants.QUEUE_NAME_FORMAT.substitute(iteration=topic_name) 34 | HowToConsumeMessageExclusiveVsSharedMode.create_queue_and_add_topic(queue_name) 35 | queue_to_consume = Queue.durable_exclusive_queue(queue_name) 36 | 37 | receiver: PersistentMessageReceiver = service.create_persistent_message_receiver_builder() \ 38 | .with_required_message_outcome_support(*required_outcomes).build(queue_to_consume) 39 | receiver.start() 40 | print(f'PERSISTENT receiver started for sync receiver... Listening to Queue [{queue_to_consume.get_name()}]') 41 | receiver.add_subscription(TopicSubscription.of(topic_name)) 42 | 43 | HowToPublishPersistentMessage.publish_string_message_non_blocking(publisher, topic, message) 44 | 45 | message: InboundMessage = receiver.receive_message() 46 | print(f"the message payload is {message.get_payload_as_string()}") 47 | receiver.settle(message, outcome_to_settle) 48 | 49 | metrics = service.metrics() 50 | message_settle_accepted = metrics.get_value(Metric.PERSISTENT_MESSSAGES_ACCEPTED) 51 | message_settle_rejected = metrics.get_value(Metric.PERSISTENT_MESSSAGES_REJECTED) 52 | message_settle_failed = metrics.get_value(Metric.PERSISTENT_MESSSAGES_FAILED) 53 | 54 | print(f"Message Settle Accepted: {message_settle_accepted}") 55 | print(f"Message Settle Rejected: {message_settle_rejected}") 56 | print(f"Message Settle Failed: {message_settle_failed}") 57 | 58 | finally: 59 | receiver.terminate() 60 | HowToConsumeMessageExclusiveVsSharedMode.delete_queue(queue_to_consume.get_name()) 61 | 62 | @staticmethod 63 | def settle_message_async(service: MessagingService, publisher, message, required_outcomes, outcome_to_settle): 64 | event = threading.Event() 65 | receiver = None 66 | 67 | def receiver_callback(self, message: InboundMessage): 68 | # Fail messages will be redelivered so we just want to settle the actual message that is not redelivered 69 | if not message.is_redelivered(): 70 | print(f"the message payload is {message.get_payload_as_string()}") 71 | receiver.settle(message, outcome_to_settle) 72 | event.set() 73 | 74 | try: 75 | queue_name = constants.QUEUE_NAME_FORMAT.substitute(iteration=topic_name) 76 | HowToConsumeMessageExclusiveVsSharedMode.create_queue_and_add_topic(queue_name) 77 | queue_to_consume = Queue.durable_exclusive_queue(queue_name) 78 | 79 | receiver: PersistentMessageReceiver = service.create_persistent_message_receiver_builder() \ 80 | .with_required_message_outcome_support(*required_outcomes).build(queue_to_consume) 81 | receiver.start() 82 | print(f'PERSISTENT receiver started for async receiver... Listening to Queue [{queue_to_consume.get_name()}]') 83 | receiver.add_subscription(TopicSubscription.of(topic_name)) 84 | message_handler = BasicTestMessageHandler(test_callback=receiver_callback) 85 | receiver.receive_async(message_handler) 86 | 87 | HowToPublishPersistentMessage.publish_string_message_non_blocking(publisher, topic, message) 88 | event.wait(5) 89 | 90 | metrics = service.metrics() 91 | message_settle_accepted = metrics.get_value(Metric.PERSISTENT_MESSSAGES_ACCEPTED) 92 | message_settle_rejected = metrics.get_value(Metric.PERSISTENT_MESSSAGES_REJECTED) 93 | message_settle_failed = metrics.get_value(Metric.PERSISTENT_MESSSAGES_FAILED) 94 | 95 | print(f"Message Settle Accepted: {message_settle_accepted}") 96 | print(f"Message Settle Rejected: {message_settle_rejected}") 97 | print(f"Message Settle Failed: {message_settle_failed}") 98 | 99 | finally: 100 | receiver.terminate() 101 | HowToConsumeMessageExclusiveVsSharedMode.delete_queue(queue_to_consume.get_name()) 102 | 103 | @staticmethod 104 | def run(): 105 | try: 106 | # Set up for required outcomes and outcome to settle 107 | required_outcomes = (Outcome.ACCEPTED, Outcome.FAILED, Outcome.REJECTED) 108 | outcome_to_settle = (Outcome.ACCEPTED, Outcome.FAILED, Outcome.REJECTED) 109 | 110 | message = constants.MESSAGE_TO_SEND 111 | messaging_service = SamplerMaster.connect_messaging_service() 112 | 113 | publisher = HowToPublishPersistentMessage.create_persistent_message_publisher(messaging_service) 114 | 115 | # We will settle the message 2 ways, sync and async, for each Outcome 116 | # Therefore, at the end, we will see each message get settle 2 times 117 | for outcome in outcome_to_settle: 118 | HowToSettlePersistentMessageWithNegativeAcknowledgement \ 119 | .settle_message_sync(service=messaging_service, publisher=publisher, message=message, 120 | required_outcomes=required_outcomes, outcome_to_settle=outcome) 121 | 122 | HowToSettlePersistentMessageWithNegativeAcknowledgement \ 123 | .settle_message_async(service=messaging_service, publisher=publisher, message=message, 124 | required_outcomes=required_outcomes, outcome_to_settle=outcome) 125 | finally: 126 | messaging_service.disconnect() 127 | 128 | 129 | if __name__ == '__main__': 130 | HowToSettlePersistentMessageWithNegativeAcknowledgement.run() 131 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_use_request_reply_pattern.py: -------------------------------------------------------------------------------- 1 | """sampler for request reply message publishing and receiving""" 2 | import concurrent 3 | from concurrent.futures.thread import ThreadPoolExecutor 4 | 5 | from solace.messaging.config.solace_properties.message_properties import SEQUENCE_NUMBER 6 | from solace.messaging.messaging_service import MessagingService 7 | from solace.messaging.publisher.outbound_message import OutboundMessage 8 | from solace.messaging.publisher.request_reply_message_publisher import RequestReplyMessagePublisher 9 | from solace.messaging.receiver.request_reply_message_receiver import RequestReplyMessageReceiver, \ 10 | RequestMessageHandler, Replier 11 | from solace.messaging.resources.topic import Topic 12 | from solace.messaging.resources.topic_subscription import TopicSubscription 13 | from howtos.sampler_boot import SamplerBoot 14 | 15 | 16 | class HowToUseRequestReplyPattern: 17 | """class contains methods on different ways to publish a request reply message""" 18 | 19 | @staticmethod 20 | def publish_request_and_process_response_message_async(service: MessagingService, request_destination: Topic, 21 | reply_timeout: int): 22 | """Mimics microservice that performs a async request 23 | Args: 24 | service: connected messaging service 25 | request_destination: where to send a request (it is same for requests and responses) 26 | reply_timeout: the reply timeout 27 | """ 28 | 29 | requester: RequestReplyMessagePublisher = service.request_reply() \ 30 | .create_request_reply_message_publisher_builder().build().start() 31 | 32 | ping_message = service.message_builder().build(payload='Ping', 33 | additional_message_properties={SEQUENCE_NUMBER: 123}) 34 | 35 | publish_request_async = requester.publish(request_message=ping_message, 36 | request_destination=request_destination, 37 | reply_timeout=reply_timeout) 38 | # we can get the reply from the future 39 | print(publish_request_async.result()) 40 | 41 | @staticmethod 42 | def publish_request_and_process_response_message_blocking(service: MessagingService, request_destination: Topic, 43 | reply_timeout: int): 44 | """Mimics microservice that performs a blocking request 45 | 46 | Args: 47 | service: connected messaging service 48 | request_destination: where to send a request (it is same for requests and responses) 49 | reply_timeout: the reply timeout 50 | """ 51 | requester: RequestReplyMessagePublisher = service.request_reply() \ 52 | .create_request_reply_message_publisher_builder().build().start() 53 | 54 | ping_message: OutboundMessage = service.message_builder().build(payload='Ping') 55 | try: 56 | 57 | reply = requester.publish_await_response(request_message=ping_message, 58 | request_destination=request_destination, 59 | reply_timeout=reply_timeout) 60 | print(f"reply: {reply}") 61 | except TimeoutError as e: 62 | print(e) 63 | 64 | @staticmethod 65 | def receive_request_and_send_response_message(service: MessagingService, for_requests: TopicSubscription): 66 | """Mimics microservice that performs a response 67 | 68 | Args: 69 | service: connected messaging service 70 | for_requests: where to expect requests 71 | """ 72 | 73 | request_receiver: RequestReplyMessageReceiver = service.request_reply() \ 74 | .create_request_reply_message_receiver_builder().build(for_requests).start() 75 | 76 | msg, replier = request_receiver.receive_message(timeout=5000) 77 | if replier is not None: 78 | outbound_msg = service.message_builder().build("pong reply") 79 | replier.reply(outbound_msg) 80 | 81 | @staticmethod 82 | def async_request_and_response(service: MessagingService, request_destination: Topic, 83 | for_requests: TopicSubscription, reply_timeout: int): 84 | with concurrent.futures.ThreadPoolExecutor(max_workers=2) as e: 85 | r_f = e.submit(HowToUseRequestReplyPattern.receive_request_and_send_response_message, 86 | service=service, 87 | for_requests=for_requests) 88 | p_f = e.submit(HowToUseRequestReplyPattern.publish_request_and_process_response_message_async, 89 | service=service, 90 | request_destination=request_destination, 91 | reply_timeout=reply_timeout) 92 | r_f.result() 93 | p_f.result() 94 | 95 | @staticmethod 96 | def blocking_request_and_response(service: MessagingService, request_destination: Topic, 97 | for_requests: TopicSubscription, reply_timeout: int): 98 | 99 | with ThreadPoolExecutor(max_workers=2) as e: 100 | r_f = e.submit(HowToUseRequestReplyPattern.receive_request_and_send_response_message, 101 | service=service, 102 | for_requests=for_requests) 103 | p_f = e.submit(HowToUseRequestReplyPattern.publish_request_and_process_response_message_blocking, 104 | service=service, 105 | request_destination=request_destination, 106 | reply_timeout=reply_timeout) 107 | r_f.result() 108 | p_f.result() 109 | 110 | @staticmethod 111 | def run(): 112 | messaging_service = None 113 | try: 114 | reply_timeout = 10000 115 | topic_name = f'request_reply/pub_sub/sampler' 116 | topic = Topic.of(topic_name) 117 | topic_subscription = TopicSubscription.of(topic_name) 118 | messaging_service = MessagingService.builder().from_properties(SamplerBoot().broker_properties()).build() 119 | messaging_service.connect() 120 | 121 | HowToUseRequestReplyPattern.async_request_and_response(service=messaging_service, 122 | request_destination=topic, 123 | for_requests=topic_subscription, 124 | reply_timeout=reply_timeout) 125 | 126 | HowToUseRequestReplyPattern.blocking_request_and_response(service=messaging_service, 127 | request_destination=topic, 128 | for_requests=topic_subscription, 129 | reply_timeout=reply_timeout) 130 | finally: 131 | if messaging_service: 132 | messaging_service.disconnect() 133 | 134 | 135 | if __name__ == '__main__': 136 | HowToUseRequestReplyPattern.run() 137 | -------------------------------------------------------------------------------- /patterns/direct_processor.py: -------------------------------------------------------------------------------- 1 | ## Goal is to demonstrate receive-process-reply (a processor) pattern. 2 | # Processor subscribes to a topic and upon receipt of the message, the payload is processed 3 | # (e.g. uppercased) and published to another designated topic 4 | 5 | import os 6 | import platform 7 | import time 8 | 9 | # Import Solace Python API modules from the solace package 10 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, \ 11 | ServiceInterruptionListener, RetryStrategy, ServiceEvent 12 | from solace.messaging.resources.topic import Topic 13 | from solace.messaging.publisher.direct_message_publisher import PublishFailureListener, FailedPublishEvent 14 | from solace.messaging.resources.topic_subscription import TopicSubscription 15 | from solace.messaging.receiver.message_receiver import MessageHandler, InboundMessage 16 | from solace.messaging.errors.pubsubplus_client_error import PubSubPlusClientError 17 | 18 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 19 | 20 | TOPIC_PREFIX = "solace/samples/python" 21 | 22 | # Inner class for connection error handling 23 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 24 | def on_reconnected(self, e: ServiceEvent): 25 | print("\non_reconnected") 26 | print(f"Error cause: {e.get_cause()}") 27 | print(f"Message: {e.get_message()}") 28 | 29 | def on_reconnecting(self, e: "ServiceEvent"): 30 | print("\non_reconnecting") 31 | print(f"Error cause: {e.get_cause()}") 32 | print(f"Message: {e.get_message()}") 33 | 34 | def on_service_interrupted(self, e: "ServiceEvent"): 35 | print("\non_service_interrupted") 36 | print(f"Error cause: {e.get_cause()}") 37 | print(f"Message: {e.get_message()}") 38 | 39 | # Inner class for publish error handling 40 | class PublisherErrorHandling(PublishFailureListener): 41 | def on_failed_publish(self, e: "FailedPublishEvent"): 42 | print("on_failed_publish") 43 | 44 | # Inner class to handle received messages 45 | class ProcessorImpl(MessageHandler): 46 | def __init__(self, publisher, messaging_service): 47 | self.publisher = publisher 48 | self.msg_builder = messaging_service.message_builder() \ 49 | .with_application_message_id("sample_id") \ 50 | .with_property("application", "samples") \ 51 | .with_property("payload", "processed") \ 52 | .with_property("language", "Python") \ 53 | 54 | 55 | def on_message(self, message: InboundMessage): 56 | try: 57 | publish_topic = Topic.of(TOPIC_PREFIX + f'/direct/processor/output') 58 | subscribe_topic = message.get_destination_name() 59 | 60 | # Check if the payload is a String or Byte, decode if its the later 61 | payload = message.get_payload_as_string() if message.get_payload_as_string() is not None else message.get_payload_as_bytes() 62 | if isinstance(payload, bytearray): 63 | print(f"Received a message of type: {type(payload)}. Decoding to string") 64 | payload = payload.decode() 65 | 66 | # Process the message. For simplicity, we will be uppercasing the payload. 67 | processed_payload = payload.upper() 68 | 69 | if message.get_application_message_id() is not None: 70 | self.msg_builder = self.msg_builder \ 71 | .with_application_message_id(message.get_application_message_id()) 72 | 73 | output_msg = self.msg_builder.build(f'{processed_payload}') 74 | self.publisher.publish(destination=publish_topic, message=output_msg) 75 | 76 | print(f'<<<<<<<<<<<<<<<<<<<<<<<<<<<<') 77 | print(f'Received input message on {subscribe_topic}') 78 | print(f'Received input message (body): {payload}') 79 | # print(f'Received input message dump (body):{message}') 80 | print(f'----------------------------') 81 | print(f'>>>>>>>>>>>>>>>>>>>>>>>>>>>>') 82 | print(f'Published output message to: {publish_topic}') 83 | print(f'Published output message (body): {processed_payload}') 84 | # print(f'Published output message dump (body):{output_msg}') 85 | print(f'----------------------------') 86 | except Exception as e: 87 | print(f'Error processing message: {e.__traceback__}') 88 | 89 | # Broker Config. Note: Could pass other properties Look into 90 | broker_props = { 91 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 92 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 93 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 94 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "" 95 | } 96 | 97 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 98 | # Note: The reconnections strategy could also be configured using the broker properties object 99 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 100 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 101 | .build() 102 | 103 | # Blocking connect thread 104 | messaging_service.connect() 105 | print(f'\nMessaging Service connected? {messaging_service.is_connected}') 106 | 107 | # Event Handling for the messaging service 108 | service_handler = ServiceEventHandler() 109 | messaging_service.add_reconnection_listener(service_handler) 110 | messaging_service.add_reconnection_attempt_listener(service_handler) 111 | messaging_service.add_service_interruption_listener(service_handler) 112 | 113 | # Create a direct message publisher and start it 114 | publisher = messaging_service.create_direct_message_publisher_builder().build() 115 | publisher.set_publish_failure_listener(PublisherErrorHandling()) 116 | 117 | # Blocking Start thread 118 | publisher.start() 119 | print(f'Direct Publisher ready? {publisher.is_ready()}') 120 | 121 | # Define a Topic subscriptions 122 | subscribe_topic_name = TOPIC_PREFIX + "/direct/processor/input" 123 | subscribe_topic = [TopicSubscription.of(subscribe_topic_name)] 124 | print(f'\nSubscribed to topic: {subscribe_topic_name}') 125 | 126 | # Build a Receiver with the given topics and start it 127 | direct_receiver = messaging_service.create_direct_message_receiver_builder()\ 128 | .with_subscriptions(subscribe_topic)\ 129 | .build() 130 | direct_receiver.start() 131 | print(f'Direct Receiver is running? {direct_receiver.is_running()}') 132 | 133 | print("\nSend a KeyboardInterrupt to stop processor\n") 134 | try: 135 | # Callback for received messages 136 | direct_receiver.receive_async(ProcessorImpl(publisher, messaging_service)) 137 | try: 138 | while True: 139 | time.sleep(1) 140 | except KeyboardInterrupt: 141 | print('\nDisconnecting Messaging Service') 142 | # Handle API exception 143 | except PubSubPlusClientError as exception: 144 | print(f'\nError occurred: {exception}') 145 | 146 | finally: 147 | print('\nTerminating receiver') 148 | direct_receiver.terminate() 149 | print('\nDisconnecting Messaging Service') 150 | messaging_service.disconnect() 151 | -------------------------------------------------------------------------------- /howtos/how_to_configure_service_connection_reconnection_retries.py: -------------------------------------------------------------------------------- 1 | """sampler module for reconnection strategy""" 2 | import time 3 | 4 | from solace.messaging.config.retry_strategy import RetryStrategy 5 | from solace.messaging.config.solace_properties import transport_layer_properties 6 | from solace.messaging.errors.pubsubplus_client_error import PubSubPlusClientError 7 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener 8 | from solace.messaging.resources.topic import Topic 9 | from howtos.sampler_boot import SolaceConstants, SamplerBoot, SamplerUtil 10 | 11 | constants = SolaceConstants 12 | boot = SamplerBoot() 13 | 14 | 15 | class HowToConnectWithDifferentStrategy: 16 | """ 17 | This is a sampler for reconnection strategy 18 | """ 19 | 20 | @staticmethod 21 | def connect_never_retry(): 22 | """ 23 | creates a new instance of message service, that is used to configure 24 | direct message instances from config 25 | 26 | Returns: new connection for Direct messaging 27 | Raises: 28 | PubSubPlusClientError 29 | """ 30 | try: 31 | messaging_service = MessagingService.builder().from_properties(boot.broker_properties()) \ 32 | .with_reconnection_retry_strategy(RetryStrategy.never_retry()).build() 33 | messaging_service.connect() 34 | 35 | return messaging_service 36 | except PubSubPlusClientError as exception: 37 | raise exception 38 | 39 | finally: 40 | messaging_service.disconnect() 41 | 42 | @staticmethod 43 | def connect_retry_interval(retry_interval): 44 | """ 45 | creates a new instance of message service, that is used to configure 46 | direct message instances from config 47 | 48 | Returns: new connection for Direct messaging 49 | Raises: 50 | PubSubPlusClientError 51 | """ 52 | try: 53 | messaging_service = MessagingService.builder().from_properties(boot.broker_properties()) \ 54 | .with_reconnection_retry_strategy(RetryStrategy.forever_retry(retry_interval)).build() 55 | messaging_service.connect() 56 | return messaging_service 57 | except PubSubPlusClientError as exception: 58 | raise exception 59 | finally: 60 | messaging_service.disconnect() 61 | 62 | @staticmethod 63 | def connect_parametrized_retry(retries, retry_interval): 64 | """ 65 | creates a new instance of message service, that is used to configure 66 | direct message instances from config 67 | 68 | Returns: new connection for Direct messaging 69 | Raises: 70 | PubSubPlusClientError 71 | """ 72 | try: 73 | message_service = MessagingService.builder() \ 74 | .from_properties(boot.broker_properties()) \ 75 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(retries, retry_interval)) \ 76 | .build(SamplerUtil.get_new_application_id()) 77 | message_service.connect() 78 | return message_service 79 | except PubSubPlusClientError as exception: 80 | print(f'Exception: {exception}') 81 | raise exception 82 | finally: 83 | message_service.disconnect() 84 | 85 | @staticmethod 86 | def connect_using_properties(retries: int, retry_interval: int): 87 | """ 88 | creates a new instance of message service, that is used to configure 89 | direct message instances from config 90 | 91 | Returns: new connection for Direct messaging 92 | Raises: 93 | PubSubPlusClientError 94 | """ 95 | service_config = dict() 96 | messaging_service = None 97 | try: 98 | service_config[transport_layer_properties.RECONNECTION_ATTEMPTS] = retries 99 | 100 | service_config[transport_layer_properties.RECONNECTION_ATTEMPTS_WAIT_INTERVAL] = retry_interval 101 | messaging_service = MessagingService.builder().from_properties(boot.broker_properties()).build() 102 | messaging_service.connect() 103 | return messaging_service 104 | except PubSubPlusClientError as exception: 105 | raise exception 106 | finally: 107 | if messaging_service: 108 | messaging_service.disconnect() 109 | 110 | @staticmethod 111 | def add_listener_when_reconnection_happens(retries: int, retry_interval: int) -> 'MessagingService': 112 | """method adds a reconnection listener when an reconnection happens using the reconnection strategy 113 | Args: 114 | retries (int): the number of retries count 115 | retry_interval (int): the retry interval value 116 | 117 | Returns: 118 | the listener events 119 | """ 120 | events = list() 121 | 122 | class SampleServiceReconnectionHandler(ReconnectionAttemptListener, ReconnectionListener): 123 | def __init__(self): 124 | self.events = list() 125 | def on_reconnecting(event): 126 | self.events.append(event) 127 | print('Got reconnection attempt event, current reconnection events {self.events}') 128 | def on_reconnected(self, event): 129 | self.events.append(event) 130 | print('Got reconnected event, current reconnection events {self.events}') 131 | 132 | 133 | messaging_service = MessagingService.builder().from_properties(boot.broker_properties()) \ 134 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(retries, retry_interval)) \ 135 | .build() 136 | event_handler = SampleServiceReconnectionHandler() 137 | try: 138 | messaging_service.add_reconnection_listener(event_handler) 139 | messaging_service.connect() 140 | # wait for reconnection here 141 | # for now ommitting this code as it requires external connection administration 142 | finally: 143 | messaging_service.disconnect() 144 | return event_handler.events # MessagingService got list 145 | 146 | @staticmethod 147 | def run(): 148 | """ 149 | :return: Success or Failed according to connection established 150 | """ 151 | print("Running connect_never_retry...") 152 | print("\tSuccess" if HowToConnectWithDifferentStrategy().connect_never_retry() is not None else "Failed") 153 | print("Running connect_retry_interval...") 154 | print("\tSuccess" if HowToConnectWithDifferentStrategy().connect_retry_interval(3) is not None else "Failed") 155 | print("Running connect_parametrized_retry...") 156 | print("\tSuccess" if HowToConnectWithDifferentStrategy().connect_parametrized_retry(3, 40) is not None else "Failed") 157 | print("Running connect_using_properties...") 158 | print("\tSuccess" if HowToConnectWithDifferentStrategy().connect_using_properties(5, 30000) is not None else "Failed") 159 | print("Running add_listener_when_reconnection_happens...") 160 | # note the check for reconnection events allows for 0 events as the trigger for 161 | # reconnect requries a manual connection administrator to force reconnection 162 | print("\tSuccess" if len(HowToConnectWithDifferentStrategy() 163 | .add_listener_when_reconnection_happens(3, 3000)) >= 0 else "Failed") 164 | 165 | 166 | if __name__ == '__main__': 167 | HowToConnectWithDifferentStrategy().run() 168 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_use_SolaceSDTTypes_and_messages.py: -------------------------------------------------------------------------------- 1 | """Sampler to describe the use of Solace SDT Maps and Streams in Python""" 2 | import os 3 | import time 4 | from concurrent.futures.thread import ThreadPoolExecutor 5 | from solace.messaging.receiver.direct_message_receiver import DirectMessageReceiver 6 | from solace.messaging.utils.manageable import Metric 7 | from howtos.how_to_access_api_metrics import HowToAccessApiMetrics 8 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, \ 9 | ServiceInterruptionListener, ServiceEvent 10 | from solace.messaging.publisher.direct_message_publisher import PublishFailureListener, FailedPublishEvent 11 | from solace.messaging.resources.topic_subscription import TopicSubscription 12 | from solace.messaging.receiver.message_receiver import MessageHandler 13 | from solace.messaging.resources.topic import Topic 14 | from typing import TypeVar, Generic 15 | from howtos.sampler_boot import SamplerBoot, SolaceConstants 16 | 17 | TOPIC_PREFIX = "samples/hello" 18 | SHUTDOWN = False 19 | 20 | X = TypeVar('X') 21 | constants = SolaceConstants 22 | boot = SamplerBoot() 23 | 24 | 25 | class MyData(Generic[X]): 26 | """ sample class for business object""" 27 | name = 'some string' 28 | 29 | def __init__(self, name): 30 | self.name = name 31 | 32 | def get_name(self): 33 | """ return the name""" 34 | return self.name 35 | 36 | 37 | # Handle received messages 38 | class MessageHandlerImpl(MessageHandler): 39 | def on_message(self, message: 'InboundMessage'): 40 | print("\n..................................................") 41 | print("\nReceiving message....") 42 | print(f"Message received as dictionary: {message.get_payload_as_dictionary()}") 43 | print(f"Message received as list: {message.get_payload_as_list()}") 44 | print(f"Message received as string: {message.get_payload_as_string()}") 45 | print("..................................................\n") 46 | 47 | 48 | # classes for error handling 49 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 50 | def on_reconnected(self, e: ServiceEvent): 51 | print("\non_reconnected") 52 | print(f"Error cause: {e.get_cause()}") 53 | print(f"Message: {e.get_message()}") 54 | 55 | def on_reconnecting(self, e: "ServiceEvent"): 56 | print("\non_reconnecting") 57 | print(f"Error cause: {e.get_cause()}") 58 | print(f"Message: {e.get_message()}") 59 | 60 | def on_service_interrupted(self, e: "ServiceEvent"): 61 | print("\non_service_interrupted") 62 | print(f"Error cause: {e.get_cause()}") 63 | print(f"Message: {e.get_message()}") 64 | 65 | 66 | class PublisherErrorHandling(PublishFailureListener): 67 | def on_failed_publish(self, e: "FailedPublishEvent"): 68 | print("on_failed_publish") 69 | 70 | 71 | class HowToWorkWithSolaceSDTTypesAndMessages: 72 | """ 73 | class to show how to create a messaging service 74 | """ 75 | 76 | @staticmethod 77 | def publish_SDTMap(messaging_service: MessagingService, destination, message): 78 | """ to publish message""" 79 | # Create a direct message publisher and start it 80 | try: 81 | direct_publisher = messaging_service.create_direct_message_publisher_builder().build() 82 | direct_publisher.set_publish_failure_listener(PublisherErrorHandling()) 83 | # direct_publisher.set_publisher_readiness_listener() 84 | 85 | # Blocking Start thread 86 | direct_publisher.start() 87 | print(f'Direct Publisher ready? {direct_publisher.is_ready()}') 88 | print("Publishing message... : ", message) 89 | direct_publisher.publish(destination=destination, message=message) 90 | print("Message published!") 91 | finally: 92 | direct_publisher.terminate(500000) 93 | print("Publisher disconnected!") 94 | 95 | @staticmethod 96 | def consume_just_SDTMapStream_payload(messaging_service, consumer_subscription): 97 | """ to consume message""" 98 | print("consume here testing") 99 | try: 100 | # Create a direct message publisher and start it 101 | direct_publisher = messaging_service.create_direct_message_publisher_builder().build() 102 | # Blocking Start thread 103 | direct_publisher.start() 104 | print("messaging_service in consumer : ", messaging_service) 105 | topics = [TopicSubscription.of(consumer_subscription)] 106 | receiver: DirectMessageReceiver = messaging_service.create_direct_message_receiver_builder() \ 107 | .with_subscriptions(topics).build() 108 | print("Receiver started...") 109 | receiver.start() 110 | 111 | # Callback for received messages 112 | receiver.receive_async(MessageHandlerImpl()) 113 | MESSAGE_TO_SEND = [[1, 'c', None, bytearray(b'1'), {'a': 2}, True, 5.5], 114 | ({"key1": 1, "key2": 2}, {"key1": 'value1', "key2": 2}, 115 | {"key1": tuple(["John", "Doe", "Alice"])}), 116 | {"key1": 'value1', 117 | "key2": True, 118 | "key3": {"key31": None, "key32": {"5": 6, "7": {"8": 9}}}, 119 | "key4": [1.1, None, 3, {"key42": [1, 2, 3]}], 120 | "key5": bytearray([0x13, 0x11, 0x10, 0x09, 0x08, 0x01]), 121 | "key6": '', 122 | "key7": (1, 2, 3)}, 123 | {"1": 2, "3": {"5": 6, "7": {"8": 9}, "11": {"a": "b", "c": [1, 2, 3]}}}, 124 | 'hello everyone'] 125 | tasks = [] 126 | with ThreadPoolExecutor() as executor: 127 | for message in MESSAGE_TO_SEND: 128 | future = executor.submit(HowToWorkWithSolaceSDTTypesAndMessages.publish_SDTMap, 129 | messaging_service=messaging_service, 130 | destination=Topic.of(consumer_subscription), message=message) 131 | tasks.append(future) 132 | # can wait of tasks or wait for for first task using concurrent.futures methods 133 | # on context exit all pending tasks in the executor must complete 134 | finally: 135 | print("Terminating receiver") 136 | receiver.terminate() 137 | 138 | @staticmethod 139 | def run(): 140 | try: 141 | os.environ.get('SOLACE_HOST') 142 | messaging_service = MessagingService.builder().from_properties(boot.broker_properties()).build() 143 | # Blocking connect thread 144 | messaging_service.connect() 145 | service_handler = ServiceEventHandler() 146 | messaging_service.add_reconnection_listener(service_handler) 147 | messaging_service.add_reconnection_attempt_listener(service_handler) 148 | messaging_service.add_service_interruption_listener(service_handler) 149 | consumer_subscription = constants.TOPIC_ENDPOINT_DEFAULT 150 | print("messaging_service : ", messaging_service) 151 | HowToWorkWithSolaceSDTTypesAndMessages() \ 152 | .consume_just_SDTMapStream_payload(messaging_service, consumer_subscription) 153 | finally: 154 | print("Disconnecting Messaging Service...") 155 | api_metrics = HowToAccessApiMetrics() 156 | api_metrics.access_individual_api_metrics(messaging_service, Metric.TOTAL_MESSAGES_SENT) 157 | api_metrics.to_string_api_metrics(messaging_service) 158 | messaging_service.disconnect() 159 | 160 | 161 | if __name__ == '__main__': 162 | HowToWorkWithSolaceSDTTypesAndMessages().run() 163 | -------------------------------------------------------------------------------- /howtos/SEMPv2/semp_endpoint.py: -------------------------------------------------------------------------------- 1 | """ 2 | module for holding semp endpoint values 3 | """ 4 | from string import Template 5 | 6 | GET_MSG_VPN_CLIENT_DETAILS_ENDPOINT = Template("/SEMP/v2/monitor/msgVpns/$msg_vpn_name/clients?select" 7 | "=clientName,msgVpnName,clientUsername&count=20") 8 | 9 | GET_MESSAGE_VPN_SERVICE_SETTINGS_ENDPOINT \ 10 | = Template("/SEMP/v2/monitor/msgVpns/$msg_vpn_name?select=msgVpnName,serviceSmfPlainTextEnabled," 11 | "serviceSmfTlsEnabled,tlsAllowDowngradeToPlainTextEnabled,serviceSmfMaxConnectionCount," 12 | "eventServiceSmfConnectionCountThreshold,serviceWebPlainTextEnabled,serviceWebTlsEnabled," 13 | "serviceWebMaxConnectionCount,eventServiceWebConnectionCountThreshold,serviceMqttPlainTextEnabled," 14 | "serviceMqttPlainTextListenPort,serviceMqttTlsEnabled,serviceMqttTlsListenPort," 15 | "serviceMqttWebSocketEnabled,serviceMqttWebSocketListenPort,serviceMqttTlsWebSocketEnabled," 16 | "serviceMqttTlsWebSocketListenPort,serviceMqttMaxConnectionCount," 17 | "eventServiceMqttConnectionCountThreshold,mqttRetainMaxMemory,serviceRestMode," 18 | "serviceRestIncomingPlainTextEnabled,serviceRestIncomingPlainTextListenPort, " 19 | "serviceRestIncomingTlsEnabled,serviceRestIncomingTlsListenPort,serviceRestIncomingMaxConnectionCount," 20 | "eventServiceRestIncomingConnectionCountThreshold,serviceRestOutgoingMaxConnectionCount," 21 | "restTlsServerCertEnforceTrustedCommonNameEnabled,restTlsServerCertMaxChainDepth," 22 | "restTlsServerCertValidateDateEnabled,serviceAmqpPlainTextEnabled,serviceAmqpPlainTextListenPort," 23 | "serviceAmqpTlsEnabled,serviceAmqpTlsListenPort,serviceAmqpMaxConnectionCount," 24 | "eventServiceAmqpConnectionCountThreshold") 25 | 26 | PATCH_MESSAGE_VPN_ENDPOINT = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name") 27 | 28 | GET_SEMP_ABOUT_ENDPOINT = "/SEMP/v2/monitor/about" 29 | GET_ALL_MSG_VPN_ENDPOINT = "/SEMP/v2/monitor/msgVpns?select=msgVpnName,state,replicationEnabled," \ 30 | "replicationRole," \ 31 | "dmrEnabled,msgSpoolCurrentQueuesAndTopicEndpoints,msgSpoolMsgCount,msgVpnConnections," \ 32 | "maxConnectionCount,msgVpnConnectionsServiceRestOutgoing," \ 33 | "serviceRestOutgoingMaxConnectionCount&count=20" 34 | 35 | GET_ALL_USER_LIST = Template("/SEMP/v2/monitor/msgVpns/$msg_vpn_name/clientUsernames" 36 | "?select=clientUsername,msgVpnName,clientProfileName,aclProfileName,enabled," 37 | "subscriptionManagerEnabled,dynamic&count=20") 38 | 39 | create_msg_vpn_endpoint = "/SEMP/v2/config/msgVpns" 40 | update_msg_vpn_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/clientProfiles/$client_profile_name") 41 | set_client_user_name_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/clientUsernames") 42 | certificate_authority_endpoint = "/SEMP/v2/config/certAuthorities" 43 | server_certificate_endpoint = "/SEMP/v2/config" 44 | 45 | message_vpn_authentication_endpoint = "/SEMP/v2/config/msgVpns?select=msgVpnName" 46 | patch_client_user_name_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/clientUsernames" 47 | "/$client_profile_name") 48 | 49 | post_user_creation_endpoint = "/SEMP/v2/config/usernames?select=userName" 50 | post_map_user_to_message_vpn_endpoint = Template("/SEMP/v2/config/usernames/$username/" 51 | "msgVpnAccessLevelExceptions?select=msgVpnName,userName") 52 | delete_user_endpoint = Template("/SEMP/v2/config/usernames/$username") 53 | get_client_connection_objects = Template("/SEMP/v2/monitor/msgVpns/$msg_vpn_name/clients/" 54 | "$client_name/connections") 55 | get_client_connection_properties = Template("/SEMP/v2/monitor/msgVpns/$msg_vpn_name/clients/$client_name") 56 | 57 | vpn_specific_user_endpoint = Template("/SEMP/v2/config/msgVpns/msg_vpn_name/clientUsernames?" 58 | "select=clientUsername,msgVpnName") 59 | 60 | create_msg_vpn_user_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/clientUsernames?select" 61 | "=clientUsername,msgVpnName") 62 | 63 | activate_msg_vpn_user_patch_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/clientUsernames/" 64 | "$client_user_name") 65 | delete_msg_vpn_user_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/clientUsernames" 66 | "/$client_user_name") 67 | allow_shared_subscription_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/clientProfiles" 68 | "/$client_profile_name") 69 | 70 | create_queue_post_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/queues?select=queueName," 71 | "msgVpnName") 72 | create_queue_patch_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/queues/$queue_name") 73 | delete_queue_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/queues/$queue_name") 74 | 75 | queue_permission_change_get = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/queues/$queue_name?select" 76 | "=msgVpnName,queueName,ingressEnabled,egressEnabled,accessType,maxMsgSpoolUsage," 77 | "eventMsgSpoolUsageThreshold,owner,permission,maxBindCount," 78 | "eventBindCountThreshold,maxMsgSize,maxDeliveredUnackedMsgsPerFlow," 79 | "deadMsgQueue,respectMsgPriorityEnabled,respectTtlEnabled,maxTtl," 80 | "maxRedeliveryCount,rejectMsgToSenderOnDiscardBehavior," 81 | "rejectLowPriorityMsgEnabled,rejectLowPriorityMsgLimit," 82 | "eventRejectLowPriorityMsgLimitThreshold,consumerAckPropagationEnabled") 83 | 84 | queue_permission_change_patch = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/queues/$queue_name") 85 | 86 | queue_permission_change_patch_engress_enable = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/queues" 87 | "/$queue_name") 88 | 89 | create_topic_on_queue_post_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/queues/$queue_name" 90 | "/subscriptions?select=subscriptionTopic,msgVpnName,queueName") 91 | 92 | create_topic_on_queue_get_endpoint = Template("/SEMP/v2/monitor/msgVpns/$msg_vpn_name/queues/$queue_name" 93 | "/subscriptions?select=msgVpnName,queueName,subscriptionTopic," 94 | "createdByManagement&count=$count") 95 | created_topic_subscriptions_count_endpoint = Template("SEMP/v2/monitor/msgVpns/$msg_vpn_name/queues" 96 | "/$queue_name?select=topicSubscriptionCount,durable") 97 | 98 | # to shut-down a queue and bring it back online the following patch url will be used 99 | shutdown_queue_patch_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/queues/$queue_name") 100 | 101 | # to add exception topic list 102 | exception_topic_list_endpoint = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/aclProfiles" 103 | "/$msg_vpn_name/publishTopicExceptions?select=publishTopicExceptionSyntax," 104 | "publishTopicException,msgVpnName,aclProfileName") 105 | 106 | # remove topics from the exception list 107 | remove_topics_from_exception_list = Template("/SEMP/v2/config/msgVpns/$msg_vpn_name/aclProfiles/$msg_vpn_name" 108 | "/publishTopicExceptions/smf,$topic_name") 109 | 110 | # end point to get number of solace clients connected 111 | sol_clients_connected = Template("/SEMP/v2/__private_monitor__/msgVpns/$msg_vpn_name/clients?select=clientName" 112 | ",msgVpnName,clientUsername,subscriptionCount,rxDiscardedMsgCount,txDiscardedMsgCount," 113 | "noSubscriptionMatchRxDiscardedMsgCount,clientAddress,slowSubscriber&count=20") 114 | -------------------------------------------------------------------------------- /patterns/guaranteed_processor.py: -------------------------------------------------------------------------------- 1 | ## Goal is to demonstrate receive-process-reply (a processor) pattern. 2 | # Processor subscribes to a topic and upon receipt of the message, the payload is processed 3 | # (e.g. uppercased) and published to another designated topic 4 | 5 | import os 6 | import platform 7 | import time 8 | import threading 9 | 10 | # Import Solace Python API modules from the solace package 11 | from solace.messaging.messaging_service import MessagingService, ReconnectionListener, ReconnectionAttemptListener, \ 12 | ServiceInterruptionListener, RetryStrategy, ServiceEvent 13 | from solace.messaging.resources.topic import Topic 14 | from solace.messaging.publisher.persistent_message_publisher import PersistentMessagePublisher, MessagePublishReceiptListener 15 | from solace.messaging.receiver.persistent_message_receiver import PersistentMessageReceiver 16 | from solace.messaging.resources.queue import Queue 17 | from solace.messaging.resources.topic_subscription import TopicSubscription 18 | from solace.messaging.receiver.message_receiver import MessageHandler, InboundMessage 19 | from solace.messaging.errors.pubsubplus_client_error import PubSubPlusClientError 20 | 21 | if platform.uname().system == 'Windows': os.environ["PYTHONUNBUFFERED"] = "1" # Disable stdout buffer 22 | 23 | lock = threading.Lock() # lock object that is not owned by any thread. Used for synchronization and counting the 24 | 25 | TOPIC_PREFIX = "solace/samples/python" 26 | 27 | # Inner class for connection error handling 28 | class ServiceEventHandler(ReconnectionListener, ReconnectionAttemptListener, ServiceInterruptionListener): 29 | def on_reconnected(self, e: ServiceEvent): 30 | print("\non_reconnected") 31 | print(f"Error cause: {e.get_cause()}") 32 | print(f"Message: {e.get_message()}") 33 | 34 | def on_reconnecting(self, e: "ServiceEvent"): 35 | print("\non_reconnecting") 36 | print(f"Error cause: {e.get_cause()}") 37 | print(f"Message: {e.get_message()}") 38 | 39 | def on_service_interrupted(self, e: "ServiceEvent"): 40 | print("\non_service_interrupted") 41 | print(f"Error cause: {e.get_cause()}") 42 | print(f"Message: {e.get_message()}") 43 | 44 | # Inner class to handle received messages 45 | class ProcessorImpl(MessageHandler): 46 | def __init__(self, publisher, messaging_service): 47 | self.publisher = publisher 48 | self.msg_builder = messaging_service.message_builder() \ 49 | .with_application_message_id("sample_id") \ 50 | .with_property("application", "samples") \ 51 | .with_property("payload", "processed") \ 52 | .with_property("language", "Python") 53 | 54 | def on_message(self, message: InboundMessage): 55 | try: 56 | publish_topic = Topic.of(TOPIC_PREFIX + f'/persistent/processor/output') 57 | destination_name = message.get_destination_name() 58 | 59 | # Check if the payload is a String or Byte, decode if its the later 60 | payload = message.get_payload_as_string() if message.get_payload_as_string() is not None else message.get_payload_as_bytes() 61 | if isinstance(payload, bytearray): 62 | print(f"Received a message of type: {type(payload)}. Decoding to string") 63 | payload = payload.decode() 64 | 65 | print(f'Received input message payload: {payload}') 66 | 67 | # Process the message. For simplicity, we will be uppercasing the payload. 68 | processed_payload = payload.upper() 69 | 70 | if message.get_application_message_id() is not None: 71 | self.msg_builder = self.msg_builder \ 72 | .with_application_message_id(message.get_application_message_id()) 73 | 74 | output_msg = self.msg_builder.build(f'{processed_payload}') 75 | self.publisher.publish(destination=publish_topic, message=output_msg) 76 | 77 | print(f'<<<<<<<<<<<<<<<<<<<<<<<<<<<<') 78 | print(f'Received input message on {destination_name}') 79 | print(f'Received input message (body): {payload}') 80 | # print(f'Received input message dump (body):{message}') 81 | print(f'----------------------------') 82 | print(f'>>>>>>>>>>>>>>>>>>>>>>>>>>>>') 83 | print(f'Published output message to: {publish_topic}') 84 | print(f'Published output message (body): {processed_payload}') 85 | # print(f'Published output message dump (body):{output_msg}') 86 | print(f'----------------------------') 87 | except Exception as e: 88 | print(f'Error processing message: {e.__traceback__}') 89 | 90 | class MessageReceiptListener(MessagePublishReceiptListener): 91 | def __init__(self): 92 | self._receipt_count = 0 93 | 94 | @property 95 | def receipt_count(self): 96 | return self._receipt_count 97 | 98 | def on_publish_receipt(self, publish_receipt: 'PublishReceipt'): 99 | with lock: 100 | self._receipt_count += 1 101 | print(f"\npublish_receipt:\n {self.receipt_count}\n") 102 | 103 | # Broker Config. Note: Could pass other properties Look into 104 | broker_props = { 105 | "solace.messaging.transport.host": os.environ.get('SOLACE_HOST') or "tcp://localhost:55555,tcp://localhost:55554", 106 | "solace.messaging.service.vpn-name": os.environ.get('SOLACE_VPN') or "default", 107 | "solace.messaging.authentication.scheme.basic.username": os.environ.get('SOLACE_USERNAME') or "default", 108 | "solace.messaging.authentication.scheme.basic.password": os.environ.get('SOLACE_PASSWORD') or "" 109 | } 110 | 111 | # Build A messaging service with a reconnection strategy of 20 retries over an interval of 3 seconds 112 | # Note: The reconnections strategy could also be configured using the broker properties object 113 | messaging_service = MessagingService.builder().from_properties(broker_props)\ 114 | .with_reconnection_retry_strategy(RetryStrategy.parametrized_retry(20,3))\ 115 | .build() 116 | 117 | # Blocking connect thread 118 | messaging_service.connect() 119 | print(f'\nMessaging Service connected? {messaging_service.is_connected}') 120 | 121 | # Event Handling for the messaging service 122 | service_handler = ServiceEventHandler() 123 | messaging_service.add_reconnection_listener(service_handler) 124 | messaging_service.add_reconnection_attempt_listener(service_handler) 125 | messaging_service.add_service_interruption_listener(service_handler) 126 | 127 | # Create a persistent message publisher and start it 128 | publisher: PersistentMessagePublisher = messaging_service.create_persistent_message_publisher_builder().build() 129 | 130 | # Blocking Start thread 131 | publisher.start() 132 | print(f'Persistent Publisher ready? {publisher.is_ready()}') 133 | 134 | # set a message delivery listener to the publisher 135 | receipt_listener = MessageReceiptListener() 136 | publisher.set_message_publish_receipt_listener(receipt_listener) 137 | 138 | # NOTE: This assumes that a persistent queue already exists on the broker with the same name 139 | # and a subscription for topic `solace/samples/python/persistent/processor/output`` 140 | queue_name = 'Q/test/input' 141 | durable_exclusive_queue = Queue.durable_exclusive_queue(queue_name) 142 | 143 | # Build a Receiver with the given topics and start it 144 | persistent_receiver: PersistentMessageReceiver = messaging_service.create_persistent_message_receiver_builder() \ 145 | .with_message_auto_acknowledgement() \ 146 | .build(durable_exclusive_queue) 147 | persistent_receiver.start() 148 | print(f'Persistent Receiver is running? {persistent_receiver.is_running()}') 149 | 150 | print("\nSend a KeyboardInterrupt to stop processor\n") 151 | try: 152 | # Callback for received messages 153 | persistent_receiver.receive_async(ProcessorImpl(publisher, messaging_service)) 154 | try: 155 | while True: 156 | time.sleep(1) 157 | except KeyboardInterrupt: 158 | print('\nDisconnecting Messaging Service') 159 | # Handle API exception 160 | except PubSubPlusClientError as exception: 161 | print(f'\nMake sure queue {queue_name} exists on broker!') 162 | 163 | finally: 164 | print('\nTerminating receiver') 165 | persistent_receiver.terminate() 166 | print('\nDisconnecting Messaging Service') 167 | messaging_service.disconnect() 168 | -------------------------------------------------------------------------------- /howtos/pubsub/how_to_use_publish_with_back_pressure.py: -------------------------------------------------------------------------------- 1 | """ Run this file to publish messages using direct message publisher with back pressure scenarios""" 2 | from typing import TypeVar 3 | 4 | from solace.messaging.errors.pubsubplus_client_error import PublisherOverflowError 5 | from solace.messaging.messaging_service import MessagingService 6 | from solace.messaging.resources.topic import Topic 7 | from howtos.sampler_boot import SamplerBoot, SolaceConstants, SamplerUtil 8 | 9 | X = TypeVar('X') 10 | constants = SolaceConstants 11 | boot = SamplerBoot() 12 | util = SamplerUtil() 13 | 14 | 15 | class HowToDirectPublishWithBackPressureSampler: 16 | """ 17 | class to show how to create a messaging service 18 | """ 19 | 20 | @staticmethod 21 | def direct_message_publish_on_backpressure_reject(messaging_service: MessagingService, destination, message, 22 | buffer_capacity, message_count): 23 | """ to publish str or byte array type message using back pressure""" 24 | try: 25 | direct_publish_service = messaging_service.create_direct_message_publisher_builder() \ 26 | .on_back_pressure_reject(buffer_capacity=buffer_capacity) \ 27 | .build() 28 | direct_publish_service.start() 29 | for e in range(message_count): 30 | direct_publish_service.publish(destination=destination, message=message) 31 | except PublisherOverflowError: 32 | PublisherOverflowError("Queue maximum limit is reached") 33 | finally: 34 | direct_publish_service.terminate() 35 | 36 | @staticmethod 37 | def direct_message_publish_outbound_with_all_props_on_backpressure_on_reject(messaging_service: MessagingService, 38 | destination, message, buffer_capacity, 39 | message_count): 40 | """ to publish outbound message using back pressure""" 41 | try: 42 | direct_publish_service = messaging_service.create_direct_message_publisher_builder() \ 43 | .on_back_pressure_reject(buffer_capacity=buffer_capacity) \ 44 | .build() 45 | direct_publish_service.start() 46 | outbound_msg = messaging_service.message_builder() \ 47 | .with_property("custom_key", "custom_value") \ 48 | .with_expiration(SolaceConstants.MESSAGE_EXPIRATION) \ 49 | .with_priority(SolaceConstants.MESSAGE_PRIORITY) \ 50 | .with_sequence_number(SolaceConstants.MESSAGE_SEQUENCE_NUMBER) \ 51 | .with_application_message_id(constants.APPLICATION_MESSAGE_ID) \ 52 | .with_application_message_type("app_msg_type") \ 53 | .with_http_content_header("text/html", "utf-8") \ 54 | .build(message) 55 | for e in range(message_count): 56 | direct_publish_service.publish(destination=destination, message=outbound_msg) 57 | except PublisherOverflowError: 58 | PublisherOverflowError("Queue maximum limit is reached") 59 | finally: 60 | direct_publish_service.terminate() 61 | 62 | @staticmethod 63 | def direct_message_publish_outbound_with_all_props_on_backpressure_elastic(messaging_service: MessagingService, 64 | destination, message, message_count): 65 | """ to publish outbound message using back pressure""" 66 | try: 67 | direct_publish_service = messaging_service.create_direct_message_publisher_builder() \ 68 | .on_back_pressure_elastic() \ 69 | .build() 70 | direct_publish_service.start() 71 | outbound_msg = messaging_service.message_builder() \ 72 | .with_property("custom_key", "custom_value") \ 73 | .with_expiration(SolaceConstants.MESSAGE_EXPIRATION) \ 74 | .with_priority(SolaceConstants.MESSAGE_PRIORITY) \ 75 | .with_sequence_number(SolaceConstants.MESSAGE_SEQUENCE_NUMBER) \ 76 | .with_application_message_id(constants.APPLICATION_MESSAGE_ID) \ 77 | .with_application_message_type("app_msg_type") \ 78 | .with_http_content_header("text/html", "utf-8") \ 79 | .build(message) 80 | for e in range(message_count): 81 | direct_publish_service.publish(destination=destination, message=outbound_msg) 82 | finally: 83 | direct_publish_service.terminate() 84 | 85 | @staticmethod 86 | def direct_message_publish_outbound_with_all_props_on_backpressure_wait(messaging_service: MessagingService, 87 | destination, message, buffer_capacity, 88 | message_count): 89 | """ to publish outbound message using back pressure""" 90 | try: 91 | direct_publish_service = messaging_service.create_direct_message_publisher_builder() \ 92 | .on_back_pressure_wait(buffer_capacity=buffer_capacity) \ 93 | .build() 94 | direct_publish_service.start() 95 | outbound_msg = messaging_service.message_builder() \ 96 | .with_property("custom_key", "custom_value") \ 97 | .with_expiration(SolaceConstants.MESSAGE_EXPIRATION) \ 98 | .with_priority(SolaceConstants.MESSAGE_PRIORITY) \ 99 | .with_sequence_number(SolaceConstants.MESSAGE_SEQUENCE_NUMBER) \ 100 | .with_application_message_id(constants.APPLICATION_MESSAGE_ID) \ 101 | .with_application_message_type("app_msg_type") \ 102 | .with_http_content_header("text/html", "utf-8") \ 103 | .build(message) 104 | for e in range(message_count): 105 | direct_publish_service.publish(destination=destination, message=outbound_msg) 106 | except PublisherOverflowError: 107 | PublisherOverflowError("Queue maximum limit is reached") 108 | finally: 109 | direct_publish_service.terminate() 110 | 111 | @staticmethod 112 | def run(): 113 | """ 114 | :return: 115 | """ 116 | service = None 117 | try: 118 | service = MessagingService.builder().from_properties(boot.broker_properties()).build() 119 | service.connect() 120 | print(f"Message service connected: {service.is_connected}") 121 | if service.is_connected: 122 | destination_name = Topic.of(constants.TOPIC_ENDPOINT_DEFAULT) 123 | message_count = 10 124 | buffer_capacity = 100 125 | print("Execute Direct Publish - String using back pressure") 126 | HowToDirectPublishWithBackPressureSampler() \ 127 | .direct_message_publish_on_backpressure_reject(service, destination_name, constants.MESSAGE_TO_SEND, 128 | buffer_capacity, message_count) 129 | 130 | print("Execute Direct Publish - String Outbound Message with all props using back pressure") 131 | HowToDirectPublishWithBackPressureSampler(). \ 132 | direct_message_publish_outbound_with_all_props_on_backpressure_on_reject(service, destination_name, 133 | constants.MESSAGE_TO_SEND 134 | + "_outbound based", 135 | buffer_capacity, 136 | message_count) 137 | 138 | print("Execute Direct Publish - String Outbound Message with all props using back pressure elastic") 139 | HowToDirectPublishWithBackPressureSampler() \ 140 | .direct_message_publish_outbound_with_all_props_on_backpressure_elastic(service, destination_name, 141 | constants.MESSAGE_TO_SEND + 142 | "_outbound based", 143 | message_count) 144 | 145 | print("Execute Direct Publish - String Outbound Message with all props using back pressure wait") 146 | HowToDirectPublishWithBackPressureSampler() \ 147 | .direct_message_publish_outbound_with_all_props_on_backpressure_wait(service, destination_name, 148 | constants.MESSAGE_TO_SEND 149 | + str("_outbound based"), 150 | buffer_capacity, 151 | message_count) 152 | else: 153 | print("failed to connect service with properties: {boot.broker_properties}") 154 | 155 | finally: 156 | if service: 157 | service.disconnect() 158 | 159 | 160 | if __name__ == '__main__': 161 | HowToDirectPublishWithBackPressureSampler().run() 162 | -------------------------------------------------------------------------------- /howtos/how_to_request_cached_data.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains example snippets on how to request cached data from a cache, through a direct receiver. These 3 | examples assume that the direct receiver has already been started, and that it was created from a messaging service 4 | which has already been connected. 5 | """ 6 | 7 | from solace.messaging.utils.cache_request_outcome_listener import CacheRequestOutcomeListener 8 | from solace.messaging.utils.cache_request_outcome import CacheRequestOutcome 9 | from solace.messaging.resources.topic_subscription import TopicSubscription 10 | from solace.messaging.resources.cached_message_subscription_request import CachedMessageSubscriptionRequest 11 | 12 | class MyCacheRequestOutcomeListener(CacheRequestOutcomeListener): 13 | def on_completion(result: CacheRequestOutcome, correlation_id: int, e: Exception): 14 | print("Completed!") 15 | 16 | class HowToUseCachedMessageSubscriptionRequests: 17 | """ 18 | Sampler for making cache requests. Usage of cache subscriptions require 19 | configuration and hosting of PubSub+ Cache application. 20 | """ 21 | def create_subscription_request_to_receive_cached_and_live_messages(subscription_expression: str, 22 | cache_name: str, 23 | cache_access_timeout: int, 24 | direct_message_receiver: 'DirectMessageReceiver'): 25 | """ 26 | An example how to create and perform CachedMessageSubscriptionRequest to receive a 27 | mix of live and cached messages matching specified topic subscription. 28 | Args: 29 | subscription_expression(str): topic subscription expression 30 | cache_name(str): name of the solace cache to retrieve from 31 | cache_access_timeout(int): solace cache request timeout, in milliseconds 32 | direct_message_receiver(DirectMessageReceiver): an already configured and connected receiver 33 | 34 | Raises: 35 | InterruptedException: if any thread has interrupted the current thread 36 | PubSubPlusClientException: if the operation could not be performed, i.e. when 37 | cache request failed and subscription rolled back/removed 38 | IllegalStateException: If the service is not running 39 | """ 40 | cached_message_subscription_request = CachedMessageSubscriptionRequest.asAvailable(cache_name, 41 | TopicSubscription.of(subscription_expression), 42 | cache_access_timeout) 43 | correlation_id = 1 44 | cache_request_outcome_listener = MyCacheRequestOutcomeListener() 45 | 46 | direct_message_receiver.request_cached(cached_message_subscription_request, correlation_id, cache_request_outcome_listener) 47 | 48 | def create_subscription_request_to_receive_latest_message(direct_message_receiver: 'DirectMessageReceiver', 49 | subscription_expression: str, 50 | cache_name: str, 51 | cache_access_timeout: int): 52 | """ 53 | An example of how to create and perform CachedMessageSubscriptionRequest to receive latest messages. When no live 54 | messages are available, cached messages matching specified topic subscription considered latest, live messages otherwise. 55 | When live messages are available then cached messages are discarded. 56 | 57 | Args: 58 | direct_message_receiver(DirectMessageReceiver): An already configured and connected receiver. 59 | subscription_expression(str): Topic subscription expression. 60 | cache_name(str): name of the solace cache to retrieve from 61 | cache_access_timeout(int): Solace cache request timeout, in milliseconds 62 | 63 | Raises: 64 | InterruptedException: If any thread has interrupted the current thread 65 | PubSubPlusClientException: If the operation could not be performed 66 | IllegalStateException: If the service is not running 67 | """ 68 | cached_message_subscription_request_for_latest_of_cached_or_live_messages = \ 69 | CachedMessageSubscriptionRequest.live_cancels_cached(cache_name, 70 | TopicSubscription.of(subscriptionExpression), 71 | cache_access_timeout) 72 | 73 | correlation_id = 2 74 | cache_request_outcome_listener = MyCacheRequestOutcomeListener() 75 | 76 | direct_message_receiver.request_cached(cached_message_subscription_request_for_latest_of_cached_or_live_messages, 77 | correlation_id, 78 | cache_request_outcome_listener) 79 | 80 | def create_cached_subscription_with_more_options(direct_message_receiver: 'DirectMessageReceiver', 81 | subscription_expression: 'SubscriptionExpression', 82 | cache_name: str, 83 | cache_access_timeout: int, 84 | max_cached_messages: int, 85 | cached_message_age: int): 86 | """ 87 | An example how to create and perform CachedMessageSubscriptionRequest to receive first cached 88 | messages when available, followed by live messages. Additional cached message properties such 89 | as max number of cached messages and age of a message from cache can be specified. Live messages 90 | will be queued until the solace cache response is received. 91 | Queued live messages are delivered to the application after the cached messages are delivered. 92 | Args: 93 | direct_message_receiver(DirectMessageReceiver): An already configured and connected receiver 94 | subscription_expression(str): topic subscription expression 95 | cache_name(str): Name of the solace cache to retrieve from 96 | cache_access_timeout(int): Solace cache request timeout, in milliseconds 97 | max_cached_messages(int): The max number of messages expected to be received from a Solace cache 98 | cached_message_age(int): The maximum age, in seconds, of the messages to retrieve from a Solace cache 99 | Raises: 100 | InterruptedException: If any thread has interrupted the current thread 101 | PubSubPlusClientException: If the operation could not be performed 102 | IllegalStateException: If the service is not running 103 | """ 104 | topic = TopicSubscription.of(subscription_expression) 105 | subscription_to_receive_cached_followed_by_live_messages = CachedMessageSubscriptionRequest.cached_first(cache_name, 106 | topic, 107 | cache_access_timeout, 108 | max_cached_messages, 109 | cached_message_age) 110 | correlation_id = 3 111 | completion_listener = MyCacheRequestOutcomeListener() 112 | 113 | direct_message_receiver.request_cached(subscription_receive_cached_followed_by_live_messages, correlation_id, completion_listener) 114 | 115 | def create_subscription_to_receiver_only_cached_messages(direct_message_receiver: 'DirectMessageReceiver', 116 | subscription_expression: str, 117 | cache_name: str, 118 | cache_access_timeout): 119 | """ 120 | An example how to create and perform CachedMessageSuibscriptionRequest to receive cached messages 121 | only, when available; no live messages are expected to be received. 122 | Args: 123 | direct_message_receiver(DirectMessageReceiver): An already configured and connected receiver. 124 | subscription_expression(str): topic subscription expression 125 | cache_name(str): name of the solace cache to retrieve from 126 | cache_access_timeout(int): solace cache request timeout, in milliseconds 127 | Raises: 128 | InterruptedException: If any thread has interrupted the current thread 129 | PubSubPlusClientException: If the operation could not be performed 130 | IllegalStateException: If the service is not running 131 | """ 132 | subscription_request_to_receive_only_cached_messages = CachedMessageSubscriptionRequest.cached_only(cache_name, 133 | TopicSubscription.of(subscription_expression), 134 | cache_access_timeout) 135 | correlation_id = 4 136 | completion_listener = MyCacheRequestOutcomeListener() 137 | 138 | direct_message_receiver.request_cached(subscription_request_to_receive_only_cached_messages, correlation_id, completion_listener) 139 | --------------------------------------------------------------------------------