Diffusion 6.0 User Guide - Diffusion

Diffusion 6.0
User Guide
Contents
List of Figures..............................................................................................12
List of Tables............................................................................................... 15
Part I: Welcome.......................................................................... 18
Introducing Diffusion................................................................................................. 19
What's new in Diffusion 6.0?...................................................................................... 21
Part II: Quick Start Guide............................................................ 24
Get Diffusion............................................................................................................. 25
Install Diffusion......................................................................................................... 25
Start the Diffusion server........................................................................................... 25
Default configuration................................................................................................. 25
The Diffusion monitoring console............................................................................... 26
Develop a publishing client........................................................................................ 26
Develop a subscribing client.......................................................................................29
Resources..................................................................................................................31
Part III: Design Guide.................................................................. 32
Support.....................................................................................................................33
System requirements for the Diffusion server....................................................................33
Platform support for the Diffusion API libraries.................................................................35
Feature support in the Diffusion API...................................................................................37
Feature packs........................................................................................................................42
Protocol support...................................................................................................................43
Browser support................................................................................................................... 44
Browser limitations.............................................................................................................. 45
WebSocket limitations............................................................................................. 45
Cross-origin resource sharing limitations...............................................................46
Browser connection limitations.............................................................................. 46
Designing your data model.........................................................................................47
Topic tree.............................................................................................................................. 47
Topic naming............................................................................................................ 49
Topic selectors..........................................................................................................50
Topics.................................................................................................................................... 57
Diffusion | 2
Properties of topics.................................................................................................. 57
JSON topics...............................................................................................................61
Binary topics............................................................................................................. 62
String topics.............................................................................................................. 64
Int64 topics............................................................................................................... 65
Double topics............................................................................................................ 66
Time series topics.....................................................................................................67
Routing topics...........................................................................................................70
Slave topics............................................................................................................... 71
RecordV2 topics........................................................................................................ 72
DEPRECATED: Record topics....................................................................................76
DEPRECATED: Single value topics........................................................................... 79
DEPRECATED: Stateless topics................................................................................ 80
Pub-sub................................................................................................................................. 81
Publishing data.........................................................................................................82
Subscribing to topics................................................................................................83
Topic notifications................................................................................................................86
Request-response messaging.............................................................................................. 87
Advanced usage....................................................................................................................90
Conflation..................................................................................................................90
Designing your solution............................................................................................. 93
Servers................................................................................................................................... 94
Fan-out.................................................................................................................................. 95
Using missing topic notifications with fan-out.......................................................97
High availability.................................................................................................................... 99
Session replication................................................................................................. 100
Topic replication.....................................................................................................104
Failover of active update sources......................................................................... 106
Topic persistence................................................................................................................107
Clients.................................................................................................................................. 108
Client types............................................................................................................. 108
Using clients............................................................................................................109
Using clients for control.........................................................................................109
User-written components.................................................................................................. 112
Publishers................................................................................................................112
Other user-written components............................................................................ 112
Third party components.................................................................................................... 114
Load balancers....................................................................................................... 114
Web servers............................................................................................................. 115
Push notification networks....................................................................................118
JMS.......................................................................................................................... 119
Example solutions.............................................................................................................. 121
Example: Simple solution...................................................................................... 121
Example: A solution using clients..........................................................................122
Example: Scalable and resilient solution..............................................................123
Security...................................................................................................................124
Role-based authorization...................................................................................................124
Permissions............................................................................................................. 129
Pre-defined roles.................................................................................................... 134
Authentication.................................................................................................................... 136
User-written authentication handlers...................................................................139
System authentication handler............................................................................. 141
Pre-defined users....................................................................................................142
DEPRECATED: Authorization handlers.............................................................................. 142
Diffusion | 3
Part IV: Developer Guide............................................................146
Best practice for developing clients.......................................................................... 148
Feature support in the Diffusion API......................................................................... 149
Getting started........................................................................................................ 154
JavaScript............................................................................................................................154
Start subscribing with JavaScript......................................................................... 157
Start publishing with JavaScript........................................................................... 161
Apple....................................................................................................................................163
Start subscribing with iOS..................................................................................... 165
Start publishing with OS X/macOS........................................................................170
Start subscribing with Swift on iOS...................................................................... 176
Start publishing with Swift on iOS........................................................................ 178
Android................................................................................................................................ 179
Start subscribing with Android..............................................................................181
Start publishing with Android............................................................................... 186
Java......................................................................................................................................191
Start subscribing with Java................................................................................... 193
Start publishing with Java.....................................................................................198
.NET......................................................................................................................................202
Start subscribing with .NET................................................................................... 203
Start publishing with .NET.....................................................................................208
C........................................................................................................................................... 216
Start subscribing with C.........................................................................................220
Start publishing with C.......................................................................................... 228
Connecting to the Diffusion server............................................................................ 243
Connecting basics...............................................................................................................245
Connecting securely............................................................................................... 251
Connecting to the Diffusion server with a security principal and credentials.....253
Connecting through an HTTP proxy......................................................................255
Connecting through a load balancer.................................................................... 257
Reconnect to the Diffusion server..................................................................................... 259
Detecting connection problems............................................................................ 261
Specifying a reconnection strategy.......................................................................262
Session failover.......................................................................................................272
Pinging the Diffusion server...............................................................................................273
Change the security principal and credentials associated with your client session......274
Session properties.............................................................................................................. 275
Session filtering...................................................................................................... 276
Receiving data from topics....................................................................................... 279
Subscribing to topics..........................................................................................................280
Using streams for subscription..........................................................................................282
Fetching the current value of a topic................................................................................287
Receiving topic notifications............................................................................................. 290
Managing topics.......................................................................................................291
Adding topics with topic specifications............................................................................ 291
DEPRECATED: Add a topic using an initial value..............................................................292
Example: Create a JSON topic...........................................................................................295
Handling subscriptions to missing topics.........................................................................305
Example: Receive missing topic notifications.................................................................. 310
Defining a recordV2 schema.............................................................................................. 320
Update recordV2 with a schema........................................................................... 324
Subscribe to recordV2 with a schema...................................................................330
Creating a metadata definition......................................................................................... 338
Diffusion | 4
Removing topics................................................................................................................. 341
Removing topics with sessions..............................................................................342
Handling subscriptions to missing topics.........................................................................344
Listening for topic events.................................................................................................. 349
Receiving topic notifications............................................................................................. 350
Updating topics....................................................................................................... 351
Example: Make exclusive updates to a topic....................................................................354
Example: Make non-exclusive updates to a topic............................................................ 370
Using time series topics........................................................................................... 375
Example: Publish a time series..........................................................................................378
Example: Subscribe to a time series................................................................................. 381
Managing subscriptions............................................................................................382
Example: Subscribe other clients to topics...................................................................... 384
Example: Receive notifications when a client subscribes to a routing topic.................. 389
Using request-response messaging........................................................................... 392
Sending request messages to a message path................................................................ 394
Sending request messages to a session........................................................................... 402
Sending request messages to a session filter...................................................................408
Authenticating new sessions.....................................................................................413
Example: Register an authentication handler.................................................................. 413
Developing a control authentication handler.................................................................. 422
Developing a composite control authentication handler................................................425
Updating the system authentication store.................................................................427
DSL syntax: system authentication store......................................................................... 428
Example: Update the system authentication store..........................................................430
Updating the security store...................................................................................... 440
DSL syntax: security store..................................................................................................440
Example: Update the security store..................................................................................443
Managing sessions................................................................................................... 452
Working with session properties....................................................................................... 452
Handling client queues...................................................................................................... 457
Flow control............................................................................................................ 458
Logging from the client............................................................................................ 459
Logging in JavaScript.........................................................................................................459
Logging in Apple................................................................................................................. 460
Logging in Android............................................................................................................. 461
Logging in Java...................................................................................................................461
Logging in .NET...................................................................................................................463
Logging in C........................................................................................................................ 464
Developing a publisher.............................................................................................464
Publisher basics.................................................................................................................. 464
Defining publishers.................................................................................................464
Loading publisher code......................................................................................... 465
Load publishers by using the API.......................................................................... 465
Starting and stopping publishers..........................................................................466
Publisher topics...................................................................................................... 467
Receiving and maintaining data............................................................................468
Publishing and sending messages........................................................................ 468
Publisher notifications........................................................................................... 469
Client handling........................................................................................................470
Publisher properties............................................................................................... 471
Using concurrent threads...................................................................................... 471
Publisher logging....................................................................................................471
General utilities.......................................................................................................471
Writing a publisher............................................................................................................. 471
Diffusion | 5
Creating a Publisher class......................................................................................472
Publisher startup.................................................................................................... 472
Data state................................................................................................................ 472
Data inputs..............................................................................................................473
Handling client subscriptions................................................................................ 474
Publishing messages.............................................................................................. 475
Handling clients...................................................................................................... 477
Publisher closedown.............................................................................................. 477
Testing a publisher.............................................................................................................478
Client queues...................................................................................................................... 478
Queue enquiries......................................................................................................478
Maximum queue depth.......................................................................................... 479
Queue notification thresholds............................................................................... 479
Tidy on unsubscribe............................................................................................... 479
Client Geo and WhoIs information.................................................................................... 480
The Diffusion WhoIs service...................................................................................481
Client notifications............................................................................................................. 482
Adding a ClientListener.......................................................................................... 483
Using DefaultClientListener................................................................................... 483
Developing other components.................................................................................. 484
Local authentication handlers...........................................................................................484
Developing a local authentication handler.......................................................... 484
Developing a composite authentication handler.................................................486
Push Notification Bridge persistence plugin.................................................................... 488
Example: Send a request message to the Push Notification Bridge............................... 489
Using Maven to build Java Diffusion applications.......................................................495
Build client applications.................................................................................................... 496
Build publishers with Maven............................................................................................. 497
Building a publisher with mvndar.........................................................................498
Build server application code with Maven....................................................................... 501
Testing.................................................................................................................... 502
Benchmarking suite............................................................................................................502
Part V: Administrator Guide....................................................... 503
Installing the Diffusion server...................................................................................504
System requirements for the Diffusion server..................................................................504
Installing the Diffusion server using the graphical installer............................................ 506
Installing the Diffusion server using the headless installer............................................. 508
Installing the Diffusion server using Red Hat Package Manager..................................... 509
Installing the Diffusion server using Docker..................................................................... 510
Next steps with Docker.......................................................................................... 511
The Diffusion license.......................................................................................................... 512
License restrictions.................................................................................................513
Updating your license file...................................................................................... 514
Installed files.......................................................................................................................515
Verifying the Diffusion installation.................................................................................... 517
Configuring your Diffusion server............................................................................. 519
XML configuration...............................................................................................................519
Obfuscation tool................................................................................................................. 521
Programmatic configuration............................................................................................. 522
Using the configuration API................................................................................... 522
Configuring the Diffusion server........................................................................................523
Configuring fan-out................................................................................................ 523
Configuring conflation............................................................................................525
Diffusion | 6
Configuring authentication handlers.................................................................... 529
Configuring performance....................................................................................... 531
Configuring topic persistence................................................................................531
Configuring connectors...................................................................................................... 532
Connectors.xml....................................................................................................... 533
Configuring user security................................................................................................... 539
Security.store.......................................................................................................... 539
SystemAuthentication.store.................................................................................. 541
Securing the console..........................................................................................................544
Configuring logging on the Diffusion server..................................................................... 545
Configuring default logging................................................................................... 546
Logs.xml.................................................................................................................. 546
Configuring log4j2.................................................................................................. 549
Log4j2.xml............................................................................................................... 550
Logging using another SLF4J implementation.....................................................551
Configuring JMX..................................................................................................................551
Configuring the Diffusion JMX connector server.................................................. 551
Configuring a remote JMX server connector........................................................ 553
Configuring a local JMX connector server............................................................ 554
Management.xml.................................................................................................... 554
Configuring the JMX adapter.................................................................................555
Publishers.xml.........................................................................................................556
Configuring replication.......................................................................................................559
Configuring the Diffusion server to use replication..............................................559
Configuring the Hazelcast datagrid.......................................................................560
Replication.xml....................................................................................................... 562
Configuring the Diffusion web server................................................................................564
Configuring Diffusion web server security............................................................ 565
WebServer.xml........................................................................................................ 566
Aliases.xml..............................................................................................................571
ConnectionValidationPolicy.xml........................................................................................572
Env.xml................................................................................................................................ 573
Mime.xml............................................................................................................................. 574
Publishers.xml.....................................................................................................................574
Statistics.xml......................................................................................................................577
SubscriptionValidationPolicy.xml......................................................................................580
Cross domain...................................................................................................................... 582
Starting the Diffusion server.....................................................................................582
Running from within a Java application.......................................................................... 583
Network security..................................................................................................... 586
Going to production................................................................................................. 588
Pre-production testing....................................................................................................... 588
Setting up your test environment......................................................................... 588
Understanding production usage conditions.......................................................590
Types of testing...................................................................................................... 592
Testing your security..............................................................................................593
Tools you can use in your pre-production testing............................................... 594
Planning for production.....................................................................................................595
Deploying to your production environment.....................................................................596
Tuning.....................................................................................................................596
Concurrency........................................................................................................................ 596
Buffer sizing........................................................................................................................ 599
Message sizing.................................................................................................................... 600
Client queues...................................................................................................................... 601
Client multiplexers..............................................................................................................601
Diffusion | 7
Connectors.......................................................................................................................... 602
Thread pools....................................................................................................................... 603
Session reconnection......................................................................................................... 606
Client failover......................................................................................................................607
Client throttling.................................................................................................................. 608
Java memory usage........................................................................................................... 609
Platform-specific issues..................................................................................................... 610
Socket issues...........................................................................................................610
Publisher design................................................................................................................. 612
Managing and monitoring your running Diffusion server............................................ 612
JMX.......................................................................................................................................613
Using Java VisualVM...............................................................................................614
Using JConsole....................................................................................................... 616
MBeans.................................................................................................................... 619
The JMX adapter.....................................................................................................629
Statistics.............................................................................................................................. 632
Configuring statistics..............................................................................................635
Diffusion monitoring console............................................................................................ 637
Logging................................................................................................................................ 648
Logging back-end................................................................................................... 649
Logging reference................................................................................................... 650
Log messages..........................................................................................................654
Connection counts..................................................................................................728
Integration with Splunk..................................................................................................... 729
Web servers............................................................................................................. 732
Diffusion web server...........................................................................................................732
Server-side processing........................................................................................... 733
Hosting a status page on the Diffusion web server..............................................734
Hosting Diffusion web clients in a third-party web server...............................................734
Running the Diffusion server inside of a third-party web application server..................735
Example: Deploying the Diffusion server within Tomcat..................................... 736
Other considerations when running the Diffusion server inside of a third-party
web application server..................................................................................... 738
Cross domain policies........................................................................................................ 739
Load balancers........................................................................................................ 739
Routing strategies at your load balancer......................................................................... 740
Monitoring available Diffusion servers from your load balancer.................................... 742
Compositing URL spaces using your load balancer.........................................................742
Secure Sockets Layer (SSL) offloading at your load balancer......................................... 743
Using load balancers for resilience................................................................................... 743
Common issues when using a load balancer................................................................... 744
JMS adapter............................................................................................................ 745
Transforming JMS messages into Diffusion messages or updates..................................746
Publishing using the JMS adapter.....................................................................................749
Sending messages using the JMS adapter....................................................................... 750
Using JMS request-response services with the JMS adapter.......................................... 752
Configuring the JMS adapter.............................................................................................754
Example: Configuring the Diffusion connection for the JMS adapter running
as a standalone client...................................................................................... 756
Example: Configuring JMS providers for the JMS adapter.................................. 756
Example: Configuring topics for use with the JMS adapter.................................758
Example: Configuring pub-sub with the JMS adapter......................................... 758
Example: Configuring messaging with the JMS adapter......................................759
Example: Configuring the JMS adapter to work with JMS services.....................761
JMSAdapter.xml......................................................................................................762
Diffusion | 8
Running the JMS adapter.................................................................................................. 772
Push Notification Bridge...........................................................................................773
Configuring your Push Notification Bridge....................................................................... 776
PushNotifications.xml............................................................................................ 778
Getting an Apple certificate for the Push Notification Bridge..............................781
Getting a Google API key for the Push Notification Bridge.................................. 782
Running the Push Notification Bridge.............................................................................. 782
JSON formats used by the Push Notification Bridge....................................................... 783
Request and response JSON formats................................................................... 783
Push notification JSON format..............................................................................786
Deploying publishers on your Diffusion server........................................................... 789
Classic deployment............................................................................................................ 789
Hot deployment..................................................................................................................790
Deployment methods.............................................................................................790
Demos..................................................................................................................... 791
Demos..................................................................................................................................792
Deploying the demos......................................................................................................... 792
Tools....................................................................................................................... 792
Tools for Amazon Elastic Compute Cloud (EC2)...............................................................793
Tools for Joyent..................................................................................................................794
Part VI: Upgrading Guide........................................................... 796
Interoperability........................................................................................................797
Upgrading from version 5.x to version 6.0................................................................. 798
Upgrading to a new patch release.............................................................................804
Chapter : Appendices.................................................................805
Appendix A: Document conventions.................................... 806
Appendix B: Glossary..........................................................807
A........................................................................................................................................... 808
C........................................................................................................................................... 809
D........................................................................................................................................... 810
E........................................................................................................................................... 811
F............................................................................................................................................812
G........................................................................................................................................... 812
H........................................................................................................................................... 813
I............................................................................................................................................ 813
J............................................................................................................................................814
L............................................................................................................................................815
M...........................................................................................................................................816
N........................................................................................................................................... 816
P........................................................................................................................................... 817
Q...........................................................................................................................................819
R........................................................................................................................................... 819
S........................................................................................................................................... 821
T........................................................................................................................................... 823
U........................................................................................................................................... 824
V........................................................................................................................................... 825
W.......................................................................................................................................... 825
Diffusion | 9
X........................................................................................................................................... 825
Appendix C: Trademarks.....................................................827
Appendix D: Copyright Notices............................................829
ANTLR.................................................................................................................................. 831
apns..................................................................................................................................... 831
Apache Commons Codec................................................................................................... 831
Apache Portable Runtime.................................................................................................. 831
Bootstrap.............................................................................................................................832
concurrent-trees................................................................................................................. 832
CQEngine............................................................................................................................. 832
cron4j................................................................................................................................... 832
d3......................................................................................................................................... 832
disruptor..............................................................................................................................833
Fluidbox............................................................................................................................... 833
gcm-server...........................................................................................................................833
GeoIP API.............................................................................................................................833
GeoLite City Database........................................................................................................ 833
geronimo-jms_1.1_spec..................................................................................................... 834
Google code prettify...........................................................................................................834
hashmap..............................................................................................................................834
Hazelcast............................................................................................................................. 834
HPPC.................................................................................................................................... 835
htmlcompressor..................................................................................................................835
inherits.................................................................................................................................835
jackson-core........................................................................................................................ 835
jackson-dataformat-cbor................................................................................................... 836
JCIP Annotations................................................................................................................ 836
JCTools................................................................................................................................ 836
jQuery.................................................................................................................................. 836
json-simple.......................................................................................................................... 836
Knockout............................................................................................................................. 837
libwebsockets..................................................................................................................... 837
log4j2................................................................................................................................... 837
loglevel................................................................................................................................ 837
long...................................................................................................................................... 837
Metrics................................................................................................................................. 838
Minimal JSON......................................................................................................................838
Modernizr.............................................................................................................................838
NLog.....................................................................................................................................838
opencsv................................................................................................................................839
OpenSSL.............................................................................................................................. 839
PCRE.................................................................................................................................... 839
Picocontainer...................................................................................................................... 840
Rickshaw..............................................................................................................................840
Servlet API........................................................................................................................... 840
SLF4J....................................................................................................................................840
slf4j-android-logger............................................................................................................ 840
SocketRocket...................................................................................................................... 840
streamsupport.................................................................................................................... 841
Tabber................................................................................................................................. 841
Tapestry (Plastic)................................................................................................................ 841
Diffusion | 10
TrueLicense......................................................................................................................... 842
when.................................................................................................................................... 842
ws......................................................................................................................................... 842
Licenses............................................................................................................................... 842
Apache License 2.0................................................................................................. 842
BSD 3-clause License..............................................................................................845
Common Development and Distribution License................................................ 846
Eclipse Public License – v 1.0................................................................................ 850
GNU General Public License, version 2, with the Classpath Exception............... 853
ISC License –........................................................................................................... 858
The GNU Lesser General Public License, version 2.1 (LGPL-2.1)..........................858
The MIT License (MIT)............................................................................................ 864
OpenSSL and SSLeay Licenses.............................................................................. 865
Diffusion | 11
List of Figures
Figure 1: Example topic tree........................................................................................... 48
Figure 2: Pub-sub model................................................................................................. 81
Figure 3: A client session registers a handler on part of the topic tree.........................88
Figure 4: A client session can send requests through a message path to a known
client session............................................................................................................... 89
Figure 5: A client can send requests through a message path to a set of client
sessions........................................................................................................................ 89
Figure 6: Message flow without conflation enabled...................................................... 91
Figure 7: Message flow with simple replace conflation enabled.................................. 91
Figure 8: Message flow with simple append conflation enabled.................................. 92
Figure 9: Message flow with merge and replace conflation enabled............................92
Figure 10: Fan-out............................................................................................................96
Figure 11: Missing topic notification propagation......................................................... 98
Figure 12: Information sharing using a datagrid......................................................... 100
Figure 13: Session replication....................................................................................... 101
Figure 14: Topic replication...........................................................................................104
Figure 15: Using a web server with Diffusion............................................................... 116
Figure 16: Deploying Diffusion inside a web application server................................. 117
Figure 17: A simple solution..........................................................................................121
Figure 18: Clients for different purposes...................................................................... 122
Diffusion | 12
Figure 19: Architecture using replication and fan-out.................................................123
Figure 20: Authentication process for clients...............................................................137
Figure 21: A composite authentication handler.......................................................... 139
Figure 22: Session state model..................................................................................... 244
Figure 23: Flow of requests and responses when connecting to Diffusion through
a proxy........................................................................................................................255
Figure 24: A stream........................................................................................................ 280
Figure 25: Flow from a subscribing client to the client that handles a missing topic
subscription............................................................................................................... 305
Figure 26: Flow from a subscribing client to the client that handles a missing topic
subscription............................................................................................................... 345
Figure 27: Data type hierarchy......................................................................................393
Figure 28: The message queue..................................................................................... 478
Figure 29: Example folder structure inside a DAR file..................................................497
Figure 30: Normal and throttled client queues............................................................609
Figure 31: Connecting to Diffusion JMX........................................................................613
Figure 32: Java VisualVM: Overview tab....................................................................... 615
Figure 33: JConsole New Connection dialog: Remote Process...................................616
Figure 34: JConsole New Connection dialog: Remote Process...................................617
Figure 35: JConsole New Connection dialog: Local Process....................................... 618
Figure 36: The server MBean stopController operation showing in JConsole............620
Figure 37: Reflecting MBeans as topics........................................................................ 630
Figure 38: Showing a composite attribute as a topic nest.......................................... 631
Figure 39: Topics reflecting an ArrayType MXBean attributes.................................... 632
Figure 40: Logging in the monitoring console............................................................. 638
Figure 41: The default console layout.......................................................................... 638
Figure 42: The table of publishers................................................................................ 639
Figure 43: Publisher statistics graphs...........................................................................640
Figure 44: The table of topics....................................................................................... 640
Diffusion | 13
Figure 45: Details of the topic publishing the CPU load of the host server.................641
Figure 46: The table of clients.......................................................................................641
Figure 47: The table of log entries................................................................................642
Figure 48: Security tables.............................................................................................. 643
Figure 49: Editing the Access Policy............................................................................. 644
Figure 50: Notification that the Diffusion server has stopped.................................... 644
Figure 51: The default Diffusion Details panel............................................................. 645
Figure 52: Editing the properties of the Diffusion Details panel................................. 646
Figure 53: Visualizing the CPU load on a server at a specific time.............................. 647
Figure 54: Editing and adding to the set of topics for this panel................................ 647
Figure 55: Welcome tab of the Splunk web UI.............................................................729
Figure 56: The Splunk Set source type dialog..............................................................730
Figure 57: The Data Preview panel............................................................................... 731
Figure 58: The Splunk search summary panel.............................................................731
Figure 59: Sticky-IP in F5 BIG-IP....................................................................................741
Figure 60: JMS message structure................................................................................ 746
Figure 61: Basic mapping from a JMS message to a Diffusion message.....................747
Figure 62: Basic mapping from a Diffusion message to a JMS message.....................747
Figure 63: Mapping from a JMS message to and from JSON in a Diffusion message.. 748
Figure 64: JMS adapter: Publishing from JMS to Diffusion......................................... 749
Figure 65: JMS adapter: Message flow from Diffusion to JMS.....................................750
Figure 66: JMS adapter: Message flow from JMS to Diffusion.....................................751
Figure 67: JMS adapter: Request-response message flow.......................................... 752
Figure 68: Requests to the Push Notification Bridge...................................................774
Figure 69: Notifications from the Push Notification Bridge........................................ 775
Diffusion | 14
List of Tables
Table 1: Supported platforms and transport protocols for the client libraries............ 35
Table 2: Capabilities provided by the Diffusion client libraries.................................... 37
Table 3: Feature packs.................................................................................................... 42
Table 4: Supported protocols by client..........................................................................43
Table 5: Supported browsers..........................................................................................44
Table 6: Support for WebSocket.....................................................................................45
Table 7: Support for CORS.............................................................................................. 46
Table 8: Maximum supported connections....................................................................46
Table 9: Restricted characters for paths used by publishers........................................ 49
Table 10: Types of topic selector....................................................................................50
Table 11: Descendant pattern qualifiers........................................................................ 51
Table 12: Properties available for topics of each type.................................................. 57
Table 13: Data types for schema fields.......................................................................... 76
Table 14: Data types for metadata fields....................................................................... 78
Table 15: Notification types............................................................................................ 86
Table 16: Supported protocols by client......................................................................109
Table 17: List of path-scoped permissions...................................................................129
Table 18: List of global permissions............................................................................. 133
Table 19: Client operations that require authentication.............................................138
Diffusion | 15
Table 20: Types of authentication handler.................................................................. 140
Table 21: Authorization handler methods................................................................... 143
Table 22: Capabilities provided by the Diffusion client libraries................................ 149
Table 23: Supported platforms and transport protocols for the client libraries........ 155
Table 24: Supported platforms and transport protocols for the client libraries........ 163
Table 25: Supported platforms and transport protocols for the client libraries........ 179
Table 26: Supported platforms and transport protocols for the client libraries........ 192
Table 27: Supported platforms and transport protocols for the client libraries........ 202
Table 28: Supported platforms and transport protocols for the client libraries........ 217
Table 29: Session filter search clause operators......................................................... 277
Table 30: Session filter boolean operators.................................................................. 278
Table 31: Time series event metadata......................................................................... 375
Table 32: Log levels....................................................................................................... 460
Table 33: Log levels....................................................................................................... 460
Table 34: Log levels....................................................................................................... 461
Table 35: Log levels....................................................................................................... 462
Table 36: Start publisher............................................................................................... 466
Table 37: Stop publisher............................................................................................... 466
Table 38: Notification methods.................................................................................... 469
Table 39: General publisher utilities.............................................................................471
Table 40: WhoIs.............................................................................................................. 480
Table 41: WhoIs service................................................................................................. 481
Table 42: Client listener notifications...........................................................................482
Table 43: Artifacts.......................................................................................................... 496
Table 44: Installed files..................................................................................................515
Table 45: Tools and utilities..........................................................................................516
Table 46: XML Value types.............................................................................................519
Diffusion | 16
Table 47: Conflation policy elements........................................................................... 525
Table 48: Conflation policy modes............................................................................... 526
Table 49: Action depending upon merge result...........................................................527
Table 50: Connectors properties...................................................................................532
Table 51: Values that can be configured for a thread pool......................................... 603
Table 52: Events that a thread pool notification handler can act on......................... 604
Table 53: Notifications as topics...................................................................................630
Table 54: Log levels....................................................................................................... 650
Table 55: Fields included in the logs............................................................................ 651
Table 56: Examples of routing strategies..................................................................... 740
Table 57: Demos provided with the Diffusion server...................................................792
Table 58: Targets............................................................................................................793
Table 59: Properties for targets start, stop and status................................................794
Table 60: Additional properties for targets deploy and undeploy.............................. 794
Table 61: API interoperation......................................................................................... 797
Table 62: API features removed in version 6.0.............................................................799
Table 63: API features deprecated in version 6.0........................................................ 801
Table 64: Typographic conventions used in this manual............................................806
Diffusion | 17
Part
I
Welcome
Welcome to the Push Technology User Manual for Diffusion™
The manual is regularly updated, but if you require further help, see the articles and forums in our Support
Center: http://support.pushtechnology.com.
New to Diffusion?
•
•
Learn what Diffusion is and what it can do for your organization: Introduction
Get started with Diffusion: Quick Start Guide on page 24
Ready to start building your Diffusion solution?
•
•
•
Decide what your Diffusion solution will look like: Design Guide on page 32
Develop your Diffusion clients: Developer Guide on page 146
Set up and manage your Diffusion server and solution: Administrator Guide on page 503
About to upgrade from an earlier version of Diffusion?
•
•
See what's new in the latest version of Diffusion: What's new in Diffusion 6.0? on page 21
Check how changes might affect your existing Diffusion solution: Upgrading
In this section:
•
•
Introducing Diffusion
What's new in Diffusion 6.0?
Diffusion | 18
Introducing Diffusion
Diffusion from Push Technology provides realtime messaging, optimized for streaming data over the
internet.
Flexibility, responsiveness and interactivity are some of the fundamental requirements for today’s
application architecture. But challenges created by unreliable and congested networks stand in the
way – particularly for mobile and IoT.
The answer? A realtime integration model, with better data efficiency to address these challenges,
while reducing data costs at the same time.
With Push Technology’s realtime messaging products – and our unique, data-efficient approach to
streaming data – developers are armed with an integration and data delivery platform optimized for
today’s internet-connected world.
Why realtime messaging?
Many of today’s apps are point-in-time representations of data, refreshing information only when
a user explicitly asks for an update, or continuously polling the backend. However, reactive apps
are infinitely more engaging, and interactive – pushing updates in real time as new data becomes
available. This allows you to scale your applications, without adding unnecessary load to backend systems and creates a layer of decoupling that protects applications from data model changes.
Realtime messaging integrates seamlessly with modern development frameworks for the web
(Angular, Meteor, React, etc) delivering data updates in real time to end users. Stop focusing on pointin-time data, and instead, focus on realtime, event-driven, responsive, and engaging applications.
Benefits of Diffusion
Diffusion offers the most intelligent and data-efficient realtime messaging products available today.
Designed to be easily integrated to new and existing application architecture, our technology gives you
a flexible, reactive, and efficient data layer for all your business needs.
Publish-subscribe integration
The Diffusion server provides a publish-subscribe integration model. The Diffusion server stores data
in a tree of topics, where each topic has a value. This value can be fetched in an ad-hoc fashion, but,
more commonly, a client session subscribes to the topics that are of interest to it. The Diffusion server
pushes each update to the topic to the client session as a stream of values.
Dynamic data model
Client sessions subscribe to data topics using wildcard selectors. When new topics are added, sessions
with matching selectors are automatically subscribed. This avoids the need for separate topic lifecycle processes, and data objects can be frequently created and deleted without impacting existing
applications.
Value-oriented programming
A value-oriented programming model is a fundamental feature of a reactive data model. Applications
are built against an API that provides streams of values, rather than individual messages that need
further decoding. Client SDKs provide a common programming model, making best use of the
features particular to their implementation language. This frees developers to focus on application
functionality, rather than data integration.
Diffusion | 19
Inverted data grid
Like a data grid, Diffusion stores values in memory. Traditionally, data grids are optimized for query
and primarily support a polling paradigm. In Diffusion, the data grid is optimized for a realtime, eventdriven communication. Often deployed as a specialized cache, data grids offload processing from a
backend system. Diffusion offers the same benefit but is designed to deliver realtime streams to a high
number of subscribed clients.
Non-blocking I/O
Network communication is performed by an event-driven kernel that uses non-blocking I/O to interact
efficiently with the host networking. Client sessions are partitioned and each partition is assigned to
a dedicated thread that manages all subscription matching and outbound communication for that
session. This lock-free design avoids contention between these sessions and allows the platform to
scale linearly across CPU resources, achieving very high message rates.
Protocol optimization
Messages are serialized into a compact binary representation called CBOR. This reduces bandwidth
consumption for every message – up to 30% compared with ASCII. A small binary header is used to
frame each message, but this is typically only two bytes when using the default WebSocket-based
protocol.
Delta streaming
Topics provide stateful streams of data to each session. Once a client session is subscribed to a topic
and receives the current state, all subsequent updates are sent as a delta (the difference from the
previous value). This happens transparently to the application, with the client SDK reconstructing the
full data payload with the delta applied – so neither application or backend require changes.
Message queue optimization
Messages that cannot be immediately delivered to a session are queued. If the network connections
fail, bandwidth is limited, or the client is simply slow, messages can back up on the queue. Diffusion
can conflate the queue to remove messages that are stale or no longer relevant, or to combine
multiple related messages into a single consolidated message.
Bandwidth management
The rate of messages delivered to a session can be artificially constrained by the platform. This can be
used to prioritize one client session over another, to place limits on bandwidth utilization, to improve
batching, or to encourage conflation. These measures might be desirable in some circumstances, but
obviously lead to increased latency for the throttled sessions.
Reliable reconnection
Client connectivity is continuously monitored by the Diffusion server using a variety of bandwidth
efficient mechanisms. If a client session is disconnected due to network failure, both the client and
server queue messages for a period of time until that session can be re-established. Once the session
is reconnected, the client and server reconcile the messages successfully received to ensure none are
lost.
Assured delivery
During an active session, messages between the client and the Diffusion server are delivered without
loss regardless of the protocol in use. Delivery is considered assured rather than guaranteed to
account for client/server or network failures that terminate an active session.
Diffusion | 20
Extensive SDK support
With simple client SDKs (Software Development Kits) available for a range of languages and
environments, there are no new protocols or technologies to learn. The SDK includes a runtime library,
API, documentation, and examples. The supported SDKs include JavaScript® (browser and Node.js),
Apple® (iOS®, OS X®/macOS® and tvOS™), Android™, Java™, .NET, and C
Protocol fallback
By default, clients use the WebSocket protocol to establish bi-directional communication with the
Diffusion server. In some cases the WebSocket protocol is not available. For example, WebSocket
connections can be blocked by a firewall or load balancer, or disallowed by the network provider. In
this case, clients can automatically fallback (cascade) to long polling over HTTP.
Session administration
A session with appropriate security permissions can act as a control client and instruct the Diffusion
server to add and remove topics, send data updates to topics, and receive notifications about events
– such as when the number of subscribers for a topic falls to zero. A control client session can also
authenticate connection requests, receive notifications about new or modified sessions, modify or
close sessions, and send and receive messages to and from individual sessions. Every session has
session properties: a set of key-value pairs that can be modified or used to filter groups of related
sessions.
Security framework
The Diffusion server has a capable security framework. Authentication can use the built-in security
database or be federated to control clients that can integrate with third-party database. Authorization
is declarative and based on a configurable hierarchy of roles. Each session is assigned to one or more
roles. Roles are granted permissions such as the ability to access, modify, subscribe to, or update
specific topics; or the ability to control other sessions. Secure communication over TLS is supported by
the Diffusion server and client libraries for all protocols.
What's new in Diffusion 6.0?
The latest version of Diffusion contains new features, performance enhancements and bug fixes.
Note: Because Diffusion 6.0 is a major version update, it contains breaking changes to the
Diffusion APIs. You may need to update code that relies on Diffusion to work with 6.0. Do not
upgrade to 6.0 from a 5.x version without testing.
A complete list of the latest updates and known issues can be found in the Release Notes available at
http://docs.pushtechnology.com/docs/6.0.2/ReleaseNotice.html.
New topic types replace single value and record
Diffusion 6.0 introduces new explicitly-typed topic types: int64, string and double. These replace the
old single value topic type, which is now deprecated.
A new “recordV2” topic type replaces the old record topic type, which is now deprecated.
The new topic types support topic replication and topic persistence. The deprecated topic types
cannot be used with topic replication and topic persistence.
For more information, see Int64 topics on page 65, String topics on page 64, Double topics on
page 66 and RecordV2 topics on page 72.
Diffusion | 21
Time series topics
A new topic type that holds an ordered series of events is available for all APIs except .NET and C. Time
series topics are useful for collaborative applications, for example chat rooms. Multiple users can
concurrently update a time series topic.
These events have a value and associated metadata including a sequence number, a timestamp, and
author. New subscribers are sent a configurable window of the latest events, followed by new events
as they happen. A separate query API allows events to be retrieved from the log maintained by the
topic.
For more information, see Time series topics on page 67.
New DONT_RETAIN_VALUE property replaces stateless topics
In previous Diffusion versions, the stateless topic type enabled you to publish a topic with no state
stored on the server or clients. Stateless topics sent only untyped byte data, meaning that clients had
to implement all the logic for interpreting and validating data.
The new DONT_RETAIN_VALUE property enables you to use an explicitly-typed topic for use cases that
previously required a stateless topic. If the property is set to true, the latest value of the topic is not
stored on the server.
Using DONT_RETAIN_VALUE reduces the topic memory footprint.
The new property is supported by binary, JSON, string, int64, double, recordv2 and time series topics.
The old stateless topic type is deprecated.
For more information, see Topic properties.
Topic persistence
The new persistence feature preserves the state of topics (and the topic tree) when a server is closed
and then restarted. Topic data is stored to the local file system and automatically reloaded on restart.
This feature is optional and is disabled by default.
For more information, see Topic persistence on page 107.
Improved topic replication
The topic replication feature (to synchronize topics between multiple servers) has been improved to
offer these benefits:
•
•
•
Support for non-exclusive updaters
Strong replication consistency guarantees
Better performance
Topic replication no longer supports the deprecated single value and record topic types. It does not
support topics created by a publisher.
For more information, see Topic replication on page 104.
Typed request-response messaging
You can now send request messages to a client, a set of clients, or a message path. The recipient of the
message can respond directly to the request.
Requests and responses each have an associated data type (JSON, binary, string, 64-bit integer,
double, or recordV2). Diffusion takes care of all data conversion, allowing applications to pass
instances of the data type directly to and from the API.
For more information, see Using request-response messaging on page 392.
Diffusion | 22
Java 8 features in the Java API.
The Java API has been updated to take advantage of the CompletableFuture facility and Lambda
expressions introduced in Java 8.
CompletableFutures provide a better interface to the result of an asynchronous action than callbacks.
CompletableFutures can be easily chained together, propagate failures naturally, and integrate well
with Java 8 Lambda expressions. The result is more expressive, shorter application code.
Most methods in the Java API that provide a single result to a callback now have a CompletableFuture
alternative.
The minimum required JDK version is now 1.8.0_131-b11. JDK 9 is not supported.
For more information, see Java on page 191.
Task Parallel Library support in the .NET API
The .NET API has been updated to take advantage of the Task Parallel Library and its Task facilities.
Tasks provide a better interface to the result of an asynchronous action than callbacks. Tasks can be
easily chained together and propagate failures naturally.
IPings, ITopics and ITopicControl methods that provide a single result to a callback now have a Task
alternative.
Topic notifications
Clients using the JavaScript, Java and Android APIs can now register to receive a stream of topic
creation and removal change events for part of the topic tree, without needing to subscribe to the
values.
See Topic notifications on page 86 for details.
Apple support for one-way messaging to sessions and filters
You can now send one-way messages directly to a client or a set of clients using the Apple API.
For more information, see Using request-response messaging on page 392.
Related concepts
Upgrading Guide on page 796
If you are planning to move from an earlier version of Diffusion to version 6.0, review the following
information about changes between versions.
Diffusion | 23
Part
II
Quick Start Guide
Push Technology’s Diffusion is the ingredient software required to resolve the limitations and challenges of
data distribution by speeding up the delivery of content, enabling rapid scale and optimizing data sent and
received.
This guide gives you an overview of installing the Diffusion server and starting to develop your own clients.
In this section:
•
•
•
•
•
•
•
•
Get Diffusion
Install Diffusion
Start the Diffusion server
Default configuration
The Diffusion monitoring console
Develop a publishing client
Develop a subscribing client
Resources
Diffusion | 24
Get Diffusion
To install Diffusion you require the product jar, Diffusion6.0.2.jar, and the standalone installer
jar, install.jar.
You can get both of these files from the Push Technology Download site: http://
download.pushtechnology.com/releases/6.0
Install Diffusion
Java 8 is required to install Diffusion.
1. Double-click on the install.jar file to launch the graphical installer.
The installer locates the Diffusion JAR file if that file is in the same directory as the installer.
2. If the installer cannot locate the Diffusion JAR file, select File > Load install file and navigate to the
Diffusion JAR file and click Open.
3. If you have a production license, select File > Load license file and navigate to the license file and
click Open.
If you do not have a production license, you can use the developer license that is included in the
Diffusion server
4. Follow the steps in the graphical installer to install the Diffusion server.
For more information about system requirements and installing the Diffusion server, see Installing the
Diffusion server on page 504.
Start the Diffusion server
The Diffusion start scripts are located in the bin directory of your Diffusion installation.
On Windows™: Use diffusion.bat to start the Diffusion server.
On Linux™ or OS X/macOS: Use diffusion.sh to start the Diffusion server.
Default configuration
The Diffusion server is configured by the files in the etc directory.
License
Diffusion includes a development license that allows up to 5 concurrent connections to the Diffusion
server.
You can upgrade your Diffusion server to use a production license by copying the production license
file into the etc directory of your Diffusion installation.
Security
Diffusion uses role-based security for authorization. A client session must have a role with sufficient
permissions to perform specific actions on the Diffusion server.
Diffusion | 25
The default security configuration is located in the etc/SystemAuthentication.store and
etc/Security.store files of your Diffusion installation.
This configuration includes principals and passwords for development use:
Principal
Password
client
password
control
password
admin
password
operator
password
We recommend that you change these passwords as soon as possible by editing the etc/
SystemAuthentication.store file.
Configuration
The Diffusion server is configured by the XML files in the etc directory of your Diffusion server.
The default configuration makes port 8080 available to connecting clients.
Related concepts
Security on page 124
Diffusion secures your data by requiring client sessions to authenticate and using role-based
authorization to define the actions that a client can perform.
The Diffusion monitoring console
Diffusion includes a monitoring console that shows realtime information about the Diffusion server
and its clients, publishers, and topics.
In a browser, go to http://localhost:8080. If your browser is not on the same system as your
Diffusion server, replace localhost with the hostname or IP address of the Diffusion server.
From this landing page you can go to either the demos page, where you can access the Diffusion
demos if you opted to deploy them during the install process, or to the Diffusion monitoring console.
The Diffusion monitoring console is secured by username and password. Use the default user 'admin'
and password 'password' to log in to the console.
For more information about the Diffusion monitoring console, see Diffusion monitoring console on
page 637
Develop a publishing client
Use the Diffusion JavaScript API to develop a Node.js client that creates a topic and updates it.
This example creates a double topic at foo/counter and then updates it by increasing its value once
per second.
1. Install Node.js and NPM on your development system.
For more information, see https://nodejs.org/en/
Diffusion | 26
2. Install the Diffusion JavaScript library on your development system.
npm install diffusion
3. Create a JavaScript file, publisher.js, to contain your client.
4. Include the Diffusion JavaScript library at the top of your publisher.js file.
var diffusion = require('diffusion');
5. Create a connection from the page to the Diffusion server.
diffusion.connect({
host : 'hostname',
principal : 'control',
credentials : 'password'
}).then(function(session) {
console.log('Connected!');
Replace 'hostname' and 'port' with the details of your Diffusion server. If running locally, use
'localhost' and '8080'.
6. Create a double topic called foo/counter:
session.topics.add('topic/json', diffusion.topics.TopicType.JSON);
The add() method takes the name of the topic and the topic type.
7. Start incrementing the value of the topic once per second:
var count = 0;
setInterval(function() {
session.topics.update('foo/counter', count++);
}, 1000);
The update() method takes the name of the topic and a JSON object.
8. Run the Node.js client:
node publisher.js
Full example
The following example code shows a Node.js JavaScript client that connects to the Diffusion server,
creates a JSON topic, and publishes an update to it.
/
*******************************************************************************
* Copyright (C) 2014, 2015 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
Diffusion | 27
* See the License for the specific language governing permissions
and
* limitations under the License.
*******************************************************************************
var diffusion = require('diffusion');
diffusion.connect({
host : 'hostname',
principal : 'control',
credentials : 'password'
}).then(function(session) {
console.log('Connected!');
// Create a topic that contains Double values
session.topics.add('foo/counter',
diffusion.topics.TopicType.DOUBLE);
// Start updating the topic every second
var count = 0;
setInterval(function() {
session.topics.update('foo/counter', count++);
}, 1000);
});
To run the example:
1. Install Node.js.
For more information, see https://nodejs.org/en/
2. Install the Diffusion JavaScript library on your development system.
npm install diffusion
3. Copy the provided code into a file called publisher.js
4. Update the connect method to include the URL of your Diffusion server.
5. If you have changed the default security configuration, change the principal and credentials to
those of a user that has the modify_topic and update_topic permissions.
6. Use Node.js to run your publishing client from the command line.
node publisher.js
The JavaScript client opens a connection to the Diffusion server, creates the topic topic/json, and
updates it each second with a timestamp.
You can use the example from Develop a subscribing client on page 29 to subscribe to the topic.
Publish using other Diffusion APIs:
•
•
•
•
•
Java
.NET
Apple
Android
C
Diffusion | 28
Develop a subscribing client
Use the Diffusion JavaScript API to develop a client that subscribes to a topic and receives updates
published through it.
This example subscribes to a double topic at foo/counter and prints updates to the browser window.
1. Ensure that the diffusion.js file, located in the clients/js directory of your Diffusion
installation, is available on your development system.
2. Create a template HTML page which displays the information.
For example, create the following index.html in your project's HTML directory.
<html>
<head>
<title>JavaScript example</title>
</head>
<body>
<p>The value of foo/counter is: <span id="display"></span></p>
</html>
3. Include the Diffusion JavaScript library in the <head> section of your index.html file.
<head>
<title>JavaScript example</title>
<script type="text/javascript" src="path_to_library/
diffusion.js"></script>
</head>
Replace path_to_library with the path to the diffusion.js file in the clients/js directory
where you installed Diffusion.
4. Create a connection from the page to the Diffusion server. Add a script element to the body
element.
<body>
<script>
diffusion.connect({
// Edit these lines to include the host and port of your
Diffusion server
host: 'hostname',
port : 'port',
// To connect anonymously you can leave out the following
parameters
principal : 'client',
credentials : 'password'
})
</script>
</body>
Replace 'hostname' and 'port' with the details of your Diffusion server. If running locally, use
'localhost' and '8080'.
5. Create a stream that matches the topic path, then subscribe to the topic.
Add the following function after the diffusion.connect() call:
}).then(function(session) {
session
.stream('foo/counter')
Diffusion | 29
.asType(diffusion.datatypes.double())
.on('value', function(topic, specification, newValue, oldValue)
{
console.log("Update for " + topic, newValue);
document.getElementById('display').innerHTML = newValue;
});
session.subscribe('foo/counter');
});
Full example
The following example code shows a browser JavaScript client that connects to the Diffusion server
and subscribes to a JSON topic.
<html>
<head>
<title>JavaScript example</title>
<script type="text/javascript" src="path_to_library/
diffusion.js"/>></script>
</head>
<body>
<p>The value of foo/counter is: <span id="display"></span></p>
<script type="text/javascript">
diffusion.connect({
// Edit these lines to include the host and port of your
Diffusion server
host : 'hostname',
port : 'port',
// To connect anonymously you can leave out the following
parameters
principal : 'user',
credentials : 'password'
}).then(function(session) {
// Add a stream that matches the topic path and wire it
to log the received values
session
.stream('foo/counter')
.asType(diffusion.datatypes.double())
.on('value', function(topic, specification, newValue,
oldValue) {
console.log("Update for " + topic, newValue);
document.getElementById('display').innerHTML =
newValue;
});
// Subscribe to the topic
session.subscribe('foo/counter');
});
</script>
</body>
</html>
To run the example:
Diffusion | 30
1. Copy the provided code into a file called index.html
2. Update the connect method to include the URL of your Diffusion server.
3. If you have changed the default security configuration, change the principal (username) and
credentials to those of a user that has the read_topic and select_topic permissions.
4. Open index.html in a browser.
The JavaScript client opens a connection to the Diffusion server, subscribes to the topic foo/counter,
and prints the updates it receives to the browser window.
You can use the example from Develop a publishing client on page 26 to create a suitable topic.
Subscribe using other Diffusion APIs:
•
•
•
•
•
Apple
Android
Java
.NET
C
Resources
•
•
•
•
•
Download site: http://download.pushtechnology.com/releases/6.0
API documentation: http://docs.pushtechnology.com/6.0
Support center: http://support.pushtechnology.com
Stack Overflow: http://stackoverflow.com/questions/tagged/push-diffusion
Github: https://github.com/pushtechnology/
Diffusion | 31
Part
III
Design Guide
This guide describes the factors to consider when designing your Diffusion solution.
In this section:
•
•
•
•
Support
Designing your data model
Designing your solution
Security
Diffusion | 32
Support
When designing your solution, refer to the support information to ensure compatibility between
the Diffusion server and your hardware, software, and operating systems. This section also provides
information about the capabilities of the Diffusion clients and the platforms the clients are supported
on.
You can also refer to the Upgrading Guide to review the compatibility between different versions of
Diffusion. For more information, see Interoperability.
System requirements for the Diffusion server
Review this information before installing the Diffusion server.
The Diffusion server is certified on the system specifications listed here. In addition, the Diffusion
server is supported on a further range of systems.
Certification
Push Technology classes a system as certified if the Diffusion server is fully
functionally tested on that system.
We recommend that you use certified hardware, virtual machines, operating systems,
and other software when setting up your Diffusion servers.
Support
In addition, Push Technology supports other systems that have not been certified.
Other hardware and virtualized systems are supported, but the performance of these
systems can vary.
More recent versions of software and operating systems than those we certify are
supported.
However, Push Technology can agree to support Diffusion on other systems. For more
information, contact Push Technology.
Physical system
The Diffusion server is certified on the following physical system specification:
•
•
•
•
Intel™ Xeon™ E-Series Processors
8 Gb RAM
8 CPUs
10 Gigabit NIC
Network, CPU, and RAM (in decreasing order of importance) are the components that have the biggest
impact on performance. High performance file system and disk are required. Intel hardware is used
because of its ubiquity in the marketplace and proven reliability.
Virtualized system
The Diffusion server is certified on the following virtualized system specification:
Host
•
•
Intel Xeon E-Series Processors
32 Gb RAM
Diffusion | 33
•
VMware vSphere® 5.5
Virtual machine
•
•
8 VCPUs
8 Gb RAM
When running on a virtualized system, over-committing VCPUs (assigning too many VCPUs compared
to the processors available on the host) can cause increased latency and unpredictable performance.
Consult the VMWare Performance Best Practices documentation for details.
Operating system
Diffusion is certified on the following operating systems:
•
•
Red Hat7.2+
Windows Server 2012 R2 and 2016
We recommend you install your Diffusion server on a Linux-based operating system with enterpriselevel support available, such as Red Hat Enterprise Linux.
Operating system configuration
If you install your Diffusion server on a Linux-based operating system and do SSL offloading of secure
client connections at the Diffusion server, you must disable transparent huge pages.
If you install your Diffusion server on a Linux-based operating system but do not do SSL offloading
of secure client connections at the Diffusion server, disabling transparent huge pages is still
recommended.
Having transparent huge pages enabled on the system your Diffusion server runs on can cause
extremely long pauses for garbage collection. For more information, see https://access.redhat.com/
solutions/46111.
Java
The Diffusion server is certified on Oracle Java Development Kit 8 (minimum update 1.8.0_131-b11).
Only the Oracle JDK is certified.
Ensure that you use the Oracle JDK and not the JRE.
JVM configuration
If you do SSL offloading of secure client connections at the Diffusion server, you must ensure that you
constrain the maximum heap size and the maximum direct memory size so that together these to
values do not use more than 80% of your system's RAM.
Networking
Push Technology recommends the following network configurations:
•
•
•
10 Gigabit network
Load balancers with SSL offloading
In virtualized environments, enable SR-IOV.
For more information about how to enable SR-IOV, see the documentation provided by your virtual
server provider. SR-IOV might be packaged using a vendor-specific name.
Diffusion | 34
Client requirements
For information about the supported client platforms, see Platform support for the Diffusion API
libraries on page 35.
Related concepts
The Diffusion license on page 512
Diffusion includes a development license that enables you to use make up to 5 concurrent connections
to the Diffusion server.
Installed files on page 515
After installing Diffusion the following directory structure exists:
Related tasks
Installing the Diffusion server using the graphical installer on page 506
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
using the graphical installer.
Installing the Diffusion server using the headless installer on page 508
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
from the command line.
Installing the Diffusion server using Red Hat Package Manager on page 509
Diffusion is available as an RPM file from the Push Technology website.
Installing the Diffusion server using Docker on page 510
Diffusion is available as a Docker® image from Docker Hub.
Verifying the Diffusion installation on page 517
Start your Diffusion server, review the logs, and connect to the console to verify that your installation is
correct.
Platform support for the Diffusion API libraries
Review this information when designing your clients to determine what platforms and transports the
Diffusion client libraries are supported on.
Supported platforms and protocols for the client libraries
Table 1: Supported platforms and transport protocols for the client libraries
Platform
Minimum supported versions
Supported transport protocols
JavaScript
es6
WebSocket
(TypeScript 1.8)
HTTP (Polling XHR)
Development
environment
WebSocket
Apple for iOS
Xcode 8
(iOS 10.0
SDK)
Runtime support
Diffusion | 35
Platform
Minimum supported versions
Supported transport protocols
Deployment
target: iOS
8.1 or later
Device
architectures:
armv7,
armv7s,
arm64
Simulator
architectures:
i386,
x86_64
Apple for OS X/macOS
Development
environment
WebSocket
Xcode
8 (OS
X/macOS
10.12 SDK)
Runtime support
Deployment
target: OS
X/macOS
10.11 or
later
Device
architectures:
x86_64
Apple for tvOS
Development
environment
WebSocket
Xcode 8
(tvOS 10.0
SDK)
Runtime support
Deployment
target:
tvOS 9.0 or
later
Device
architectures:
arm64
Simulator
architectures:
x86_64
Android
API 19 / v4.4 / KitKat
WebSocket
Diffusion | 36
Platform
Minimum supported versions
Note: Push Technology
provides only besteffort support for
Jelly Bean (API 16-18,
v4.1-4.3).
Java
Supported transport protocols
HTTP (polling)
Oracle Java Development Kit
8 (minimum update 1.8.0_131b11). JDK 9 not supported.
WebSocket
HTTP (Polling)
Note: We recommend
that you run your
clients on the JDK
rather than the JRE.
.NET
4.6.1
WebSocket
C for Linux
Red Hat and CentOS™, version
7.2 and later
WebSocket
Ensure that you use a C99capable compiler.
C for Windows
Visual C Compiler 2013 or later,
Windows 7 or later
WebSocket
C for OS X/macOS
For building using GCC, use
Xcode 8.0 or later
WebSocket
Note: Protocols are supported for both secure and standard connections.
Feature support in the Diffusion API
Review this information when designing your clients to determine which APIs provide the functionality
you require.
Features are sets of capabilities provided by the Diffusion API. Some features are not supported or not
fully supported in some APIs.
The Diffusion libraries also provide capabilities that are not exposed through their APIs. Some of these
capabilities can be configured.
Table 2: Capabilities provided by the Diffusion client libraries
Capability
JavaScript
Apple
Android
Java
.NET
C
Connecting
Connect to the
Diffusion server
Cascade connection
through multiple
transports
Diffusion | 37
Capability
JavaScript
Apple
Android
Java
.NET
C
Connect
asynchronously
Connect
synchronously
Connect using a
URL-style string as a
parameter
Connect using
individual
parameters
Connect securely
Configure SSL
context or behavior
Connect through an
HTTP proxy
Connect through a
load balancer
Pass a request path
to a load balancer
Reconnecting
Reconnect to the
Diffusion server
Configure a
reconnection
timeout
Define a custom
reconnection
strategy
Resynchronize
message streams on
reconnect
Abort reconnect if
resynchronization
fails
Maintain a recovery
buffer of messages
on the client to
resend to the
Diffusion server on
reconnect
Configure the clientside recovery buffer
Diffusion | 38
Capability
JavaScript
Apple
Android
Java
.NET
C
Detect
disconnections by
monitoring activity
Detect
disconnections by
using TCP state
Ping the Diffusion
server
Change the principal
used by the
connected client
session
Receiving data from topics
Subscribe to a topic
or set of topics
Receive data as a
value stream
Receive data as
content
Fetch the state of a
topic
Managing topics
Create a topic
Create a slave topic
Create/update/
query time series
topics
Create a topic from
an initial value
Create a topic from
a topic specification
Create a topic from
topic details
Create a topic with
metadata
Listen for
topic events
(including topic
has subscribers
and topic has zero
subscribers)
Diffusion | 39
Capability
JavaScript
Apple
Android
Java
.NET
C
Get topic
notifications
Delete a topic
Delete a branch of
the topic tree
Mark a branch of
the topic tree for
deletion when this
client session is
closed
Updating topics
Update a topic
Perform exclusive
updates
Perform nonexclusive updates
Managing subscriptions
Subscribe or
unsubscribe another
client to a topic
Subscribe or
unsubscribe another
client to a topic
based on session
properties
Handling
subscriptions to
routing topics
Handling
subscriptions to
missing topics
Request-response messaging
Send a request to a
path
Send a request to a
client session
Send a request to a
set of client sessions
based on session
properties
Respond to requests
sent to a session
Diffusion | 40
Capability
JavaScript
Apple
Android
Java
.NET
C
Respond to requests
sent to a path
One-way messaging
Send a one-way
message to a path
Send a one-way
message to a client
session
Send a one-way
message to a set
of client sessions
based on session
properties
Receive one-way
messages
Handle one-way
messages sent to a
path
Managing security
Authenticate client
sessions and assign
roles to client
sessions
Configure how the
Diffusion server
authenticates
client sessions and
assign roles to client
sessions
Configure the
roles assigned to
anonymous sessions
and named sessions
Configure the
permissions
associated with
roles assigned to
client sessions
Managing other clients
Receive
notifications about
client session events
including session
properties
Diffusion | 41
Capability
JavaScript
Apple
Android
Java
.NET
C
Get the properties
of a specific client
session
Update user-defined
session properties
of a client session or
set of sessions
Receive
notifications about
client queue events
Conflate and
throttle clients
Close a client
session
Push notifications (The Push Notification Bridge must be enabled)
Receive push
notifications
Request that push
notifications be sent
from a topic to a
client
Publish an update
to a topic that sends
push notifications
Other capabilities
Flow control
Feature packs
Some advanced Diffusion features are available in add-on packs.
Some advanced features are only available for production use if your license includes the correct addon packs.
The pack you have depends on your license. If you need to upgrade your license, contact
consulting@pushtechnology.com .
Table 3: Feature packs
Feature
Diffusion
Diffusion
with Scale &
Availability
Pack
Diffusion
with Scale &
Availability
and AutoScale Packs
All features not mentioned below
Diffusion | 42
Feature
Diffusion
Diffusion
with Scale &
Availability
Pack
Diffusion
with Scale &
Availability
and AutoScale Packs
Persistence
Fan-out
Replication
Kubernetes support
Prometheus support
Auto-Scale Pack requires Scale & Availability Pack and cannot be bought separately.
Note: The trial developer license you get when you download Diffusion gives you access to all
features, but is limited to five concurrent connections.
Specific license restrictions
As well as the differences between packs, each license can also have restrictions such as an expiry
date, or a maximum number of concurrent client connections. See License restrictions on page 513
for details.
Protocol support
Each client supports varying transports. A table of the supported transports for each client is
presented here.
All protocols supported by Diffusion can be used for both secure (using TLS) and standard
connections. For more information, see SSL and TLS support on page 44.
The following table lists the protocols supported for each client:
Table 4: Supported protocols by client
Client
WebSocket
HTTP
Polling
JavaScript API
Apple API
Android API
Java API
.NET API
Diffusion | 43
Client
WebSocket
HTTP
Polling
C API
The JavaScript client is fully supported only on certain browsers. Best effort support is provided for
other browsers but the software/hardware combination might not be reproducible, particularly for
mobile browsers. For more information about supported browsers, see Browser support on page
44.
SSL and TLS support
Diffusion supports only those SSL versions and cipher suites with no known vulnerabilities.
The following SSL and TLS versions are supported by default:
•
•
•
•
SSLv2Hello
TLSv1
TLSv1.1
TLSv1.2
The following cipher suites are supported by default:
•
•
•
•
•
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
For more information, see Network security on page 586.
Browser support
Some of the client libraries are intended to be run within browser environments. Diffusion clients can
use most commercial browsers and their variants. However, some Diffusion API protocols might not be
available.
Diffusion supports the latest release of the following browser versions based on the original Diffusion
release date.
Table 5: Supported browsers
Browser
Version
Google Chrome™
Mozilla Firefox
®
•
•
53
®
49
®
Microsoft Internet Explorer
11
Apple Safari®
8
Push Technology runs full regression tests on the browsers and versions documented above for
every patch release.
Push Technology carries out basic validation testing on the latest versions of these browsers but
full support is available only at the next minor release.
Diffusion | 44
•
•
Support for older versions of browsers is provided on a best-effort basis, unless specified
otherwise.
Support for other browsers is provided on a best-effort basis.
Mobile browsers
We do not test our JavaScript client libraries with mobile browsers or within mobile applications
that wrap a browser application in native code. If you are developing a Diffusion client for a mobile
platform, such as iOS or Android, we recommend that you use the provided client libraries for these
platforms to develop a native application.
Diffusion JavaScript clients running within a native wrapper or in a mobile browser are supported on a
best effort basis and we might not be able to provide support for older versions of the mobile platform.
Browser limitations
Some browsers cannot use all the Diffusion protocols or features. If you experience problems when
developing with protocols or client libraries that use the browser, check whether the browser supports
this function.
Browser environments are not uniform and some browsers might have functional limitations. It is
important to be aware of these limitations when developing for compliance with target browsers.
WebSocket limitations
WebSocket is an Internet Engineering Task Force (IETF) protocol used by Diffusion to provide a fullduplex communication channel over a single TCP connection. It is not supported by all browser
versions.
Table 6: Support for WebSocket
Version
WebSocket support?
Internet Explorer 9.0 and NO
earlier
Internet Explorer 10.0
and later
YES (see note)
Firefox
YES
Chrome
YES
Safari
YES
®
Opera
YES
iOS
YES
Android
YES
Note: Internet Explorer 11 contains a bug that causes WebSocket connections to be dropped
after 30 seconds of inactivity. To work around this problem set the system ping frequency to 25
seconds or less. You can set the system ping frequency in the Server.xml configuration file.
For more information, see
Diffusion | 45
Cross-origin resource sharing limitations
CORS allows resources to be accessed by a web page from a different domain. Some browsers do not
support this capability.
Table 7: Support for CORS
Version
WebSocket support?
Internet Explorer 9.0 and NO
earlier
Internet Explorer 10.0
and later
YES
Firefox
YES
Chrome
YES
Safari
YES
Opera
YES
iOS
YES
Android
YES
Browser connection limitations
Browsers limit the number of HTTP connections with the same domain name. This restriction is
defined in the HTTP specification (RFC2616). Most modern browsers allow six connections per domain.
Most older browsers allow only two connections per domain.
The HTTP 1.1 protocol states that single-user clients should not maintain more than two connections
with any server or proxy. This is the reason for browser limits. For more information, see RFC 2616 –
Hypertext Transfer Protocol, section 8 – Connections.
Modern browsers are less restrictive than this, allowing a larger number of connections. The RFC does
not specify how to prevent the limit being exceeded. Either connections can be blocked from opening
or existing connections can be closed.
Table 8: Maximum supported connections
Version
Maximum connections
Internet Explorer 7.0
2
Internet Explorer 8.0 and 6
9.0
Internet Explorer 10.0
8
Internet Explorer 11.0
13
Firefox
6
Chrome
6
Safari
6
Opera
6
iOS
6
Diffusion | 46
Version
Maximum connections
Android
6
Some Diffusion protocols like HTTP Polling (XHR) use up to two simultaneous connections per
Diffusion client. It is important to understand that the maximum number of connections is per browser
and not per browser tab. Attempting to run multiple clients within the same browser might cause this
limit to be reached.
Reconnection can be used to maintain a larger number of clients than is usually allowed. When TCP
connections for HTTP requests are closed, the Diffusion sends another HTTP request which the server
accepts. Be aware of cases where Diffusion tries to write a response to closed polling connections
before the client can re-establish them. This behavior results in an IO Exception and the Diffusion
server closes the client unless reconnection is enabled. When the client tries to re-establish the poll, it
is aborted.
Another way to get around browser limits is by providing multiple subdomains. Each subdomain is
allowed the maximum number of connections. By using numbered subdomains, a client can pick a
random subdomain to connect to. Where the DNS server allows subdomains matching a pattern to be
resolved as the same server, tab limits can be mitigated substantially.
Designing your data model
Distribute your data in a data model that fits the needs of your organization and customers.
There are a number of things to consider when designing your data model:
•
•
•
•
•
•
The structure of your topic tree
The types of topic to use
The format of your data
How you publish data to topics
Your conflation strategy
Whether you also use messaging to send data.
These considerations are not separate. The decisions you make about one aspect of your data model
affect other aspects.
The data model is defined on the Diffusion server by your publishers or clients. The topic structure,
topic types, and data format are not persisted on the Diffusion server through a restart or upgrade.
Design your solution to create your data model on the Diffusion server afresh after the Diffusion server
is restarted or upgraded.
Topic tree
Diffusion primarily distributes data using a pub-sub model, where content is published to topics. These
topics are arranged as a tree.
What is the topic tree?
The topic tree is the organizational structure of the Diffusion topics. A topic of any type can be created
any point in the topic tree where a topic does not already exist.
Locations in the topic tree are referred to by their topic path, which is the level names in the tree that
lead to the topic, separated by the slash character (/).
Diffusion | 47
Figure 1: Example topic tree
In the preceding image, topics exist at baz and qux. The topic path for baz is /foo/baz. The topic path
for qux is /foo/bar/qux
You can create a topic at /foo/bar/qux without having to create topics at /foo or /foo/bar beforehand.
There can be multiple topics that have the same name, but topic paths are unique.
When interacting with topics in the topic tree, your clients can reference a topic by its topic path or by
a topic selector with a pattern expression that matches the topic path. Clients can use topic selectors
to reference sets of topics in the tree that all match the topic selector's pattern expression.
Considerations when designing your topic tree
•
Does the source information have a logical organization?
•
If the data structure of the source information is complex, it can be mapped to a hierarchical
structure.
How many topics?
•
If the number of topics is small, a flat topic tree might be appropriate.
How do clients subscribe to the data?
•
If there are topics that clients generally subscribe to together, these topics can be organized in the
same branch of the topic tree. This enables a client use a topic selector to subscribe to the branch
of the topic tree and receive updates for all topics in that branch.
The size of your topic tree can be constrained by your hardware.
An extremely large topic tree can cause long GC pauses. Ensure that you do sufficient testing with
your topic tree before going to production.
•
If the size of your topic tree structure is caused by a lot of duplication, use routing topics to reduce
it.
A topic cannot be bound to the / topic path. This is because each segment of a topic path must
have one or more characters. This means there can be no single topic that acts as the root topic for
all possible topics in the topic tree. Instead each top-level topic whose path contains a single part
acts as the root topic for their branch of the topic tree.
However, the / path can be used as a routing path when sending or receiving messages, which uses
paths but does not use any topics that are bound to them.
Related concepts
Topic selectors on page 50
Diffusion | 48
A topic selector identifies one or more topics. You can create a topic selector object from a pattern
expression.
Topic naming
Consider the following restrictions when deciding on your topic paths.
Restricted characters
Topics are identified by a path. A path is made up of path segments separated by the slash character
(/).
Each path segment can be made up of one or more Unicode characters but must not contain any
of the restricted characters mentioned below. The slash character (/) is not permitted in any path
segment.
Reserved spaces
Paths starting with the path segments Diffusion and @ are reserved for internal use.
Recommendations
Although all Unicode characters except the slash character (/) are supported, we recommend avoiding
common regular expression metacharacters such as '*' and '?' in path names to avoid having to quote
these characters in topic selector expressions.
Publisher API restricted characters
The Publisher API is used to create Java publishers that are hosted in the Diffusion server. This API has
a wider set of restrictions on characters in topic paths.
In addition to the slash character (/), which is restricted in all path segments, the following characters
are not permitted in paths to topics that are created by or selected by any publishers:
Table 9: Restricted characters for paths used by publishers.
Character
Reason for restriction
[]\^$.|?*+()
These are all metacharacters used in regular
expressions. Any path String that contains any
of these characters is assumed to be a topic
selector. These characters cannot be used in path
segments.
Control/Reserved
No characters with a hexadecimal value of less
than 0x2D. This includes some punctuation
characters such as comma (,).
Whitespace
No characters defined as whitespace in Java (as
indicated by the isWhiteSpace method of the
Java Character class).
Diffusion | 49
Topic selectors
A topic selector identifies one or more topics. You can create a topic selector object from a pattern
expression.
Pattern expressions
Use pattern expressions to create a topic selector of one of the types described in the following table.
The type of the topic selector is indicated by the first character of the pattern expression.
Table 10: Types of topic selector
Topic selector
type
Initial character
Description
Path
>
A path pattern expression must contain a valid topic path.
A valid topic path comprises path segments separated by
path separators (/). A path segment comprises one or more
UTF characters except for slash (/).
A PATH selector returns only the topic with the given path.
See Path examples on page 52
Abbreviated path
Any character
except the
following:
An abbreviated path pattern expression is an alternative
syntax for a path pattern selector. It must be a valid topic
path.
•
A path pattern expression must contain a valid topic path.
A valid topic path comprises path segments separated by
path separators (/). A path segment comprises one or more
UTF characters except for slash (/).
•
•
•
•
•
•
•
Hash symbol
(#)
Question mark
(?)
Greater than
symbol (>)
Asterisk (*)
Dollar sign ($)
Percentage
symbol (%)
Ampersand (&)
Less than
symbol (<)
Abbreviated path pattern expressions are converted into
PATHselectors and return only the topic with the given
path. See Abbreviated path examples on page 52
Split-path
?
A split-path pattern expression contains a list of
regular expressions separated by the / character. Each
regular expression describes a part of the topic path. A
SPLIT_PATH_PATTERN selector returns topics for which
each regular expression matches the part of the topic path
at the corresponding level. See Split-path examples on
page 53
Full-path
*
A full-path pattern expression contains a regular
expression. A FULL_PATH_PATTERN selector returns topics
for which the regular expression matches the full topic
path. See Full-path examples on page 54
Diffusion | 50
Topic selector
type
Initial character
Description
Note: Full-path pattern topic selectors are more
powerful than split-path pattern topic selectors,
but are evaluated less efficiently at the server. If
you are combining expressions, use selector sets
instead.
Selector set
#
A selector set pattern expression contains a list of selectors
separated by the separator ////. A SELECTOR_SET topic
selector returns topics that match any of the selectors.
Note: Use the anyOf() method for a simpler
method of constructing SELECTOR_SET topic
selectors.
See Selector set examples on page 54
Regular expressions
Diffusion topic selectors use regular expressions. Any regular expression can be used in split-path and
full-path patterns, with the following restrictions:
•
•
•
It cannot be empty
In split-path patterns, it cannot contain the path separator (/)
In full-path patterns, it cannot contain the selector set separator (////)
Depending on what the topic selector is used for, regular expressions in topic selectors can be
evaluated on the client or on the Diffusion server. For more information, see Regular expressions on
page 55.
Descendant pattern qualifiers
You can modify split-path or full-path pattern expressions by appending a descendant pattern
qualifier. These are described in the following table:
Table 11: Descendant pattern qualifiers
Qualifier
Behavior
None
Select only those topics that match the selector.
/
Select only the descendants of the matching topics and exclude the
matching topics.
//
Select both the matching topics and their descendants.
Topic path prefixes
The topic selector capabilities in the Diffusion API provide methods that enable you to get the topic
path prefix from a topic selector.
A topic path prefix is a concrete topic path to the most specific part of the topic tree that contains all
topics that the selector can specify. For example, for the topic selector ?foo/bar/baz/.*/bing,
the topic path prefix is foo/bar/baz.
The topic path prefix of a selector set is the topic path prefix that is common to all topic selectors in
the selector set.
Diffusion | 51
Path examples
The following table contains examples of path pattern expressions:
Expression
Matches alpha/beta?
Matches alpha/beta/
gamma?
>alpha/beta
yes
no
>/alpha/beta/
yes
no
Notes
This pattern expression
is equivalent to the
pattern expression
in the preceding
row. In an absolute
topic path, single
leading or trailing
slash characters (/) are
removed because the
topic path is converted
to canonical form.
A path pattern
expression can return
a maximum of one
topic. The trailing
slash in this example
is not treated as a
descendant qualifier
and is removed.
>alpha/beta/gamma
no
yes
>beta
no
no
The full topic path must
be specified for a path
pattern expression to
match a topic.
>.*/.*
no
no
The period (.) and
asterisk (*) characters
are valid in path
segments. In a path
pattern expression
these characters match
themselves and are not
evaluated as part of a
regular expression.
Abbreviated path examples
The following table contains examples of abbreviated path pattern expressions:
Expression
Matches alpha/beta?
Matches alpha/beta/
gamma?
alpha/beta
yes
no
/alpha/beta/
yes
no
Notes
This pattern expression
is equivalent to the
pattern expression
Diffusion | 52
Expression
Matches alpha/beta?
Matches alpha/beta/
gamma?
Notes
in the preceding row.
In an absolute topic
path, single leading
and trailing slash
characters (/) are
removed because the
topic path is converted
to canonical form.
A path pattern
expression can return
a maximum of one
topic. The trailing
slash in this example
is not treated as a
descendant qualifier
and is removed.
alpha/beta/gamma
no
yes
beta
no
no
The full topic path must
be specified for a path
pattern expression to
match a topic.
Split-path examples
The following table contains examples of split-path pattern expressions:
Expression
Matches alpha/
beta?
Matches alpha/
beta/gamma?
Notes
?alpha/beta
yes
no
?alpha/beta/
no
yes
The trailing slash character (/) is
treated as a descendant pattern
qualifier in split-path pattern
expressions. It returns descendants
of the matching topics, but not the
matching topics themselves.
?alpha/beta//
yes
yes
Two trailing slash characters (//)
is treated as a descendant pattern
qualifier in split-path pattern
expressions. It returns matching topics
and their descendants.
?alpha/beta/
gamma
no
yes
?beta
no
no
?.*
no
no
Each level of a topic path must have a
regular expression specified for it for a
split-path pattern expression to match
a topic.
Diffusion | 53
Expression
Matches alpha/
beta?
Matches alpha/
beta/gamma?
?.*/.*
yes
no
?alpha/.*//
yes
yes
Notes
In this pattern expression, “alpha/.*”
matches all topics in the alpha branch
of the topic tree. The descendant
pattern qualifier (//) indicates that the
matching topics and their descendants
are to be returned.
Full-path examples
The following table contains examples of full-path pattern expressions:
Expression
Matches alpha/
beta?
Matches alpha/
beta/gamma?
Notes
*alpha/beta
yes
no
*alpha/beta/
gamma
no
yes
*alpha/beta/
no
yes
The trailing slash character (/) is
treated as a descendant pattern
qualifier in split-path pattern
expressions. It returns descendants
of the matching topics, but not the
matching topics themselves.
*alpha/beta//
yes
yes
Two trailing slash characters (//)
is treated as a descendant pattern
qualifier in split-path pattern
expressions. It returns matching topics
and their descendants.
*beta
no
no
In a full-path pattern selector the
regular expression must match the full
topic path for a topic to be matched.
*.*beta
yes
no
The regular expression matches the
whole topic path including topic
separators (/).
Selector set examples
Use the anyOf methods to create a SELECTOR_SET TopicSelector object.
The following example code shows how to use the anyOf(TopicSelector... selectors)
method to create a selector set topic selector:
// Use your session to create a TopicSelectors instance
TopicSelectors selectors = session.topicSelectors();
// Create topic selectors for the individual pattern expressions
TopicSelector pathSelector = selectors.parse(">foo/bar");
TopicSelector splitPathSelector = selectors.parse("?f.*/bar\d+");
TopicSelector fullPathSelector = selectors.parse("*f.*\d+");
Diffusion | 54
// Use the individual topic selectors to create the selector set
topic selector
TopicSelector selector = selectors.anyOf(pathSelector,
splitPathSelector, fullPathSelector);
// Use the topic selector as a parameter to methods that perform
actions on topics or groups of topics
The following example code shows how to use the anyOf(String... selectors) method to
create the same topic selector as in the previous code example, but in fewer steps:
// Use your session to create a TopicSelectors instance
TopicSelectors selectors = session.topicSelectors();
// Create the selector set topic selector by passing in a list of
// pattern expressions
TopicSelector selector = selectors.anyOf(">foo/bar", "?f.*/bar\d+",
"*f.*\d+");
// Use the topic selector as a parameter to methods that perform
actions on topics or groups of topics
Related concepts
Topic tree on page 47
Diffusion primarily distributes data using a pub-sub model, where content is published to topics. These
topics are arranged as a tree.
Regular expressions
Depending on what the topic selector is used for, regular expressions in topic selectors can be
evaluated on the client or on the Diffusion server. On the Diffusion server, regular expressions are
evaluated as Java-style regular expressions. On clients, regular expressions are evaluated according to
the conventions of the client library.
The following client uses of topic selectors are evaluated on the Diffusion server:
•
•
•
•
•
•
Subscribing to topics
Unsubscribing from topics
Subscribing another client to topics
Unsubscribing another client from topics
Fetching topic states
Removing topics
The following client uses of topic selectors are evaluated on the client:
•
•
Creating a stream to receive updates published to topics
Creating a stream to receive messages sent on message paths
The regular expression evaluation on each of the client libraries and on the Diffusion server are all
based on the same style of regular expressions. The behavior of topic selectors on the clients and on
the Diffusion server is the same for all standard uses of regular expressions. More advanced or complex
regular expressions might differ slightly in behavior.
See the following sections for platform-specific information.
On the Diffusion server
Topic selectors evaluated on the Diffusion server are evaluated as Java-style regular expressions and
are based on java.util.regex.Pattern.
Diffusion | 55
For more information about Java-style regular expressions, see Java regular expressions.
On Java and Android clients
Topic selectors evaluated on these clients are evaluated as Java-style regular expressions. There are
no differences between how a regular expression is evaluated on these clients and how it is evaluated
on the Diffusion server.
For more information about Java-style regular expressions, see Java regular expressions.
On .NET clients
Topic selectors evaluated on the .NET client are evaluated as .NET Framework regular expressions,
these are compatible with Perl 5 regular expressions. For more information about .NET regular
expressions, see https://msdn.microsoft.com/en-us/library/az24scfc(v=vs.110).aspx.
The .NET evaluation of regular expressions can differ from the Java evaluation of the same
regular expression on the Diffusion server. For examples of how these can differ, see http://
stackoverflow.com/a/545348.
Ensure that you test the behavior of complex regular expressions that you use with the .NET client.
On Apple clients
Topic selectors evaluated on the Apple client are evaluated according to the
NSRegularExpression class, which uses ICU regular expression syntax. For more information
about Apple regular expressions, see:
•
•
https://developer.apple.com/library/ios/documentation/Foundation/Reference/
NSRegularExpression_Class/
http://userguide.icu-project.org/strings/regexp
.
The Apple evaluation of regular expressions can differ from the Java evaluation of the same regular
expression on the Diffusion server. Ensure that you test the behavior of complex regular expressions
that you use with the Apple client.
On JavaScript clients
Topic selectors evaluated on the JavaScript client are based on the ECMAScript standard. For more
information about JavaScript regular expressions, see:
•
•
•
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.5
The JavaScript evaluation of regular expressions can differ from the Java evaluation of the same
regular expression on the Diffusion server. Ensure that you test the behavior of complex regular
expressions that you use with the JavaScript client.
On C clients
Topic selectors evaluated on the C client use PCRE. For more information about C regular expressions,
see http://www.pcre.org/.
The C evaluation of regular expressions can differ from the Java evaluation of the same regular
expression on the Diffusion server. Ensure that you test the behavior of complex regular expressions
that you use with the C client.
Diffusion | 56
Topics
Consider the types of topic you want to use and how.
A topic is a channel through which data can be distributed to clients. Topics provide a logical link
between publishing clients and subscribing clients. For more information, see Pub-sub on page 81.
Diffusion topics are typed. Data sent through topics must match the data type of the topic.
Topics that store values
You can publish data to these topics and the data is streamed to subscribing clients. These topics
provide a range of possible data types:
•
•
•
•
•
•
•
•
•
JSON
Binary
String
Int64
Double
RecordV2
DEPRECATED: Record
DEPRECATED: Single value
DEPRECATED: Stateless
Advanced topics
Diffusion includes advanced topic types that provide additional capabilities such as storing a series of
events or routing subscriptions to other topics.
•
•
•
Time series
Routing
Slave
Properties
You can also assign properties to topics when you create them. The properties a topic can have can
change depending on the topic type. For more information, see Properties of topics on page 57.
Properties of topics
When you create a topic, you can specify properties that the topic has. The available properties
depend on the topic type.
The following table shows which properties are available. Some property names have been
abbreviated; see below the table for the full names.
Table 12: Properties available for topics of each type
Type PUBLISH
VALUES
ONLY
DONT
RETAIN
VALUES
SLAVE
MASTER
TOPIC
TIDY
TIME
ON
SERIES
UNSUB. EVENT
VALUE
TYPE
TIME
SERIES
RETAINED
RANGE
TIME
VALIDATE SCHEMA
SERIES VALUES
SUBSCR.
RANGE
JSON
Diffusion | 57
Type PUBLISH
VALUES
ONLY
DONT
RETAIN
VALUES
SLAVE
MASTER
TOPIC
TIDY
TIME
ON
SERIES
UNSUB. EVENT
VALUE
TYPE
TIME
SERIES
RETAINED
RANGE
TIME
VALIDATE SCHEMA
SERIES VALUES
SUBSCR.
RANGE
Binary
String
Int64
Double
Time
series
REQUIRED
Routing
Slave
REQUIRED
RecordV2
DEPRECATED:
Record
DEPRECATED:
Single
value
DEPRECATED:
Stateless
PUBLISH_VALUES_ONLY
By default, delta streaming is enabled. If this property is set to true, delta streaming
is disabled and all values are published in full.
If there is little or no relationship between one value published to a topic and the
next, delta streams will not reduce the amount of data transmitted. For such topics, it
is better to set PUBLISH_VALUES_ONLY.
DONT_RETAIN_VALUE
If set to true, the latest value of the topic is not retained by the Diffusion server or the
client that publishes it. New clients that subscribe do not receive an initial value. No
value will be returned for fetch operations that select the topic.
For time series topics, if DONT_RETAIN_VALUE is set to true, time series events are
still retained, but the latest value is not stored separately.
The DONT_RETAIN_VALUE property is useful for applications like a feed of news
items, or for values that are only valid at the moment of publication. You can combine
this with VALIDATE_VALUES.
Diffusion | 58
Using DONT_RETAIN_VALUE reduces the topic memory footprint, but disables delta
streaming. Disabling delta streaming is likely to increase the bandwidth used unless
subsequent values are unrelated.
This property replaces the deprecated stateless topic type which sends untyped byte
data and requires clients to interpret and validate the data.
SLAVE_MASTER_TOPIC
The path to the topic that acts as the master topic to a slave topic. A topic is not
required to exist at this path at the time the slave topic is created.
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
TIME_SERIES_EVENT_VALUE_TYPE
Set this to the type name of a Diffusion data type. All events in the time series are of
this data type. The type name can be one of the following values:
•
•
•
•
•
•
json
binary
string
int64
double
record_v2
TIME_SERIES_RETAINED_RANGE
Set this to a range expression that specifies the range of events retained by a time
series topic. When a new event is added to the time series, older events that fall
outside of the range are discarded. If the property is not specified, a time series topic
retains the ten most recent events.
For more information about range expressions, see Range expressions on page 60.
TIME_SERIES_SUBSCRIPTION_RANGE
Set this to a range expression that specifies the range of events sent to all new
subscribers.
If a range expression is specified for this property, the specified subscription range
is sent to the client session. This is true whether delta streams are enabled for the
topic or not. However, to receive all the events in the specified range, the subscribing
client session must register a stream before it subscribes to the topic. If a stream is
not registered before subscription, the session receives only the latest value.
If the property is not specified, new subscribers will be sent the latest event if delta
streams are enabled for the topic and no events if delta streams are disabled for the
topic.
For more information about range expressions, see Range expressions on page 60.
VALIDATE_VALUES
If set to true, the topic rejects updates that would create invalid instances of the
topic's data type.
If set to anything other than true, no validation is performed and all values are
streamed to subscribing clients whether they are valid data or not.
Diffusion | 59
Validation has a performance overhead and is disabled by default.
Note: If validation is disabled and the value provided is not valid, the
client might produce errors or other unexpected behavior. The exact
error varies depending on the client platform. To avoid this, use the
client-side validation method provided by the Diffusion API.
SCHEMA
Optionally, define valid records and fields of a recordV2 topic.
A recordV2 topic contains records, which can be divided into fields. The schema
names the records and fields and provides a mechanism for direct access to the
fields. The schema is also used to validate the data to ensure it complies with the
schema definition. The schema property is supplied as a JSON string that can be
generated from a Schema object.
If no schema is provided, the topic data can be free format.
Range expressions
A range expression can contain the following constraints:
A limit constraint
A limit constraint specifies the maximum number of events from the end of the time
series.
For example, the following expression requests the five most recent events:
limit 5
A last constraint
A last constraint specifies the maximum duration of events from the end of the time
series. The duration is expressed as an integer followed by one of the following time
units:
•
•
•
MS – milliseconds
S – seconds
H – hours
For example, the following expression requests all recent events that are no more
than 30 seconds older than the latest event:
last 30s
Both a limit and a last constraint
For example, the following expression requests the ten most recent events that are no
more than one minute older than the latest event:
last 60s limit 10
If a range expression contains multiple constraints, the constraint that selects the
smallest range is used.
Range expressions are not case sensitive.
Diffusion | 60
JSON topics
A topic that provides data in JSON format. The data is transmitted in a binary form for increased
efficiency and can be transmitted as a structural delta to reduce the amount of data sent. JSON topics
are stateful: each topic stores a JSON value the Diffusion server.
Why use a JSON topic?
JSON is a human-readable, industry-standard format for your data. JSON is natively supported by
JavaScript and there are third-party libraries available for other platforms. For more information
about JSON, see http://www.json.org/.
A JSON topic enables multiple fields to be maintained in the same topic as part of a composite data
type. All updates made at the same time to parts of a JSON topic are sent out to the client together.
This enables a set of parts to be treated as a transactional group.
Deltas of change are calculated at the Diffusion server such that only those parts that have changed
since the last update are sent out to the subscribed clients. These structural deltas ensure that the
minimum amount of data is sent to clients.
The current value of the topic is cached on the client. When deltas are sent, the client can
automatically apply these deltas to the value to calculate the new value.
If your data structure is too complex to be represented by a topic tree or might make the topic tree
structure difficult to manage, it might be more appropriate to represent part of the data structure
inside a JSON topic as JSON objects.
The value of the topic is transmitted as CBOR. For more information about CBOR, see http://cbor.io/.
The value of the topic is accessible both as JSON and CBOR.
Properties of a JSON topic
When you create a JSON topic you can specify the following properties in the topic specification:
PUBLISH_VALUES_ONLY
By default, delta streaming is enabled. If this property is set to true, delta streaming
is disabled and all values are published in full.
If there is little or no relationship between one value published to a topic and the
next, delta streams will not reduce the amount of data transmitted. For such topics, it
is better to set PUBLISH_VALUES_ONLY.
DONT_RETAIN_VALUE
If set to true, the latest value of the topic is not retained by the Diffusion server or the
client that publishes it. New clients that subscribe do not receive an initial value. No
value will be returned for fetch operations that select the topic.
For time series topics, if DONT_RETAIN_VALUE is set to true, time series events are
still retained, but the latest value is not stored separately.
The DONT_RETAIN_VALUE property is useful for applications like a feed of news
items, or for values that are only valid at the moment of publication. You can combine
this with VALIDATE_VALUES.
Using DONT_RETAIN_VALUE reduces the topic memory footprint, but disables delta
streaming. Disabling delta streaming is likely to increase the bandwidth used unless
subsequent values are unrelated.
This property replaces the deprecated stateless topic type which sends untyped byte
data and requires clients to interpret and validate the data.
TIDY_ON_UNSUBSCRIBE
Diffusion | 61
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
VALIDATE_VALUES
If set to true, the topic rejects updates that would create invalid instances of the
topic's data type.
If set to anything other than true, no validation is performed and all values are
streamed to subscribing clients whether they are valid data or not.
Validation has a performance overhead and is disabled by default.
Note: If validation is disabled and the value provided is not valid, the
client might produce errors or other unexpected behavior. The exact
error varies depending on the client platform. To avoid this, use the
client-side validation method provided by the Diffusion API.
Considerations when using a JSON topic
It is possible to store any data in a JSON topic, even if it is not valid JSON. However, this can cause
problems when clients receive unexpected values.
You can validate JSON in the client, or on the server by setting VALIDATE_VALUES to true.
All languages parse JSON as text. Only JavaScript has native support for parsing JSON. Other
languages must use third-party libraries. The JavaScript clients provide facilities for converting JSON
text to and from a Diffusion JSON value.
Diffusion JSON values are transmitted as a CBOR representation. Applications can also access the
CBOR binary data directly, and it is often more efficient to do so than to first convert the data to JSON
text. For example, Java applications can use the third party Jackson library to map the CBOR data
directly to Java objects.
The Publisher API provides the capability to create and update JSON topics using
TopicDataFactory.newUniversalData.
Binary topics
A topic that streams binary data as bytes and uses efficient binary deltas to stream only the data that
changes between updates. Binary topics are stateful: each topic stores a binary value on the Diffusion
server.
Why use a binary topic?
You can use a binary topic to transmit any arbitrary binary data without the overhead of encoding it to
a string or the risk of the binary data being incorrectly escaped.
Binary topics can use binary deltas to send only the data that has changed when this is more efficient
than sending the full value.
You can use a binary topic to transmit very large strings. This enables a client to use the binary delta
capability to transmit only the changed parts of a string rather than the whole value. This reduces the
amount of data transmitted over the network.
Binary formats, such as Google protocol buffers, can be streamed using a binary topic.
Diffusion | 62
Properties of a binary topic
When you create a binary topic you can specify the following properties in the topic specification:
PUBLISH_VALUES_ONLY
By default, delta streaming is enabled. If this property is set to true, delta streaming
is disabled and all values are published in full.
If there is little or no relationship between one value published to a topic and the
next, delta streams will not reduce the amount of data transmitted. For such topics, it
is better to set PUBLISH_VALUES_ONLY.
DONT_RETAIN_VALUE
If set to true, the latest value of the topic is not retained by the Diffusion server or the
client that publishes it. New clients that subscribe do not receive an initial value. No
value will be returned for fetch operations that select the topic.
For time series topics, if DONT_RETAIN_VALUE is set to true, time series events are
still retained, but the latest value is not stored separately.
The DONT_RETAIN_VALUE property is useful for applications like a feed of news
items, or for values that are only valid at the moment of publication. You can combine
this with VALIDATE_VALUES.
Using DONT_RETAIN_VALUE reduces the topic memory footprint, but disables delta
streaming. Disabling delta streaming is likely to increase the bandwidth used unless
subsequent values are unrelated.
This property replaces the deprecated stateless topic type which sends untyped byte
data and requires clients to interpret and validate the data.
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
VALIDATE_VALUES
If set to true, the topic rejects updates that would create invalid instances of the
topic's data type.
If set to anything other than true, no validation is performed and all values are
streamed to subscribing clients whether they are valid data or not.
Validation has a performance overhead and is disabled by default.
Note: If validation is disabled and the value provided is not valid, the
client might produce errors or other unexpected behavior. The exact
error varies depending on the client platform. To avoid this, use the
client-side validation method provided by the Diffusion API.
Binary values are always valid so setting this property has no effect.
Considerations when using a binary topic
Data on binary topics contains no implicit information about its structure.
Data on binary topics cannot be viewed in the console.
The Publisher API provides the capability to create and update JSON topics using
TopicDataFactory.newUniversalData.
Diffusion | 63
String topics
A topic that provides data in string format. The data is transmitted in a binary form for increased
efficiency. String topics are stateful: each topic stores a JSON value on the Diffusion server.
Why use a string topic?
A string topic enables you to explicitly type the data that you send through the topic as a string.
String topics support null data values.
Deltas of change are calculated at the Diffusion server such that only those parts that have changed
since the last update are sent out to the subscribed clients. This ensures that the minimum amount of
data is sent to clients.
The current value of the topic is cached on the client. When deltas are sent, the client can
automatically apply these deltas to the value to calculate the new value.
The value of the topic is stored and transmitted as a CBOR-encoded string, or CBOR Null. CBOR
encodes strings using UTF-8. For more information about CBOR, see http://cbor.io/.
Properties of a string topic
When you create a string topic you can specify the following properties in the topic specification:
PUBLISH_VALUES_ONLY
By default, delta streaming is enabled. If this property is set to true, delta streaming
is disabled and all values are published in full.
If there is little or no relationship between one value published to a topic and the
next, delta streams will not reduce the amount of data transmitted. For such topics, it
is better to set PUBLISH_VALUES_ONLY.
DONT_RETAIN_VALUE
If set to true, the latest value of the topic is not retained by the Diffusion server or the
client that publishes it. New clients that subscribe do not receive an initial value. No
value will be returned for fetch operations that select the topic.
For time series topics, if DONT_RETAIN_VALUE is set to true, time series events are
still retained, but the latest value is not stored separately.
The DONT_RETAIN_VALUE property is useful for applications like a feed of news
items, or for values that are only valid at the moment of publication. You can combine
this with VALIDATE_VALUES.
Using DONT_RETAIN_VALUE reduces the topic memory footprint, but disables delta
streaming. Disabling delta streaming is likely to increase the bandwidth used unless
subsequent values are unrelated.
This property replaces the deprecated stateless topic type which sends untyped byte
data and requires clients to interpret and validate the data.
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
VALIDATE_VALUES
Diffusion | 64
If set to true, the topic rejects updates that would create invalid instances of the
topic's data type.
If set to anything other than true, no validation is performed and all values are
streamed to subscribing clients whether they are valid data or not.
Validation has a performance overhead and is disabled by default.
Note: If validation is disabled and the value provided is not valid, the
client might produce errors or other unexpected behavior. The exact
error varies depending on the client platform. To avoid this, use the
client-side validation method provided by the Diffusion API.
Considerations when using a string topic
The Publisher API provides the capability to create and update string topics using
TopicDataFactory.newUniversalData.
Int64 topics
A topic that provides data in 64-bit integer format. The data is transmitted in a binary form for
increased efficiency. Int64 topics are stateful: each topic stores a 64-bit integer on the Diffusion server.
Why use an int64 topic?
An int64 topic enables you to explicitly type the data that you send through the topic as a 64-bit
integer.
Int64 topics support null data values.
The value of the topic is transmitted as CBOR. For more information about CBOR, see http://cbor.io/.
The value of the topic is accessible both as a 64-bit integer and CBOR.
Properties of an int64 topic
When you create an int64 topic you can specify the following properties in the topic specification:
DONT_RETAIN_VALUE
If set to true, the latest value of the topic is not retained by the Diffusion server or the
client that publishes it. New clients that subscribe do not receive an initial value. No
value will be returned for fetch operations that select the topic.
For time series topics, if DONT_RETAIN_VALUE is set to true, time series events are
still retained, but the latest value is not stored separately.
The DONT_RETAIN_VALUE property is useful for applications like a feed of news
items, or for values that are only valid at the moment of publication. You can combine
this with VALIDATE_VALUES.
Using DONT_RETAIN_VALUE reduces the topic memory footprint, but disables delta
streaming. Disabling delta streaming is likely to increase the bandwidth used unless
subsequent values are unrelated.
This property replaces the deprecated stateless topic type which sends untyped byte
data and requires clients to interpret and validate the data.
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
Diffusion | 65
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
VALIDATE_VALUES
If set to true, the topic rejects updates that would create invalid instances of the
topic's data type.
If set to anything other than true, no validation is performed and all values are
streamed to subscribing clients whether they are valid data or not.
Validation has a performance overhead and is disabled by default.
Note: If validation is disabled and the value provided is not valid, the
client might produce errors or other unexpected behavior. The exact
error varies depending on the client platform. To avoid this, use the
client-side validation method provided by the Diffusion API.
Considerations when using an int64 topic
Deltas are not available for data published to int64 topics.
Because of a limitation of the JavaScript platform, the validity of the values converted to an Int64
from a Number or converted to a Number from an Int64 can only be guaranteed for values up to 253
- 1. Converting between a String and an Int64 has fully guaranteed precision.
The Publisher API provides the capability to create and update int64 topics using
TopicDataFactory.newUniversalData.
Double topics
A topic that provides data in double precision floating point format (IEEE 754). The data is transmitted
in a binary form for increased efficiency. Double topics are stateful: each topic stores a double value on
the Diffusion server.
Why use a double topic?
A double topic enables you to explicitly type the data that you send through the topic as a double
precision floating point number.
Double topics support null data values.
The value of the topic is transmitted as CBOR. For more information about CBOR, see http://cbor.io/.
The value of the topic is accessible both as a double and CBOR.
Properties of a double topic
When you create a double topic you can specify the following properties in the topic specification:
DONT_RETAIN_VALUE
If set to true, the latest value of the topic is not retained by the Diffusion server or the
client that publishes it. New clients that subscribe do not receive an initial value. No
value will be returned for fetch operations that select the topic.
For time series topics, if DONT_RETAIN_VALUE is set to true, time series events are
still retained, but the latest value is not stored separately.
The DONT_RETAIN_VALUE property is useful for applications like a feed of news
items, or for values that are only valid at the moment of publication. You can combine
this with VALIDATE_VALUES.
Diffusion | 66
Using DONT_RETAIN_VALUE reduces the topic memory footprint, but disables delta
streaming. Disabling delta streaming is likely to increase the bandwidth used unless
subsequent values are unrelated.
This property replaces the deprecated stateless topic type which sends untyped byte
data and requires clients to interpret and validate the data.
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
VALIDATE_VALUES
If set to true, the topic rejects updates that would create invalid instances of the
topic's data type.
If set to anything other than true, no validation is performed and all values are
streamed to subscribing clients whether they are valid data or not.
Validation has a performance overhead and is disabled by default.
Note: If validation is disabled and the value provided is not valid, the
client might produce errors or other unexpected behavior. The exact
error varies depending on the client platform. To avoid this, use the
client-side validation method provided by the Diffusion API.
Considerations when using a double topic
Deltas are not available for data published to double topics.
The Publisher API provides the capability to create and update double topics using
TopicDataFactory.newUniversalData.
Time series topics
A time series topic holds a sequence of events.
Note: Time series topics are supported by the JavaScript, Java, Android and Apple APIs.
Why use a time series topic?
A time series topic holds a sequence of events. Time series topics are useful for collaborative
applications such as chat rooms. Multiple users can concurrently update a time series topic.
Each event in a time series topic has a value. Each time series topic has an associated data type which
determines what type of value its events have: binary, double, int64, JSON, string or recordV2.
All the events in a given time series topic must have the same value type.
Each event is assigned metadata by the Diffusion server. This metadata consists of a sequence
number, a timestamp, and an author (where the author is the principal used by the client session that
creates the event).
A time series is an append-only data structure. A session can update the topic by providing a new
event, which the server will append to the end of the time series. A session can also edit an existing
event in time series, providing a new value. Editing an event does not remove or modify the original
event, but instead appends an edit event to the end of the time series. Edit events have an additional
metadata field, the sequence number of the original event, allowing subscribers to associate the new
value with the replaced value.
Diffusion | 67
You can subscribe to events that are published to a time series topic and receive updates in the same
way as for other topics. New subscribers also receive an initial subset of the most recent events. You
can configure a subscription range to control how far back in time the initial set goes.
You can query a time series topic to receive a range of events based on the timestamp or sequence
number of the events in a series.
By default, queries return a merged view of a time series that includes edit events in the place of the
original events. A session with the QUERY_OBSOLETE_TIME_SERIES_EVENTS permission can submit a
modified query which returns an unmerged view that includes both original events and the edit events
that replace them.
Properties of a time series topic
When you create a time series topic you must specify the following property in the topic specification:
TIME_SERIES_EVENT_VALUE_TYPE
Set this to the type name of a Diffusion data type. All events in the time series are of
this data type. The type name can be one of the following values:
•
•
•
•
•
•
json
binary
string
int64
double
record_v2
You can also specify the following optional properties in the topic specification:
PUBLISH_VALUES_ONLY
By default, delta streaming is enabled. If this property is set to true, delta streaming
is disabled and all values are published in full.
If there is little or no relationship between one value published to a topic and the
next, delta streams will not reduce the amount of data transmitted. For such topics, it
is better to set PUBLISH_VALUES_ONLY.
DONT_RETAIN_VALUE
If set to true, the latest value of the topic is not retained by the Diffusion server or the
client that publishes it. New clients that subscribe do not receive an initial value. No
value will be returned for fetch operations that select the topic.
For time series topics, if DONT_RETAIN_VALUE is set to true, time series events are
still retained, but the latest value is not stored separately.
The DONT_RETAIN_VALUE property is useful for applications like a feed of news
items, or for values that are only valid at the moment of publication. You can combine
this with VALIDATE_VALUES.
Using DONT_RETAIN_VALUE reduces the topic memory footprint, but disables delta
streaming. Disabling delta streaming is likely to increase the bandwidth used unless
subsequent values are unrelated.
This property replaces the deprecated stateless topic type which sends untyped byte
data and requires clients to interpret and validate the data.
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
Diffusion | 68
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
TIME_SERIES_RETAINED_RANGE
Set this to a range expression that specifies the range of events retained by a time
series topic. When a new event is added to the time series, older events that fall
outside of the range are discarded. If the property is not specified, a time series topic
retains the ten most recent events.
For more information about range expressions, see Range expressions on page 60.
TIME_SERIES_SUBSCRIPTION_RANGE
Set this to a range expression that specifies the range of events sent to all new
subscribers.
If a range expression is specified for this property, the specified subscription range
is sent to the client session. This is true whether delta streams are enabled for the
topic or not. However, to receive all the events in the specified range, the subscribing
client session must register a stream before it subscribes to the topic. If a stream is
not registered before subscription, the session receives only the latest value.
If the property is not specified, new subscribers will be sent the latest event if delta
streams are enabled for the topic and no events if delta streams are disabled for the
topic.
For more information about range expressions, see Range expressions on page 60.
VALIDATE_VALUES
If set to true, the topic rejects updates that would create invalid instances of the
topic's data type.
If set to anything other than true, no validation is performed and all values are
streamed to subscribing clients whether they are valid data or not.
Validation has a performance overhead and is disabled by default.
Note: If validation is disabled and the value provided is not valid, the
client might produce errors or other unexpected behavior. The exact
error varies depending on the client platform. To avoid this, use the
client-side validation method provided by the Diffusion API.
Considerations when using a time series topic
All the events in a time series topic are stored in memory on the Diffusion server. If the Diffusion server
is restarted, the events in the time series are lost unless topic persistence is enabled.
A time series topic retains a range of the most recent events. Older events are discarded. By default,
only the ten most recent events are retained (this includes edit events). You can configure the
"retained range" property to retain more events.
Enabling DONT_RETAIN_VALUE for a time series does not prevent time series events being retained.
It only prevents separate storage of the latest value. Enabling DONT_RETAIN_VALUE for a time series
only produces a small memory saving compared to using it for other topic types.
If you are considering using PUBLISH_VALUES_ONLY for a time series topic, use DONT_RETAIN_VALUE,
which has the same effect of disabling delta streaming, but additionally saves memory.
A query against a time series topic only returns edit events that refer to original events in the view
range of the query. If the original event is no longer stored on the server due to the retained range,
related edit events will never be returned. These "orphaned" edit events will stay on the server until
more events have been appended and they are pushed out of the retained range.
Diffusion | 69
If subscribing to a time series topic and using a value stream to receive data, ensure that the client
adds the value stream before subscribing to the topic to receive all events in the configurable window.
If the client session adds the value stream before it subscribes to the time series topic, the client
session only receives the latest event on the time series topic.
The Publisher API does not support interaction with time series topics.
Related concepts
Using time series topics on page 375
A client can subscribe to a time series topic using a value stream, query to retrieve values within a
range, append new values, or apply an edit event to override the value of an earlier event.
Routing topics
A special type of topic, which can map to a different real topic for every client that subscribes to it. In
this way, different clients can see different values for what is effectively the same topic from the client
point of view.
When a client subscribes to a routing topic, the request is either passed to a client that has registered
as a routing subscription handler for the topic or handled by a server-side routing handler. The routing
handler assigns a linked topic to represent it to that client.
The routing handler can assign a different linked topic to each client that subscribes to the routing
topic. The linked topic can be a topic of one of the following types:
•
•
•
•
•
JSON
Binary
Single value
Record
Stateless
When updates are received on the linked topic, those updates are propagated through the routing
topic to the subscribing clients.
The subscribing client is not aware of the linked topic. It is subscribed to the routing topic and all the
updates that the client receives contain only the routing topic path and not the linked topic path.
Why use a routing topic?
Use routing topics when you want your subscribing clients to all have the same subscription behavior,
but the data they receive to be decided by a routing handler depending on criteria about that client.
For example, your subscribing clients can subscribe to a routing topic called Price, but the routing
handler assigns each client a different linked topic depending on the client's geographic location. This
way, clients in different countries can act in the same way, but receive localized information.
Properties of a routing topic
When you create a routing topic you can specify the following properties in the topic specification:
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
Diffusion | 70
Considerations when using a routing topic
Using routing topics requires that you write a routing handler that is either hosted on the server or
registered by a client with the required permissions. The following client APIs can register a routing
handler: Java, .NET, or Android API.
A subscribing client only needs permission to subscribe to the routing topic. Permission to subscribe to
the linked topic is not required.
If the linked topic is removed, subscribing clients are automatically unsubscribed from the routing
topic.
If you attempt to fetch from a routing topic that routes to a stateless topic, no data is returned.
You cannot use topic replication to replicate routing topics between Diffusion servers.
When using automatic fan-out to propagate topics from a primary server to one or more secondary
servers, the routing subscription handlers for a routing topic must be registered at the primary and all
secondary servers. The routing logic provided by the handlers on the primary and secondary server
must be identical.
Slave topics
A special type of topic that has no state of its own but is a reference to the state of another topic.
A slave topic acts as an alias to another topic, the master topic. Updates published to the master are
fanned out to subscribers of the slave. The slave cannot be updated directly. The master topic must be
one of the following types of topic:
•
•
•
•
•
•
•
JSON
Binary
String
Int64
Double
Record
Single value
The link between a slave topic and a master topic is defined when the slave topic is created. This is
different to routing topics where the link between topics is defined when a client session subscribes.
If the master topic does not exist when the slave topic is created, the slave topic is created as an
unbound slave topic that is not visible to subscribers. When a topic is created at the master topic path,
the slave topic becomes bound and can be subscribed to by client sessions.
Properties of a slave topic
When you create a slave topic you must specify the following property in the topic specification:
SLAVE_MASTER_TOPIC
The path to the topic that acts as the master topic to a slave topic. A topic is not
required to exist at this path at the time the slave topic is created.
When you create a slave topic you can specify the following optional properties in the topic
specification:
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
Diffusion | 71
Why use a slave topic?
You can use slave topics to provide the same data from multiple topic paths and manage the topics
from only one topic.
You can use a slave topic to act as a redirection to a succession of master topics. For example, you
can create a slave topic called latest that is linked to a master topic where data is published about a
current event. When that event is no longer current, you can remove the slave topic and recreate it
now linked to the master topic where data is published about what is now the current event.
The subscribing client sessions can subscribe to the latest slave topic and they continue to be
subscribed to the slave topic and receive the latest data, even as the master topic that provides the
data changes.
Considerations when using a slave topic
A client only needs permissions on the slave topic. Permission to subscribe to the linked topic is not
required.
More than one slave can point to the same master topic.
A slave topic cannot also act as a master topic to another slave topic.
Removing a master topic causes all linked slave topics to become unbound and any clients that are
subscribed to the slave topic become unsubscribed. If a new master topic is created at the linked path,
the slave topic is bound and the clients are resubscribed.
When using topic replication to replicate slave topics between Diffusion servers, be aware that a
replicated slave topic is linked to a master topic located on the same Diffusion server as the replicated
slave topic. This is true whether that master topic is created by replication or directly.
Slave topics created by the Publisher API cannot link to master topics created by a client.
Slave topics created by a client cannot link to master topics created by the Publisher API.
Slave topics created by the Publisher API behave differently to those created by the client API. When a
publisher creates a slave topic, the master topic must already exist. When a master topic is removed
any slave topics that were created by the Publisher API are removed.
RecordV2 topics
A topic that streams data in recordV2 format, where the data is divided into multiple records, each of
which can contain multiple fields. RecordV2 topics are stateful: each topic stores a value consisting of
one or more records on the Diffusion server.
You can optionally define the format of the data by using a schema that is associated with the
recordV2 topic. You can use a schema to validate the data at the server. A schema also provides a
convenient way of building a topic value using the fields defined within the schema, or interpreting a
topic value in terms of named records and fields. For more information, see RecordV2 schema on page
74.
If you choose not to provide a schema, the data is treated as free format: the meaning of each field is
up to your application, and Diffusion does not perform any validation.
.
Why use a recordV2 topic?
A recordV2 topic enables multiple fields to be maintained in the same topic as part of a composite data
type. All updates made at the same time to fields on a record topic are sent out to the client together.
This enables a set of fields to be treated as a transactional group.
Diffusion | 72
Deltas of change are calculated at the server such that only those fields that have changed since the
last update are sent out to the subscribed clients. This ensures that the minimum amount of data is
sent to clients.
Properties of a recordV2 topic
When you create a recordV2 topic you can specify the following properties in the topic specification:
PUBLISH_VALUES_ONLY
By default, delta streaming is enabled. If this property is set to true, delta streaming
is disabled and all values are published in full.
If there is little or no relationship between one value published to a topic and the
next, delta streams will not reduce the amount of data transmitted. For such topics, it
is better to set PUBLISH_VALUES_ONLY.
DONT_RETAIN_VALUE
If set to true, the latest value of the topic is not retained by the Diffusion server or the
client that publishes it. New clients that subscribe do not receive an initial value. No
value will be returned for fetch operations that select the topic.
For time series topics, if DONT_RETAIN_VALUE is set to true, time series events are
still retained, but the latest value is not stored separately.
The DONT_RETAIN_VALUE property is useful for applications like a feed of news
items, or for values that are only valid at the moment of publication. You can combine
this with VALIDATE_VALUES.
Using DONT_RETAIN_VALUE reduces the topic memory footprint, but disables delta
streaming. Disabling delta streaming is likely to increase the bandwidth used unless
subsequent values are unrelated.
This property replaces the deprecated stateless topic type which sends untyped byte
data and requires clients to interpret and validate the data.
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
SCHEMA
Optionally, define valid records and fields of a recordV2 topic.
A recordV2 topic contains records, which can be divided into fields. The schema
names the records and fields and provides a mechanism for direct access to the
fields. The schema is also used to validate the data to ensure it complies with the
schema definition. The schema property is supplied as a JSON string that can be
generated from a Schema object.
If no schema is provided, the topic data can be free format.
VALIDATE_VALUES
If set to true, the topic rejects updates that would create invalid instances of the
topic's data type.
If set to anything other than true, no validation is performed and all values are
streamed to subscribing clients whether they are valid data or not.
Validation has a performance overhead and is disabled by default.
Diffusion | 73
Note: If validation is disabled and the value provided is not valid, the
client might produce errors or other unexpected behavior. The exact
error varies depending on the client platform. To avoid this, use the
client-side validation method provided by the Diffusion API.
Considerations when using a recordV2 topic
RecordV2 replaces the older, deprecated record topic type. You can migrate your applications from
deprecated record topics to recordV2 topics with minimal changes. Using recordV2 instead of the
deprecated record type is required to use topic persistence and topic replication.
The data within a recordV2 topic can either be free format or constrained by a schema. If no schema
property is specified, the topic will be treated as free format.
Update a recordV2 topic with a value updater. See Updating topics on page 351 for more information
about value updaters.
Receive data from a recordV2 topic with a value stream. See Using streams for subscription on page
282 for more information.
The Publisher API provides the capability to create and update recordV2 topics using
TopicDataFactory.newUniversalData.
Related concepts
RecordV2 schema on page 74
A schema is an optional way to define how data is formatted when it is published on a recordV2 topic.
A schema defines and names the permitted records and fields within the topic, and enables direct
access to the fields.
Update recordV2 with a schema on page 324
The following example demonstrates how to create and update recordV2 topics, including the use of a
schema.
Subscribe to recordV2 with a schema on page 330
The following example demonstrates how to process information from subscribed recordV2 topics,
including the use of a schema.
Related tasks
Defining a recordV2 schema on page 320
You can use the API to specify a schema that defines the content of a recordV2 topic.
RecordV2 schema
A schema is an optional way to define how data is formatted when it is published on a recordV2 topic.
A schema defines and names the permitted records and fields within the topic, and enables direct
access to the fields.
The recordV2 topic type contains data organized into records and fields. You can optionally provide a
schema which defines the expected structure of the data.
RecordV2 structure
The recordV2 topic type has a value consisting of records, which contain fields. Each recordV2 topic
can contain one or more records. Each record can contain one or many fields.
Diffusion | 74
Using a schema
With a schema, you can define how the records and fields within a recordV2 topic are laid out.
Fields and records within a schema are identified by a name. Every record must have a name that is
unique within the content. Every field must have a name that is unique within the enclosing record.
Every field or record defined in the schema can represent one or more possible occurrences of that
field or record in the data. The number of possible occurrences of a record or field is described by its
multiplicity.
The order in which records and fields are defined within the schema sets the order that they appear
within the topic.
Records
A record can contain one or more fields.
Every record has multiplicity.
Fields
A field defines an elementary data item within a record.
Every field has the following properties:
•
•
Multiplicity
Data type
Multiplicity
The multiplicity of a field or record in a schema defines the number of times it can occur in the topic.
Multiplicity is set by providing a minimum value and a maximum value.
Fixed multiplicity means the minimum and maximum are the same. For example, if a field has a
minimum of 5 and a maximum of 5, there must be exactly five occurrences of the field within its
enclosing record.
Variable multiplicity means the minimum and maximum are different. For example, a schema could
specify that there must be between one and five occurrences of a field within its enclosing record.
Variable multiplicity is only allowed in the last record in a topic, or the last field in a record.
Use a minimum value of 0 to define an optional field/record. A fixed multiplicity of 0 is not allowed.
A maximum value of -1 is used to represent that there is no limit to how many times the field or record
can occur.
Data type
The data type of a field defines the type of values it can contain. The following table describes the data
types that are available.
Diffusion | 75
Table 13: Data types for schema fields
Data type
Description
String
A character string.
Integer
An integer represented in the content as a character string.
If a field is defined as this type, it can only contain numeric digits
with an optional leading sign. Fields of this type cannot be empty.
Decimal
A decimal number represented in the content as a character string.
Decimal fields have the number of places to the right of the
decimal point defined by the scale. Such values can be parsed
from a character string with any number of digits to the right of the
decimal point. Half-up rounding is applied to achieve the target
scale. Output of the field is rendered with the specified scale.
Fields of this type cannot be empty.
For comparison purposes the scale is ignored: a value of 1.50 is the
same as 1.5.
Defining a schema
See Defining a recordV2 schema on page 320 for details of how to define a schema and apply it to a
recordV2 topic.
Related concepts
RecordV2 topics on page 72
A topic that streams data in recordV2 format, where the data is divided into multiple records, each of
which can contain multiple fields. RecordV2 topics are stateful: each topic stores a value consisting of
one or more records on the Diffusion server.
Update recordV2 with a schema on page 324
The following example demonstrates how to create and update recordV2 topics, including the use of a
schema.
Subscribe to recordV2 with a schema on page 330
The following example demonstrates how to process information from subscribed recordV2 topics,
including the use of a schema.
Related tasks
Defining a recordV2 schema on page 320
You can use the API to specify a schema that defines the content of a recordV2 topic.
DEPRECATED: Record topics
A topic that streams data in the deprecated record format. You should now use the new recordV2 topic
type.
Describe the layout of the data by using content metadata in the schema. For more information, see
Record metadata on page 77.
Diffusion | 76
Why use a record topic?
A record topic enables multiple fields to be maintained in the same topic as part of a composite data
type. All updates made at the same time to fields on a record topic are sent out to the client together.
This enables a set of fields to be treated as a transactional group.
Deltas of change are calculated at the server such that only those fields that have changed since the
last update are sent out to the subscribed clients. This ensures that the minimum amount of data is
sent to clients.
Properties of a record topic
When you create a record topic you can specify the following properties in the topic specification:
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
Considerations when using a record topic
This topic type is deprecated. Use the new recordV2 topic type instead.
You must define the metadata when you create the record topic. This is more complex than using a
JSON topic for composite data.
The metadata format used by record topics is only used by Diffusion. If you require a topic with a
composite data type, you can use JSON topics.
Record topic updates represent all possible records and fields within the content. Fields that have
not changed are sent within delta updates as zero-length strings. Because unchanged fields are
represented this way a client cannot differentiate between a field that has not changed and an empty
field. You can specify a special character that is used to represent an empty field.
The current value of a record topic is not cached on the client. Because of this, deltas are not
processed automatically and your application code must cache the topic value and correctly apply
incoming deltas. This lack of caching also requires that a client must register a stream against a topic
before subscribing to the topic in order to receive a value. If a client subscribes to a record topic then
registers a stream, the client receives only deltas until the next time a full value is published.
Record metadata
Metadata defines how data is formatted when it is published on a topic. Define the metadata structure
for a topic that describes the grouping, order, type, and multiplicity of data items published on a topic.
Updates and messages contain data. This data can be formatted in whatever way your application
requires. For example,
•
•
•
When creating a record topic, define a metadata structure that describes the data format both for
updates published on that topic and those sent on to the subscribing clients.
When creating a single value topic, you can define field metadata that constrains the data type that
updates published to the topic can have.
When sending a message through a message path, you can use metadata to create the content of
your message.
Metadata structure
The metadata structure is made up of nested records and fields. The outer container is the content.
This contains one or more records. Each record can contain one or many fields.
Diffusion | 77
Fields and records are identified by a name. Every record must have a name that is unique within the
content. Every field must have a name that is unique within the enclosing record.
Every field or record defined in the metadata structure can represent one or more possible
occurrences of that field or record in the data. The number of possible occurrences of a record or field
is described by its multiplicity.
The order in which records and fields are defined within their enclosing container defines the order
that they appear in data.
Field metadata
A metadata field defines an elementary data item within a record.
Every field has the following properties:
•
•
•
Data type
Multiplicity
Default value
Data type
The data type of a field defines its actual representation within the data. The following table describes
the data types that are available.
Table 14: Data types for metadata fields
Data type
Description
Default
String
A character string.
Zero-length string
Integer string
An integer represented in the content as a
character string.
0
If a field is defined as this type, it can only contain
numeric digits with an optional leading sign.
Fields of this type cannot be empty.
Decimal string
A decimal number represented in the content as
a character string.
0.00 (depending on
scale)
Decimal fields have the number of places to
the right of the decimal point defined by the
scale, the default being 2. Such values can be
parsed from a character string with any number
of digits to the right of the decimal point. Half-up
rounding is applied to achieve the target scale.
Output of the field is rendered with the specified
scale. Fields of this type cannot be empty.
For comparison purposes the scale is ignored: a
value of 1.50 is the same as 1.5.
Diffusion | 78
Data type
Description
Default
Custom string
This is a special type where the behavior is
delegated to a user-written custom field handler.
This type is available in all topic data types.
Multiplicity
The multiplicity of a metadata field or record defines the number of times the corresponding data can
occur within the enclosing record or content.
Multiplicity is defined in terms of the minimum and maximum number of occurrences. Some data
representations support variable numbers of records and field, whereas others (such a record data)
only support fixed number of records and fields (where minimum=maximum) except in the last
position.
Fixed multiplicity is defined by a single number. For example, a multiplicity of 5 on a field indicates
that there must be exactly five occurrences of the field within its enclosing record.
Variable multiplicity is defined by a minimum value and a maximum value and is represented with the
notation n..n. For example, multiplicity of 1..5 on a field specifies that there must be between one and
five occurrences of the field within its enclosing record.
A special maximum value of -1 is used to represent no maximum. For example, a multiplicity of 1..-1 on
a field specifies there can be any number of occurrences of the field, but there must be at least one.
Optional nodes are defined by a minimum value of 0. For example, a multiplicity of 0..1 on a field
specifies that there can be zero of one occurrences of the field within its enclosing record. A fixed
multiplicity of 0 is not allowed.
Variable multiplicity fields must be defined at the end of their containing record. Variable multiplicity
records must be defined at the end of the content.
Default value
You can specify a default value for a field. If you do not specify a default value, the default value for the
data type is used. When content is created using metadata, default initialization applies the default
values specified for each field.
DEPRECATED: Single value topics
A topic that streams data as a single value that can be constrained to a defined data type. Single value
topics are stateful: each topic stores a value on the Diffusion server.
Note: The single value topic type is deprecated. Instead use one of the following topics,
depending on the format of the data you are publishing:
•
•
•
String topics on page 64
Double topics on page 66
Int64 topics on page 65
The value of the topic state is stored as a string. However, the type of the single value can be
constrained in certain ways, for example, to hold a string, an integer, or a decimal number. The type
of the single value is described using field metadata in the schema. For more information, see Record
metadata on page 77.
Why use a single value topic?
For the majority of use cases, single value topics are the most appropriate model for your data. Single
value topics are easy to create and update, and the data the topic contains is simpler.
Diffusion | 79
If a structure is required for your data, you can use the design of your topic tree to define the structure.
Single value topics are supported by all client APIs.
By defining the type of the single value of the topic, you benefit from automatic validation and
formatting of the data.
Properties of a single value topic
When you create a single value topic you can specify the following properties in the topic specification:
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
Considerations when using a single value topic
A single value topic can only hold textual data. This type of topic data cannot be used to publish nontextual data, such as a PNG or PDF file.
Single value topics cannot be used to make multiple updates transactionally. If you have multiple
items of data that you want to publish at the same time and have received by subscribing clients at
the same time, you cannot split these items of data across multiple single value topics. In this case, it
is more appropriate to use a topic with a composite data type such as a JSON topic or record topic. A
topic with a composite data type can contain fields for each of the data items.
DEPRECATED: Stateless topics
A topic that has no state held on the Diffusion server or on the client that publishes to it.
Note:
The stateless topic type is deprecated. Instead, use one of the following topic types with the
DONT_RETAIN_VALUE property set to true:
•
•
•
•
•
•
•
Binary topics on page 62
JSON topics on page 61
String topics on page 64
Double topics on page 66
Int64 topics on page 65
RecordV2 topics on page 72
Time series topics on page 67
A stateless topic has no state. It can be used for publishing and receiving updates, but not for fetching
the current topic state.
Stateless topics are the only type of topic provided in other typical pub-sub solutions.
Why use a stateless topic?
You can use stateless topics for data streams where there is no current state of the data, only updates.
For example, a feed of news items.
All handling of the topic and topic data of a stateless topic is done by your client application. Because
of this, the format of the data published on a stateless topic is entirely flexible. The topic content
is treated as byte data by the Diffusion server. How that byte data is handled and interpreted is
determined by your client applications.
Diffusion | 80
Properties of a stateless topic
When you create a stateless topic you can specify the following properties in the topic specification:
TIDY_ON_UNSUBSCRIBE
If set to true, when a session unsubscribes from the topic, any updates for the topic
that are still queued for the session are removed.
There is a performance overhead to using this option as the client queue must be
scanned to find topic updates to remove, however it may prove useful for preventing
unwanted data being sent to sessions. This property is disabled by default.
Considerations when using a stateless topic
A stateless topic does not store state. You cannot fetch the topic state and when you first subscribe to
a stateless topic you do not receive the topic state as a value as you do with a stateful topic.
You must write all of the logic in your client to handle the byte data that is published on a stateless
topic. This might mean it takes longer to get started using a stateless topic compared to topics that are
handled by the Diffusion server.
You also lose some of the benefits of having a topic whose content is understood by the Diffusion
server, such as validation, formatting, conflation, and deltas.
Pub-sub
Having decided on your topic structure and the format of your data, consider how you publish the data
through the topics.
Pub-sub is the primary model of data distribution used by Diffusion. Clients subscribe to a topic. When
data is published to the topic as an update, the Diffusion server pushes that update out to all of the
subscribed clients.
Figure 2: Pub-sub model
A client can both publish to topics and subscribe to topics, depending on the permissions that client
has.
Concepts
Update
An update is data published to a topic by a client or publisher that is applied to
the topic to change the topic state. The updated data is then pushed out to all
subscribing clients.
State
Diffusion | 81
The latest published values of all data items on the topic. The state of a topic is stored
on the Diffusion server.
Value
A value is an update that contains the current state of all data on the topic.
Delta
A delta is an update that contains only those items of data that have changed on the
topic since the last update was sent.
Topic loading
When a client first subscribes to a topic, it is sent a topic load message. A topic load is
a value update that contains the current state of the topic.
Fetch
A request for the current state of all data on the topic. A client can fetch a topic's state
without being subscribed to the topic. This request-response mechanism of getting
data from a topic is separate from topic subscriptions.
Topic notifications
A client can register to receive topic notifications which provide information about
which topics exist in the topic tree, but not the topic values. This is useful if your
client needs to monitor the structure of the topic tree (or part of the tree) without
the overhead of receiving all the values. Registering for notifications is separate from
subscribing to a topic.
Publishing data
Consider the following information when deciding how to publish data to topics.
Data type
The updates that you publish to a topic must have a data type and format that matches the data type
of the topic.
For example, if your topic is a single value topic where the data is of type integer, all updates published
to the topic must contain a single piece of integer data.
Similarly, if your topic is a record topic with a metadata structure defined, all updates published to the
topic must have the same metadata structure.
Updaters
You can use one of the following types of updater:
Value updater
This is the preferred type of updater to use with JSON and binary topics. When used
as exclusive updaters, value updaters cache the values they use to update topics. This
enables them to calculate and send deltas, reducing the volume of data sent to the
Diffusion server.
Standard updater
This type of updater updates topics that use content to represent their data values.
Updaters do not cache values and send all of the data passed to them to the Diffusion
server without performing any optimization.
Both updater types can be used exclusively or non-exclusively.
For more information, see Updaters.
Diffusion | 82
Exclusive updating
To update a topic exclusively, a client registers as the update source for that topic. Only one client can
be the active update source for a topic and any attempts by other clients to update that topic fail.
Implementing exclusive updating is more complex than non-exclusive updating as it involves the extra
step of registering as an update source.
A single client acting as the exclusive updater can be an advantage if you require that a single client
has ownership of a topic or branch of the topic tree. This requires less coordination and management
than updating a single topic from multiple clients.
If the ordering of the updates is important, use exclusive updating to ensure that a single client has
control over what data is published and when.
If you are using high-availability topic replication, clients must update the replicated topics
exclusively. Non-exclusive updates are not replicated by high-availability topic replication.
Non-exclusive updating
To update a topic non-exclusively, a client publishes updates to the topic and, if no other client has
registered to update the topic exclusively, the update is applied to the topic.
Non-exclusive updating is the simpler way to update a topic.
Clients that update a topic non-exclusively risk their updates being overwritten by updates from other
clients or that updates from multiple clients are published in a different order than intended.
If you use a value updater non-exclusively, the updater does not cache the value used to update the
topic.
Non-exclusive updating is not supported with topics that are replicated using the high-availability
capability.
Dynamically adding topics
A publishing client can create topics dynamically as and when the topics are required. For example, in
response to a subscription request from another client for a non-existent topic.
Security
To publish data to a topic, a client must have the update_topic permission for that topic.
For more information, see Permissions on page 129.
Subscribing to topics
Consider the following information when deciding how clients subscribe to topics.
For a client to receive a stream of updates from a topic, the following conditions must be met:
•
•
•
The client must subscribe using a selector that matches the topic.
The topic must exist on the Diffusion server.
The client must register at least one stream that matches the topic.
When all these conditions are met, the stream receives a subscription notification and an initial value
for the topic. The client receives subsequent updates to the topic through the stream.
The order in which these conditions are met does not affect the receipt of the subscription notification
and the initial value of the topic.
An exception is in the case of record topics. For more information, see Considerations when using
legacy record topics on page 86.
Diffusion | 83
Permissions
To subscribe to a topic, a client must have the select_topic permission and the read_topic permission
for that topic. For more information, see Permissions on page 129.
The rest of this section assumes that the client has the required permissions to complete the described
actions.
Subscription flow
1. The prerequisite conditions for subscribing to a topic are met:
a. The client selects a set of topics to subscribe to, or that selection is made on the client's behalf
by another client or by a publisher.
A client can select multiple topics using a topic selector. This subscription can be to topics that
match a particular regular expression or to topics in a particular branch of the topic tree. For
more information, see Topic selectors on page 50.
The selection made by the subscribe request is persistent and is stored on the Diffusion server.
Because selections are stored, the client can subscribe, pre-emptively, to topics that do not
currently exist.
b. The topic exists on the Diffusion server.
The topic can be created before or after any subscribe request that selects it. In both cases, the
client that makes the request is subscribed to the topic when both the selection and the topic
exist.
2. Both prerequisite conditions for a subscription are met and the client is subscribed to the topic.
The intersection of the topic paths that the client has selected for subscription and the topics that
exist on the Diffusion server defines the list of subscriptions that client has made.
For each client that connects to the Diffusion server, the Diffusion server stores a separate list of
subscriptions.
3. When the subscription is made, the value of the subscribed topic is sent to the client.
Diffusion | 84
Subsequent updates are sent as values or as deltas, depending on the topic specification and the
nature of the update.
Client handling of data from subscribed topics
1. When the client makes a subscription to a topic, the value of the topic is sent to the client.
Subsequent updates are sent as values or as deltas.
2. Values for each subscribed topic are stored in the subscribed topic cache on the client.
This is not the case for stateless or record topics, see Considerations when subscribing on page
85 and Considerations when using legacy record topics on page 86.
3. The client registers streams against a topic selector. A list of streams is maintained in the stream
registry.
4. If one or more streams exists that matches a topic, the value for that topic is received through the
matching streams.
Considerations when subscribing
The subscriptions a client has, which are defined by the intersection of the topics that exist on the
Diffusion server with the selections made by the client, determine what data is sent to the client from
the Diffusion server. To reduce the amount of data sent between the Diffusion server and the client,
only subscribe to those topics that the client uses.
The streams a client registers determine what topic values are available for the client to work with.
Adding and removing streams as they are needed by the client enables your application to access
topic values stored in the subscribed topic cache in real time.
When a client subscribes to a stateless topic, the values received are not stored in the subscribed topic
cache. If a stream is created after the topic is subscribed, the stream does not receive a value until the
next time the topic is updated.
Diffusion | 85
Considerations when using legacy record topics
When a client subscribes to a topic that uses the deprecated record type, the values received are not
stored in the subscribed topic cache. If a stream is created after the topic is subscribed, the stream
might only receive delta values. In this case, the client has no value to apply the deltas to and the
data is incorrect. When subscribing to record topics, always create the stream before requesting a
subscription.
This does not apply to the recordV2 topic type.
Topic notifications
Topic notifications enable a client to receive information about the topic tree structure, without topic
values.
A client can receive notifications about changes to selected topics through the topic notifications
feature.
The client must use a topic notification listener to receive notifications. Use topic selectors to specify
which topics the client will be notified about.
Selection and deselection
A client can request selections at any time, even if the topics do not exist at the server. Selections are
stored on the server and any subsequently added topics that match registered selectors will generate
notifications.
Notification contents
Each notification includes:
•
•
The topic specification of the topic
A notification type describing the change
Table 15: Notification types
Value
Meaning
ADDED
A new topic has been added matching a
registered selector
REMOVED
A selected topic has been removed
SELECTED
A newly-registered selector matched a topic that
already exists
DESELECTED
An existing topic is no longer selected due to a
selector being removed
For example, suppose a topic tree contains only the topic a/b/c. A listener registers the topic selector ?
a// which selects the topic a and all topics below it.
The listener will receive a notification containing the topic specification of the topic at a/b/c, and the
notification type SELECTED.
If a new topic is added at a/b/c/d, another notification will be received with the specification of the
new topic, and a notification type ADDED.
Diffusion | 86
Immediate descendant notifications
Listeners receive notifications about whether each selected topic has unselected immediate
descendants.
An immediate descendant means the first bound topic on any branch below a given topic path. By
monitoring immediate descendant notifications, you can implement a listener which selects deeper
topic paths as more topics are added, in order to walk the topic tree.
For example, in a topic tree which contains only these topics:
•
•
•
•
•
a
a/b
a/c
a/c/d
a/e/f/g
The immediate descendants of a are a/b, a/c and a/e/f/g.
a/c/d is not an immediate descendant of a, because its parent a/c is a descendant of a.
Immediate descendant notifications provide the topic path and a notification type (with the same
possible values as above).
In the example topic tree above, suppose that a topic notification listener had selected topic a using
the topic selector "a". If a topic is now added at a/x, the listener receives an immediate descendant
notification with the path a/x and the notification type ADDED.
If a topic is then added at a/x/y, the listener does not receive another notification, because a/x/y is not
an immediate descendant.
Considerations when using topic notifications
Topic notifications are useful when your client needs to know which topics are present, without
the overhead of receiving the topic values. This can be useful when developing monitoring tools or
interfaces designed to browse large numbers of topics.
A client will only be notified about topics for which it has both select_topic and read_topic
permissions.
The select_topic permission is required to select a topic with a listener. The read_topic permission is
required to receive notifications for a topic.
Request-response messaging
You can send request messages directly to a client session, a set of client sessions, or a message path.
The recipient of a message can respond to the request.
Concepts
Request
A message sent from one client session to another session, to a message path, or to a
set of sessions.
Response
A message sent in reply to a request message.
Data type
Request and response messages can contain data of one of the following types:
JSON, binary, string, 64-bit integer, or double.
Diffusion | 87
The response message is not required to be the same data type as the request it
responds to.
Message path
The path used to address the request messages.
The message path is made up of path segments separated by the slash character (/).
Each path segment can be made up of one or more Unicode characters.
Handler
An object registered by client session to handle requests sent on message paths in a
specific branch of the path hierarchy, and to respond to those requests.
Stream
An object used by a client session to receive requests sent to that client session, and
to respond to those requests.
Session properties
Properties assigned to a session, either by the Diffusion server or by an
authentication handler. These properties can be used to select the set of sessions to
send requests to.
For more information, see Session properties on page 275.
Send request messages in the following ways:
Send requests to a message path
Figure 3: A client session registers a handler on part of the topic tree
A client session with the send_to_message_handler permission can send requests on a message path.
The sending client session does not know which client session, if any, receives the request.
A client session with the register_handler permission can register a handler on a part of the topic tree.
This client session receives any requests that are sent on message paths in that part of the topic tree
and sends a response.
For more information, see Sending request messages to a message path on page 394.
Diffusion | 88
Send request messages to a specific client session
Figure 4: A client session can send requests through a message path to a known client session
A client session with the send_to_session permission that knows the session ID of a client session can
send requests through a message path to the known client session.
The responding client must have a request stream registered against a message path to receive
requests sent through that message path and respond to them.
For more information, see Sending request messages to a session on page 402.
Send request messages to a set of client sessions
Figure 5: A client can send requests through a message path to a set of client sessions
A client session with the send_to_session permission can send requests through a message path to a
filter that selects client sessions based on their session properties.
The responding client session must have a request stream registered against a message path to
receive and respond to requests sent through that message path.
For more information, see Sending request messages to a session on page 402.
Considerations when using request-response messaging
•
•
The data type of the request is not required to match the data type of the response. For more
information, see Typed requests and responses on page 392.
Messaging can use message paths that are the same as topic paths with topics bound to them.
However, there is no connection between messaging and topics. For more information, see
Message path on page 393.
One-way messaging
Diffusion also provides a capability to send one-way messages to a client session, a set of client
sessions, or a message path. These messages cannot be responded to directly.
In one-way messaging, the message data is not typed. Applications are responsible for serializing
messages to and from a binary format.
Messages sent using one-way messaging can include additional options, such as headers and a
message priority. These additional options are provided to allow compatibility with messaging to
publishers.
Diffusion | 89
One-way messaging provides the following guarantees:
•
•
If sending to a message path completes successfully, the message was definitely passed to a
publisher or a message handler registered by a client session.
If sending to a session completes successfully, the message was definitely passed to a message
stream registered by the session.
When sending to a filter completes successfully and returns the number of sessions that match the
filter, one-way messaging cannot guarantee that the message has been delivered to those sessions.
Advanced usage
This section contains information about the advanced capabilities available when interacting with
your data.
Conflation
Conflation of messages is the facility to treat two messages as being essentially the same and avoiding
sending duplicate information to clients.
Updates and messages for a client are queued on the Diffusion server. Each client has its own
outbound queue.
Conflation removes an existing message from the outbound client queue and replaces it with a newer,
equivalent message either at the position of the old message or at the end of the client queue. The
replacement message can either be a new update or a merge of the new update and an existing
update.
Conflation is an optional feature that can be applied to all clients, clients connecting through a specific
connector, or can be applied programmatically on a client-by-client basis.
Advantages of message conflation
There are many scenarios in realtime data distribution where data being communicated need not
only be current, but must always be consistent. Structural conflation maximizes for concurrency while
ensuring consistent delivery.
Concurrency
Both simple and structural conflation maximize for concurrency through avoiding
distributing soon to be stale data. The higher the rate of change, the higher the value
extracted. On other words, where clients or servers are running near to saturation
based on available connectivity Diffusion can automatically adapt to this load by
minimizing the data distributed.
In addition to the load-adaptive nature of conflation the effect is fair for clients
connected to the same topic. The frequency of distributed changes is evenly
amortized across clients.
Conflation favors currency and reduces (but cannot entirely eliminate) the delivery of
stale data.
Consistency
Structural conflation synthesizes complex event processing techniques in a highly
efficient (lock-free wait-free concurrency) form inside the server. The specific
knowledge of data structures and the semantic concerns for distributing data for a
given topic in a given system allows consistent views of the data to be delivered in a
way that is not possible with messaging technologies that treat messages as opaque.
Diffusion | 90
Considerations when using conflation
•
•
•
•
Do not use conflation if there are relationships or dependencies between topics. Conflation
alters the order of updates. If a conflated topic is temporally or causally related to another topic,
conflation can cause unwanted behavior.
Do not use conflation if individual updates carry forensic storage or audit trail requirements.
Delta updates are conflated. Snapshots are not conflated.
Normal priority updates are conflated. High or low priority updates are not conflated.
Types of message conflation
The types of conflation are simple conflation, where a new update replaces an older update, and
structural conflation, where the data from two updates is combined in accordance with a user-defined
operation to create a new update. Both types of conflation can either append the new update to the
end of the queue or replace an existing update on the queue.
No conflation
Figure 6: Message flow without conflation enabled
With no conflation, a stream of messages to a client is delivered to the client in the order that they are
published or sent. In example shown in the diagram, two messages for topic A, one message for topic
B, and two messages for topic C are ready to send to the client.
This is a scenario common to all messaging platforms.
Simple conflation
When a new message is published or sent to a topic, simple conflation removes any earlier messages
for that topic from the queue. The new message can be added to the queue either in the position of
the earlier message that was removed (replace) or at the end, preserving the original message order
(append).
Figure 7: Message flow with simple replace conflation enabled
With simple replace conflation, only the most recent message for a topic is delivered to the client.
In the example shown in the diagram, the first message published or sent to topic A is removed and
the second message is added to the queue in the position that the first message occupied. The same
occurs for the messages sent to topic C.
Diffusion | 91
Figure 8: Message flow with simple append conflation enabled
With simple append conflation, only the most recent message for a topic is delivered to the client.
Messages are delivered in time order. In the example shown in the diagram, the first message
published or sent to topic A is removed and the second message is added to the end of the queue. The
same occurs for the messages sent to topic C.
Note: Use this option with care because there is the possibility that messages for a topic can
continue to be added to the end of the queue and a value not be delivered for the topic.
In both the append and the replace example, although five messages were ready to send, only three
messages were sent. This saves bandwidth and ensures clients receive current data only.
Structural conflation
Structural conflation allows a user-defined operation to be plugged into Diffusion so that rather than
refreshing stale data with fresh data, a computation can be performed to merge, aggregate, reverse or
combine the effects of multiple changes into a single consistent and current notification to the client.
Figure 9: Message flow with merge and replace conflation enabled
In the example shown in the diagram, the operation is the summation of numeric data. The user
provides the merge algorithm, that is the summing of the values of two successive messages, and
Diffusion sends a single resulting message rather than the individual messages that were combined.
The messages A3 and C3 are new messages generated from the merging process.
This is suitable for any scenario where the result is required but individual components that combine
to form the result are not required.
The preceding example shows merge and replace. You can merge and append in a similar way as
described for simple conflation above.
Various options are available to the user-written merger so that instead of returning a merged
message it can indicate that either of the input messages be queued or that the no conflation option
be chosen.
Selection of messages for conflation
The preceding examples assume that when a new message is queued for a client it replaces or merges
with the last message queued for the message topic. This is the default behavior. When operating
conflation in this way there is only ever one message per conflated topic awaiting delivery to the client.
Diffusion | 92
You can specify a user-defined matcher that is used to determine the message that is to be replaced
or merged with. This can be used to inspect the content of the messages queued for a topic to select
which to conflate. When operating conflation in this way it is likely there can be more than one
message per conflated topic awaiting delivery to the client.
How conflation works
Conflation is implemented as a lock-free, wait-free algorithm and can be scaled to meet demands of
large numbers of concurrently subscribed clients.
Conflation policies
Conflation policies configure how conflation takes place on topics. One or more conflation policies can
be configured, each defining different conflation mechanisms. The policies can define the following
behaviors:
•
•
•
How messages are matched
Whether replacement is done in place or by appending
How to merge the two messages
Conflation process
When conflation is enabled for a client, every time a new update is enqueued for the client it is
considered for conflation.
1. The Diffusion server checks whether a conflation policy is mapped to the topic that the update is
published on.
2. If a policy is mapped to the topic, the matching criteria of the policy is used to scan the updates
for that topic that exist on the queue (from newest to oldest) for an existing update that matches
the one being queued. If no match is found, the new update is queued at the end of the queue as
normal.
Note:
Fast caches and lookup mechanisms are used to find policies and updates in the queue.
The whole client queue is not scanned when looking for a match, only updates for the same
topic.
If default matching (by topic) is used, no comparison with the existing updates is required.
This means that the conflation mechanism is as efficient as possible.
3. If the matching criteria finds a matching update in the queue, the conflation policy defines the
following behaviors:
•
•
Whether the update to queue as a result of conflation is the new update or an update produced
by merging the content of the matching update and new update.
Whether the update to queue replaces the matching update or the matching update is removed
and the new update added to the end of the queue.
Conflation occurs on a client-by-client basis in the multiplexer thread. Results of merge actions are
cached to ensure that merges of identical message pairs are not repeated for different clients.
Designing your solution
Decide how your solution components interact to most efficiently and securely distribute your data.
There are a number of things to consider when designing your Diffusion solution:
•
•
The number, distribution, and configuration of your Diffusion servers
How you use clients in your solution
Diffusion | 93
•
•
•
The additional components to develop
The third-party components you might include in your solution
Securing your solution
These considerations are not separate. The decisions you make about one aspect of your solution can
affect other aspects.
Servers
Consider the quantity, distribution, location and configuration of your Diffusion servers.
How many Diffusion servers?
Consider the following factors when deciding how many Diffusion servers to use in your solution:
Number of client connections
How many client connections do you expect to occur concurrently? For a greater
number of concurrent client connections, you might require more Diffusion servers to
spread the load between.
Volume of data
At what rate are you publishing updates and sending messages? How large are the
updates and messages? If you are distributing a greater volume of data, you might
require more Diffusion servers to spread the load between.
Hardware capabilities
The number of concurrent client connections and the volume of data that a single
Diffusion server can handle depend on the hardware that the Diffusion server runs on.
In order of importance, the following hardware components have the biggest impact
on the server performance:
•
•
•
Network interface controller (NIC)
Central processing unit (CPU)
Random access memory (RAM)
Resilience and failover requirements
Ensure that you have enough Diffusion servers that if one or more becomes
unavailable, for example when updating the server or due to a failure of the hosting
system, the remaining Diffusion servers can spread the resulting load increase.
You can also use replication between Diffusion servers to increase your solution's
resilience. For more information, see High availability on page 99.
Distribution of servers
How you wish to distribute your servers has an effect on how many servers you
require.
For example, if your client base is distributed geographically, you might want to
locate your Diffusion servers in different territories. This enables your servers to be
more responsive because of their proximity to clients. In this case, the number of
territories your client base is spread over affects the number of servers you require.
You can easily scale your solution by adding additional Diffusion servers if your requirements change.
How are your Diffusion servers configured?
Consider the following factors when deciding how to configure the Diffusion servers in your solution:
Diffusion | 94
Ports
What ports do you want to provide access to your Diffusion server on? By default,
your Diffusion server supports client connections on port 8080.
Reconnection behavior
Do you want to allow clients that lose their connection to reconnect to the server?
How long do you want to keep client sessions available after the client loses
connection?
Replication
Replication enables Diffusion servers to share information about topics and client
sessions with each other through a data grid.
For more information, see High availability on page 99.
Performance
Tuning your Diffusion servers for performance is best done as part of testing your
solution before going to production. This enables you to observe the behavior of your
solution in action and configure its performance accordingly.
For more information, see Tuning on page 596.
For more information, see Configuring your Diffusion server on page 519.
This manual describes the factors that you must consider when designing your Diffusion solution.
However, these factors are too many and too interlinked for this manual to provide specific guidance.
Push Technology provides Consulting Services that can work with you to advise on a solution that best
fits your requirements. Email for more information.
Fan-out
Consider whether to use fan-out to replicate topic information from primary servers on one or more
secondary servers.
A fan-out distribution comprises many servers that host the same topic or topics. When the topic is
updated on a primary server, the update is fanned out to replica topics on secondary servers.
Why use fan-out?
Having a primary server feed out updates to a number of secondary servers provides a solution where
the same topics and data are available from multiple servers. You can use this solution to load balance
a large number of client connections across a set of Diffusion servers and provide those clients with
the same access to data.
How fan-out works
Fan-out is configured on the secondary server or secondary servers in the solution.
Diffusion | 95
Figure 10: Fan-out
•
•
•
•
•
•
•
•
A secondary server connects to a primary server as a client.
The secondary server subscribes to a set of topics on the primary server.
This set of topics is defined by a selector in the configuration of the secondary server.
The secondary server replicates the subscribed topics locally.
When updates are made to the topics on the primary server, the secondary server receives these
updates through the standard pub-sub feature in the same way as any other client of the primary
server.
The secondary server applies the updates to its replica topics.
Any clients subscribed to a replica topic on the secondary server receive the updates through the
standard pub-sub feature.
If a topic is removed at the primary server, the secondary server removes its replica topic.
If a topic is added at the primary server that matches the set of topics subscribed by the secondary
server, the secondary server creates a local replica topic.
A secondary server can connect as a client and subscribe to topics on more than one primary server.
However, ensure that the secondary server does not attempt to replicate the same topic from multiple
sources as this can cause the data on the topic to be incorrect.
Creating topics on the primary server is an asynchronous action, because of this a client or publisher
that creates a topic on the primary server receives a completed callback saying that the topic has been
created. However, receiving this callback does not indicate that the topic has been replicated by fanout and created on a secondary server.
Topic aliasing is not supported for topics that are replicated by fan-out. Ensure that aliasing is not
enabled at the primary server.
Topic types supported by fan-out
Fan-out supports all topic types. However, there are additional considerations for the following topic
types:
•
Slave topics
The order of topic creation on the secondary server can prevent slave topics from being replicated.
For example, if a slave topic refers to a topic that does not yet exist because it is in a branch not yet
replicated or because it is lower down the link hierarchy.
Diffusion | 96
•
Routing topics
To use fan-out with routing topics, the routing subscription handlers for a routing topic must be
registered at the primary and all secondary servers. The routing logic provided by the handlers on
the primary and secondary server must be identical.
Topic replication and fan-out
A secondary server cannot replicate the same topic from more than one primary server or multiple
times from the same primary server. Validation of the path prefix of the selectors is in place to
prevent this occurring, but the use of regular expressions in topic selectors can result in an overlap of
replication which can cause problems.
Missing topic notifications generated by subscription or fetch requests to a secondary server are
propagated to missing topic handlers registered against the primary servers. For more information,
see Using missing topic notifications with fan-out on page 97.
Fan-out and load balancers
If you add a secondary server to a load balancer pool before all topics have propagated from
the primary server, it can result in a large number of messages being generated, leading to
MESSAGE_QUEUE_LIMIT_REACHED errors appearing in the logs.
If you experience this problem, introduce a delay between enabling fan-out and adding any of the
secondary servers to a load balancer pool. There is currently no built-in way to determine when
propagation is complete, so you will need to experiment to find out how long the delay needs to be for
your configuration.
Reconnection and disconnection
You can configure fan-out servers to use the standard reconnect mechanism. If the connection
between the secondary server and the primary server is lost, the secondary server can reconnect
to the same session. However, if messages are lost between the primary and secondary server, the
reconnection is aborted and the session closed. The secondary server must connect again to the
primary server with a new session.
If a disconnection between the primary and secondary server results in the session being closed, the
secondary server removes all the topics that it has replicated from that primary server. (Only topics
explicitly defined by a selector are removed.) Clients subscribing to these topics on the secondary
server become unsubscribed. If the secondary server connects again to that primary server with a new
session, the secondary server recreates the topics. Clients connecting to the secondary server become
resubscribed to the topics.
Related concepts
Configuring fan-out on page 523
Configure the the Diffusion server to act as a client to one or more other Diffusion servers and replicate
topics from those servers.
Using missing topic notifications with fan-out
Missing topic notifications generated by subscription or fetch requests to a secondary server are
propagated to missing topic handlers registered against the primary servers.
Control client sessions can use missing topic notifications to monitor the activity of end-user client
sessions. In response to subscription or fetch requests to missing topics, the control client session can
choose to take an action, such as creating the missing topic.
For more information, see Handling subscriptions to missing topics on page 305.
Diffusion | 97
How notification propagation works
A missing topic notification is propagated from a secondary server to a missing topic handler
registered against a primary server if and only if all of the following conditions are met:
•
•
•
•
•
There is a session between the secondary server and the primary server.
The selector used for the subscription or fetch request to the secondary server intersects with one
or more of the fan-out links to the primary server that are configured at the secondary server.
On the secondary server, there are no currently replicated topics that match both the fan-out link
and selector used in the subscription or fetch request.
The primary server has no topics that match the selector used in the subscription or fetch request.
One or more missing topic handlers are registered against the primary server for a path that
matches the selector. The following rules are used to select which missing topic handler receives
the notification:
•
•
If multiple handlers are registered for the branch, the handler for the most specific topic path is
notified.
If there is more than one handler for a path, the Diffusion server notifies a single handler.
The handler can use the supplied callback to respond proceed or cancel. The subscription or fetch
operation is delayed until the handler responds, and is abandoned if the response is cancel.
Example flow
Figure 11: Missing topic notification propagation
1. A control client connects to the primary server and registers a missing topic notification handler
against the A branch of the topic tree.
2. A secondary server connects to the primary server and replicates the A branch of the topic tree.
3. On the secondary server the replicated part of the topic tree comprises the following topics: A, A/B
and A/C.
4. An end-user client attempts to subscribe to A/D, which does not exist.
5. The topic A/D is in part of the topic tree that is matched by a fan-out link selector, so the secondary
server propagates the missing topic notification to the primary server.
6. The topic A/D does not exist on the primary server, so the primary server sends the missing topic
notification to the handler registered by the control client.
Diffusion | 98
Missing topic notification handlers at both the primary and secondary servers
A single subscription or fetch can cause a missing topic notification to be sent to a handler registered
against the secondary server as well as a handler registered against a primary server.
The decision about whether to notify the handlers registered against a primary server is based on the
intersection of the selector used by the subscription or fetch with the selector used to configure the
fan-out link. It is possible for a missing topic notification to be sent to the primary server, but not to
local handlers because the selector matches other (non-replicated) topics hosted by the secondary.
In particularly complex configurations, multiple primary servers might receive the notification or there
can be multiple tiers of fan-out connections.
Where multiple handlers are notified, the subscription or fetch operation is delayed until the all
handlers respond, and the operation is abandoned if any response is cancel.
Considerations when using missing topic notifications with fan-out
Missing topic notifications are only propagated if both the primary and secondary server are Diffusion
version 5.9.1 or later.
The intersection of the selector used by the subscription or fetch request with a selector used for a fanout link is calculated based only on the path-prefix of each selector. Complex selectors that use regular
expressions can produce false positive results or false negative results. We recommend that you do not
use regular expressions in the selectors used to configure fan-out links.
Ensure that the principal that the secondary server uses to make the fan-out connection to the primary
server has the SELECT_TOPIC permission for the path prefix of the selector that triggered the missing
topic notification.
A current session must exist between the secondary server and the primary server to forward
notifications. If there is no session or the session fails while the missing topic notification is in-flight,
the secondary server logs a warning message and discards the notification. The subscription or fetch
operation is completed as if the primary handler had responded proceed.
The robustness of the session between the servers can be improved by configuring reconnection.
Fan-out connections can have a large number of messages in flight. It might be necessary to tune the
reconnection time-out and increase queue depth and recovery buffer sizes.
Related concepts
Handling subscriptions to missing topics on page 305
A client can handle subscription or fetch requests for topics that do not exist and can act on those
notifications, for example, by creating a topic on demand that matches the request.
High availability
Consider how to replicate session and topic information between Diffusion servers to increase
availability and reliability.
Diffusion uses a datagrid to share session and topic information between Diffusion servers and provide
high availability for clients connecting to load-balanced servers.
Diffusion | 99
Figure 12: Information sharing using a datagrid
Diffusion uses Hazelcast™ as its datagrid. Hazelcast is a third-party product that is included in the
Diffusion server installation and runs within the Diffusion server process.
The datagrid is responsible for the formation of clusters and the exchange of replicated data. These
clusters operate on a peer-to-peer basis and by default there is no hierarchy of servers within the
cluster.
Servers reflect session and topic information into the datagrid. If a server becomes unavailable,
another server can access the session and topic information that is stored in the datagrid and take
over the responsibilities of the first server.
Considerations
Consider the following factors when using replication with Hazelcast:
•
•
By default Hazelcast uses multicast to discover other nodes to replicate data to. This is not secure
for production use. In production, configure your Hazelcast nodes to replicate data only with
explicitly defined nodes. For more information, see Configuring the Hazelcast datagrid on page
560.
When Diffusion servers are merged into a cluster, the servers can have inconsistent replicated
data. Unresolved inconsistencies can cause upredictable behavior. If the inconsistencies cannot be
resolved, the inconsistent Diffusion server or servers are shutdown and must be restarted.
Diffusion servers in a cluster can become inconsistent in a number of circumstances, for example, if
a network partitions and then heals.
Session replication
You can use session replication to ensure that if a client connection fails over from one server to
another the state of the client session is maintained.
When a connection from a client through the load balancer to a Diffusion server fails, the load balancer
routes the client connection to another Diffusion server. This server has access to the session and
client information that is replicated in the datagrid.
Clients that connect to a specific Diffusion server and not through a load balancer cannot use session
replication.
Diffusion | 100
Diffusion | 101
The load balancer is configured to route based on the client's session ID and requests from the
client go to the same server until that server becomes unavailable.
2. Information about the client session is reflected into the datagrid.
The following information is replicated:
•
•
•
•
session ID
session principal
session properties
list of topic selections
The following information is not replicated and is created anew on the server a client fails over to:
3.
4.
5.
6.
7.
• session start time
• statistics
• client queue
A client loses connection to the Diffusion server if the server becomes unavailable.
The client can reconnect and the load balancer routes the connection to another Diffusion server.
This Diffusion server has access to all of the client information shared into the datagrid by the first
Diffusion server.
The server uses the list of topic selections to recover the set of subscribed topics and subscribes the
client to these topics.
Subscribing the client to topics provides full value messages for all topics that contain the current
topic state.
The client can reconnect to its session only if it reconnects within the reconnect time specified in the
Connectors.xml configuration file. If the client does not reconnect within that time, the client
session information is removed from the datagrid.
Considerations
Consider the following factors when using session replication:
•
•
•
•
•
Replication of session information into the datagrid is not automatic. It must be configured at the
server.
Messages in transit are not preserved.
When a client session reconnects it does not need to authenticate again. The client uses a session
token to reacquire its session. Ensure that this token is secure by using a secure transport to
connect, for example, WSS.
The failover appears to the client as a disconnection and subsequent reconnection. To take
advantage of high server availability, clients must implement a reconnect process.
The Diffusion server that a client reconnection attempt is forwarded to depends on your load
balancer configuration. Sticky load balancing can be turned on to take advantage of reconnection
or turned off to rely on session replication and failover.
Differences between session reconnection and session failover
When a client loses a load-balanced connection to Diffusion, one of the following things can occur
when the client attempts to reconnect through the load balancer:
Session reconnection
The load balancer forwards the client connection to the Diffusion server it was
previously connected to, if that server is still available. For more information, see
Reconnect to the Diffusion server on page 259.
Session failover
Diffusion | 102
The load balancer forwards the client connection to a different Diffusion server
that shares information about the client's session, if session replication is enabled
between the servers.
Prefer session reconnection to session failover wherever possible by ensuring that the load balancer is
configured to route all connections from a specific client to the same server if that server is available.
Session reconnection is more efficient as less data must be sent to the client and has less risk of data
loss, as sent messages can be recovered, in-flight requests are not lost, and handlers do not need to be
registered again.
For more information, see Routing strategies at your load balancer on page 740.
To a client the process of disconnection and subsequent reconnection has the following differences for
session reconnection or session failover.
Session reconnection
Session failover
The client connects to the same Diffusion server
it was previously connected to.
The client connects to a Diffusion server different
to the one it was previously connected to.
The client sends its last session token to the server.
The server authenticates the client connection or validates its session token.
The server uses the session token to
resynchronize the streams of messages between
the server and client by resending any messages
that were lost in transmission from a buffer of
sent messages.
The server uses the session token to retrieve
the session state and topic selections from the
datagrid.
If lost messages cannot be recovered because
they are no longer present in a buffer, the server
aborts the reconnection.
The server sends any messages that have been
queued since the session disconnected.
The server uses the state to recover the session,
uses the topic selections to match the subscribed
topics, and sends the session the current topic
value for each subscribed topic.
Any in-flight requests made by the client session
to the previous server are cancelled and the
client session is notified by a callback. All
handlers, including authentication handlers
and update sources, that the client session had
registered with the previous server are closed
and receive a callback to notify them of the
closure.
Related concepts
Session reconnection on page 606
You can configure the session reconnection feature by configuring the connectors at the Diffusion
server to keep the client session in a disconnected state for a period before closing the session.
Related tasks
Configuring the Diffusion server to use replication on page 559
You can configure replication by editing the etc/Replication.xml files of your Diffusion servers.
Related reference
Topic replication on page 104
Diffusion | 103
You can use topic replication to ensure that the structure of the topic tree, topic definitions, and topic
data are synchronized between servers.
Failover of active update sources on page 106
You can use failover of active update sources to ensure that when a server that is the active update
source for a section of the topic tree becomes unavailable, an update source on another server is
assigned to be the active update source for that section of the topic tree. Failover of active update
sources is enabled for any sections of the topic tree that have topic replication enabled.
Configuring the Hazelcast datagrid on page 560
You can configure how the built-in Hazelcast datagrid replicates data within your solution
architecture.
Topic replication
You can use topic replication to ensure that the structure of the topic tree, topic definitions, and topic
data are synchronized between servers.
Figure 14: Topic replication
1. Servers with topic replication enabled for a section of the topic tree share information about
that section of the topic tree through the datagrid. The topic information and topic data are
synchronized on all the servers.
2. A new topic is created on one server in the replicated section of the topic tree.
3. The new topic is replicated on the other servers with identical topic information. When its topic
data is updated on the first server, that data is replicated on the other servers.
Diffusion | 104
Considerations
Consider the following factors when using topic replication:
•
•
•
Avoid registering a large number of update sources. Do not design your solution so that each topic
has its own exclusive update source. This will cause performance problems when starting new
servers and joining them to existing clusters, due to the overhead of sharing the update source
registrations.
Only publishing topics can be replicated.
Replication is supported only for the following types of topic:
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
•
JSON
Binary
Int64
String
Double
RecordV2(not the older, deprecated record topic type)
Stateless
Slave
A replicated slave topic is linked to a master topic located on the same Diffusion server as the
replicated slave topic. This is true whether that master topic is created by replication or directly.
Replication is not supported for the deprecated single value and record topic types.
Replication is not supported for topics created by a publisher.
Any topic that is part of a replicated branch of the topic tree and is not one of the supported types
of topic is not replicated. Instead that topic path remains unbound.
Only topic-wide messages are replicated. Messages sent to a single client or to all clients except
one are not replicated.
Replication of topic information into the datagrid is not automatic. It must be configured at the
server. This gives a performance advantage, as you can choose which parts of your topic tree to
replicate.
Updates to a particular topic will always be delivered to all subscribers in the same order. However,
delivery order is not guaranteed across different topics. For example, if you update topic 1 then
topic 2, some subscribers might receive the update to topic 2 before the update to topic 1.
Replication of topic data can impact performance.
Avoid registering requests for topic removal on client session close against replicated topics. When
a replicated topic is removed from a server as a result of a client session closing, it is removed from
all other servers that replicate that topic. For more information, see Removing topics with sessions
on page 342.
Related tasks
Configuring the Diffusion server to use replication on page 559
You can configure replication by editing the etc/Replication.xml files of your Diffusion servers.
Related reference
Session replication on page 100
You can use session replication to ensure that if a client connection fails over from one server to
another the state of the client session is maintained.
Failover of active update sources on page 106
You can use failover of active update sources to ensure that when a server that is the active update
source for a section of the topic tree becomes unavailable, an update source on another server is
assigned to be the active update source for that section of the topic tree. Failover of active update
sources is enabled for any sections of the topic tree that have topic replication enabled.
Configuring the Hazelcast datagrid on page 560
Diffusion | 105
You can configure how the built-in Hazelcast datagrid replicates data within your solution
architecture.
Failover of active update sources
You can use failover of active update sources to ensure that when a server that is the active update
source for a section of the topic tree becomes unavailable, an update source on another server is
assigned to be the active update source for that section of the topic tree. Failover of active update
sources is enabled for any sections of the topic tree that have topic replication enabled.
A client must register as an update source to update a replicated topic. Replicated topics cannot be
updated non-exclusively. For more information about update sources, see Updating topics on page
351.
1. A client (CLIENT 1) connects to a Diffusion server (SERVER 1) and registers an update source for a
section of the topic tree that has topic replication enabled. This update source is the active update
source.
2. Another client (CLIENT 2) connects to another Diffusion server (SERVER 2) and registers an update
source for the same section of the topic tree. This update source is a standby update source.
3. The topics on SERVER 2 continue to receive their updates from CLIENT 1 through the datagrid.
4. If SERVER 1 or CLIENT 1becomes unavailable, the update source registered by CLIENT 2 becomes
active. SERVER 2 sends CLIENT 2 a callback to notify it that it is the active update source.
On SERVER 2, the topics in that section of the topic tree receive their updates from CLIENT 2.
SERVER 2 reflects this topic data into the datagrid.
Considerations
Consider the following factors when using failover of active update sources:
•
•
•
If the topic paths that the updating client uses to register an update source do not match the topic
paths configured in the Replication.xml configuration file of the server, unexpected behavior
can occur.
The mechanism that provides failover of active update sources assumes that all servers have the
same configuration and that all control clients implement the same behavior as part of a scalable
and highly available deployment. If this is not the case, unexpected behavior can occur.
Do not use topic replication and failover of active update sources on sections of the topic tree that
are owned and updated by publishers. Topic updates sent by publishers are not replicated.
Related concepts
Updating topics on page 351
A client can use the TopicUpdateControl feature to update topics.
Related tasks
Configuring the Diffusion server to use replication on page 559
You can configure replication by editing the etc/Replication.xml files of your Diffusion servers.
Related reference
Session replication on page 100
You can use session replication to ensure that if a client connection fails over from one server to
another the state of the client session is maintained.
Topic replication on page 104
You can use topic replication to ensure that the structure of the topic tree, topic definitions, and topic
data are synchronized between servers.
Configuring the Hazelcast datagrid on page 560
Diffusion | 106
You can configure how the built-in Hazelcast datagrid replicates data within your solution
architecture.
Topic persistence
Consider if you want to enable topic persistence for fast recovery of topic state when Diffusion servers
restart.
Topic persistence
Topic persistence enables a server to store the state of topics (and the topic tree) to the local file
system as a special persistence log file. When the server restarts, topics are automatically restored to
the state they were in when the server was stopped.
The persistence log only stores the most recent state of each topic, not the topic history.
Persistence avoids the need for the server to rebuild the topic tree from scratch when it starts.
Persistence is useful to speed up development, as well as for backing up and restoring the state of
topics at a particular point in time.
Considerations
Consider the following factors when using topic persistence:
•
•
•
•
•
•
•
•
•
•
•
•
Persistence is either enabled or disabled on each server, and it is disabled by default. If it is
enabled, it is applied to all supported topics. There is no way to apply it to just part of the topic
tree.
Persistence does not support the deprecated single value and record topic types.
Persistence does not support publisher-created topics.
Each server has an append-only log file of topic events. By default, this is stored in a directory
named persistence under the Diffusion server home directory.
The log file is written until it reaches 200MiB in size. It is then switched out of service and
automatically compacted to save storage space. Compaction removes redundant information
(for example, if a topic has been removed, the history of values for that topic is removed during
compaction, and only the last value is retained).
Enabling persistence consumes a significant amount of storage space. You will need to allow
200MiB for the active log file, plus space for the compacted file. The size of the compacted file
depends on the details of your application. The more topics you have, the bigger the file will be.
As a rough indication of the size of the compacted file, it contains two records for each topic: a
topic record and a value record. The size of each topic record will be approximately the length of
the topic name plus 20 bytes. The size of each value record will be approximately the length of the
value or delta plus 13 bytes. In practice, you should allow extra space and make sure to monitor the
free space available on the server.
You can configure the directory where the persistence files are stored. See Configuring topic
persistence on page 531 for details.
You should only back up or restore a persistence log while the Diffusion server is not running.
Enabling persistence increases memory usage. The compaction service uses about as much
memory as it takes to store the topics themselves.
If servers close unexpectedly (for example, due to a crash), the persistence feature may not log the
most recent topic updates, resulting in some data loss when the server restarts.
Current information is always prioritised. During recovery from the file system, if a newer state of a
topic is available (for example, from an application update or by replication from another server),
that state will be used instead.
Diffusion | 107
•
If you do not want topics to be restored when you next start the server, simply delete all the log
files.
Related tasks
Configuring topic persistence on page 531
Use the Server.xml configuration file to configure the topic persistence feature.
Clients
Consider how you use clients in your solution.
Clients are key to a Diffusion solution. Your solution must include clients as an endpoint to distribute
data to. However, clients can also be used for control purposes.
When using clients in your solution, consider the following:
•
•
What types of client you require
What you use your clients for
Client types
Diffusion provides APIs for many languages and platforms. Some of these APIs have different levels of
capability.
A client's type is a combination of the API it uses and the protocol it uses to connect to the Diffusion
server.
APIs
JavaScript
Use this API to develop browser or Node.js™ clients.
Apple
Use this API to develop clients in Objective-C for iOS, tvOS, or macOS.
Android
Use this API to develop mobile clients in Java.
Java
Use this API to develop Java clients.
.NET
Use this API to develop clients in C#.
C
Use this API to develop C clients for Linux, Windows, or macOS.
Protocols
The following protocols, and their secure versions, are available:
WebSocket
The WebSocket implementation provides a browser-based full duplex connection,
built on top of WebSocket framing. This complies with the WebSocket standards and
is usable with any load balancer or proxy with support for WebSocket.
HTTP Polling
Diffusion | 108
The HTTP polling protocol uses HTTP requests with header m=1 to poll the server and
HTTP requests with header m=2 to send messages to the server.
Table 16: Supported protocols by client
Client
WebSocket
HTTP
Polling
JavaScript API
Apple API
Android API
Java API
.NET API
C API
Using clients
Most clients connect to the Diffusion server only to subscribe to topics and receive message data on
those topics. Some clients can also perform control actions such as creating and updating topics or
handling events.
Subscribe to topics and receive data
The majority of clients that connect to the Diffusion server, do so to subscribe to topics and receive
updates that are published to those topics. These are the clients used by your customers to interact
with the data your organization provides.
Control Diffusion, other clients, or the data
You can also develop clients that control aspects of the Diffusion server, other clients, or the
data distributed by Diffusion. These are the clients that are used by users or systems inside your
organization.
Using clients for control
Clients can perform control actions that affect the Diffusion server, other clients, or the data
distributed by Diffusion.
Note: Support for these control features can differ slightly between APIs. For more
information, see the documentation for the feature.
When designing your Diffusion solution, decide whether you want to use clients to perform the
following actions:
Diffusion | 109
Create and delete topics
Clients can create any type of topic on the Diffusion server. These topics can be created explicitly or
dynamically in response to a subscription request from another client.
These topics have the lifespan of the Diffusion server unless the client specifies that the topic be
removed when the client session closes.
Clients can also delete topics from the Diffusion server.
You can also use publishers to create and delete topics.
For more information, see Managing topics on page 291.
Publish updates to topics
Clients can publish updates to topics that are pushed out to clients subscribed to that topic. These
updates can be made exclusively, so that only one client can update a given topic, or non-exclusively,
allowing any client to update a given topic.
Note: Do not design your solution to require a large number of update sources (for example,
do not give each topic its own exclusive topic updater).
You can also use publishers to publish updates to topics.
For more information, see Updating topics on page 351.
Subscribe other clients to topics
Clients can subscribe other client sessions to topics and also unsubscribe other client session from
topics.
For more information, see Managing subscriptions on page 382.
Authenticate other clients
Clients can provide authentication decisions about whether to allow or deny other client sessions
connecting to the Diffusion server. These clients can also assign roles to the connecting client sessions
that define the permissions the connecting client has.
Diffusion | 110
You can also use the system authentication handler or an authentication handler located on the
Diffusion server to authenticate other clients.
For more information, see User-written authentication handlers on page 139.
Modify the security information stored on the Diffusion server
Clients can modify the information stored in the security store on the Diffusion server. The security
store can be used to specify which permissions are assigned to roles and which roles are assigned to
anonymous sessions, and named-principal sessions.
For more information, see Updating the security store on page 440.
Modify the authentication information stored on the Diffusion server
Clients can modify the information stored in the system authentication store on the Diffusion server.
The system authentication store can be used to specify which principals a client session can use to
connect and what roles are assigned to an authenticated client session.
For more information, see Updating the system authentication store on page 427.
Manage the flow of data to clients
Updates are pushed to subscribing clients through client queues. Clients can receive notifications
when client queues reach a certain threshold. These clients can manage the client queues by turning
on throttling or conflation for the queue.
For more information, see Managing sessions on page 452.
To handle messages sent to message paths by clients and send messages to specific clients
Clients can send messages through message paths to specific clients. Clients can also register to
handle messages that are sent to a message path. Messages sent using topic paths do not update the
topic.
You can also use publishers to handle messages on message paths and send messages to clients.
For more information, see .
Diffusion | 111
User-written components
Consider which components you must develop to create your solution.
Publishers
Consider whether to develop publishers to distribute data in your solution.
Note: We recommend using a client to create and publish to topics, instead of a publisher.
Publishers are written in Java and deployed on the Diffusion server.
You can deploy one or more publishers on a Diffusion server. A publisher can provide the behavior
of one or more topics but a topic can belong to only one publisher. The publisher infrastructure is
provided by Diffusion and the behavior is provided by the user by writing a publisher.
Why use publishers?
Publishers enable you to manage your topics and updates, and customize their behavior. Unlike
clients, publishers are located on the Diffusion server so can communicate more swiftly with the server
and do not become disconnected from the server.
Publishers provide the following capabilities:
•
•
•
•
•
•
•
•
•
•
Create topics
Remove publisher-created topics
Publish updates to topics
Define topic load data
Provide topic state to fetch requests
Send and receive messages to message paths
Handle requests for topics that do not exist
Validate client connections
Receive notifications of client events
Subscribe clients to topics
Considerations when using a publisher
Publishers can only be written in Java.
Publishers cannot remove topics created by a client.
Clients cannot remove topics created by a publisher.
Other user-written components
Diffusion provides many opportunities to create user-written components that define custom
behavior. Consider whether to develop any of these components as part of your solution.
Server-related components
All of these components must be created as Java classes and put on the classpath of the Diffusion
server.
Authentication handlers
These components handle authentication of clients that connect to the Diffusion
server or change the principal that they use to connect to the Diffusion server. If the
client connection is allowed, the authentication handler assigns roles to the client
session.
Diffusion | 112
You can have zero, one, or many authentication handlers configured on your
Diffusion server.
For more information, see Developing a local authentication handler on page 484
and Developing a composite authentication handler on page 486.
Note: Local authentication handlers, on the Diffusion server, can be
written only in Java. However, control authentication handlers that
are part of a client whose API supports Authentication Control can be
written in other languages.
Hooks
Startup and shutdown hooks are called by the Diffusion server. The startup hook
is instantiated and called as the Diffusion server starts and before publishers are
loaded. The shutdown hook is called as the Diffusion server stops.
For example, you can use a shutdown hook to persist some aspect of the state of the
Diffusion server to disk.
HTTP service handlers
These components handle HTTP requests as part of an HTTP service in the Diffusion
server's built-in web server. Provide a user-written HTTP service handler to enable
the Diffusion web server to handle any kind of HTTP request.
Thread pool handlers
These handlers define custom behavior in the Diffusion server related to the inbound
thread pool.
You can provide a rejection handler that customizes the behavior when a task cannot
be run by the thread pool. By default, if a task cannot be run by the inbound thread
pool — for example, if the thread pool is overloaded — the calling thread blocks until
there is space on the queue.
You can provide a notification handler that receives notifications when events occur
on the inbound thread pool.
Topic- and data-related components
All of these components must be created as Java classes and put on the classpath of the Diffusion
server.
Message matchers
Message matchers are used to customize conflation behavior. These classes that
define how the Diffusion server locates messages on a client's message queue that
are to be conflated.
By default, messages for conflation are matched if they are on the same topic.
For more information, see Conflation on page 90.
Message mergers
Message mergers are used to customize conflation behavior. These classes that
define how the Diffusion server conflates matching messages.
By default, the older of the matching messages is removed.
For more information, see Conflation on page 90.
Custom field handlers
These components handle the data in custom fields of your record topics. A custom
field handler can define the default value of a custom field, parse incoming data into
Diffusion | 113
the format required by the custom field, and compare data in custom fields of the
same type for equality.
For more information, see Record metadata on page 77.
Routing topic handlers
These components handle the behavior of a routing topic. When you create a routing
topic, you provide a routing topic handler that, when a subscription to the routing
topic is made, maps the routing topic to another topic on the Diffusion server on a
client-by-client basis.
For more information, see Routing topics on page 70.
Third party components
Diffusion interacts with a number of third-party components. Consider how you use these components
as part of your solution.
Load balancers
We recommend that you use load balancers in your Diffusion solution.
Why use load balancers?
Balancing client traffic across multiple Diffusion servers
Distribute incoming requests from clients fairly over the Diffusion servers in your
solution and ensure that all traffic for a specific client is routed to the same Diffusion
server.
Compositing URL spaces
If your Diffusion servers are located at a different URL to the Diffusion browser clients
hosted by your web servers, you can use a load balancer to composite the URL
spaces. This enables Diffusion solutions to interoperate with browser security.
SSL offloading
Diffusion | 114
Diffusion clients can connect to your solution using TLS or SSL. The TLS/SSL can
terminate at your load balancer or at your Diffusion server. Terminating the TLS at
the load balancer reduces CPU cost on your Diffusion servers.
Considerations when using load balancers
Do not use connection pooling for connections between the load balancer and the Diffusion server.
If multiple client connections are multiplexed through a single server-side connection, this can cause
client connections to be prematurely closed.
In Diffusion, a client is associated with a single TCP/HTTP connection for the lifetime of that
connection. If a Diffusion server closes a client, the connection is also closed. Diffusion makes no
distinction between a single client connection and a multiplexed connection, so when a client sharing
a multiplexed connection closes, the connection between the load balancer and Diffusion is closed,
and subsequently all of the client-side connections multiplexed through that server-side connection
are closed.
Multiple users masquerading behind a proxy or access point can have the same IP address, and all
requests from clients with that IP address are routed to the same Diffusion instance. Load balancing
still occurs, but some hosts might be unfairly loaded.
Web servers
Consider how to use web servers as part of your Diffusion solution.
If you are using Diffusion in conjunction with a web client or web application, this web client or
application must be hosted on a web server.
While the Diffusion server includes a web server, this internal web server is intended for the following
uses:
•
•
•
Hosting the Diffusion landing page, demos, and monitoring console
Providing an endpoint for the HTTP-based transports used by Diffusion clients
Optionally, hosting a static page you can use the check the status of the Diffusion server
For more information, see Diffusion web server on page 732.
Do not use the Diffusion web server as the host for your production website. Instead use a third-party
web server.
There are two ways you can use Diffusion with a third-party web server:
•
•
As separate, complementary components in your solution.
With the Diffusion server deployed inside a web application server.
Diffusion | 115
Use a separate web server with the Diffusion server
Figure 15: Using a web server with Diffusion
Why use a separate web server with Diffusion?
You can use a third-party web server to host your Diffusion browser clients.
A third-party web server provides the following advantages over the lightweight internal Diffusion web
server:
•
•
•
Greater ability to scale
More comprehensive security
Server-side code and dynamic web pages
If your organization already uses a third-party web server, Diffusion augments this component instead
of replacing it.
Using a separate web server with the Diffusion server provides the following advantages over
deploying the Diffusion server inside a web application server:
•
•
•
•
The load balancer set up is simpler
You can scale the number of Diffusion servers and the number of web servers in your solution
independently and more flexibly
The web server and the Diffusion server do not share a JVM process, which can cause performance
advantages
The web server and the Diffusion server are independent components, which makes them unlikely
to be affected by any problems that occur in the other component
For more information, see Hosting Diffusion web clients in a third-party web server on page 734.
Considerations when using a separate web server with the Diffusion server
If your web server hosts a client that makes requests to a Diffusion server in a different URL space, you
can use a load balancer to composite the URL spaces and interoperate with browser security or you
can set up cross-domain policy files that allow requests to the different URL space.
When the Diffusion server is separate from the web server, the web server has no access to the
Diffusion Publisher API.
Diffusion | 116
Deploy the Diffusion server inside a web application server
Figure 16: Deploying Diffusion inside a web application server
Why deploy Diffusion inside a web application server?
You can also host your Diffusion server inside a third-party web application server that has the
capability to host Java servlets.
This provides the advantage of only setting up a single server and having a single application to
manage when hosting your web application.
The web application server has access to the Diffusion Publisher API of the Diffusion server is hosts.
This enables your web application to use server-side logic to include Diffusion information in your web
pages.
For more information, see Running the Diffusion server inside of a third-party web application server
on page 735.
Considerations when deploying the Diffusion server inside a web server
When running inside a web application server, the Diffusion server still requires its own internal web
server to communicate with clients over HTTP-based transports.
Your web application and your Diffusion server, while hosted by the same server, can have different
port numbers. This can result in cross-origin security concerns for some browsers. You can use a load
balancer to composite the ports or you can set up cross-domain policy files that allow requests to the
different ports.
The load balancer configuration can be more complex when deploying the Diffusion server inside
a web application server. If you have multiple web application server and Diffusion server pairs,
configure your load balancer to ensure that requests from a client always go to a pair and not to the
web application server from one pair and the Diffusion server from another pair.
When running the Diffusion server inside of a web application server, the Diffusion server and the web
application server share a JVM process. This can lead to large GC pauses. Ensure that you test this
configuration and tune the JVM
Related concepts
Web servers on page 732
Diffusion | 117
Diffusion incorporates its own basic web server for a limited set of uses. The Diffusion server also
interacts with third-party web servers that host Diffusion web clients. The Diffusion server is also
capable of being run as a Java servlet inside a web application server.
Diffusion web server on page 732
Diffusion incorporates its own web server. This web server is required to enable a number of Diffusion
capabilities, but we recommend that you do not use it to host your production web applications.
Configuring the Diffusion web server on page 564
Use the WebServer.xml and Aliases.xml configuration files to configure the behavior of the
Diffusion web server.
Configuring Diffusion web server security on page 565
When configuring your Diffusion web server, consider the security of your solution.
Running the Diffusion server inside of a third-party web application server on page 735
Diffusion can run as a Java servlet inside any Java application server.
Hosting Diffusion web clients in a third-party web server on page 734
Host Diffusion web clients on a third-party web server to enable your customers to access them.
Push notification networks
Consider whether your solution will interact with push notification networks.
Push notification networks can relay data to a client, even when that client is not running.
Diffusion Push Notification Bridge
The Push Notification Bridge is a Diffusion client that subscribes to topics on behalf of other Diffusion
clients and uses a push notification network to relay topic updates to the device where the client
application is located.
The Push Notification Bridge supports the following push notification networks:
•
•
APNs
GCM
For more information about how the Push Notification Bridge works, see Push Notification Bridge on
page 773.
Why use the Push Notification Bridge
Diffusion clients on Android or iOS devices might not be running all the time to conserve battery or to
enable other processes to run. However, the user might still want to receive realtime updates while the
Diffusion client is not running.
By using push notification networks, Diffusion can deliver data to destinations on these devices at any
time.
Considerations when using the Push Notification Bridge
•
•
•
•
The Push Notification Bridge supports only single value topics.
Push notification networks identify an app on a device (a push notification destination), not an
individual user or session.
If a client requests push notification for a topic and also subscribes to that topic, when the client
is connected to Diffusion it receives topic updates once through the Diffusion server and once
through the push notification network. The client must handle removing the duplicate messages
from the information presented to the user.
Push notification networks currently limit the size of notifications to 2 KB or less.
Diffusion | 118
•
By default, the bridge does not persist the notification subscription requests sent by the clients. If
the bridge stops and restarts, this information is lost and notifications are no longer sent.
To ensure that the notification subscriptions are persisted by the bridge, implement a persistence
solution. For more information, see Push Notification Bridge persistence plugin on page 488.
Related concepts
Push Notification Bridge persistence plugin on page 488
The Push Notification Bridge stores subscription information in memory. To persist this information
past the end of the bridge process, implement a persistence plugin.
Example: Send a request message to the Push Notification Bridge on page 489
The following examples use the API to send a request message on a topic path to communicate
with the Push Notification Bridge. The request message is in JSON and can be used to subscribe or
unsubscribe from receiving push notifications when specific topics are updated.
Push Notification Bridge on page 773
The Push Notification Bridge is a Diffusion client that subscribes to topics on behalf of other Diffusion
clients and uses a push notification network to relay topic updates to the device where the client
application is located.
JMS
Consider whether to incorporate JMS providers into your solution.
If a third-party JMS provider is part of your solution, you can map JMS queues and topics to Diffusion
topics by using the Diffusion JMS adapter.
We support integration with JMS providers that conform to version 1.1 or later of the JMS
specification.
The following JMS products have been tested and are certified by Push Technology for use with the
JMS adapter:
•
•
Apache ActiveMQ v5.11
IBM MQ v8
Why use a third-party JMS provider
If you are already using a JMS provider to move data in your internal system, you can integrate it with
Diffusion to distribute that data to clients and users outside of your organization.
Diffusion JMS adapter
The JMS adapter for Diffusion, enables Diffusion clients to transparently send data to and receive data
from destinations (topics and queues) on a JMS server. It is highly configurable and can support the
following scenarios:
Pub-sub
Messages on a JMS destination can be published to a Diffusion topic. For more
information, see Publishing using the JMS adapter on page 749.
Messaging
Messages can be sent between JMS destinations and Diffusion clients.
•
•
A message on a JMS destination can be relayed to a Diffusion client through a
topic path.
A Diffusion client can send a message on a message path and the message is
relayed to a JMS destination.
Diffusion | 119
For more information, see Sending messages using the JMS adapter on page 750.
Request-response
The JMS provider can integrate with services that interact using an asynchronous
request-response pattern. Diffusion exposes these JMS services through its
messaging capabilities. For more information, see Using JMS request-response
services with the JMS adapter on page 752.
Data that flows between JMS and Diffusion must be transformed. JMS messages contain headers,
properties, and a payload. Diffusion messages contain just content. For more information about
how data is transformed between JMS and Diffusion, see Transforming JMS messages into Diffusion
messages or updates on page 746.
Running the JMS adapter in the Diffusion server or as a standalone application
The JMS adapter is provided in the following forms:
Within the Diffusion server
The JMS adapter can be configured to run as part of the Diffusion server process. A
JMS adapter running within the Diffusion server cannot become disconnected from
the Diffusion server.
As a standalone client
The JMS adapter is a Java application that can be run on any system and acts
as a client to the Diffusion server. Topics created by the JMS adapter running as
a standalone client are not deleted from the Diffusion server if the JMS adapter
becomes disconnected. You can use this capability to design a highly available
solution.
For more information, see JMS adapter on page 745.
Considerations when using the JMS adapter
Topics defined and created by the JMS adapter when it runs within the Diffusion server are removed
when the JMS adapter is stopped.
Topics defined and created by the JMS adapter when it runs as a standalone client are not deleted
from the Diffusion server when the JMS adapter client session is closed.
The JMS adapter supports interaction with Diffusion topics that are either stateful (single value) or
stateless topics.
Only textual content and JMS TextMessages are supported. Binary content is not currently supported.
You cannot currently publish data to a Diffusion topic and have it sent to a JMS destination.
Data must be transformed between JMS messages and Diffusion content.
If multiple Diffusion servers subscribe to the same JMS queue in a request-response scenario, there is
the risk of one server consuming messages intended for another server. Use JMS selectors to ensure
that the JMS adapter only receives those messages intended for it.
The creation of temporary queues and topics by the JMS adapter is not currently supported.
Durable subscriptions are not supported.
JMS transactions are not supported.
The only acknowledgment mode that is supported is AUTO_ACKNOWLEDGE.
Session properties are not currently supported. The exception is the $Principal property.
Diffusion | 120
Related concepts
Transforming JMS messages into Diffusion messages or updates on page 746
JMS messages are more complex than Diffusion content. A transformation is required between the two
formats.
Sending messages using the JMS adapter on page 750
The JMS adapter can send messages from a Diffusion client to a JMS destination and messages from a
JMS destination to a specific Diffusion client.
Publishing using the JMS adapter on page 749
The JMS adapter can publish data from a JMS destination onto topics in the Diffusion topic tree.
Using JMS request-response services with the JMS adapter on page 752
You can use the messaging capabilities of the JMS adapter to interact with a JMS service through
request-response.
Configuring the JMS adapter on page 754
Use the JMSAdapter.xml configuration file to configure the JMS adapter to send and receive
messages with destinations on a JMS server.
Example solutions
This section includes some example solutions that you can refer to when designing your own solution.
Example: Simple solution
This solution uses a firewall to restrict incoming traffic and a load balancer to balance the traffic
between multiple Diffusion servers.
Figure 17: A simple solution
•
•
•
Client applications can connect to Diffusion from the internet through a firewall.
The firewall protects the DMZ from unwanted traffic. It allows connections on port 80 and redirects
these connections to port 8080.
The load balancer balances the Diffusion connections between all the Diffusion servers in the DMZ.
You can also use the load balancer to filter the URL space and to perform SSL offloading.
Diffusion | 121
•
•
The Diffusion servers receive connections from external clients on port 8080. This port is protected
by an authentication handler that performs strict authentication on the incoming connections.
Authentication handlers can be local to the server or part of a control client.
The Diffusion servers receive connections from internal clients on another port, for example 9090.
The authentication controls on this port are less strict because these connections come from
within your network. Internal connections can come from any of the following components:
•
•
Browsers accessing the Diffusion console
Internal clients, such as control clients.
Example: A solution using clients
Clients with different uses connect to the Diffusion server in this example solution.
Figure 18: Clients for different purposes
This example solution uses three kinds of client, each for a different purpose:
Clients subscribing to topics
These clients are used by your customers to receive the data you distribute. You can
use any of the provided APIs to create these, depending on how your customers want
to access your data. For example,
•
•
Use the Apple API to create an iPhone app.
Use the JavaScript API to create a browser client.
These clients subscribe to the topics that are of interest to your customer, receive
updates published on these topics, and display the information to your customers.
Clients creating and updating topics
These clients are used by your organization to distribute your data. You must use an
API that provides control features to create these clients. For example, the JavaScript
API or the .NET API.
These clients create the topics required to support your data structure and to publish
data from your data sources to topics on the Diffusion server.
Clients authenticating other clients
These clients are used by your organization to authenticate connections from other
clients. You must use an API that provides control features to create these clients. For
example, the Java API.
Diffusion | 122
These clients are called by the Diffusion server to provide an authentication decision
when another client connects to the Diffusion server anonymously or with a
principal. In addition to deciding whether the other client is allowed to connect, the
authenticating client can assign roles to the client session.
The authenticating client can use information stored elsewhere in your system, for
example in an LDAP server, to make the authentication decision and assign roles.
Example: Scalable and resilient solution
This solution uses replication to share information between primary servers and make them highly
available. The solution also uses fan-out to spread the data from the primary servers behind the
firewall to secondary servers in the DMZ.
Figure 19: Architecture using replication and fan-out
1. Three clients register handlers with each of the Diffusion servers behind the firewall. These clients
can be located on the same system as the server or on remote systems. Each Diffusion server load
balances requests between clients that have registered to handle requests of that type. If one of the
clients becomes unavailable, the requests can be directed to another client. You can connect more
client sessions to deal with higher volumes of requests.
2. The Diffusion servers inside the firewall replicate information into a datagrid. If a Diffusion server
that was handling a client session or topic becomes unavailable, the responsibility for that client
session or topic can be passed to another Diffusion server that has access to all the information for
that session or topic through the datagrid.
3. The Diffusion servers outside of the firewall, in the DMZ, are configured to use automated fan-out
to connect to the Diffusion servers inside the firewall. Specified topics on the primary server are
fanned out to the secondary servers.
4. You can use a load balancer to spread requests from subscribing clients across many secondary
Diffusion servers. If a server becomes unavailable, clients can be directed to another server.
Diffusion | 123
Security
Diffusion secures your data by requiring client sessions to authenticate and using role-based
authorization to define the actions that a client can perform.
Concepts
Principal
The principal is a user or system user that has an identity that can be authenticated.
When a principal is authenticated is becomes associated with a session. The default
principal that is associated with a session is ANONYMOUS.
Session
A session is a set of communications between the Diffusion server and a client.
Permission
A permission represents the right to perform an action on the Diffusion server or on
data.
Role
A role is a named set of permissions and other roles. Principals and sessions can both
be assigned roles.
Role hierarchy
Roles are hierarchical. A role can include other roles and, by doing so, have the
permissions assigned to the included roles. A role cannot include itself, either directly
or indirectly – through a number of included roles.
Related reference
Default configuration on page 25
The Diffusion server is configured by the files in the etc directory.
Role-based authorization
Diffusion restricts the ability to perform actions to authorized principals. Roles are used to map
permissions to principals.
Associating permissions with roles
The association between roles and permissions is defined in the security store.
Diffusion | 124
A fixed set of permissions is defined by the Diffusion server. These permissions are used to control
access to actions and data on the Diffusion server.
Roles are used to associate permissions to principals. Permissions are assigned to roles, and roles are
assigned to principals.
A role can be assigned zero, one, or many permissions. The same permission can be assigned
to multiple roles. Roles can also include other roles to form a role hierarchy, and so inherit their
permissions. The permissions assigned to a role and the role hierarchy are defined in the security
store.
You can update the security store by editing the store file, installation_directory/etc/
Security.store, and restarting the Diffusion server.
You can update the security store from a client using the SecurityControl feature.
Associating roles with principals
The association between roles and principals is defined in the system authentication store or by userwritten authentication handlers.
Diffusion | 125
The association between principals and roles is defined in the following ways:
•
•
•
In a user-defined store that your user-written authentication handlers refer to. For example, an
LDAP server.
A user-written authentication handler can also hard code the relationship between principals and
roles, if that is appropriate.
In the system authentication store of the Diffusion server
The system authentication store is designed to hold information about Diffusion administration
users and system clients. It can manage hundreds or perhaps thousands of principals, but does
not provide the administration tools necessary to support millions of principals. We recommend
that you delegate such "internet scale" use cases to a third-party identity provider using a custom
authentication handler. For example, by using the OAuth or OpenID protocol.
You can update the system authentication store in the following ways:
•
•
From a client using the SystemAuthenticationControl feature.
By editing the store file, installation_directory/etc/
SystemAuthentication.store, and restarting the Diffusion server.
Assigning roles to client sessions
Roles are assigned to a new client session after client authentication.
Diffusion | 126
The roles assigned to a client session determine the actions that client session can perform.
A client session is assigned roles in the following ways:
•
•
If the client session connects to the Diffusion server anonymously, the client session is assigned the
default assigned roles for the ANONYMOUS principal.
Anonymous authentication can be enabled or disabled in the system authentication store. If
enabled, roles can also be specified.
When a client session authenticates with a principal, the client session can be assigned the
following roles:
•
•
The default assigned roles for a named principal.
The set of roles assigned to a principal by the authentication handler that accepts the client
session's authentication request. This authentication handler can be one of the following types:
•
•
The system authentication handler, in which case the roles that are assigned are those
associated with that principal in the system authentication store.
A user-written authentication handler, in which case the roles that are assigned are those
defined by the handler or a user-defined store.
For example: A client session authenticates with the Diffusion server using the principal Armstrong.
The first authentication handler that is called is a user-written authentication handler. This
authentication handler abstains from the authentication decision, so does not assign roles to the
client session. The next authentication handler that is called is the system authentication handler.
The system authentication handler does not abstain from the authentication decision. It uses the
information in the system authentication store to decide to allow the authentication request. In the
system authentication store, the principal Armstrong is associated with the roles ALPHA, BETA, and
EPSILON. These roles are assigned to the client session.
After the authentication request has been allowed, no further authentication handlers are called to
make a decision or assign roles. However, the Diffusion server also assigns the default assigned roles
for a named principal to the client session. The default assigned roles defined in the security store are
GAMMA and RHO.
Diffusion | 127
After authenticating with the principal Armstrong, the client session has the following roles assigned to
it:
•
•
•
•
•
ALPHA
BETA
EPSILON
GAMMA
RHO
Authorizing actions
When a client requests to perform an action or access data that requires a permission, the Diffusion
server checks whether the client session is assigned a role that includes the required permission.
The client requests to perform an action. If the action requires that the client session has a permission,
the Diffusion server checks what roles the client session is assigned and checks in the security store
whether any of these roles have the required permission.
For example: A client requests to subscribe to the topic A/B/C. To subscribe to a topic, a client session
must have the select_topic permission for that topic. The client session has the ALPHA and BETA roles.
In the security store, the ALPHA role does not include the select_topic permission, but the BETA role
does include the select_topic permission for the A/B/C topic. Because the client session is assigned the
BETA role, it has the required permission and can subscribe to the topic.
Related concepts
Authentication on page 136
You can implement and register handlers to authenticate clients when the clients try to perform
operations that require authentication.
DEPRECATED: Authorization handlers on page 142
An authorization handler can control authorization and permissions for clients and users.
Related reference
Configuring user security on page 539
Diffusion | 128
You can use the Security.store and SystemAuthentication.store files in the etc
directory of your Diffusion server to configure the security roles and how they are assigned.
Permissions
The actions a client session can take in Diffusion are controlled by a set of permissions. These
permissions are assigned to roles.
Permissions can have one of the following scopes:
Path
Permissions at path scope apply to actions on a topic path or message path.
Path-scoped permissions are defined against paths. The permissions that apply to
a topic path or to a message path are the set of permissions defined at the most
specific path.
Global
Permissions at global scope apply to actions on the Diffusion server.
Path permissions
The path-scoped permissions are listed in the following table:
Table 17: List of path-scoped permissions
Name
Description
select_topic
Use a topic selector that selects the topic path.
A session must have this permission for the path
prefix of any topic selector used to subscribe or
fetch.
read_topic
Grant read access to the topics.
If a session does not have this permission for a
topic, that topic does not match subscriptions
and is excluded from fetch requests. Also, the
topic's details cannot be retrieved.
query_obsolete_time_series_events
Evaluate a query on a time series topic that can
potentially return a non-current view of all or
part of a time series. Such queries include value
range queries that specify an edit range, and all
types of edit range query. Evaluating a query also
requires read_topic.
edit_time_series_events
Submit edit events to a time series topic.
Updating a time series topic also requires
update_topic.
edit_own_time_series_events
Submit edit events to a time series topic where
the event author is the same as the principal of
the calling session. Updating a time series topic
also requires update_topic.
update_topic
Update topics at or below a topic branch.
modify_topic
Create or modify topics at or below a topic
branch.
Diffusion | 129
Name
Description
send_to_message_handler
Send a message to the Diffusion server through a
message path.
send_to_session
Send a message to a client session through a
message path.
Understanding path-scoped permissions
Path-scoped permissions are assigned to roles for specific paths. The permission assignment applies
to all descendant paths, unless there is a more specific assignment.
To evaluate whether a client session has access to a permission for a topic or message path, the
Diffusion server starts at that path and searches up the path hierarchy to find the nearest permission
assignment. The first assignment is the only one considered, even if the client has roles involved in
assignments further up the path hierarchy.
Default path-scoped assignments can also be defined. These are used if no path assignment matches.
Diffusion | 130
Diffusion | 131
Path scope example
In this example, client sessions with the role ALPHA have the following permissions on each topic in
the topic tree:
A
A permission set is defined for the path A.
These permissions give client sessions with the ALPHA role read_topic, update_topic,
and modify_topic permissions on the topic A.
A/B
No permission set is defined for the path A/B. In this case, the permissions at the most
specific scope are those defined for the path A
These permissions give client sessions with the ALPHA role read_topic, update_topic,
and modify_topic permissions on the topic B.
A/C
A permission set is defined for the path A/C. These permissions do not include any
permissions for the ALPHA role.
Client sessions with the ALPHA role have no permissions on the topic C. Permissions
are defined for the ALPHA role at a less specific scope. However, these permissions
are not referred to or inherited if any permissions are defined at a more specific
scope. Only the most specific set of permissions is used. In this case, those
permissions are only for the BETA role and not the ALPHA role.
A/C/D
A permission set is defined for the path A/C/D.
These permissions give client sessions with the ALPHA role read_topic and
update_topic permissions on the topic D.
The role ALPHA has only these permissions even though at A/C the role has no
permissions defined and at A the role has additional permissions. Only the most
specific set of permissions is used.
The BETA role also has permissions defined at this scope. These permissions do not
affect the permissions that the ALPHA role has at this scope.
Diffusion | 132
Understanding the select_topic and read_topic permissions
The default configuration grants the select_topic and read_topic permissions to all sessions then
protects the topics on paths below the Diffusion path using the OPERATOR role. You can alter this
configuration to protect sensitive topics.
A session that does not have the select_topic permission for a particular path cannot subscribe
directly to topics at that path. However, the session can be independently subscribed to that topic by
a control session that has modify_session permission in addition to the select_topic permission for
that path. The subscribed session requires the read_topic permission for that topic for the subscription
to the topic to occur. The control session cannot subscribe a session to a topic if that session does
not have the read_topic permission for the topic. When this occurs, the topic is filtered out of the
subscription.
Use the select_topic permission with some care because topic selectors can use wild card expressions.
For example, with the default configuration, the OPERATOR role is required to use topic selector
expressions such as Diffusion or ?Diffusion//", but the CLIENT role is sufficient to use the topic selector
expression ?// which selects all of the topics in the topic tree.
In the default configuration, this does not cause a problem as sessions that do not have the OPERATOR
role also do not have the read_topic permission for topic paths below Diffusion. Any matching topics
are filtered from subscription and fetch results for those sessions.
Managing all subscriptions from a separate control session
You can prevent client sessions from subscribing themselves to topics and control all subscriptions
from a separate control client session that uses SubscriptionControl feature to subscribe clients to
topics.
To restrict subscription capability to control sessions, configure the following permissions:
Control session:
•
•
Grant the modify_session permission
Grant the select_topic permission
This can either be granted for the default path scope or more selectively to restrict the topic
selectors the control session can use.
Other sessions:
•
•
•
Grant read_topic to the appropriate topics.
Deny the select_topic permission by default.
Do not assign the session a role that has the select_topic permission for the default path scope.
This prevents the session from subscribing to all topics using a wildcard selector.
Optionally, grant the select_topic permission to specific branches of the topic tree to which the
session can subscribe freely.
Global permissions
The global permissions are listed in the following table:
Table 18: List of global permissions
Name
Description
view_session
List or listen to client sessions.
modify_session
Alter a client session. This covers a range of
actions including the following:
Diffusion | 133
Name
Description
•
•
•
•
subscribe a session to a topic
throttle a session
enable conflation for a session
close a session
register_handler
Register any handler with the Diffusion server.
authenticate
Register an authentication handler.
The register_handler permission is also required
to perform this action.
view_server
Read administrative information about the
Diffusion server.
For example, through JMX.
control_server
•
•
Shut down the Diffusion server.
Start and stop publishers.
These actions can be taken only from the console
or JMX. Client sessions cannot shut down the
Diffusion server or start and stop publishers.
view_security
View the security policy.
modify_security
Change the security policy.
Related reference
Pre-defined roles on page 134
Diffusion has a pre-defined set of roles with associated permissions.
Pre-defined users on page 142
Diffusion has a pre-defined set of users with associated password and roles.
Pre-defined roles
Diffusion has a pre-defined set of roles with associated permissions.
Clients can edit this set of roles. For more information, see Updating the security store on page 440.
CLIENT TOPIC_
CLIENT_ AUTHENTICATION OPERATOR ADMINISTRATOR
CONTROL CONTROL _HANDLER
and JMX_
ADMINISTRATOR
select topic
Default
scope
select topic
"Diffusion"
topic
read topic
Diffusion | 134
CLIENT TOPIC_
CLIENT_ AUTHENTICATION OPERATOR ADMINISTRATOR
CONTROL CONTROL _HANDLER
and JMX_
ADMINISTRATOR
Default
scope
read topic
"Diffusion"
topic
modify topic
Default
scope
modify topic
"Diffusion"
topic
update topic
Default
scope
update topic
"Diffusion"
topic
query_obsolete_time_series_events
edit_time_series_events
edit_own_time_series_events
send to
message
handler
Default
scope
send to
message
handler
"Diffusion"
topic
send to
session
Default
scope
view session
Diffusion | 135
CLIENT TOPIC_
CLIENT_ AUTHENTICATION OPERATOR ADMINISTRATOR
CONTROL CONTROL _HANDLER
and JMX_
ADMINISTRATOR
modify
session
register
handler
authenticate
view security
modify
security
view server
control
server
Related reference
Permissions on page 129
The actions a client session can take in Diffusion are controlled by a set of permissions. These
permissions are assigned to roles.
Pre-defined users on page 142
Diffusion has a pre-defined set of users with associated password and roles.
Authentication
You can implement and register handlers to authenticate clients when the clients try to perform
operations that require authentication.
The handlers you can implement and register are the following:
•
•
Any number of local authentication handlers
Any number of control authentication handlers
The server calls the authentication handlers (local and control) in the order that they are defined in the
Server.xml file.
If no handlers are defined, the server allows the client operation by default.
Diffusion | 136
Authentication process
Figure 20: Authentication process for clients
1. A client tries to perform an operation that requires authentication. For more information, see Client
operations that require authentication on page 138.
2. The server calls the authentication handlers one after another in the order that they are listed
in the Server.xml file. It passes the following parameters to each authentication handler's
authenticate() method:
Principal
A string that contains the name of the principal or identity that is connecting to the
server or performing the action. This can have a value of Session.ANONYMOUS.
Credentials
The Credentials object contains an array of bytes that holds a piece of
information that authenticates the principal. This can be empty or contain a
password, a cryptographic key, an image, or any other piece of information. The
authentication handler is responsible for interpreting the bytes.
SessionProperties
This contains information about the client. The available properties depend on what
information the server holds about the client session. Some session information
might not be available on initial connection.
Diffusion | 137
This information can be used in the authentication decision. For example, an
authentication handler can allow connection only from clients that connect from a
specific country.
When it registers with the server, a control authentication handler can specify what
properties it requires, so only these properties are sent by the server (if available).
This reduces the amount of data sent across the control client connection.
Callback
A callback that the authentication handler can use to respond to the authentication
request by using the callback's allow(), deny(), or abstain() method.
If the authentication handler is a local authentication handler, the authentication logic is done
on the server. If the authentication handler is a control authentication handler, the parameters
are passed to a control client and the control client handles the authentication logic and returns a
response.
3. Each authentication handler can return a response of ALLOW, DENY, or ABSTAIN.
•
•
If the authentication handler returns DENY, the client operation is rejected.
If the authentication handler returns ALLOW, the decision is passed to the authorization
handlers. The authentication handler can also provide a list of roles to assign to the client
session.
• If the authentication handler returns ABSTAIN, the decision is passed to the next
authentication handler listed in the Server.xml configuration file.
4. If all authentication handlers respond with an ABSTAIN decision, the response defaults to DENY.
Client operations that require authentication
The following client operations require authentication with the server:
Table 19: Client operations that require authentication
Client operation
Behavior if
operation is
allowed
Behavior if operation is
denied
Connect to server
The client
connection to
the server is
accepted.
The client connection to
the server is rejected and
is dropped.
Change the principal
associated with a client
session
The principal is The principal is not
changed.
changed, but the client
session is not dropped.
Related concepts
Role-based authorization on page 124
Diffusion restricts the ability to perform actions to authorized principals. Roles are used to map
permissions to principals.
DEPRECATED: Authorization handlers on page 142
Diffusion | 138
An authorization handler can control authorization and permissions for clients and users.
User-written authentication handlers
You can implement authentication handlers that authenticate clients that connect to the Diffusion
server or perform an action that requires authentication.
The authentication handlers can be implemented either remotely, in a client, or locally, on the
server. The authentication handlers can be individual authentication handlers, that perform a single
authentication check, or composite authentication handlers, that delegate to one or more individual
authentication handlers.
Local authentication handlers
A local authentication handler is an implementation of the AuthenticationHandler interface.
Local authentication handlers can be implemented only in Java. The class file that contains a local
authentication handler must be located on the classpath of the Diffusion server.
Control authentication handlers
A control authentication handler can be implemented in any language where the Diffusion API
includes the AuthenticationControl feature. A control authentication handler can be registered by any
client that has the authenticate and register_handler permissions.
For more information, see Authenticating new sessions on page 413.
Composite authentication handlers
A composite authentication handler delegates the authentication decision to an ordered list of one or
more individual authentication handlers and returns a combined decision.
Figure 21: A composite authentication handler
•
•
•
•
If an individual handler allows the client action, the composite handler responds with an ALLOW
decision.
If an individual handler denies the client action, the composite handler responds with a DENY
decision.
If an individual authentication handler abstains, the composite handler calls the next individual
handler.
If all individual handlers abstain, the composite handler responds with an ABSTAIN decision.
Diffusion | 139
A composite authentication handler can be either local or control. A local composite authentication
handler can delegate the authentication decision to one or more authentication handlers. A composite
control authentication handler can delegate the authentication decision to one or more control
authentication handlers.
The use of composite authentication handlers is optional. There are two reasons to consider using
them:
•
•
Composite authentication handlers enable you to combine authentication handlers together,
which reduces the possibility of misconfiguration.
Composite control authentication handlers improve efficiency by reducing the number of
messages sent between the Diffusion server and clients.
The following table matrix shows the four types of authentication handler.
Table 20: Types of authentication handler
Individual
Composite
Local
Implement the
AuthenticationHandler interface.
For more information, see Developing
a local authentication handler on page
484.
Extend the
CompositeAuthenticationHandler
class. For more information, see
Developing a composite authentication
handler on page 486
Control
Implement the
ControlAuthenticationHandler
interface. For more information, see
Developing a control authentication
handler on page 422.
Extend the
CompositeControlAuthenticationHandler
class. For more information, see
Developing a composite control
authentication handler on page 425
Related concepts
Configuring authentication handlers on page 529
Authentication handlers and the order that the Diffusion server calls them in are configured in the
Server.xml configuration file.
Related tasks
Developing a local authentication handler on page 484
Implement the AuthenticationHandler interface to create a local authentication handler.
Developing a composite authentication handler on page 486
Extend the CompositeAuthenticationHandler class to combine the decisions from multiple
authentication handlers.
Developing a control authentication handler on page 422
Implement the ControlAuthenticationHandler interface to create a control authentication
handler.
Developing a composite control authentication handler on page 425
Extend the CompositeControlAuthenticationHandler class to combine the decisions from
multiple control authentication handlers.
Developing a local authentication handler on page 484
Implement the AuthenticationHandler interface to create a local authentication handler.
Developing a composite authentication handler on page 486
Diffusion | 140
Extend the CompositeAuthenticationHandler class to combine the decisions from multiple
authentication handlers.
System authentication handler
Diffusion provides an authentication handler that uses principal, credential, and roles information
stored in the Diffusion server to make its authentication decision.
System authentication store
The principal, credentials, and role information located in the system authentication store is used by
the system authentication handler to authenticate users.
The system authentication store is designed to hold information about Diffusion administration users
and system clients. It can manage hundreds or perhaps thousands of principals, but does not provide
the administration tools necessary to support millions of principals. We recommend that you delegate
such "internet scale" use cases to a third-party identity provider using a custom authentication
handler. For example, by using the OAuth or OpenID protocol.
By default the following information is set in the system authentication store file,
SystemAuthentication.store located in the etc directory:
allow anonymous connections [ "CLIENT" ]
add principal "client" "password" [ "CLIENT" ]
add principal "control" "password" [ "CLIENT_CONTROL" "TOPIC_CONTROL"
"AUTHENTICATION_HANDLER" ]
add principal "admin" "password" [ "ADMINISTRATOR" ]
add principal "operator" "password" [ "OPERATOR" ]
You can edit the usernames and passwords in this file by hand and restart the Diffusion server to
reload the file. However, any password you enter in plaintext is hashed by the Diffusion server when it
starts and the plaintext value in this file is replaced with the hashed value.
The default hash scheme used is PBKDF-SHA256-1000. You can specify a different hash scheme in the
Server.xml configuration file. For more information, see .
Behavior of the system authentication handler
The system authentication handler behaves in the following way:
•
•
•
•
•
If anonymous connections are allowed in the system authentication store and a client session
connects anonymously, the system authentication handler returns an ALLOW decision and the list
of roles an anonymous client session is assigned.
If anonymous connections are not allowed in the system authentication store and a client session
connects anonymously, the system authentication handler returns a DENY decision.
If a client session connects with a principal listed in the system authentication store and the correct
credentials, the system authentication handler returns an ALLOW decision and the list of roles that
client session is assigned.
If a client session connects with a principal listed in the system authentication store and incorrect
credentials, the system authentication handler returns a DENY decision.
If a client session connects with a principal that is not listed in the system authentication store, the
system authentication handler returns an ABSTAIN decision.
Related concepts
Configuring authentication handlers on page 529
Diffusion | 141
Authentication handlers and the order that the Diffusion server calls them in are configured in the
Server.xml configuration file.
Updating the system authentication store on page 427
A client can use the SystemAuthenticationControl feature to update the system authentication store.
The information in the system authentication store is used by the system authentication handler to
authenticate users and assign roles to them.
Pre-defined users
Diffusion has a pre-defined set of users with associated password and roles.
You can use the SystemAuthenticationControl feature to edit this set of users.
Note: This set of users and passwords are well known and not secure. Change the passwords
or remove the users before putting Diffusion into production.
The users defined in the system authentication store are only authenticated if the system
authentication handler is configured. For more information, see Configuring authentication handlers
on page 529.
User
Password
Associated roles
client
password
CLIENT
control
password
CLIENT_CONTROL,
TOPIC_CONTROL,
AUTHENTICATION_HANDLER
admin
password
ADMINISTRATOR
operator
password
OPERATOR
Anonymous connections
CLIENT
Related reference
Pre-defined roles on page 134
Diffusion has a pre-defined set of roles with associated permissions.
Permissions on page 129
The actions a client session can take in Diffusion are controlled by a set of permissions. These
permissions are assigned to roles.
DEPRECATED: Authorization handlers
An authorization handler can control authorization and permissions for clients and users.
Role-based authorization
Attention: The new role-based security model has superseded authorization handlers. Rolebased security enables you to more simply manage permissions and users. We recommend
you use role-based authorization instead of authorization handlers. For more information, see
Role-based authorization on page 124.
An authorization handler is a user-written Java class that must implement the
AuthorisationHandler interface in the .
Diffusion | 142
Such a handler can be used to restrict access of clients according to any criteria that is appropriate.
One capability within Diffusion is for a client to be able to specify Credentials when they connect that
can be checked by the authorization handler.
The handler can either be specified in etc/Server.xml in which case it is loaded
when the server starts or can be set programmatically within a publisher using the
Publishers.setAuthorisationHandler method.
There can only be one handler and it is system wide across all publishers, although you can have
authorization at the publisher level.
If an authorization handler is not specified, credentials sent by a client are assumed to be valid. A
publisher has access to the credentials to perform finer-grained authorization, if required.
The authorization handler interface has the following methods:
Table 21: Authorization handler methods
DEPRECATED: canConnect(Client)
This method is called to establish whether the
client can connect and is called before any client
validation policy is called.
canSubscribe(Client, Topic)
This method is called when a client subscribes
to a topic. If topic information is sent with the
connection, this method is called after the
canConnect method.
Note: This is called for every topic
being subscribed to, even if subscribed
as a result of a topic selector being
specified. However (by default), if a topic
is rejected by this method, it is not called
again for any children (or descendants)
of the topic.
canSubscribe(Client,
TopicSelector)
This method is called when a client attempts to
subscribe to a topic selector pattern (as opposed
to a simple topic name). If topic information is
sent with the connection, this method is called
after the canConnect method.
canFetch(Client, Topic)
This method is called when a client sends a fetch
request to obtain the current state of a topic.
Note: This is called for every topic being
fetched, even if fetched as a result of a
topic selector being specified. However
(by default), if a topic is rejected by this
method, it is not be called again for any
children (or descendants) of the topic.
canFetch(Client, TopicSelector)
This method is called when a client attempts to
fetch topics using a topic selector pattern (as
opposed to a simple topic name).
canWrite(Client, Topic)
This method is called when a client sends a
message on a given topic, if false is returned the
message is ignored, and the publisher will not
be notified of the message. When implementing
Diffusion | 143
this method, be aware that performance can be
impacted if many clients send messages or if a
few clients send large messages.
DEPRECATED:
credentialsSupplied(Client,
Credentials)
This method is called when a client submits
credentials after connection. It can be used to
validate the credentials and must return true
if the credentials are OK. If this returns false, a
Credentials Rejected message are sent back to
the client.
Authentication
Note: The use of authorization handlers for authentication is deprecated. We recommend
that you re-implement your authentication logic using authentication handlers. For more
information, see User-written authentication handlers on page 139.
When a client connects to Diffusion it has the option of supplying user credentials. These credentials
are basically tokens called username and password. These tokens can be used for any purpose. When
canConnect is called, you can get the credentials from the Client object.
An example of this is:
public boolean canConnect(Client client) {
Credentials creds = client.getCredentials();
// No creds supplied, so reject the connection
if (creds == null) {
return false;
}
String username = creds.getUsername().toLowerCase();
If the credentials are null, none were supplied, which is different from empty credentials. If you set the
username as an empty string (that is, an anonymous user) the password is not stored and you cannot
retrieve it with getCredentials.
Clients can connect without credentials and submit them later or replace the credentials at any time
whilst connected. The authorization handler is notified when new credentials are submitted and can
choose to set the new credentials on the client.
The Credentials class has username and password attributes, but also allows for an attachment.
It is here that a user normally sets any security object required. Returning true will allow the user to
connect, returning false will result in the client connection being refused.
Subscription authorization
Subscription authorization is the allowing of a client to subscribe to a topic. In this case the
canSubscribe is called. Returning true here allows the publisher to have any topic loaders and
subscription methods called. Returning false will not notify the client that the subscription was invalid.
public boolean canSubscribe(Client client, Topic topic) {
// Everyone has access to the top level topic
if (topic.getName().equals(CHAT_TOPIC)) {
return true;
}
User user = (User) client.getCredentials().attachment();
Diffusion | 144
return user.isAllowedRoom(topic.getNodeName());
}
Authorization handler
Authorization at the publisher level can also be achieved. This is required if there are many publishers
running within the same Diffusion Server and they have different security settings. The following code
example works if the publishers all implement AuthorisationHandler
public boolean canSubscribe(Client client, Topic topic) {
AuthorisationHandler handler =
(AuthorisationHandler)Publishers.getPublisherForTopic(topic);
// Call the publisher in question
return handler.canSubscribe(client, topic);
}
Permissions
The permissions process governs whether a client is able to send messages to a publisher, or in other
words, is the topic read only. This is handled by the canWrite method. Again a good pattern might
be to look at the credentials attachment object to see if this is permissible.
public boolean canWrite(Client client, Topic topic) {
User user = (User) client.getClientCredentials().attachment();
return user.canWriteMessages(topic);
}
Related concepts
Role-based authorization on page 124
Diffusion restricts the ability to perform actions to authorized principals. Roles are used to map
permissions to principals.
Authentication on page 136
You can implement and register handlers to authenticate clients when the clients try to perform
operations that require authentication.
Diffusion | 145
Part
IV
Developer Guide
This guide describes how to develop clients, publishers, and server-side components that interact with the
Diffusion server.
Note: We recommend that you develop clients for most use cases. Our client APIs provide access
to the majority of Diffusion capabilities. Publishers and server-side components provide a few
advanced features that are not available on clients.
Diffusion clients
The Diffusion client API is a consistent and modular API that provides an asynchronous and session-oriented
approach to developing your clients.
The Diffusion client API is available for the following platforms:
•
•
•
•
•
•
Java
.NET
JavaScript
Android
Apple
C
In this section:
•
•
•
•
•
•
•
•
•
•
•
Best practice for developing clients
Feature support in the Diffusion API
Getting started
Connecting to the Diffusion server
Receiving data from topics
Managing topics
Updating topics
Using time series topics
Managing subscriptions
Using request-response messaging
Authenticating new sessions
Diffusion | 146
•
•
•
•
•
•
•
•
Updating the system authentication store
Updating the security store
Managing sessions
Logging from the client
Developing a publisher
Developing other components
Using Maven to build Java Diffusion applications
Testing
Diffusion | 147
Best practice for developing clients
Follow these best practises to develop resilient and well performing clients.
Use an asynchronous programming model
All calls in the Diffusion API are asynchronous. Ensure that you code your client using asynchronous
models to gain the advantages this provides.
Asynchronous calls remove the possibility of your client becoming blocked on a call. The Diffusion API
also provides context-specific callbacks, enabling you to pass contextual information with a callback,
and a wide range of event notifications.
Write good callbacks
The Diffusion API invokes callbacks using a thread from Diffusion thread pool. Callbacks for a
particular session are called in order, one at a time. Consider the following when writing callbacks:
•
•
Do not sleep or call blocking operations in a callback. If you do so, other pending callbacks for the
session are delayed. If you must call a blocking operation, schedule it in a separate application
thread.
You can use the full Diffusion API to make other requests to the Diffusion server. If you want to
make many requests based on a single callback notification, be aware that Diffusion client flow
control is managed differently in callback threads. Less throttling is applied and it is easier to
overflow the Diffusion server by issuing many thousands of requests. If you have a lot of requests to
make, it is better to schedule the work in an application thread.
Use a modular design
The Diffusion API provides interfaces on a feature-by-feature basis. There is a clear delineation
between features. At runtime, the client starts only those services that it uses.
You can take advantage of the modular design of the Diffusion API by designing multiple smaller and
more modular control clients. Smaller modules are easier to design, maintain and keep running.
Develop separate clients for different control responsibilities. For example, have a client or set of
clients responsible for authentication and a different client or set of clients responsible for creating
topics.
Also consider separating the responsibility for different parts of the topic tree between clients. For
example, have a client or set of clients responsible for updating the Tennis branch of the topic tree and
a different client or set of clients responsible for updating the Rugby branch of the topic tree.
Make your client resilient and defensive
If the Diffusion server restarts, all topic information — tree structure and topic state — is removed, all
subscription information is removed, and all clients are disconnected. Security and authentication
information is persisted.
If your client disconnects and cannot reconnect to the same session, all of its subscriptions and any
handlers it has registered are lost.
Ensure that you program your clients to handle and respond to these possibilities.
Diffusion | 148
Feature support in the Diffusion API
Review this information when designing your clients to determine which APIs provide the functionality
you require.
Features are sets of capabilities provided by the Diffusion API. Some features are not supported or not
fully supported in some APIs.
The Diffusion libraries also provide capabilities that are not exposed through their APIs. Some of these
capabilities can be configured.
Table 22: Capabilities provided by the Diffusion client libraries
Capability
JavaScript
Apple
Android
Java
.NET
C
Connecting
Connect to the
Diffusion server
Cascade connection
through multiple
transports
Connect
asynchronously
Connect
synchronously
Connect using a
URL-style string as a
parameter
Connect using
individual
parameters
Connect securely
Configure SSL
context or behavior
Connect through an
HTTP proxy
Connect through a
load balancer
Pass a request path
to a load balancer
Reconnecting
Reconnect to the
Diffusion server
Diffusion | 149
Capability
JavaScript
Apple
Android
Java
.NET
C
Configure a
reconnection
timeout
Define a custom
reconnection
strategy
Resynchronize
message streams on
reconnect
Abort reconnect if
resynchronization
fails
Maintain a recovery
buffer of messages
on the client to
resend to the
Diffusion server on
reconnect
Configure the clientside recovery buffer
Detect
disconnections by
monitoring activity
Detect
disconnections by
using TCP state
Ping the Diffusion
server
Change the principal
used by the
connected client
session
Receiving data from topics
Subscribe to a topic
or set of topics
Receive data as a
value stream
Receive data as
content
Fetch the state of a
topic
Managing topics
Create a topic
Diffusion | 150
Capability
JavaScript
Apple
Android
Java
.NET
C
Create a slave topic
Create/update/
query time series
topics
Create a topic from
an initial value
Create a topic from
a topic specification
Create a topic from
topic details
Create a topic with
metadata
Listen for
topic events
(including topic
has subscribers
and topic has zero
subscribers)
Get topic
notifications
Delete a topic
Delete a branch of
the topic tree
Mark a branch of
the topic tree for
deletion when this
client session is
closed
Updating topics
Update a topic
Perform exclusive
updates
Perform nonexclusive updates
Managing subscriptions
Subscribe or
unsubscribe another
client to a topic
Subscribe or
unsubscribe another
client to a topic
Diffusion | 151
Capability
JavaScript
Apple
Android
Java
.NET
C
based on session
properties
Handling
subscriptions to
routing topics
Handling
subscriptions to
missing topics
Request-response messaging
Send a request to a
path
Send a request to a
client session
Send a request to a
set of client sessions
based on session
properties
Respond to requests
sent to a session
Respond to requests
sent to a path
One-way messaging
Send a one-way
message to a path
Send a one-way
message to a client
session
Send a one-way
message to a set
of client sessions
based on session
properties
Receive one-way
messages
Handle one-way
messages sent to a
path
Managing security
Authenticate client
sessions and assign
roles to client
sessions
Configure how the
Diffusion server
Diffusion | 152
Capability
JavaScript
Apple
Android
Java
.NET
C
authenticates
client sessions and
assign roles to client
sessions
Configure the
roles assigned to
anonymous sessions
and named sessions
Configure the
permissions
associated with
roles assigned to
client sessions
Managing other clients
Receive
notifications about
client session events
including session
properties
Get the properties
of a specific client
session
Update user-defined
session properties
of a client session or
set of sessions
Receive
notifications about
client queue events
Conflate and
throttle clients
Close a client
session
Push notifications (The Push Notification Bridge must be enabled)
Receive push
notifications
Request that push
notifications be sent
from a topic to a
client
Publish an update
to a topic that sends
push notifications
Other capabilities
Diffusion | 153
Capability
JavaScript
Apple
Android
Java
.NET
C
Flow control
Getting started
Get started developing Diffusion clients by downloading one of our SDKs, discovering its capabilities,
and starting to stream realtime data through the Diffusion server.
JavaScript
The JavaScript API is provided in the file diffusion.js and can be accessed through the web or
through NPM.
Include JavaScript in a web page:
<script src="http://download.pushtechnology.com/clients/6.0.2/js/
diffusion.js"/>
This hosted version of the Diffusion JavaScript library is served with GZIP compression enabled. GZIP
compression reduces the library to 20% of its uncompressed size and ensuring fast page loads.
Use with Node.js:
Install with npm:
npm install diffusion
Include in your Node.js application:
var diffusion = require('diffusion');
You can also download the JavaScript file as a tarball package that can be installed locally by using
NPM:
http://download.pushtechnology.com/clients/6.0.2/js/diffusionjs-6.0.2.tgz
Get the minified JavaScript:
Download the latest JavaScript file from the following URL:
http://download.pushtechnology.com/clients/6.0.2/js/diffusion.js
The JavaScript file is also located in your Diffusion server installation:
diffusion_directory/clients/js
The Diffusion JavaScript client library is a full featured library and as such is provided as a large
download file. However, when served with GZIP compression, the size of the served file is significantly
smaller than the size of the downloaded file. Ensure that you enable GZIP compression on the web
server that hosts the JavaScript client library.
Diffusion | 154
The minified version of the JavaScript client library is approximately 70% of the size of the unminified
version.
Get the unminified JavaScript:
Download the latest unminified JavaScript client library from the following URL:
http://download.pushtechnology.com/clients/6.0.2/js/diffusionunminified.js
The unminified JavaScript file is also located in your Diffusion server installation:
diffusion_directory/clients/js
The minified version of the Diffusion JavaScript client library is created with Browserify. The minified
version might not be compatible with certain JavaScript frameworks. This unminified version is
provided to enable you to include Diffusion in projects using any framework.
The unminified form of JavaScript client library also gives you the option to perform minification of
your whole client application and make further size savings.
Use TypeScript definitions with the JavaScript client library:
If you got the JavaScript client library using NPM, the TypeScript definitions are included.
You can also download a TypeScript definition file from the following URL:
http://download.pushtechnology.com/clients/6.0.2/js/
diffusion-6.0.2.d.ts
The TypeScript file is also located in your Diffusion server installation:
diffusion_directory/clients/js
Include the TypeScript definition file in your IDE project to use the TypeScript definitions when
developing a JavaScript client for Diffusion.
Capabilities
To see the full list of capabilities supported by the JavaScript API, see Feature support in the Diffusion
API on page 37.
Support
For information about the browsers supported by the Diffusion JavaScript client, see Browser support
on page 44.
Table 23: Supported platforms and transport protocols for the client libraries
Platform
Minimum supported versions
Supported transport protocols
JavaScript
es6
WebSocket
(TypeScript 1.8)
HTTP (Polling XHR)
Resources
•
Examples for the JavaScript API.
Diffusion | 155
•
JavaScript API documentation
Using
Promises
The Diffusion JavaScript API uses the Promises/A+ specification.
Views
The JavaScript API provides a view capability.
Use views to subscribe to multiple topics by using a topic selector and receive all the
data from all topics in the selector set as a single structure when any of the topics
are updated. If the topic selector matches a topic which is subsequently added or
removed, the view is updated.
The following example shows views being used to present data from multiple topics
as a single structure:
diffusion.connect({
host
: 'localhost',
port
: 8080,
secure
: false,
principal
: 'control',
credentials : 'password'
}).then(function(session) {
// Assuming a topic tree:
//
// scores
//
|-- football
//
|
|-- semi1
//
|
|-- semi2
//
|
|-- final
//
|
//
|-- tennis
//
|-- semi1
//
|-- semi2
//
|-- final
// Use a regular expression to create a view of the
topics tracking the
// scores during the finals for each sport.
var view = session.view('?scores/.*/final');
// Alternatively, we can use a topic set. Note that
the topics do not need
// to be under a common root, they may be anywhere
within the topic tree.
var view2 = session.view('#>scores/football/final////
>scores/tennis/final');
// If any of the topics in the view change, display
which topic changed
// and its new value.
view.on({
update : function(value) {
// Get and print the entire view structure.
console.log('Update: ', JSON.stringify(value,
undefined, 4));
Diffusion | 156
// Get individual topics. Returns a Buffer,
which is automatically
// converted to a String during
concatenation, below.
//
// Note that the structure may not exist if
the value has not been
// updated.
console.log('Football score: ' +
value.scores.football.final);
console.log('Tennis score : ' +
value.scores.tennis.final);
// or ...
// console.log('Football score: ' +
value['scores']['football']['final']);
}
});
// The structure can also be accessed outside the
update event.
console.log('Football score: ' +
view.get().scores.football.final);
});
Regular expressions
The JavaScript client uses a different regular expression engine to the Diffusion
server. Some regular expressions in topic selectors are evaluated on the client and
others on the Diffusion server. It is possible that topic selectors that include complex
or advanced regular expressions can behave differently on the client and on the
Diffusion server.
For more information, see Regular expressions on page 55.
Start subscribing with JavaScript
Create a JavaScript browser client within minutes that connects to the Diffusion server. This example
creates a web page that automatically updates and displays the value of a topic.
Before you begin
To complete this example, you need a Diffusion server and a web server where you can host your client
application. Get the diffusion.js file from the clients/js directory of your Diffusion
You also require either a named user that has a role with the select_topic and read_topic permissions
or that anonymous client connections are assigned a role with the select_topic and read_topic
permissions. For example, the “CLIENT” role. For more information about roles and permissions, see
Role-based authorization on page 124.
About this task
This example steps through the lines of code required to subscribe to a topic. There are several
different topic types which provide data in different formats. This example shows you how to
subscribe to a JSON topic. The full code example is provided after the steps.
Procedure
1. Create a template HTML page which displays the information.
Diffusion | 157
For example, create the following index.html file.
<html>
<head>
<title>JavaScript example</title>
</head>
<body>
<span>The value of foo/counter is: </span>
<span id="update">Unknown</span>
</body>
</html>
If you open the page in a web browser, it looks like the following screenshot:
2. Include the Diffusion JavaScript library in the <head> section of your index.html file.
<head>
<title>JavaScript example</title>
<script type="text/javascript" src="path_to_library/
diffusion.js"></script>
</head>
3. Create a connection from the page to the Diffusion server. Add a script element to the body
element.
<body>
<span>The value of foo/counter is: </span>
<span id="update">Unknown</span>
<script type="text/javascript">
diffusion.connect({
// Edit these lines to include the host and port of
your Diffusion server
host : 'hostname',
port : 'port',
// To connect anonymously you can leave out the
following parameters
principal : 'user',
credentials : 'password'
}).then(function(session) {
alert('Connected: ' + session.isConnected());
}
);
</script>
Diffusion | 158
</body>
Where hostname is the name of the system hosting your Diffusion server, user is the name of a user
with the permissions required to subscribe to a topic, and password is the user's password.
If you open the page in a web browser it looks like the following screenshot:
4. Subscribe to a topic and receive data from it.
Add the following function before the diffusion.connect() call:
function subscribeToJsonTopic(session) {
session.subscribe('foo/counter');
session.stream('foo/
counter').asType(diffusion.datatypes.json()).on('value',
function(topic, specification, newValue, oldValue) {
console.log("Update for " + topic, newValue.get());
document.getElementById('display').innerHTML =
JSON.stringify(newValue.get());
});
}
The subscribe() method of the session object takes the name of the topic to subscribe to
and emits an update event. The attached function takes the data from the topic and updates the
update element of the web page with the topic data.
5. Change the function that is called on connection to the subscribeToJsonTopic function you
just created.
.then(subscribeToJsonTopic);
If you open the page in a web browser it looks like the following screenshot:
Diffusion | 159
Results
The web page is updated every time the value of the foo/counter topic is updated. You can update
the value of the foo/counter topic by creating a publishing client to update the topic. To create
and publish to the foo/counter topic, you require a user with the modify_topic and update_topic
permissions. For more information, see Start publishing with JavaScript on page 161.
Example
The completed index.html file contains the following code:
<html>
<head>
<title>JavaScript example</title>
<script type="text/javascript" src="path_to_library/
diffusion.js"/>></script>
</head>
<body>
<p>The value of foo/counter is: <span id="display"></span></p>
<script type="text/javascript">
diffusion.connect({
// Edit these lines to include the host and port of
your Diffusion server
host : 'hostname',
port : 'port',
// To connect anonymously you can leave out the
following parameters
principal : 'user',
credentials : 'password'
}).then(function(session) {
// Add a stream that matches the topic path and wire
it to log the received values
session
.stream('foo/counter')
.asType(diffusion.datatypes.double())
.on('value', function(topic, specification,
newValue, oldValue) {
console.log("Update for " + topic, newValue);
Diffusion | 160
document.getElementById('display').innerHTML =
newValue;
});
// Subscribe to the topic
session.subscribe('foo/counter');
});
</script>
</body>
</html>
Start publishing with JavaScript
Create a Node.js client that publishes data through topics on the Diffusion server.
Before you begin
To complete this example, you need a Diffusion server and a development system with Node.js and
npm installed on it.
You also require either a named user that has a role with the modify_topic and update_topic
permissions. For example, the “ADMINISTRATOR” role. For more information about roles and
permissions, see Role-based authorization on page 124.
About this task
This example steps through the lines of code required to publish to a topic. There are several different
topic types which provide data in different formats. This example shows you how to publish to a JSON
topic. The full code example is provided after the steps.
Procedure
1. Install the Diffusion JavaScript library on your development system.
npm install --save diffusion
2. Create the JavaScript file that will be your publishing client.
For example, publishing.js
a) Require the Diffusion library.
const diffusion = require('diffusion');
b) Connect to the Diffusion server.
diffusion.connect({
host : 'host-name',
principal : 'control-user',
credentials : 'password'
}).then(function(session) {
console.log('Connected!');
});
Where host-name is the name of the system that hosts your Diffusion server, control-user is the
name of a user with the permissions required to create and update topics, and password is the
user's password.
Diffusion | 161
c) Create a JSON topic called foo/counter.
session.topics.add("foo/counter",
diffusion.topics.TopicType.JSON);
d) Every second update the value of the topic with the value of the counter.
setInterval(function() {
session.topics.update('foo/counter', { count : i
++ });
}, 1000);
3. Use Node.js to run your publishing client from the command line.
node publishing.js
Results
The publisher updates the value of the foo/counter topic every second. You can watch the topic value
being updated by subscribing to the topic.
•
You can use the example subscribing client from Start subscribing with JavaScript on page 157 to
subscribe to foo/counter and output the value on a web page.
Example
The completed publishing.js file contains the following code:
const diffusion = require('diffusion');
diffusion.connect({
host : 'hostname',
principal : 'control-user',
credentials : 'password'
}).then(function(session) {
console.log('Connected!');
var i = 0;
// Create a JSON topic
session.topics.add("foo/counter",
diffusion.topics.TopicType.JSON);
// Start updating the topic every second
setInterval(function() {
session.topics.update("foo/counter", { count : i++ });
}, 1000);
});
What to do next
Now that you have the outline of a publisher, you can use it to publish your own data instead of a
counter.
Diffusion | 162
Apple
The Apple SDK is provided for iOS, OS X/macOS, and tvOS.
Get the Apple SDK for iOS:
Download the SDK from the following URL:
http://download.pushtechnology.com/clients/6.0.2/apple/diffusioniphoneos-6.0.2.zip
The SDK file is also located in your Diffusion server installation:
diffusion_directory/clients/apple/diffusion-iphoneos-6.0.2.zip
Get the Apple SDK for OS X/macOS:
Download the SDK from the following URL:
http://download.pushtechnology.com/clients/6.0.2/apple/diffusionmacosx-6.0.2.zip
The SDK file is also located in your Diffusion server installation:
diffusion_directory/clients/apple/diffusion-macosx-6.0.2.zip
Get the Apple SDK for tvOS:
Download the SDK from the following URL:
http://download.pushtechnology.com/clients/6.0.2/apple/diffusionappletvos-6.0.2.zip
The SDK file is also located in your Diffusion server installation:
diffusion_directory/clients/apple/diffusion-appletvos-6.0.2.zip
Capabilities
To see the full list of capabilities supported by the Apple API, see Feature support in the Diffusion API
on page 37.
Support
Table 24: Supported platforms and transport protocols for the client libraries
Platform
Minimum supported versions
Supported transport protocols
Apple for iOS
Development
environment
WebSocket
Xcode 8
(iOS 10.0
SDK)
Runtime support
Diffusion | 163
Platform
Minimum supported versions
Supported transport protocols
Deployment
target: iOS
8.1 or later
Device
architectures:
armv7,
armv7s,
arm64
Simulator
architectures:
i386,
x86_64
Apple for OS X/macOS
Development
environment
WebSocket
Xcode
8 (OS
X/macOS
10.12 SDK)
Runtime support
Deployment
target: OS
X/macOS
10.11 or
later
Device
architectures:
x86_64
Apple for tvOS
Development
environment
WebSocket
Xcode 8
(tvOS 10.0
SDK)
Runtime support
Deployment
target:
tvOS 9.0 or
later
Device
architectures:
arm64
Simulator
architectures:
x86_64
Diffusion | 164
Resources
•
•
Objective-C examples for the Apple API.
Apple API documentation
Using
Applications in background state
Apple applications can be sent to the background. When this happens your
application is notified by the applicationDidEnterBackground callback.
Applications go into background state before being suspended.
Applications can be sent to the background or suspended at any time. We
recommend that your Diffusion app saves its state – in particular, any topic
subscriptions – as this state changes.
When your Diffusion app is sent to the background, we recommend the client
closes its session with the Diffusion server. When the Diffusion app returns to the
foreground, it can open a new client session with the Diffusion server and use the
saved state to restore topic subscriptions.
For more information, see the Apple App Life Cycle documentation and Strategies for
Handling App State Transitions.
Consider using push notifications to deliver data to your users when your client
application is in background state.
Regular expressions
The Apple client uses a different regular expression engine to the Diffusion server.
Some regular expressions in topic selectors are evaluated on the client and others
on the Diffusion server. It is possible that topic selectors that include complex
or advanced regular expressions can behave differently on the client and on the
Diffusion server.
For more information, see Regular expressions on page 55.
Start subscribing with iOS
Create an Objective-C iOS client within minutes that connects to the Diffusion server. This example
creates a client that prints the value of a topic to the console when the topic is updated.
Before you begin
To complete this example, you need Apple's Xcode installed on your development system and a
Diffusion server.
You also require either a named user that has a role with the select_topic and read_topic permissions
or that anonymous client connections are assigned a role with the select_topic and read_topic
permissions. For example, the “CLIENT” role. For more information about roles and permissions, see
Role-based authorization on page 124.
About this task
This example steps through the lines of code required to subscribe to a topic and was created using
Xcode version 7.1 and the Diffusion dynamically linked framework targeted at iOS 8.
Skip to the full example.
Procedure
1. Get the Diffusion Apple SDK for iOS.
Diffusion | 165
The diffusion-iphoneos-version.zip file is located in the clients directory of your
Diffusion installation.
This example uses the iOS framework provided in diffusion-iphoneos-version.zip.
Frameworks are also available for OS X/macOS-targeted development in diffusionmacosx-version.zip and tvOS-targeted development in diffusiontvos-version.zip.
2. Extract the contents of the diffusion-iphoneos-version.zip file to your preferred
location for third-party SDKs for use within Xcode.
For example, ~/Documents/code/SDKs/diffusion-iphoneos-version.
3. Create a new project in Xcode.
a) From the File menu, select New > Project...
The Choose a template for your new project wizard opens.
b) Select iOS > Application on the left.
c) Select Single View Application on the right and click Next.
Xcode prompts you to Choose options for your new project.
d) Configure your project appropriately for your requirements.
Select Objective-C as the Language.
For example, use the following values:
•
•
•
Product Name: TestClient
Language: Objective-C
Devices: Universal
For this example, it is not necessary to select Use Core Data, Include Unit Tests, or Include UI
Tests.
e) Click the Next button.
Xcode prompts you to select a destination directory for your new project.
f) Select a target directory.
For example: ~/Documents/code/
g) Click the Create button.
Xcode creates a new project that contains the required files.
4. Import the Diffusion framework, Diffusion.framework, into Xcode.
a) From the View menu, select Navigators > Show Project Navigator
b) Select the root node of the project in the Project Navigator in the left sidebar.
The project editor opens in the main panel.
c) In the project editor, click on the project name at the top left and select Targets > TestClient.
The target editor opens in the main panel.
d) In the target editor, select the General tab.
e) Expand the Embedded Binaries section and click the + icon (Add Items).
The Choose items to add wizard opens.
f) Click the Add Other... button.
g) Navigate to the location of the expanded Diffusion.framework SDK for iOS8 and click
Open.
Xcode prompts you to choose options for adding these files.
h) Select the options you require for adding the files and click Finish
Unless you require different options, use the following values:
•
•
Do not select Copy items if needed.
Select Create groups.
Diffusion | 166
5. Make the framework visible to your code.
Despite the framework now appearing under both Embedded Binaries and Linked Frameworks
and Libraries, it is still necessary to tell Xcode where the header files for the framework can be
found.
a) Select the Build Settings tab in the target editor.
You can also do this at project level if you prefer and depending on your requirements.
b) Click on All to display All Build Settings.
c) Expand the Search Paths section.
d) Expand the Framework Search Paths section and click the + icon next to Release.
Add the absolute path to the iOS8 directory that contains Diffusion.framework.
Note: Ensure that you use the path to the directory, not to the
Diffusion.framework file. Linking to the file causes a silent failure.
6. Import the Diffusion module into the ViewController.h file.
@import Diffusion;
7. In the ViewController.m file, create a connection to the Diffusion server.
a) Define a long-lived session property.
Add the session instance to the class extension to maintain a strong reference to the session
instance:
@interface ViewController ()
@property PTDiffusionSession* session;
@end
The strong reference ensures that once the session instance has been opened, it remains open.
b) Open a session connection to the Diffusion server.
This example opens the session when the view controller loads.
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"Connecting...");
// Connect anonymously.
// Replace 'hostname' with that of your server.
[PTDiffusionSession openWithURL:[NSURL
URLWithString:@"ws://hostname"]
completionHandler:^(PTDiffusionSession
*session, NSError *error)
{
if (!session) {
NSLog(@"Failed to open session: %@", error);
return;
}
// At this point we now have a connected session.
NSLog(@"Connected.");
// Maintain strong reference to session instance.
self.session = session;
}];
}
Replace hostname with the host name of your Diffusion server.
8. Subscribe to a topic.
Diffusion | 167
a) In the ViewController.h file, conform to the topic stream delegate protocol.
In this example, the single view controller class handles topic stream update messages.
@interface ViewController : UIViewController
<PTDiffusionTopicStreamDelegate>
b) In the ViewController.h file, create a UILabel control.
Add a label to your user interface and bind it to an outlet in the view controller:
@property(nonatomic) IBOutlet UILabel *counterLabel;
This label is used to display the value of the topic as it updates.
c) In the ViewController.m file, implement the topic stream update method.
This is a required method in the topic stream delegate protocol.
-(void)diffusionStream:(PTDiffusionStream *)stream
didUpdateTopicPath:(NSString *)topicPath
content:(PTDiffusionContent *)content
context:(PTDiffusionUpdateContext *)context {
// Interpret the received content's data as a string.
NSString *counterString = [[NSString alloc]
initWithData:content.data
encoding:NSUTF8StringEncoding];
// Diffusion sends messages to delegates on the main
dispatch queue so it's
// safe to update the user interface here.
self.counterLabel.text = counterString;
}
d) In the ViewController.h file, within the session's completionHandler callback,
register with the Topics feature as the fallback topic stream handler.
[session.topics addFallbackTopicStreamWithDelegate:self];
e) Next, request that the Diffusion server subscribe your session to the foo/counter topic.
[session.topics subscribeWithTopicSelectorExpression:@"foo/
counter"
completionHandler:^(NSError *error)
{
if (error) {
NSLog(@"Subscribe request failed. Error: %@",
error);
} else {
NSLog(@"Subscribe request succeeded.");
}
}];
9. Build and Run.
Results
The client app updates the label every time the value of the foo/counter topic is updated. You can
update the value of the foo/counter topic by creating a publishing client to update the topic. To create
and publish to the foo/counter topic, you require a user with the modify_topic and update_topic
permissions. For more information, see Start publishing with OS X/macOS on page 170.
Diffusion | 168
Full example
The completed view controller implementation for the subscribing client contains the following
code.
ViewController.h:
@import UIKit;
@interface ViewController : UIViewController
<PTDiffusionTopicStreamDelegate>
@property(nonatomic) IBOutlet UILabel *counterLabel;
@end
ViewController.m:
#import "ViewController.h"
@interface ViewController ()
@property PTDiffusionSession* session;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"Connecting...");
// Connect anonymously.
// Replace 'hostname' with that of your server.
[PTDiffusionSession openWithURL:[NSURL
URLWithString:@"ws://hostname"]
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
if (!session) {
NSLog(@"Failed to open session: %@", error);
return;
}
// At this point we now have a connected session.
NSLog(@"Connected.");
// Maintain strong reference to session instance.
self.session = session;
// Register self as the fallback handler for topic
updates.
[session.topics addFallbackTopicStreamWithDelegate:self];
NSLog(@"Subscribing...");
[session.topics
subscribeWithTopicSelectorExpression:@"foo/counter"
completionHandler:^(NSError *error)
{
if (error) {
NSLog(@"Subscribe request failed. Error: %@",
error);
} else {
NSLog(@"Subscribe request succeeded.");
Diffusion | 169
}
}];
}];
}
-(void)diffusionStream:(PTDiffusionStream *)stream
didUpdateTopicPath:(NSString *)topicPath
content:(PTDiffusionContent *)content
context:(PTDiffusionUpdateContext *)context {
// Interpret the received content's data as a string.
NSString *counterString = [[NSString alloc]
initWithData:content.data
encoding:NSUTF8StringEncoding];
// Diffusion sends messages to delegates on the main dispatch
queue so it's
// safe to update the user interface here.
self.counterLabel.text = counterString;
}
@end
Start publishing with OS X/macOS
Create an OS X/macOS client that publishes data through topics on the Diffusion server.
Before you begin
To complete this example, you need Apple's Xcode installed on your development system and a
Diffusion server.
You also require a named user that has a role with the modify_topic and update_topic permissions.
For example, the “TOPIC_CONTROL” role. For more information about roles and permissions, see
Role-based authorization on page 124.
About this task
This example steps through the lines of code required to create and publish to a topic and was created
using Xcode version 7.1 and the Diffusion dynamically linked framework targeted at OS X/macOS.
Skip to the full example.
Procedure
1. Get the Diffusion Apple SDK for OS X/macOS.
The diffusion-macosx-version.zip file is located in the clients directory of your
Diffusion installation.
This example uses the OS X/macOS framework provided in diffusionmacosx-version.zip. Frameworks are also available for iOS-targeted development in
diffusion-iphoneos-version.zip and tvOS-targeted development in diffusiontvos-version.zip.
2. Extract the contents of the diffusion-macosx-version.zip file to your preferred location
for third-party SDKs for use within Xcode.
For example, ~/Documents/code/SDKs/diffusion-macosx-version.
3. Create a new project in Xcode.
a) From the File menu, select New > Project...
Diffusion | 170
The Choose a template for your new project wizard opens.
b) Select OS X > Application on the left.
c) Select Command Line Tool on the right and click Next.
Xcode prompts you to Choose options for your new project.
d) Configure your project appropriately for your requirements.
Select Objective-C as the Language.
For example, use the following values:
• Product Name: CounterPublisher
• Language: Objective-C
e) Click the Next button.
Xcode prompts you to select a destination directory for your new project.
f) Select a target directory.
For example: ~/Documents/code/
g) Click the Create button.
Xcode creates a new project that contains the required files.
4. Link to the Diffusion framework.
For more information, see the Xcode documentation http://help.apple.com/xcode/mac/8.1/#/
dev51a648b07.
5. Create a CounterPublisher.h file.
a) Bring up the context menu for the root node of the project in the Project Navigator in the left
sidebar.
Select New File
b) In the dialog that opens, select Header File
c) Name the header file CounterPublisher.h and click Create.
6. Import the Foundation module into the CounterPublisher.h file.
@import Foundation;
7. Define the CounterPublisher interface in the CounterPublisher.h file.
@interface CounterPublisher : NSObject
-(void)startWithURL:(NSURL *)url;
@end
8. Create a CounterPublisher.m file.
a) Bring up the context menu for the root node of the project in the Project Navigator in the left
sidebar.
Select New File
b) In the dialog that opens, select Objective-C File
c) Name the file CounterPublisher.m and click Create.
9. In the CounterPublisher.m file, import the required modules and set up properties and
variables.
a) Import Diffusion and CounterPublisher.h
#import "CounterPublisher.h"
@import Diffusion;
b) Define a long-lived session property.
Diffusion | 171
Add the session instance to the class extension to maintain a strong reference to the session
instance:
@interface CounterPublisher ()
@property(nonatomic) PTDiffusionSession* session;
@end
The strong reference ensures that once the session instance has been opened, it remains open.
c) Declare an integer _counter to hold the value to publish to the topic.
@implementation CounterPublisher {
NSUInteger _counter;
}
d) Define the topic path of the topic to create and publish to.
static NSString *const _TopicPath = @"foo/counter";
10.In the CounterPublisher.m file, create a method that starts the session with the Diffusion
server.
a) Call the method startWithURL and give it a signature that matches that defined in
CounterPublisher.h
-(void)startWithURL:(NSURL *)url {
}
b) Inside the method, define the security principal and credentials that the client uses to connect.
PTDiffusionCredentials *credentials =
[[PTDiffusionCredentials alloc]
initWithPassword:@"password"];
PTDiffusionSessionConfiguration *sessionConfiguration =
[[PTDiffusionSessionConfiguration alloc]
initWithPrincipal:@"principal"
credentials:credentials];
Replace principal and password with the username and password to connect to the Diffusion
server with. This user must have sufficient permissions to create and update the topic, for
example, by being assigned the “TOPIC_CONTROL” role.
c) Open a session on the Diffusion server.
[PTDiffusionSession openWithURL:url
configuration:sessionConfiguration
completionHandler:^(PTDiffusionSession
*session, NSError *error)
{
if (!session) {
NSLog(@"Failed to open session: %@", error);
return;
}
// At this point we now have a connected session.
NSLog(@"Connected.");
// Maintain strong reference to session instance.
self.session = session;
Diffusion | 172
// Next step
}];
11.Create a topic.
After connecting a session and creating a strong reference to it, use session.topicControl to
create a topic:
// Send request to add topic for publishing to.
[session.topicControl addWithTopicPath:_TopicPath
type:PTDiffusionTopicType_SingleValue
value:nil
completionHandler:^(NSError *error)
{
if (error) {
NSLog(@"Failed to add topic: %@", error);
return;
}
// Next step
}];
12.Update the topic.
a) After successfully creating the topic, call the updateCounter method:
[self updateCounter];
b) At the top-level of the CounterPublisher.m file, define the updateCounter method:
-(void)updateCounter {
// Get the updater to be used for non-exclusive topic
updates.
PTDiffusionTopicUpdater *updater =
self.session.topicUpdateControl.updater;
// Format string content for the update.
NSString *string = [NSString stringWithFormat:@"%lu",
(unsigned long)_counter++];
NSData *data = [string
dataUsingEncoding:NSUTF8StringEncoding];
PTDiffusionContent *content = [[PTDiffusionContent alloc]
initWithData:data];
// Send request to update topic.
NSLog(@"Updating: %@", string);
[updater updateWithTopicPath:_TopicPath
value:content
completionHandler:^(NSError *error)
{
if (error) {
NSLog(@"Failed to update topic: %@", error);
}
}];
// Schedule another update in one second's time.
__weak CounterPublisher *const weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(1.0
* NSEC_PER_SEC)),
dispatch_get_main_queue(), ^
{
Diffusion | 173
[weakSelf updateCounter];
});
}
This method recursively calls itself via a weak reference to self.
13.In the main.m, add the code needed to run your publishing client from the command line:
a) Import the Foundation module and the CounterPublisher.h file.
@import Foundation;
#import "CounterPublisher.h"
b) In a main method, create a CounterPublisher and call its startWithURL method:
int main(int argc, const char * argv[]) {
@autoreleasepool {
CounterPublisher *const publisher = [CounterPublisher
new];
NSURL *const url = [NSURL
URLWithString:@"wss://hostname"];
[publisher startWithURL:url];
// Run in an infinite Loop.
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
Replace hostname with the host name of your Diffusion server.
14.Build and Run.
Results
The client publishes a value to the foo/counter topic every second. You can subscribe to the foo/
counter topic by creating a client to subscribe to the topic. For more information, see Start subscribing
with iOS on page 165.
Full example
The completed implementation of the publishing client files contain the following code:
main.m:
@import Foundation;
#import "CounterPublisher.h"
/**
Wrapper around the counter publisher example class demonstrating
how it can
be launched as a command line tool.
*/
int main(int argc, const char * argv[]) {
@autoreleasepool {
CounterPublisher *const publisher = [CounterPublisher
new];
NSURL *const url = [NSURL
URLWithString:@"wss://hostname"];
[publisher startWithURL:url];
// Run, Infinite Loop.
[[NSRunLoop currentRunLoop] run];
}
Diffusion | 174
return 0;
}
CounterPublisher.h:
@import Foundation;
@interface CounterPublisher : NSObject
-(void)startWithURL:(NSURL *)url;
@end
CounterPublisher.m:
@import Diffusion;
@interface CounterPublisher ()
@property(nonatomic) PTDiffusionSession* session;
@end
@implementation CounterPublisher {
NSUInteger _counter;
}
@synthesize session = _session;
static NSString *const _TopicPath = @"foo/counter";
-(void)startWithURL:(NSURL *)url {
NSLog(@"Connecting...");
// Connect with control client credentials.
PTDiffusionCredentials *credentials =
[[PTDiffusionCredentials alloc]
initWithPassword:@"password"];
PTDiffusionSessionConfiguration *sessionConfiguration =
[[PTDiffusionSessionConfiguration alloc]
initWithPrincipal:@"principal"
credentials:credentials];
[PTDiffusionSession openWithURL:url
configuration:sessionConfiguration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
if (!session) {
NSLog(@"Failed to open session: %@", error);
return;
}
// At this point we now have a connected session.
NSLog(@"Connected.");
// Maintain strong reference to session instance.
self.session = session;
// Send request to add topic for publishing to.
[session.topicControl addWithTopicPath:_TopicPath
Diffusion | 175
type:PTDiffusionTopicType_SingleValue
value:nil
completionHandler:^(NSError *error)
{
if (error) {
NSLog(@"Failed to add topic: %@", error);
return;
}
// At this point we now have a topic.
[self updateCounter];
}];
}];
}
-(void)updateCounter {
// Get the updater to be used for non-exclusive topic updates.
PTDiffusionTopicUpdater *updater =
self.session.topicUpdateControl.updater;
// Format string content for the update.
NSString *string = [NSString stringWithFormat:@"%lu",
(unsigned long)_counter++];
NSData *data = [string
dataUsingEncoding:NSUTF8StringEncoding];
PTDiffusionContent *content = [[PTDiffusionContent alloc]
initWithData:data];
// Send request to update topic.
NSLog(@"Updating: %@", string);
[updater updateWithTopicPath:_TopicPath
value:content
completionHandler:^(NSError *error)
{
if (error) {
NSLog(@"Failed to update topic: %@", error);
}
}];
// Schedule another update in one second's time.
__weak CounterPublisher *const weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(1.0 *
NSEC_PER_SEC)),
dispatch_get_main_queue(), ^
{
[weakSelf updateCounter];
});
}
@end
Start subscribing with Swift on iOS
Create a client that connects to the Diffusion server. This example uses Swift to create a client and
subscribe to a string topic.
Before you begin
To use this example, you need Apple's Xcode installed on your development system and a Diffusion
server.
Diffusion | 176
You also require that anonymous client connections to the server are assigned a role with the
select_topic and read_topic permissions. For example, the “CLIENT” role. For more information about
roles and permissions, see Role-based authorization on page 124.
Results
This simple subscribing example connects to the Diffusion server. It then adds a local fallback string
value stream on which to receive notifications regarding updates, followed by sending a request to
Diffusion to subscribe to a topic.
You will need a caller to instantiate a new SimpleSubscriber instance and then call publish().
Full example
SimpleSubscriber.swift
import Foundation
import Diffusion
let url = NSURL(string: "wss://host_name")
class SimpleSubscriber: PTDiffusionStringValueStreamDelegate {
var session: PTDiffusionSession?
func subscribe() {
// Connect to Diffusion Cloud
PTDiffusionSession.open(with: url! as URL) { (session,
error) -> Void in
self.session = session; // maintain strong reference
to session
// Add a new stream
let stream =
PTDiffusionPrimitive.stringValueStream(with: self)
session!.topics.addFallbackStream(stream)
// Subscribe to the topic
session!.topics.subscribe(
withTopicSelectorExpression: "example/topic")
{ (error) -> Void in }
}
}
func diffusionStream(_ stream: PTDiffusionStream,
didSubscribeToTopicPath topicPath:
String,
specification:
PTDiffusionTopicSpecification) {
print("Subscribed: \(topicPath)")
}
func diffusionStream(_ stream: PTDiffusionValueStream,
didUpdateTopicPath topicPath: String,
specification:
PTDiffusionTopicSpecification,
oldString: String?,
newString: String?) {
print("Update: \(String(describing: newString))")
}
func diffusionStream(_ stream: PTDiffusionStream,
Diffusion | 177
didUnsubscribeFromTopicPath topicPath:
String,
specification:
PTDiffusionTopicSpecification,
reason:
PTDiffusionTopicUnsubscriptionReason) {
print("Unsubscribed: \(topicPath)")
}
func diffusionDidClose(_ stream: PTDiffusionStream) {
print("Closed")
}
func diffusionStream(_ stream: PTDiffusionStream,
didFailWithError error: Error) {
print("Failed: \(error)")
}
}
Start publishing with Swift on iOS
Create a client that publishes data through topics on the Diffusion server. This example uses Swift to
create a publishing client.
Before you begin
To complete this example, you need Apple's Xcode installed on your development system and a
Diffusion server.
You also require a named user that has a role with the modify_topic and update_topic permissions.
For example, the “TOPIC_CONTROL” role. For more information about roles and permissions, see
Role-based authorization on page 124.
Results
This simple publishing example connects to the Diffusion server using a principal and password
credentials. It then adds a string topic. Once it has received a response from Diffusion to say that the
topic has been created it then updates it to a value of "Hello World" using the non-exclusive string
value updater, obtained from the topic update control feature.
You will need a caller to instantiate a new SimplePublisher instance and then call publish().
Full example
The completed publishing client class contains the following code:
import Foundation
import Diffusion
class SimplePublisher {
func publish() {
// Connect to Diffusion Cloud
let credentials = PTDiffusionCredentials(password:
"<password>")
let configuration = PTDiffusionSessionConfiguration(
principal: "<system user>",
credentials: credentials)
let url = NSURL(string: "wss://host_name")
Diffusion | 178
PTDiffusionSession.open(with: url! as URL, configuration:
configuration) {
(session, error) -> Void in
// Add the topic
session!.topicControl.add(withTopicPath: "example/
topic",
type: PTDiffusionTopicType.string) {
(error) -> Void in
// Update the topic
let updater =
session!.topicUpdateControl.updater.stringValueUpdater()
updater.update(withTopicPath: "example/topic",
value: "Hello World") {
(error) -> Void in }
}
}
}
}
Android
The Android API is bundled in a JAR file and is supported on Android KitKat and later.
Get the Android SDK as a JAR:
Download the JAR from the following URL:
http://download.pushtechnology.com/clients/6.0.2/android/diffusionandroid-6.0.2.jar
The JAR file is also located in your Diffusion server installation:
diffusion_directory/clients/android/diffusion-android-6.0.2.jar
Capabilities
To see the full list of capabilities supported by the Android API, see Feature support in the Diffusion API
on page 37.
Support
Table 25: Supported platforms and transport protocols for the client libraries
Platform
Minimum supported versions
Supported transport protocols
Android
API 19 / v4.4 / KitKat
WebSocket
Note: Push Technology
provides only besteffort support for
Jelly Bean (API 16-18,
v4.1-4.3).
HTTP (polling)
Diffusion | 179
Resources
•
•
Java examples for the Android API.
Android API documentation
Using
Considerations and capabilities that are specific to the Android API
Diffusion connections
Ensure that you use the asynchronous open() method with a callback. Using the
synchronous open() method might open a connection on the same thread as the UI
and cause a runtime exception. However, the synchronous open() method can be
used in any thread that is not the UI thread.
Applications in background state
Android applications can be sent to the background and their activity stopped. When
this happens your application is notified by the onStop() callback of the Android
Activity class. An application's activity can be stopped when the user switches to
another application, starts a new activity from within the application, or receives a
phone call.
When your application's activity is stopped, we recommend that it saves its state
locally – in particular, any topic subscriptions it has made – and closes its client
session with the Diffusion server. When the Diffusion app returns to the foreground,
open a new client session with the Diffusion server and use the saved state to restore
topic subscriptions.
For more information, see the Android Activity Lifecycle documentation and Stopping
and Restarting an Activity.
Consider using push notifications to deliver data to your users when your client
application is in background state. For more information, see Push notification
networks on page 118.
Writing good callbacks
The Android client library invokes callbacks using a thread from Diffusion thread
pool. Callbacks for a particular session are called in order, one at a time. Consider the
following when writing callbacks:
•
•
Do not sleep or call blocking operations in a callback. If you do so, other pending
callbacks for the session are delayed. If you must call a blocking operation,
schedule it in a separate application thread.
You can use the full Diffusion API to make other requests to the server. If you
want to make many requests based on a single callback notification, be aware
that Diffusion client flow control is managed differently in callback threads.
Less throttling is applied and it is easier to overflow the servers by issuing many
thousands of requests. If you have a lot of requests to make, it is better to
schedule the work in an application thread.
Regular expressions
The Android client uses the same regular expression engine to the Diffusion server.
Some regular expressions in topic selectors are evaluated on the client and others
on the Diffusion server. There is no difference in how these regular expressions are
evaluated in the Android client.
Diffusion | 180
Start subscribing with Android
Create an Android client application within minutes that connects to the Diffusion server. This example
creates a client that prints the value of a JSON topic to the console when the topic is updated.
Before you begin
To complete this example, you need Android Studio installed on your development system and a
Diffusion server.
This example was tested in Android Studio 2.3.3. If you are using a different version of Android Studio,
the details of some steps may vary slightly.
You also require that anonymous client connections are assigned a role with the select_topic and
read_topic permissions. For example, the “CLIENT” role. For more information about roles and
permissions, see Role-based authorization on page 124.
About this task
This example steps through the lines of code required to subscribe to a topic. The full code example is
provided after the steps.
Procedure
1. Set up a project in Android Studio that uses the Diffusion API.
a) Create a new project using API Level 21 or later.
b) Copy the diffusion-android-x.x.x.jar file into the app/libs folder of your project.
c) In Android Studio, right-click on the libs folder in the left-hand panel (the Project Tool
Window), then select Add as Library.
If the libs folder is not shown in the left-hand panel, use the pull-down menu at the top of the
panel to select Project view.
2. In your project's AndroidManifest.xml file set the INTERNET permission.
<uses-permission android:name="android.permission.INTERNET"/>
Insert the element between the opening <manifest> tag and the opening <application> tag.
This permission is required to use the Diffusion API.
3. Open your project's MainActivity.java file.
This file is where you develop the code to interact with the Diffusion server.
The empty MainActivity.java file contains the following boilerplate code:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
4. Import the following packages and classes:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
Diffusion | 181
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import com.pushtechnology.diffusion.client.features.Topics;
import
com.pushtechnology.diffusion.client.features.Topics.TopicStream;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionFactory;
import
com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.datatype.json.JSON;
import com.pushtechnology.diffusion.datatype.json.JSONDataType;
public class MainActivity extends AppCompatActivity {
}
5. Create a SessionHandler inner class that implements SessionFactory.OpenCallback.
This inner class will contain the code that interacts with the Diffusion server.
private class SessionHandler implements SessionFactory.OpenCallback
{
private Session session = null;
@Override
public void onOpened(Session session) {
this.session = session;
}
@Override
public void onError(ErrorReason errorReason) {
}
public void close() {
if ( session != null ) {
session.close();
}
}
}
6. In the onOpened method, create the code required to subscribe to the foo/counter topic.
a) Get the Topics feature.
// Get the Topics feature to subscribe to topics
final Topics topics = session.feature( Topics.class );
b) Add an instance of Topics.ValueStream.Default<JSON> as the topic stream for the
foo/counter topic, and subscribe to the topic.
topics.addStream("foo/counter", JSON.class, new
Topics.ValueStream.Default<JSON>() {
@Override
public void onSubscription(String topicPath,
TopicSpecification specification) {
Log.i("diffusion", "Subscribed to: " + topicPath);
}
c) Override the onValue method to print the value of the topic to the log when it changes.
@Override
public void onValue(
Diffusion | 182
String topicPath,
TopicSpecification specification,
JSON oldValue,
JSON newValue) {
Log.i("diffusion", topicPath + ": " +
newValue.toJsonString());
}
7. In the MainActivity class, declare an instance of session handler.
private SessionHandler sessionHandler = null;
8. Override the onCreate method of the MainActivity class to open the session with the
Diffusion server.
private SessionHandler sessionHandler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sessionHandler == null) {
sessionHandler = new SessionHandler();
Diffusion.sessions()
.principal("username")
.password("password")
.open("ws://host:port", sessionHandler);
}
}
You can connect securely, using :
Diffusion.sessions().open("wss://host:port",
sessionHandler);
Or you can connect with a principal and credentials if that principal is assigned a role with the
select_topic and read_topic permissions:
Diffusion.sessions().principal("username").password("password").open("wss://h
sessionHandler);
Replace the host, port, principal, and password values with your own information.
9. Override the onDestroy method of the MainActivity class to close the session with the
Diffusion server.
if ( sessionHandler != null ) {
sessionHandler.close();
sessionHandler = null;
}
super.onDestroy();
10.Compile and run your client.
Diffusion | 183
Results
The client outputs the value to the log console every time the value of the foo/counter JSON topic is
updated. You can update the value of the foo/counter topic by creating a publishing client to update
the topic. To create and publish to the foo/counter topic, you require a user with the modify_topic and
update_topic permissions. For more information, see Start publishing with Android on page 186.
Full example
The completed MainActivity class contains the following code:
package com.pushtechnology.demo.subscribe;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import com.pushtechnology.diffusion.client.features.Topics;
import
com.pushtechnology.diffusion.client.features.Topics.TopicStream;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionFactory;
import
com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.datatype.json.JSON;
import com.pushtechnology.diffusion.datatype.json.JSONDataType;
public class MainActivity extends AppCompatActivity {
/**
* A session handler that maintains the diffusion session.
*/
private class SessionHandler implements
SessionFactory.OpenCallback {
private Session session = null;
@Override
public void onOpened(Session session) {
this.session = session;
// Get the Topics feature to subscribe to topics
final Topics topics = session.feature( Topics.class );
// Subscribe to the "counter" topic and establish a
JSON value stream
topics.addStream("foo/counter", JSON.class, new
Topics.ValueStream.Default<JSON>() {
@Override
public void onSubscription(String topicPath,
TopicSpecification specification) {
Log.i("diffusion", "Subscribed to: " +
topicPath);
}
@Override
public void onValue(
String topicPath,
TopicSpecification specification,
JSON oldValue,
JSON newValue) {
Diffusion | 184
Log.i("diffusion", topicPath + ": " +
newValue.toJsonString());
}
});
topics.subscribe("foo/counter", new
Topics.CompletionCallback.Default());
}
@Override
public void onError(ErrorReason errorReason) {
Log.e( "Diffusion", "Failed to open session because: "
+ errorReason.toString() );
session = null;
}
public void close() {
if (session != null) {
session.close();
}
}
}
private SessionHandler sessionHandler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sessionHandler == null) {
sessionHandler = new SessionHandler();
Diffusion.sessions()
.principal("username")
.password("password")
.open("wss://host:port", sessionHandler);
}
}
@Override
protected void onDestroy() {
if (sessionHandler != null ) {
sessionHandler.close();
sessionHandler = null;
}
super.onDestroy();
}
}
}
Related information
http://developer.android.com/training/index.html
Diffusion | 185
Start publishing with Android
Create an Android client that publishes data through topics on the Diffusion server.
Before you begin
To complete this example, you need Android Studio installed on your development system and a
Diffusion server.
This example was tested in Android Studio 2.3.3. If you are using a different version of Android Studio,
the details of some steps may vary slightly.
You also require a named user that has a role with the modify_topic and update_topic permissions.
For example, the “ADMINISTRATOR” role. For more information about roles and permissions, see Rolebased authorization on page 124.
About this task
This example steps through the lines of code required to publish to a JSON topic. The full code
example is provided after the steps.
Procedure
1. Set up a project in Android Studio that uses the Diffusion API.
a) Create a new project using API Level 21 or later.
b) Copy the diffusion-android-x.x.x.jar into the app/libs folder of your project.
c) In Android Studio, right-click on the libs folder in the left-hand panel (the Project Tool
Window), then select Add as Library.
If the libs folder is not shown in the left-hand panel, use the pull-down menu at the top of the
panel to select Project view.
2. In your project's AndroidManifest.xml file set the INTERNET permission.
<uses-permission android:name="android.permission.INTERNET"/>
Insert the element between the opening <manifest> tag and the opening <application> tag.
This permission is required to use the Diffusion API.
3. Open your project's MainActivity.java file.
This file is where you develop the code to interact with the Diffusion server.
The empty MainActivity.java file contains the following boilerplate code:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
4. Import the following packages and classes:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
Diffusion | 186
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import com.pushtechnology.diffusion.client.features.Topics;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateContro
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionFactory;
import
com.pushtechnology.diffusion.client.topics.details.TopicType;
import com.pushtechnology.diffusion.datatype.json.JSON;
import com.pushtechnology.diffusion.datatype.json.JSONDataType;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class MainActivity extends AppCompatActivity {
}
The com.pushtechnology.diffusion.client packages contain the classes to use to
interact with the Diffusion server.
5. Create a SessionHandler inner class that implements SessionFactory.OpenCallback.
This inner class will contain the code that interacts with the Diffusion server.
private class SessionHandler implements
SessionFactory.OpenCallback {
private Session session = null;
@Override
public void onOpened(Session session) {
this.session = session;
}
@Override
public void onError(ErrorReason errorReason) {
}
public void close() {
if ( session != null ) {
session.close();
}
}
}
6. In the onOpened method, add the code required to create the foo/counter topic and update it
with an incrementing value.
a) Use the TopicControl feature to create a JSON topic.
// Create a JSON topic 'foo/counter'
session.feature(TopicControl.class).addTopic(
"foo/counter",
TopicType.JSON,
new TopicControl.AddCallback.Default());
Diffusion | 187
b) Get the TopicUpdateControl feature and JSON data type.
// Get the TopicUpdateControl feature and JSON data
type
final JSONDataType jsonDataType =
Diffusion.dataTypes().json();
final TopicUpdateControl updateControl = session
.feature(TopicUpdateControl.class);
c) Loop once a second updating the foo/counter topic with an incrementing count from 0 to 1000.
Use the non-exclusive updateControl.updater().update() method to update the
topic while still allowing other clients to update the topic.
final AtomicInteger i = new AtomicInteger(0);
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new
Runnable() {
@Override
public void run() {
// Create the json value
final JSON value = jsonDataType.fromJsonString(
String.format("{\"count\" : %d }",
i.getAndIncrement()));
// Update the topic
updateControl.updater().update(
"counter",
value,
new
TopicUpdateControl.Updater.UpdateCallback.Default());
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
7. In the MainActivity class, declare an instance of session handler.
private SessionHandler sessionHandler = null;
8. Override the onCreate method of the MainActivity class to open the session with the
Diffusion server.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sessionHandler == null) {
sessionHandler = new SessionHandler();
Diffusion.sessions()
.principal("username")
.password("password")
.open("ws://host:port", sessionHandler);
}
}
Diffusion | 188
Or you can connect securely, using :
Diffusion.sessions().principal("principal").password("password").open("wss://
sessionHandler);
Replace the host, port, principal, and password values with your own information.
9. Override the onDestroy method of the MainActivity class to close the session with the
Diffusion server.
if ( sessionHandler != null ) {
sessionHandler.close();
sessionHandler = null;
}
super.onDestroy();
10.Compile and run your client.
Results
The client publishes a JSON value to the foo/counter topic every second. You can subscribe to the foo/
counter topic by creating a client to subscribe to the topic. For more information, see Start subscribing
with Android on page 181.
Full example
The completed MainActivity class contains the following code:
package com.pushtechnology.demo.update;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import com.pushtechnology.diffusion.client.features.Topics;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionFactory;
import
com.pushtechnology.diffusion.client.topics.details.TopicType;
import com.pushtechnology.diffusion.datatype.json.JSON;
import com.pushtechnology.diffusion.datatype.json.JSONDataType;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class MainActivity extends AppCompatActivity {
/**
* A session handler that maintains the diffusion session.
*/
private class SessionHandler implements
SessionFactory.OpenCallback {
private Session session = null;
Diffusion | 189
@Override
public void onOpened(Session session) {
this.session = session;
// Create a JSON topic 'foo/counter'
session.feature(TopicControl.class).addTopic(
"foo/counter",
TopicType.JSON,
new TopicControl.AddCallback.Default());
// Get the TopicUpdateControl feature and JSON data
type
final JSONDataType jsonDataType =
Diffusion.dataTypes().json();
final TopicUpdateControl updateControl = session
.feature(TopicUpdateControl.class);
final AtomicInteger i = new AtomicInteger(0);
// Schedule a recurring task that increments the
counter and updates the "counter" topic with a json value
// every second
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new
Runnable() {
@Override
public void run() {
// Create the json value
final JSON value =
jsonDataType.fromJsonString(
String.format("{\"count\" : %d }",
i.getAndIncrement()));
// Update the topic
updateControl.updater().update(
"counter",
value,
new
TopicUpdateControl.Updater.UpdateCallback.Default());
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
}
@Override
public void onError(ErrorReason errorReason) {
Log.e("Diffusion", "Failed to open session because: "
+ errorReason.toString());
session = null;
}
public void close() {
if (session != null) {
session.close();
}
}
}
private SessionHandler sessionHandler = null;
@Override
Diffusion | 190
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sessionHandler == null) {
sessionHandler = new SessionHandler();
Diffusion.sessions()
.principal("username")
.password("password")
.open("ws://host:port", sessionHandler);
}
}
@Override
protected void onDestroy() {
if (sessionHandler != null) {
sessionHandler.close();
sessionHandler = null;
}
super.onDestroy();
}
}
Related information
http://developer.android.com/training/index.html
Java
The Java API is provided as a JAR file for Oracle Java Development Kit 8 (minimum update 1.8.0_131b11).
Get the Java client libraries using Maven™:
Add the Push Technology public repository to your pom.xml file:
<repositories>
<repository>
<id>push-repository</id>
<url>https://download.pushtechnology.com/maven/</url>
</repository>
</repositories>
Declare the following dependency in your pom.xml file:
<dependency>
<groupId>com.pushtechnology.diffusion</groupId>
<artifactId>diffusion-client</artifactId>
<version>version</version>
</dependency>
Diffusion | 191
Get the Java client libraries using Gradle:
Add the Push Technology public repository to your build.gradle file:
repositories { maven { url "http://download.pushtechnology.com/
maven/" } }
Declare the following dependency in your build.gradle file:
compile 'com.pushtechnology.diffusion:diffusion-client:5.9.0'
Get the Java client libraries:
Download the JAR file from the following URL:
http://download.pushtechnology.com/clients/6.0.2/java/diffusionclient.jar
The ZIP file is also located in your Diffusion server installation:
diffusion_directory/clients/java/diffusion-client-6.0.2.jar
Capabilities
To see the full list of capabilities supported by the Java API, see Feature support in the Diffusion API on
page 37.
Support
Table 26: Supported platforms and transport protocols for the client libraries
Platform
Minimum supported versions
Supported transport protocols
Java
Oracle Java Development Kit
8 (minimum update 1.8.0_131b11). JDK 9 not supported.
WebSocket
HTTP (Polling)
Note: We recommend
that you run your
clients on the JDK
rather than the JRE.
Resources
•
•
Examples for the Java API.
Java API documentation
Using
Certificates
Diffusion Java clients use certificates to validate the security of their connection to
the Diffusion server. The client validates the certificate sent by the Diffusion server
against the set of certificates trusted by the .
Diffusion | 192
If the certificate sent by the Diffusion server cannot be validated
against any certificates in the set trusted by the JDK, you
receive an exception that contains the following message:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target.
Diffusion is authenticated using the certificates provided by your certificate authority
for the domain you host the Diffusion server on.
To ensure that the certificate is validated, set up a trust store for the client and add
the appropriate certificates to that trust store:
1. Obtain the appropriate intermediate certificate from the certificate authority.
2. Use keytool to create a trust store for your client that includes this certificate.
For more information, see https://docs.oracle.com/cd/E19509-01/820-3503/ggfka/
index.html
3. Use system properties to add the trust store to your client.
For example:
System.setProperty("javax.net.ssl.trustStore",
"truststore_name");
Or at the command line:
-Djavax.net.ssl.keyStore=path_to_truststore
Writing good callbacks
The Java client library invokes callbacks using a thread from Diffusion thread pool.
Callbacks for a particular session are called in order, one at a time. Consider the
following when writing callbacks:
•
•
Do not sleep or call blocking operations in a callback. If you do so, other pending
callbacks for the session are delayed. If you must call a blocking operation,
schedule it in a separate application thread.
You can use the full Diffusion API to make other requests to the server. If you
want to make many requests based on a single callback notification, be aware
that Diffusion client flow control is managed differently in callback threads.
Less throttling is applied and it is easier to overflow the servers by issuing many
thousands of requests. If you have a lot of requests to make, it is better to
schedule the work in an application thread.
Regular expressions
The Java client uses the same regular expression engine to the Diffusion server. Some
regular expressions in topic selectors are evaluated on the client and others on the
Diffusion server. There is no difference in how these regular expressions are evaluated
in the Java client.
Start subscribing with Java
Create a Java client within minutes that connects to the Diffusion server. This example creates a client
that prints the value of a topic to the console when the topic is updated.
Before you begin
To complete this example, you need a Diffusion server.
Diffusion | 193
You also require either a named user that has a role with the select_topic and read_topic permissions
or that anonymous client connections are assigned a role with the select_topic and read_topic
permissions. For example, the “CLIENT” role. For more information about roles and permissions, see
Role-based authorization on page 124.
About this task
This example steps through the lines of code required to subscribe to a topic. There are several
different topic types which provide data in different formats. This example shows you how to
subscribe to a JSON topic. The full code example is provided after the steps.
Procedure
1. Include the client jar file on the build classpath of your Java client. You can use one of the following
methods:
•
You can use Maven to declare the dependency. First add the Push Technology public repository
to your pom.xml file:
<repositories>
<repository>
<id>push-repository</id>
<url>https://download.pushtechnology.com/maven/</url>
</repository>
</repositories>
Next declare the following dependency in your pom.xml file:
<dependency>
<groupId>com.pushtechnology.diffusion</groupId>
<artifactId>diffusion-client</artifactId>
<version>version</version>
</dependency>
Where version is the Diffusion version, for example 6.0.2.
If you are not using Maven, you can include the diffusion-client-version.jar file that
is located in the clients/java directory of your Diffusion server installation.
•
A diffusion-api-version.jar is also provided. This file contains only the development
interfaces without any client library capabilities and can be used for developing and compiling
your Java clients. However, to run your Diffusion Java client you must use the diffusionclient-version.jar file.
2. Create a client class that imports the following packages and classes:
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.content.Content;
import com.pushtechnology.diffusion.client.features.Topics;
import
com.pushtechnology.diffusion.client.features.Topics.ValueStream;
import com.pushtechnology.diffusion.client.session.Session;
import
com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.datatype.json.JSON;
public class SubscribingClient {
}
Diffusion | 194
3. Create a main method for the client.
public class SubscribingClient {
public static void main(String... arguments) throws
Exception {
}
4. In the main method, connect to the Diffusion server.
// Connect anonymously
// Replace 'host' with your hostname
final Session session =
Diffusion.sessions().open("ws://host:port");
Or you can connect securely, using :
final Session session =
Diffusion.sessions().open("ws://host:port");
Or you can connect with a principal and credentials if that principal is assigned a role with the
read_topic permission:
final Session session =
Diffusion.sessions().principal("principal")
.password("password").open("ws://host:port");
Replace the host, port, principal, and password values with your own information.
5. Next, in the main method, get the Topics feature.
// Get the Topics feature to subscribe to topics
final Topics topics = session.feature(Topics.class);
The Topics feature enables a client to subscribe to a topic or fetch its state. For more information,
see .
6. Within the SubscribingClient class, create an inner class that extends
ValueStream.Default<JSON> and overrides the onValue method.
This inner class defines the behavior that occurs when a topic that the client subscribes to is
updated. In this example, the value stream prints the topic name and the value of the update to the
console.
private static class ValueStreamPrintLn extends
ValueStream.Default<Content> {
@Override
public void onValue(
String topicPath,
TopicSpecification specification,
Content oldValue,
Content newValue) {
System.out.println(topicPath + ":
" +
newValue.asString());
}
}
Diffusion | 195
7. Back in the main method of the SubscribingClient class, use the addStream method
to associate an instance of the value stream that you created with the JSON topic you want to
subscribe to.
// Add a new stream for 'foo/counter'
topics.addStream("foo/counter", JSON.class, new
Topics.ValueStream.Default<JSON>() {
@Override
public void onSubscription(String topicPath,
TopicSpecification specification) {
System.out.println("Subscribed to: " + topicPath);
}
@Override
public void onValue(String topicPath,
TopicSpecification specification, JSON oldValue, JSON newValue) {
System.out.println(topicPath + " : " +
newValue.toJsonString());
}
});
8. Next, use the subscribe method to subscribe to the topic foo/counter.
// Subscribe to the topic 'foo/counter'
topics.subscribe("foo/counter", new
Topics.CompletionCallback.Default());
9. Use a Thread.sleep() to hold the client open for a minute while the updates are received and
output.
// Wait for a minute while the stream prints updates
Thread.sleep(60000);
10.Compile and run your client.
Ensure that the diffusion-client-version.jar file is included in your compiled client or
on its classpath.
We recommend that you run your client using the JDK rather than the JRE. The JDK includes
additional diagnostic capabilities that might be useful.
Results
The client outputs the value to the console every time the value of the foo/counter topic is updated.
You can update the value of the foo/counter topic by creating a publishing client to update the
topic. To create and publish to the foo/counter topic, you require a user with the modify_topic and
update_topic permissions. For more information, see Start publishing with Java on page 198.
Full example
The completed SubscribingClient class contains the following code:
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.content.Content;
import com.pushtechnology.diffusion.client.features.Topics;
import
com.pushtechnology.diffusion.client.features.Topics.ValueStream;
import com.pushtechnology.diffusion.client.session.Session;
import
com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.datatype.json.JSON;
Diffusion | 196
/**
* A client that subscribes to the topic 'foo/counter.
*
* @author Push Technology Limited
* @since 5.9
*/
public class SubscribingClient {
/**
* Main.
*/
public static void main(String... arguments) throws Exception
{
// Connect anonymously
// Replace 'host' with your hostname
final Session session =
Diffusion.sessions().open("ws://host:port");
// Get the Topics feature to subscribe to topics
final Topics topics = session.feature(Topics.class);
// Add a new stream for 'foo/counter'
topics.addStream("foo/counter", JSON.class, new
Topics.ValueStream.Default<JSON>() {
@Override
public void onSubscription(String topicPath,
TopicSpecification specification) {
System.out.println("Subscribed to: " + topicPath);
}
@Override
public void onValue(String topicPath,
TopicSpecification specification, JSON oldValue, JSON newValue) {
System.out.println(topicPath + " : " +
newValue.toJsonString());
}
});
// Subscribe to the topic 'foo/counter'
topics.subscribe("foo/counter", new
Topics.CompletionCallback.Default());
// Wait for a minute while the stream prints updates
Thread.sleep(60000);
}
/**
* A topic stream that prints updates to the console.
*/
private static class ValueStreamPrintLn extends
ValueStream.Default<Content> {
@Override
public void onValue(
String topicPath,
TopicSpecification specification,
Content oldValue,
Content newValue) {
System.out.println(topicPath + ":
" +
newValue.asString());
}
}
Diffusion | 197
}
Start publishing with Java
Create a Java client that publishes data through topics on the Diffusion server.
Before you begin
To complete this example, you need a Diffusion server and a development system with Java installed
on it.
You also require a principal that has a role with the modify_topic and update_topic permissions. For
example, the “ADMINISTRATOR” role. For more information about roles and permissions, see Rolebased authorization on page 124.
About this task
This example steps through the lines of code required to publish a value to a JSON topic. The full code
example is provided after the steps.
Procedure
1. Include the client jar file on the build classpath of your Java client. You can use one of the following
methods:
•
You can use Maven to declare the dependency. First add the Push Technology public repository
to your pom.xml file:
<repositories>
<repository>
<id>push-repository</id>
<url>https://download.pushtechnology.com/maven/</url>
</repository>
</repositories>
Next declare the following dependency in your pom.xml file:
<dependency>
<groupId>com.pushtechnology.diffusion</groupId>
<artifactId>diffusion-client</artifactId>
<version>version</version>
</dependency>
Where version is the Diffusion version, for example 6.0.2.
If you are not using Maven, you can include the diffusion-client.jar file that is located
in the clients/java directory of your Diffusion server installation.
2. Create a PublishingClient class that imports the following packages and classes:
•
import com.pushtechnology.diffusion.client.Diffusion;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateContro
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateContro
import com.pushtechnology.diffusion.client.session.Session;
import
com.pushtechnology.diffusion.client.topics.details.TopicType;
import com.pushtechnology.diffusion.datatype.json.JSON;
Diffusion | 198
import com.pushtechnology.diffusion.datatype.json.JSONDataType;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public final class PublishingClient {
}
The com.pushtechnology.diffusion.client packages contain the classes to use to
interact with the Diffusion server. The java.util.concurrent.CompletableFuture
and java.util.concurrent.TimeUnit classes are used to simplify this example. The
CompletableFuture result is accessed with get, which is synchronous. However, the Diffusion API is
designed to be most powerful when used asynchronously.
3. Create a main method.
public final class PublishingClient {
public static void main(String... arguments) throws
InterruptedException, ExecutionException, TimeoutException {
}
}
4. Inside the main method, connect to the Diffusion server.
Make sure to edit the host value in the example to match your Diffusion server's hostname or IP
address.
The principal you use to connect must have modify_topic and update_topic permissions. In this
example, we use the default control principal.
final Session session =
Diffusion.sessions().principal("control")
.password("password").open("ws://host:8080");
Or you can connect securely to the Diffusion server using :
.open("wss://host:port");
Replace the host, port, principal, and password values with your own information.
You can choose to connect anonymously if anonymous sessions are assigned the modify_topic and
update_topic permissions. However, we do not recommend that anonymous sessions are given
write access to data on the Diffusion server.
5. Next, in the main method, get the TopicControl and TopicUpdateControl features.
// Get the TopicControl and TopicUpdateControl feature
TopicControl topicControl =
session.feature(TopicControl.class);
TopicUpdateControl updateControl = session
.feature(TopicUpdateControl.class);
The TopicControl feature enables a client to create and delete topics. For more information, see
Managing topics on page 291.
The TopicUpdateControl feature enables a client to publish updates to a topic. For more
information, see Updating topics on page 351.
Diffusion | 199
6. Next, create an instance of JSONDataType to store the value of the JSON topic.
final JSONDataType jsonDataType =
Diffusion.dataTypes().json();
7. Next, in the main method, use the TopicControl feature to create the foo/counter topic.
// Create a JSON topic 'foo/counter'
final CompletableFuture<TopicControl.AddTopicResult> future
= topicControl.addTopic(
"foo/counter",
TopicType.JSON);
// Wait for the CompletableFuture to complete
future.get(10, TimeUnit.SECONDS);
8. Next, in the main method, loop once a second updating the foo/counter topic with an
incrementing count from 0 to 1000.
Use the updateControl.updater().update() method to update a topic without locking
that topic.
// Update the topic
final UpdateCallback updateCallback = new
UpdateCallback.Default();
for (int i = 0; i < 1000; ++i) {
final JSON value =
jsonDataType.fromJsonString(String.format("{\"count\" : %d }",
i));
// Use the non-exclusive updater to update the topic
without locking it
updateControl.updater().update(
"foo/counter",
Integer.toString(i),
updateCallback);
Thread.sleep(1000);
}
9. Compile and run your client.
We recommend that you run your client using the JDK rather than the JRE. The JDK includes
additional diagnostic capabilities that might be useful.
Results
The client publishes a value to the foo/counter topic every second. You can subscribe to the foo/
counter topic by creating a client to subscribe to the topic. For more information, see Start subscribing
with Java on page 193.
Full example
The completed PublishingClient class contains the following code:
import com.pushtechnology.diffusion.client.Diffusion;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl
import com.pushtechnology.diffusion.client.session.Session;
Diffusion | 200
import
com.pushtechnology.diffusion.client.topics.details.TopicType;
import com.pushtechnology.diffusion.datatype.json.JSON;
import com.pushtechnology.diffusion.datatype.json.JSONDataType;
import java.util.concurrent.CountDownLatch;
/**
* A client that publishes an incrementing count to the topic
'foo/counter'.
*
* @author Push Technology Limited
* @since 5.9
*/
public final class PublishingClient {
/**
* Main.
*/
public static void main(String... arguments) throws
InterruptedException, ExecutionException, TimeoutException {
// Connect using a principal with 'modify_topic' and
'update_topic'
// permissions
// Change 'host' to the hostname/address of your Diffusion
server
final Session session =
Diffusion.sessions().principal("control")
.password("password").open("ws://host:port");
// Get the TopicControl and TopicUpdateControl feature
final TopicControl topicControl =
session.feature(TopicControl.class);
final TopicUpdateControl updateControl =
session.feature(TopicUpdateControl.class);
final JSONDataType jsonDataType =
Diffusion.dataTypes().json();
// Create a JSON topic 'foo/counter'
final CompletableFuture<TopicControl.AddTopicResult>
future = topicControl.addTopic(
"foo/counter",
TopicType.JSON);
// Wait for the CompletableFuture to complete
future.get(10, TimeUnit.SECONDS);
// Update the topic
final UpdateCallback updateCallback = new
UpdateCallback.Default();
for (int i = 0; i < 1000; ++i) {
final JSON value =
jsonDataType.fromJsonString(String.format("{\"count\" : %d }",
i));
// Use the non-exclusive updater to update the topic
without locking it
updateControl.updater().update(
"foo/counter",
value,
updateCallback);
Diffusion | 201
Thread.sleep(1000);
}
}
}
.NET
The .NET API is provided as a DLL file compatible with .NET Framework 4.6.1 and above.
Get the .NET SDK from NuGet:
Use the following command in the NuGet Package Manager Console:
PM> Install-Package PushTechnology.UnifiedClientInterface
Get the .NET SDK:
Download the DLL file from the following URL:
http://download.pushtechnology.com/clients/6.0.2/dotnet/
PushTechnology.ClientInterface.dll
The .NET SDK also requires NLog:
http://download.pushtechnology.com/clients/6.0.2/dotnet/NLog.dll
You can download these XML files to get IntelliSense documentation:
http://download.pushtechnology.com/clients/6.0.2/dotnet/
PushTechnology.ClientInterface.XML
http://download.pushtechnology.com/clients/6.0.2/dotnet/NLog.xml
These files are is also located in your Diffusion server installation:
diffusion_directory/clients/dotnet
Capabilities
To see the full list of capabilities supported by the .NET API, see Feature support in the Diffusion API on
page 37.
Support
Table 27: Supported platforms and transport protocols for the client libraries
Platform
Minimum supported versions
Supported transport protocols
.NET
4.6.1
WebSocket
Diffusion | 202
Resources
•
•
Examples for the .NET API.
.NET API documentation
Using
Certificates
Diffusion .NET clients use certificates to validate the security of their connection to
the Diffusion server. The client validates the certificate sent by the Diffusion server
against the set of certificates trusted by the .NET Framework.
If the certificate sent by the Diffusion server cannot be validated against any
certificates in the set trusted by the .NET Framework, you must set up a trust store for
the client and add the appropriate certificates to that trust store.
Diffusion is authenticated using the certificates provided by your certificate authority
for the domain you host the Diffusion server on.
1. Obtain the appropriate intermediate certificate from the certificate authority.
2. Use the Microsoft Management Console to import the certificate into the
Trusted Root Certification Authorities folder. For more information, see https://
msdn.microsoft.com/en-us/library/aa738659(v=vs.110).aspx
Writing good callbacks
The .NET client library invokes callbacks using a single inbound thread. Callbacks for
a particular session are called in order, one at a time. Consider the following when
writing callbacks:
•
•
Do not sleep or call blocking operations in a callback. If you do so, other pending
callbacks for the session are delayed. If you must call a blocking operation,
schedule it in a separate application thread.
You can use the full Diffusion API to make other requests to the server. If you
want to make many requests based on a single callback notification, be aware
that Diffusion client flow control is managed differently in callback threads.
Less throttling is applied and it is easier to overflow the servers by issuing many
thousands of requests. If you have a lot of requests to make, it is better to
schedule the work in an application thread.
Regular expressions
The .NET client uses a different regular expression engine to the Diffusion server.
Some regular expressions in topic selectors are evaluated on the client and others
on the Diffusion server. It is possible that topic selectors that include complex
or advanced regular expressions can behave differently on the client and on the
Diffusion server.
For more information, see Regular expressions on page 55.
Start subscribing with .NET
Create a .NET client within minutes that connects to the Diffusion server. This example creates a client
that prints the value of a JSON topic to the console when the topic is updated.
Before you begin
To complete this example, you need a Diffusion server.
Diffusion | 203
You also require either a named user that has a role with the select_topic and read_topic permissions
or that anonymous client connections are assigned a role with the select_topic and read_topic
permissions. For example, the “CLIENT” role. For more information about roles and permissions, see
Role-based authorization on page 124.
About this task
This example steps through the lines of code required to subscribe to a topic. The full code example is
provided after the steps.
Procedure
1. Create a .NET project that references the following DLL file located in the clients/dotnet
directory of your .NET installation:
PushTechnology.ClientInterface.dll
The assembly contains the interface classes and interfaces that you use when
creating a control client.
The .NET assembly is also available through NuGet.
Use the following command in the NuGet Package Manager Console:
PM> Install-Package PushTechnology.UnifiedClientInterface
2. In your project, create a C# file that uses the following packages:
using
using
using
using
using
using
using
using
System;
System.Threading;
PushTechnology.ClientInterface.Client.Callbacks;
PushTechnology.ClientInterface.Client.Factories;
PushTechnology.ClientInterface.Client.Features;
PushTechnology.ClientInterface.Client.Features.Topics;
PushTechnology.ClientInterface.Client.Topics.Details;
PushTechnology.ClientInterface.Data.JSON;
namespace PushTechnology.ClientInterface.GettingStarted {
public sealed class SubscribingClient {
}
}
3. Create a Main method.
public sealed class SubscribingClient
{
public static void Main( string[] args ) {
}
}
4. In the Main method, connect to the Diffusion server.
public static void Main( string[] args ) {
// Connect anonymously
// Replace 'host' with your hostname
var session =
Diffusion.Sessions.Open("ws://host:port");
}
Diffusion | 204
Or you can connect securely, using :
var session =
Diffusion.sessions().Open("wss://host:port");
Or you can connect with a principal and credentials if that principal is assigned a role with the
read_topic permission:
var session =
Diffusion.Sessions().Principal("principal")
.Password("password").Open("wss://host:port");
Replace the host, port, principal, and password values with your own information.
5. Next, in the Main method, get the Topics feature.
// Get the Topics feature to subscribe to topics
var topics = session.Topics;
The Topics feature enables a client to subscribe to a topic or fetch its state.
6. Within the subscribing client class, create an inner class that implements IValueStream for a
JSON topic.
This inner class defines the behavior that occurs when a topic that the client subscribes to is
updated. In this example, the topic stream prints the topic path and the content of the update to
the console.
internal sealed class JSONStream : IValueStream<IJSON> {
public void OnClose() {
Console.WriteLine( "The subscription stream is now
closed." );
}
public void OnError( ErrorReason errorReason ) {
Console.WriteLine( "An error has occured : {0}",
errorReason );
}
public void OnSubscription( string topicPath,
ITopicSpecification specification ) {
Console.WriteLine( "Client subscribed to {0} ",
topicPath );
}
public void OnUnsubscription( string topicPath,
ITopicSpecification specification, TopicUnsubscribeReason reason )
{
Console.WriteLine( "Client unsubscribed from {0} :
{1}", topicPath, reason );
}
public void OnValue( string topicPath, ITopicSpecification
specification, IJSON oldValue, IJSON newValue ) {
Console.WriteLine( "New value of {0} is {1}",
topicPath, newValue.ToJSONString() );
}
}
Diffusion | 205
7. Back in the Main method of the subscribing client class, use the AddStream method to associate
an instance of the topic stream that you created with the topic you want to subscribe to. In this
example, the topic path is "foo/counter".
var topic = ">foo/counter";
// Add a topic stream for 'foo/counter' and request
subscription
var jsonStream = new JSONStream();
topics.AddStream( topic, jsonStream );
topics.Subscribe( topic, new
TopicsCompletionCallbackDefault() );
8. Use a Thread.sleep() to hold the client open for 10 minutes while the updates are received
and output.
//Stay connected for 10 minutes
Thread.Sleep( TimeSpan.FromMinutes( 10 ) );
session.Close();
9. Compile and run your client.
Results
The client outputs the value to the console every time the value of the foo/counter topic is updated.
You can update the value of the foo/counter topic by creating a publishing client to update the
topic. To create and publish to the foo/counter topic, you require a user with the modify_topic and
update_topic permissions. For more information, see Start publishing with .NET on page 208.
Full example
The completed subscribing client class contains the following code:
/**
* Copyright © 2016, 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS"
BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using
using
using
using
using
using
System;
System.Threading;
PushTechnology.ClientInterface.Client.Callbacks;
PushTechnology.ClientInterface.Client.Factories;
PushTechnology.ClientInterface.Client.Features;
PushTechnology.ClientInterface.Client.Features.Topics;
Diffusion | 206
using PushTechnology.ClientInterface.Client.Topics.Details;
using PushTechnology.ClientInterface.Data.JSON;
namespace PushTechnology.ClientInterface.GettingStarted {
/// <summary>
/// A client that subscribes to the topic 'foo/counter'.
/// </summary>
public sealed class SubscribingClient {
public static void Main( string[] args ) {
// Connect anonymously
var session = Diffusion.Sessions.Open( "ws://
localhost:8080" );
// Get the Topics feature to subscribe to topics
var topics = session.Topics;
var topic = ">foo/counter";
// Add a topic stream for 'random/JSON' and request
subscription
var jsonStream = new JSONStream();
topics.AddStream( topic, jsonStream );
topics.Subscribe( topic, new
TopicsCompletionCallbackDefault() );
//Stay connected for 10 minutes
Thread.Sleep( TimeSpan.FromMinutes( 10 ) );
session.Close();
}
}
/// <summary>
/// Basic implementation of the IValueStream<TValue> for JSON
topics.
/// </summary>
internal sealed class JSONStream : IValueStream<IJSON> {
/// <summary>
/// Notification of stream being closed normally.
/// </summary>
public void OnClose() {
Console.WriteLine( "The subscription stream is now
closed." );
}
/// <summary>
/// Notification of a contextual error related to this
callback.
/// </summary>
/// <remarks>
/// Situations in which <code>OnError</code> is called
include the session being closed, a communication
/// timeout, or a problem with the provided parameters. No
further calls will be made to this callback.
/// </remarks>
/// <param name="errorReason">Error reason.</param>
public void OnError( ErrorReason errorReason ) {
Console.WriteLine( "An error has occured : {0}",
errorReason );
}
/// <summary>
/// Notification of a successful subscription.
Diffusion | 207
/// </summary>
/// <param name="topicPath">Topic path.</param>
/// <param name="specification">Topic specification.</
param>
public void OnSubscription( string topicPath,
ITopicSpecification specification ) {
Console.WriteLine( "Client subscribed to {0} ",
topicPath );
}
///
///
///
///
///
<summary>
Notification of a successful unsubscription.
</summary>
<param name="topicPath">Topic path.</param>
<param name="specification">Topic specification.</
param>
/// <param name="reason">Error reason.</param>
public void OnUnsubscription( string topicPath,
ITopicSpecification specification, TopicUnsubscribeReason
reason ) {
Console.WriteLine( "Client unsubscribed from {0} :
{1}", topicPath, reason );
}
///
///
///
///
///
<summary>
Topic update received.
</summary>
<param name="topicPath">Topic path.</param>
<param name="specification">Topic specification.</
param>
/// <param name="oldValue">Value prior to update.</param>
/// <param name="newValue">Value after update.</param>
public void OnValue( string topicPath, ITopicSpecification
specification, IJSON oldValue, IJSON newValue ) {
Console.WriteLine( "New value of {0} is {1}",
topicPath, newValue.ToJSONString() );
}
}
}
Start publishing with .NET
Create a .NET client that publishes data through topics on the Diffusion server.
Before you begin
To complete this example, you need a Diffusion server and a development system with the .NET
Framework installed on it.
You also require either a named user that has a role with the modify_topic and update_topic
permissions. For example, the “ADMINISTRATOR” role. For more information about roles and
permissions, see Role-based authorization on page 124.
About this task
This example steps through the lines of code required to subscribe to a JSON topic. The full code
example is provided after the steps.
Diffusion | 208
Procedure
1. Create a .NET project that references the following DLL file located in the clients/dotnet
directory of your .NET installation:
PushTechnology.ClientInterface.dll
The assembly contains the interfaces classes and interfaces that you use when
creating a control client.
Use the following command in the NuGet Package Manager Console:
PM> Install-Package PushTechnology.UnifiedClientInterface
2. In your project, create a C# file that uses the following packages:
using System;
using System.Threading;
using PushTechnology.ClientInterface.Client.Callbacks;
using PushTechnology.ClientInterface.Client.Factories;
using
PushTechnology.ClientInterface.Client.Features.Control.Topics;
using PushTechnology.ClientInterface.Data.JSON;
namespace PushTechnology.ClientInterface.GettingStarted {
}
3. Create a Main method.
public sealed class PublishingClient
{
public static void Main( string[] args ) {
}
}
4. Inside the Main method, connect to the Diffusion server.
public static void Main( string[] args ) {
// Connect using a principal with 'modify_topic' and
'update_topic'
// permissions
var session =
Diffusion.Sessions.Principal("principal").Password("password").Open("ws://hos
}
Or you can connect securely to the Diffusion server using :
.Open("wss://host:port");
Replace the host, port, principal, and password values with your own information.
You can choose to connect anonymously if anonymous sessions are assigned the modify_topic and
update_topic permissions. However, we do not recommend that anonymous sessions are given
write access to data on the Diffusion server.
5. Next, in the Main method, get the TopicControl and TopicUpdateControl features.
// Get the TopicControl and TopicUpdateControl features
var topicControl = session.TopicControl;
var updateControl = session.TopicUpdateControl;
Diffusion | 209
The TopicControl feature enables a client to create and delete topics. For more information, see .
The TopicUpdateControl feature enables a client to publish updates to a topic. For more
information, see .
6. Next, in the Main method, use the TopicControl feature to create the foo/counter JSON topic.
// Create a JSON topic 'foo/counter'
var topic = "foo/counter";
var addCallback = new AddCallback();
topicControl.AddTopicFromValue(
topic,
Diffusion.DataTypes.JSON.FromJSONString( "{\"date
\":\"To be updated\",\"time\":\"To be updated\"}" ),
addCallback );
// Wait for the OnTopicAdded callback, or a failure
if ( !
addCallback.Wait( TimeSpan.FromSeconds( 5 ) ) ) {
Console.WriteLine( "Callback not received
within timeout." );
session.Close();
return;
} else if ( addCallback.Error != null ) {
Console.WriteLine( "Error : {0}",
addCallback.Error.ToString() );
session.Close();
return;
}
The AddTopicFromValue() method requires a callback, which you must create.
7. Implement an instance of ITopicControlAddCallback as an internal sealed class.
internal sealed class AddCallback : ITopicControlAddCallback {
private readonly AutoResetEvent resetEvent = new
AutoResetEvent( false );
/// <summary>
/// Any error from this AddCallback will be stored here.
/// </summary>
public Exception Error {
get;
private set;
}
/// <summary>
/// Constructor.
/// </summary>
public AddCallback() {
Error = null;
}
/// <summary>
/// This is called to notify that a call context was closed
prematurely, typically due to a timeout or the
/// session being closed.
/// </summary>
/// <remarks>
/// No further calls will be made for the context.
/// </remarks>
public void OnDiscard() {
Diffusion | 210
Error = new Exception( "This context was closed
prematurely." );
resetEvent.Set();
}
/// <summary>
/// This is called to notify that the topic has been added.
/// </summary>
/// <param name="topicPath">The full path of the topic that
was added.</param>
public void OnTopicAdded( string topicPath ) {
Console.WriteLine( "Topic {0} added.", topicPath );
resetEvent.Set();
}
/// <summary>
/// This is called to notify that an attempt to add a topic
has failed.
/// </summary>
/// <param name="topicPath">The topic path as supplied to
the add request.</param>
/// <param name="reason">The reason for failure.</param>
public void OnTopicAddFailed( string topicPath,
TopicAddFailReason reason ) {
Error = new Exception( string.Format( "Failed to add
topic {0} : {1}", topicPath, reason ) );
resetEvent.Set();
}
///
///
///
///
<summary>
Wait for one of the callbacks for a given time.
</summary>
<param name="timeout">Time to wait for the callback.</
param>
/// <returns><c>true</c> if either of the callbacks has
been triggered. Otherwise <c>false</c>.</returns>
public bool Wait( TimeSpan timeout ) {
return resetEvent.WaitOne( timeout );
}
}
8. Back in the Main method, loop once a second updating the foo/counter topic with an
incrementing count.
Use the updateControl.Updater.Update() method to update a topic without locking that
topic.
var updateCallback = new UpdateCallback( topic );
for ( var i = 0; i < 1000; ++i ) {
updateControl.Updater.Update( topic, i.ToString(),
updateCallback );
Thread.Sleep( 1000 );
}
The Update() method requires a callback, which you must create.
9. Implement an instance of ITopicUpdaterUpdateCallback as an internal sealed class.
internal sealed class UpdateCallback :
ITopicUpdaterUpdateCallback {
private readonly string topicPath;
Diffusion | 211
/// <summary>
/// Constructor.
/// </summary>
/// <param name="topicPath">The topic path.</param>
public UpdateCallback( string topicPath ) {
this.topicPath = topicPath;
}
/// <summary>
/// Notification of a contextual error related to
this callback.
/// </summary>
/// <remarks>
/// Situations in which <code>OnError</code> is
called include the session being closed, a communication
/// timeout, or a problem with the provided
parameters. No further calls will be made to this callback.
/// </remarks>
/// <param name="errorReason">A value representing
the error.</param>
public void OnError( ErrorReason errorReason ) {
Console.WriteLine( "Topic {0} could not be
updated : {1}", topicPath, errorReason );
}
/// <summary>
/// Indicates a successful update.
/// </summary>
public void OnSuccess() {
Console.WriteLine( "Topic {0} updated
successfully.", topicPath );
}
}
10.Finally, in the Main method, close the session between your client and the Diffusion server.
// Close session
session.Close();
11.Compile and run your client.
Results
The client publishes a value to the foo/counter topic every second. You can subscribe to the foo/
counter topic by creating a client to subscribe to the topic. For more information, see Start subscribing
with .NET on page 203.
Full example
The completed publishing client class contains the following code:
/**
* Copyright © 2016, 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
Diffusion | 212
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS"
BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using System;
using System.Threading;
using PushTechnology.ClientInterface.Client.Callbacks;
using PushTechnology.ClientInterface.Client.Factories;
using
PushTechnology.ClientInterface.Client.Features.Control.Topics;
using PushTechnology.ClientInterface.Data.JSON;
namespace PushTechnology.ClientInterface.GettingStarted {
/// <summary>
/// A client that publishes an incrementing count to the JSON
topic "foo/counter".
/// </summary>
public sealed class PublishingClient {
public static void Main( string[] args ) {
// Connect using a principal with 'modify_topic' and
'update_topic' permissions
var session =
Diffusion.Sessions.Principal( "control" ).Password( "password" ).Open( "ws://
localhost:8080" );
// Get the TopicControl and TopicUpdateControl
features
var topicControl = session.TopicControl;
var updateControl = session.TopicUpdateControl;
// Create a JSON topic 'foo/counter'
var topic = "foo/counter";
var addCallback = new AddCallback();
topicControl.AddTopicFromValue(
topic,
Diffusion.DataTypes.JSON.FromJSONString( "{\"date
\":\"To be updated\",\"time\":\"To be updated\"}" ),
addCallback );
// Wait for the OnTopicAdded callback, or a failure
if ( !addCallback.Wait( TimeSpan.FromSeconds( 5 ) ) )
{
Console.WriteLine( "Callback not received within
timeout." );
session.Close();
return;
} else if ( addCallback.Error != null ) {
Console.WriteLine( "Error : {0}",
addCallback.Error.ToString() );
session.Close();
return;
}
// Update topic every 300 ms for 30 minutes
var updateCallback = new UpdateCallback( topic );
for ( var i = 0; i < 3600; ++i ) {
Diffusion | 213
var newValue =
Diffusion.DataTypes.JSON.FromJSONString(
"{\"date\":\"" +
DateTime.Today.Date.ToString( "D" ) + "\"," +
"\"time\":\"" +
DateTime.Now.TimeOfDay.ToString( "g" ) + "\"}" );
updateControl.Updater.ValueUpdater<IJSON>().Update( topic,
newValue, updateCallback );
Thread.Sleep( 300 );
}
// Close session
session.Close();
}
}
/// <summary>
/// Basic implementation of the ITopicControlAddCallback.
/// </summary>
internal sealed class AddCallback : ITopicControlAddCallback {
private readonly AutoResetEvent resetEvent = new
AutoResetEvent( false );
/// <summary>
/// Any error from this AddCallback will be stored here.
/// </summary>
public Exception Error {
get;
private set;
}
/// <summary>
/// Constructor.
/// </summary>
public AddCallback() {
Error = null;
}
/// <summary>
/// This is called to notify that a call context was
closed prematurely, typically due to a timeout or the
/// session being closed.
/// </summary>
/// <remarks>
/// No further calls will be made for the context.
/// </remarks>
public void OnDiscard() {
Error = new Exception( "This context was closed
prematurely." );
resetEvent.Set();
}
/// <summary>
/// This is called to notify that the topic has been
added.
/// </summary>
/// <param name="topicPath">The full path of the topic
that was added.</param>
public void OnTopicAdded( string topicPath ) {
Console.WriteLine( "Topic {0} added.", topicPath );
resetEvent.Set();
Diffusion | 214
}
/// <summary>
/// This is called to notify that an attempt to add a
topic has failed.
/// </summary>
/// <param name="topicPath">The topic path as supplied to
the add request.</param>
/// <param name="reason">The reason for failure.</param>
public void OnTopicAddFailed( string topicPath,
TopicAddFailReason reason ) {
Error = new Exception( string.Format( "Failed to add
topic {0} : {1}", topicPath, reason ) );
resetEvent.Set();
}
///
///
///
///
<summary>
Wait for one of the callbacks for a given time.
</summary>
<param name="timeout">Time to wait for the callback.</
param>
/// <returns><c>true</c> if either of the callbacks has
been triggered. Otherwise <c>false</c>.</returns>
public bool Wait( TimeSpan timeout ) {
return resetEvent.WaitOne( timeout );
}
}
/// <summary>
/// A simple ITopicUpdaterUpdateCallback implementation that
prints confimation of the actions completed.
/// </summary>
internal sealed class UpdateCallback :
ITopicUpdaterUpdateCallback {
private readonly string topicPath;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="topicPath">The topic path.</param>
public UpdateCallback( string topicPath ) {
this.topicPath = topicPath;
}
/// <summary>
/// Notification of a contextual error related to this
callback.
/// </summary>
/// <remarks>
/// Situations in which <code>OnError</code> is called
include the session being closed, a communication
/// timeout, or a problem with the provided parameters. No
further calls will be made to this callback.
/// </remarks>
/// <param name="errorReason">A value representing the
error.</param>
public void OnError( ErrorReason errorReason ) {
Console.WriteLine( "Topic {0} could not be updated :
{1}", topicPath, errorReason );
}
/// <summary>
/// Indicates a successful update.
Diffusion | 215
/// </summary>
public void OnSuccess() {
Console.WriteLine( "Topic {0} updated successfully.",
topicPath );
}
}
}
C
The C client libraries are provided for Linux, Windows, and OS X/macOS.
Get the C client libraries for Linux:
Download the ZIP file from the following URL:
http://download.pushtechnology.com/clients/6.0.2/c/diffusionc-6.0.2.zip
The ZIP file is also located in your Diffusion server installation:
diffusion_directory/clients/c/diffusion-c-6.0.2.zip
Get the C client libraries for Windows:
Download the ZIP file from the following URL:
http://download.pushtechnology.com/clients/6.0.2/c/diffusion-cwindows-6.0.2.zip
The ZIP file is also located in your Diffusion server installation:
diffusion_directory/clients/c/diffusion-c-windows-6.0.2.zip
Get the C client libraries for OS X/macOS:
Download the ZIP file from the following URL:
http://download.pushtechnology.com/clients/6.0.2/c/diffusion-cosx-6.0.2.zip
The ZIP file is also located in your Diffusion server installation:
diffusion_directory/clients/c/diffusion-c-osx-6.0.2.zip
Capabilities
To see the full list of capabilities supported by the C API, see Feature support in the Diffusion API on
page 37.
Diffusion | 216
Support
Table 28: Supported platforms and transport protocols for the client libraries
Platform
Minimum supported versions
Supported transport protocols
C for Linux
Red Hat and CentOS, version 7.2 WebSocket
and later
Ensure that you use a C99capable compiler.
C for Windows
Visual C Compiler 2013 or later,
Windows 7 or later
WebSocket
C for OS X/macOS
For building using GCC, use
Xcode 8.0 or later
WebSocket
If you require libraries compiled on a different platform, this can be provided as an additional
service by our Consulting Services team. Contact support@pushtechnology.com to discuss your
requirements.
Resources
•
•
Examples for the C API.
C API documentation
Using
On Linux
The C libraries are provided compiled for 64-bit Linux in the file diffusionc-version.zip. A dynamic library, libdiffusion.so, and a static library,
libdiffusion.a, are available. These libraries are supported on Red Hat and
CentOS version 6.5 and later.
To use the C API on Linux ensure that the following dependencies are available on
your development system:
•
Perl Compatible Regular Expressions (PCRE) library, version 8.3 or later
•
For more information, see http://pcre.org
OpenSSL library, version 1.0.2a or later
•
For more information, see https://www.openssl.org
zLib library, version 1.2 or later
For more information, see http://www.zlib.net
You can download these dependencies through your operating system's package
manager.
The C client library statically links to APR version 1.5 with APR-util. Ensure that you
set APR_DECLARE_STATIC and APU_DECLARE_STATIC before you use any APR
includes. You can set these values in the following ways:
•
•
By including diffusion.h before any APR includes. The diffusion.h file
sets these values.
As command-line flags
For more information, see http://apr.apache.org
Diffusion | 217
On Windows
The C library is provided as a static library compiled for 32-bit and 64-bit Windows in
the file diffusion-c-windows-version.zip. This static library, uci.lib, is
compiled with Visual C Compiler 2013 (version 120), which is shipped by default with
Microsoft Visual Studio 2013. You must use this version of Visual C Compiler or later
and use Windows 7 or later. Earlier versions are not supported.
Other Windows compilers, such as Clang and GCC, are not supported.
When compiling with Visual C Compiler 2013, define /D WIN32 in the compiler
settings.
To use the C API on Windows ensure that the following dependencies are available on
your development system:
•
Perl Compatible Regular Expressions (PCRE) library, version 8.3 or later
•
For more information, see http://pcre.org
OpenSSL library, version 1.0.2a or later
•
For more information, see https://www.openssl.org
zLib library, version 1.2 or later
For more information, see http://www.zlib.net
We provide these dependencies in the diffusion-c-windows-version.zip
file.
The C client library statically links to APR version 1.5 with APR-util. Ensure that you
set APR_DECLARE_STATIC and APU_DECLARE_STATIC before you use any APR
includes. You can set these values in the following ways:
•
•
By including diffusion.h before any APR includes. The diffusion.h file
sets these values.
As command-line flags
For more information, see http://apr.apache.org
On OS X/macOS
The C library is provided as a static library, libdiffusion.a, compiled for 64-bit
OS X/macOS in the file diffusion-c-osx-version.zip.
To use the C API on OS X/macOS ensure that the following dependencies are available
on your development system:
•
Perl Compatible Regular Expressions (PCRE) library, version 8.3 or later
•
For more information, see http://pcre.org
zLib library, version 1.2 or later
For more information, see http://www.zlib.net
You can download this dependencies using brew.
The C client library statically links to APR version 1.5 with APR-util. Ensure that you
set APR_DECLARE_STATIC and APU_DECLARE_STATIC before you use any APR
includes. You can set these values in the following ways:
•
•
By including diffusion.h before any APR includes. The diffusion.h file
sets these values.
As command-line flags
For more information, see http://apr.apache.org
Diffusion | 218
For building using GCC, use Xcode 7.1 or later, which includes Apple LLVM.
Defining the structure of record topic data using XML
Data on record topics can be structured using metadata. Other Diffusion APIs provide
builder methods you can use to define the metadata structure. The C API uses XML to
define the structure of a record topic's metadata.
The following schema describes the structure of that XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://
www.w3.org/2001/XMLSchema">
<xs:element name="field" type="field"/>
<xs:element name="message" type="message"/>
<xs:element name="record" type="record"/>
<xs:complexType name="record">
<xs:complexContent>
<xs:extension base="node">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="record"/>
<xs:element ref="field"/>
</xs:choice>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="node">
<xs:sequence/>
<xs:attribute name="name" type="xs:string"
use="required"/>
<xs:attribute name="multiplicity" type="xs:string"/>
</xs:complexType>
<xs:complexType name="field">
<xs:complexContent>
<xs:extension base="node">
<xs:sequence/>
<xs:attribute name="type" type="dataType"
use="required"/>
<xs:attribute name="default" type="xs:string"/>
<xs:attribute name="scale" type="xs:integer"/>
<xs:attribute name="allowsEmpty"
type="xs:boolean"/>
<xs:attribute name="customFieldHandlerClassName"
type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="message">
<xs:complexContent>
<xs:extension base="record">
<xs:sequence/>
<xs:attribute name="topicDataType"
type="topicDataType"/>
</xs:extension>
Diffusion | 219
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="dataType">
<xs:restriction base="xs:string">
<xs:enumeration value="integerString"/>
<xs:enumeration value="string"/>
<xs:enumeration value="customString"/>
<xs:enumeration value="decimalString"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="topicDataType">
<xs:restriction base="xs:string">
<xs:enumeration value="record"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Threading model
The C API is not thread-safe. Session and their derived artifacts must belong to a
single thread or only be acted upon by a single thread at any time.
Internally, the C client creates threads for managing the connection to the Diffusion
server. All callbacks into user-defined code are synchronous and it usually the case
that these must execute as quickly as possible. If this code runs for a non-trivial
amount of time, ensure that it hands off work to your own threads.
It is safe to send messages while processing callbacks, as outbound messages are
queued and are sent as soon as possible.
Ensure that callbacks do not alter the session as this can lead to undefined behavior.
This includes calling functions such as session_close() from the session state
change callback.
Always call session_close() and session_free() from the same thread that
created the session with session_create() or session_create_async().
This allows the threads to be joined and reaped correctly, and is a requirement of the
APR library which the C API relies on.
Regular expressions
The C client uses a different regular expression engine to the Diffusion server. Some
regular expressions in topic selectors are evaluated on the client and others on the
Diffusion server. It is possible that topic selectors that include complex or advanced
regular expressions can behave differently on the client and on the Diffusion server.
For more information, see Regular expressions on page 55.
Start subscribing with C
Create a C client within minutes that connects to the Diffusion server. This example creates a client
that subscribes to a JSON topic called "processes" and prints its value to the console when the topic is
updated.
Before you begin
The C client libraries rely on a number of dependencies. Depending on the platform you are using,
these dependencies might be included in the client library. If they are not included in the client library,
ensure that the dependencies are available on your development system.
For more information about dependencies on each supported platform, see C on page 216.
Diffusion | 220
The C client library statically links to APR version 1.5 with APR-util. Ensure that you set
APR_DECLARE_STATIC and APU_DECLARE_STATIC before you use any APR includes. You can set
these values in the following ways:
•
•
By including diffusion.h before any APR includes. The diffusion.h file sets these values.
As command-line flags
For more information, see http://apr.apache.org
To complete this example, you need a Diffusion server.
You also must either:
•
•
create a named user that has a role with the select_topic and read_topic permissions
assign a role with those permissions to anonymous client sessions.
An example of a suitable role is “CLIENT”. For more information about roles and permissions, see Rolebased authorization on page 124.
About this task
This example steps through the lines of code required to subscribe to a topic. The full code example is
provided after the steps.
Procedure
1. Get the Diffusion C client library for your platform and extract the ZIP file.
The C client library is available in the clients/c directory of the Diffusion installation.
2. Create a C file called cjson-subscribing-example.c.
a) Include the following libraries:
#include <stdio.h>
#include <apr.h>
#include "diffusion.h"
#include "args.h"
b) Define some values to be used later.
#define SYNC_DEFAULT_TIMEOUT 5000 * 1000
apr_pool_t *pool = NULL;
apr_thread_mutex_t *mutex = NULL;
apr_thread_cond_t *cond = NULL;
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'p', "principal", "Principal (username) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, "user"},
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, "password"},
{'t', "topic", "Topic name to subscribe", ARG_OPTIONAL,
ARG_HAS_VALUE, "time"},
END_OF_ARG_OPTS
};
Diffusion | 221
The ARG_OPTS_T array is used to store arguments that can be passed when running the code,
along with defaults if a value is not provided. You may wish to replace the defaults with your
own values. hostname is the name of the system hosting your Diffusion server, port is the port
the Diffusion server accepts client connections on, user is the name of a principal with the
permissions required to subscribe to a topic, and password is the principal's password.
3. Create a main method.
Inside the main method add the following lines:
int
main(int argc, char **argv)
{
/*
* Standard command-line parsing.
*/
HASH_T *options = parse_cmdline(argc, argv, arg_opts);
if(options == NULL || hash_get(options, "help") != NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
const char *url = hash_get(options, "url");
const char *principal = hash_get(options, "principal");
CREDENTIALS_T *credentials = NULL;
const char *password = hash_get(options, "credentials");
if(password != NULL) {
credentials =
credentials_create_password(password);
}
const char *topic_name = hash_get(options, "topic");
}
This does standard parsing of the command-line parameters.
4. Set up a mutex and condition variable, then create a session with the server.
apr_initialize();
apr_pool_create(&pool, NULL);
apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_UNNESTED,
pool);
apr_thread_cond_create(&cond, pool);
DIFFUSION_ERROR_T error = { 0 };
SESSION_T *session = session_create(url, principal,
credentials, NULL, NULL, &error);
if(session == NULL) {
fprintf(stderr, "Failed to create session: %s\n",
error.message);
return EXIT_FAILURE;
}
5. Handle any incoming subscription notifications, subscribe to a topic provided in the command line
parameters (or the default one), process topic updates for 60 seconds then shut down.
notify_subscription_register(session,
(NOTIFY_SUBSCRIPTION_PARAMS_T) { .on_notify_subscription =
on_notify_subscription });
subscribe(session, (SUBSCRIPTION_PARAMS_T)
{ .topic_selector = topic_name, .on_topic_message =
on_topic_message, .on_subscribe = on_subscribe });
Diffusion | 222
// Receive updates for 2 minutes
sleep(120);
session_close(session, NULL);
session_free(session);
credentials_free(credentials);
hash_free(options, NULL, free);
apr_thread_cond_destroy(cond);
apr_thread_mutex_destroy(mutex);
apr_pool_destroy(pool);
apr_terminate();
return EXIT_SUCCESS;
Note that this code relies on callbacks which we will add in the following steps.
6. Above the main method, create the on_notify_subscription callback to handle when this client
receives a notification that it has been subscribed to a topic. Note that this could be called for
topics that we have not explicitly subscribed to. Other control clients (or publishers) may request to
subscribe this client to a topic.
static int
on_notify_subscription(SESSION_T *session, const
SVC_NOTIFY_SUBSCRIPTION_REQUEST_T *request, void *context)
{
printf("on_notify_subscription: %d: \"%s\"\n",
request->topic_info.topic_id,
request->topic_info.topic_path);
return HANDLER_SUCCESS;
}
7. Create the on_subscribe callback to handle when the server responds to say that a topic *
subscription request has been received and processed.
static int
on_subscribe(SESSION_T *session, void *context_data)
{
printf("on_subscribe\n");
return HANDLER_SUCCESS;
}
8. Create the on_topic_message callback. When a subscribed message is received, this callback is
invoked to print the JSON value.
static int
on_topic_message(SESSION_T *session, const TOPIC_MESSAGE_T *msg)
{
printf("Received message for topic %s (%ld bytes)\n", msg>name, msg->payload->len);
if(msg->details != NULL) {
if(msg->details->topic_type == TOPIC_TYPE_JSON) {
// Convert payload to a JSON string
BUF_T *json = cbor_to_json(msg->payload->data, msg>payload->len);
printf("As JSON: %.*s\n", (int)json->len, json>data);
buf_free(json);
Diffusion | 223
}
else {
printf("Hexdump of binary data:\n");
hexdump_buf(msg->payload);
}
}
else {
printf("Payload: %.*s\n",
(int)msg->payload->len,
msg->payload->data);
}
return HANDLER_SUCCESS;
}
9. Build your C client.
a) Create a Makefile in the same directory as your C file.
An example Makefile is provided after the steps.
b) Run the make command to build the example.
The cjson-subscribing-example binary is created in the target/bin directory.
10.Run your C client from the command line.
Results
The client prints to the console every time the value of the subscribed topic is updated. You can
update the value of the topic by creating a publishing client to update the topic. By default, the client
subscribes to the processes topic used in the Start publishing with C on page 228 example.
Example
The completed cjson-subscribing-example.c file contains the following code:
/**
* Copyright © 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS"
BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*
* This example is written in C99. Please use an appropriate C99
capable compiler
*
* @author Push Technology Limited
* @since 6.0
*/
/*
Diffusion | 224
* The purpose of this example is to show how to subscribe to a
JSON topic.
*/
#include <stdio.h>
#include <apr.h>
#ifdef WIN32
#define sleep(x) Sleep(1000 * x)
#endif
#include "diffusion.h"
#include "args.h"
// 5 seconds
#define SYNC_DEFAULT_TIMEOUT 5000 * 1000
apr_pool_t *pool = NULL;
apr_thread_mutex_t *mutex = NULL;
apr_thread_cond_t *cond = NULL;
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'p', "principal", "Principal (username) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, "client"},
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, "password"},
{'t', "topic", "Topic name to subscribe", ARG_OPTIONAL,
ARG_HAS_VALUE, "time"},
END_OF_ARG_OPTS
};
/*
* When a subscribed message is received, this callback is
invoked.
*/
static int
on_topic_message(SESSION_T *session, const TOPIC_MESSAGE_T *msg)
{
printf("Received message for topic %s (%ld bytes)\n", msg>name, msg->payload->len);
if(msg->details != NULL) {
if(msg->details->topic_type == TOPIC_TYPE_JSON) {
// Convert payload to a JSON string
BUF_T *json = cbor_to_json(msg->payload->data,
msg->payload->len);
printf("As JSON: %.*s\n", (int)json->len, json>data);
buf_free(json);
}
else {
printf("Hexdump of binary data:\n");
hexdump_buf(msg->payload);
}
}
else {
printf("Payload: %.*s\n",
(int)msg->payload->len,
msg->payload->data);
Diffusion | 225
}
return HANDLER_SUCCESS;
}
/*
* This callback is fired when Diffusion responds to say that a
topic
* subscription request has been received and processed.
*/
static int
on_subscribe(SESSION_T *session, void *context_data)
{
printf("on_subscribe\n");
return HANDLER_SUCCESS;
}
/*
* We use this callback when Diffusion notifies us that we've been
subscribed
* to a topic. Note that this could be called for topics that we
haven't
* explicitly subscribed to - other control clients or publishers
may ask to
* subscribe us to a topic.
*/
static int
on_notify_subscription(SESSION_T *session, const
SVC_NOTIFY_SUBSCRIPTION_REQUEST_T *request, void *context)
{
printf("on_notify_subscription: %d: \"%s\"\n",
request->topic_info.topic_id,
request->topic_info.topic_path);
return HANDLER_SUCCESS;
}
int
main(int argc, char **argv)
{
/*
* Standard command-line parsing.
*/
HASH_T *options = parse_cmdline(argc, argv, arg_opts);
if(options == NULL || hash_get(options, "help") != NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
const char *url = hash_get(options, "url");
const char *principal = hash_get(options, "principal");
CREDENTIALS_T *credentials = NULL;
const char *password = hash_get(options, "credentials");
if(password != NULL) {
credentials =
credentials_create_password(password);
}
const char *topic_name = hash_get(options, "topic");
/*
* Setup mutex and condition variable.
*/
apr_initialize();
apr_pool_create(&pool, NULL);
Diffusion | 226
apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_UNNESTED,
pool);
apr_thread_cond_create(&cond, pool);
/*
* Create a session with the Diffusion server.
*/
DIFFUSION_ERROR_T error = { 0 };
SESSION_T *session = session_create(url, principal,
credentials, NULL, NULL, &error);
if(session == NULL) {
fprintf(stderr, "Failed to create session: %s\n",
error.message);
return EXIT_FAILURE;
}
notify_subscription_register(session,
(NOTIFY_SUBSCRIPTION_PARAMS_T) { .on_notify_subscription =
on_notify_subscription });
subscribe(session, (SUBSCRIPTION_PARAMS_T)
{ .topic_selector = topic_name, .on_topic_message =
on_topic_message, .on_subscribe = on_subscribe });
// Receive updates for 2 minutes
sleep(120);
session_close(session, NULL);
session_free(session);
credentials_free(credentials);
hash_free(options, NULL, free);
apr_thread_cond_destroy(cond);
apr_thread_mutex_destroy(mutex);
apr_pool_destroy(pool);
apr_terminate();
return EXIT_SUCCESS;
}
The Makefile contains the following code:
# The following two variables must be set.
#
# Directory containing the C client include files.
# DIFFUSION_C_CLIENT_INCDIR =
#
# Directory containing libdiffusion.a
# DIFFUSION_C_CLIENT_LIBDIR =
ifndef DIFFUSION_C_CLIENT_INCDIR
$(error DIFFUSION_C_CLIENT_INCDIR is not set)
endif
ifndef DIFFUSION_C_CLIENT_LIBDIR
$(error DIFFUSION_C_CLIENT_LIBDIR is not set)
endif
CC
= gcc
# Extra definitions from parent directory, if they exist.
-include ../makefile.defs
Diffusion | 227
CFLAGS
+= -g -Wall -Werror -std=c99
-D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=700 -I
$(DIFFUSION_C_CLIENT_INCDIR)
LDFLAGS
+= $(DIFFUSION_C_CLIENT_LIBDIR)/libdiffusion.a
-lpthread -lpcre -lz $(LIBS)
# If you have openssl installed then you can uncomment
these.
ifdef HAVE_OPEN_SSL
LDFLAGS
+= -lssl -lcrypto
endif
ARFLAGS
+=
SOURCES = json/cjson-subscribing-example.c
TARGETDIR
= target
OBJDIR
= $(TARGETDIR)/objs
BINDIR
= $(TARGETDIR)/bin
OBJECTS
= $(SOURCES:.c=.o)
TARGETS
= cjson-subscribing-example
all:
prepare $(TARGETS)
.PHONY:
all
prepare:
mkdir -p $(OBJDIR) $(BINDIR)
$(OBJDIR)/%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
cjson-subscribing-example: json/cjson-subscribingexample.c
$(CC) $^ $(CFLAGS) $(LDFLAGS) -lm -o $(BINDIR)/$@
clean:
rm -rf $(TARGETS) $(OBJECTS) $(TARGETDIR) core a.out
Start publishing with C
Create a C client that publishes data through topics on the Diffusion server.
Before you begin
The C client libraries rely on a number of dependencies. Depending on the platform you are using,
these dependencies might be included in the client library. If they are not included in the client library,
ensure that the dependencies are available on your development system.
For more information about dependencies on each supported platform, see C on page 216.
The C client library statically links to APR version 1.5 with APR-util. Ensure that you set
APR_DECLARE_STATIC and APU_DECLARE_STATIC before you use any APR includes. You can set
these values in the following ways:
•
•
By including diffusion.h before any APR includes. The diffusion.h file sets these values.
As command-line flags
For more information, see http://apr.apache.org
To complete this example, you need a Diffusion server and a development system with the .NET
Framework installed on it.
You also require a named user that has a role with the modify_topic and update_topic permissions.
For example, the “ADMINISTRATOR” role. For more information about roles and permissions, see Rolebased authorization on page 124.
This example also requires the external cJSON library which can be downloaded from https://
github.com/DaveGamble/cJSON/.
Diffusion | 228
About this task
This example illustrates how to create a client that publishes a list of running processes to a JSON
topic. The full code example is provided after the steps.
Procedure
1. Get the Diffusion C client library for your platform and extract the ZIP file.
The C client library is available in the clients/c directory of the Diffusion installation.
2. Get the cJSON.c and cJSON.h files from https://github.com/DaveGamble/cJSON/ and add them to
the same directory.
3. Create a C file called cjson-publishing-example.c.
a) Include the following libraries:
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<string.h>
<math.h>
<time.h>
#ifdef WIN32
#define sleep(x) Sleep(1000 * x) // Windows specific
#endif
#include <apr.h>
#include "diffusion.h"
#include "args.h"
#include "cJSON.h"
b) Set up JSON parsing with cJSON.
static void json_to_cbor(cJSON *item, CBOR_GENERATOR_T
*cbor_generator);
c) Create some required values:
#define SYNC_DEFAULT_TIMEOUT 5000 * 1000
int volatile g_active = 0;
CONVERSATION_ID_T volatile *g_updater_id;
apr_pool_t *pool = NULL;
apr_thread_mutex_t *mutex = NULL;
apr_thread_cond_t *cond = NULL;
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'p', "principal", "Principal (username) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, "control" },
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, "password" },
{'t', "topic", "Topic name to create and update",
ARG_OPTIONAL, ARG_HAS_VALUE, "time" },
END_OF_ARG_OPTS
};
d)
Diffusion | 229
Add the main method:
int
main(int argc, char **argv)
{
/*
* Standard command-line parsing.
*/
HASH_T *options = parse_cmdline(argc, argv, arg_opts);
if(options == NULL || hash_get(options,
"help") != NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
const char *url = hash_get(options, "url");
const char *principal = hash_get(options, "principal");
CREDENTIALS_T *credentials = NULL;
const char *password = hash_get(options, "credentials");
if(password != NULL) {
credentials =
credentials_create_password(password);
}
const char *topic_name = hash_get(options, "topic");
/*
* Setup mutex and condition variable.
*/
apr_initialize();
apr_pool_create(&pool, NULL);
apr_thread_mutex_create(&mutex,
APR_THREAD_MUTEX_UNNESTED, pool);
apr_thread_cond_create(&cond, pool);
/*
* Create a session with the Diffusion server.
*/
DIFFUSION_ERROR_T error = { 0 };
SESSION_T *session = session_create(url, principal,
credentials, NULL, NULL, &error);
if(session == NULL) {
fprintf(stderr, "Failed to create session: %s
\n", error.message);
return EXIT_FAILURE;
}
/*
* Synchronously create a topic holding JSON content.
*/
TOPIC_DETAILS_T *topic_details =
create_topic_details_json();
const ADD_TOPIC_PARAMS_T add_topic_params = {
.topic_path = topic_name,
.details = topic_details,
.on_topic_added = add_topic_callback,
.on_topic_add_failed = add_topic_callback
};
apr_thread_mutex_lock(mutex);
add_topic(session, add_topic_params);
apr_status_t rc = apr_thread_cond_timedwait(cond, mutex,
SYNC_DEFAULT_TIMEOUT);
apr_thread_mutex_unlock(mutex);
Diffusion | 230
topic_details_free(topic_details);
if(rc != APR_SUCCESS) {
fprintf(stderr, "Timed out while waiting for
topic to be created\n");
return EXIT_FAILURE;
}
/*
* Register an updater for the topic.
*/
const UPDATE_SOURCE_REGISTRATION_PARAMS_T
update_reg_params = {
.topic_path = topic_name,
.on_active = register_updater_callback,
.on_standby = register_updater_callback,
.on_close = register_updater_callback
};
apr_thread_mutex_lock(mutex);
g_updater_id = register_update_source(session,
update_reg_params);
rc = apr_thread_cond_timedwait(cond, mutex,
SYNC_DEFAULT_TIMEOUT);
apr_thread_mutex_unlock(mutex);
if(rc != APR_SUCCESS) {
fprintf(stderr, "Timed out while waiting to
register an updater\n");
return EXIT_FAILURE;
}
/*
* Define default parameters for an update source.
*/
UPDATE_VALUE_PARAMS_T update_value_params_base = {
.updater_id = (CONVERSATION_ID_T *)g_updater_id,
.topic_path = (char *)topic_name,
.on_success = on_update_success,
.on_failure = on_update_failure
};
time_t current_time;
struct tm *time_info;
char json[255];
char format_string[] = "{\"day\":\"%d\",\"month\":\"%m
\",\"year\":\"%Y\",\"hour\":\"%H\",\"minute\":\"%M\",\"second\":
\"%S\"}";
/*
* Forever, until deactivated.
*/
while(g_active) {
// Get current time
current_time = time(NULL);
if(current_time == ((time_t)-1)) {
fprintf(stderr, "Failure to obtain the
current time\n");
return EXIT_FAILURE;
}
// Get UTC time info
time_info = gmtime( &current_time );
if(time_info == NULL) {
fprintf(stderr, "Failure to obtain UTC
time info\n");
Diffusion | 231
return EXIT_FAILURE;
}
// Construct JSON string based on current time
//if(strftime(json, sizeof(json), "{\"day\":
%e}", &time_info) == 0) {
if(strftime(json, sizeof(json), format_string,
time_info) == 0) {
fprintf(stderr, "Failure to construct
JSON value\n");
return EXIT_FAILURE;
}
printf("Updated value: %s\n", json);
// Convert JSON to CBOR
cJSON *json_object = cJSON_Parse(json);
CBOR_GENERATOR_T *cbor_generator =
cbor_generator_create();
json_to_cbor(json_object, cbor_generator);
// Extract the CBOR-encoded data and wrap it in
a BUF_T structure.
BUF_T *cbor_buf = buf_create();
buf_write_bytes(cbor_buf, cbor_generator->data,
cbor_generator->len);
// Issue an update request to Diffusion. Under
the covers,
// this transmits a binary delta of changes,
assuming those
// changes are smaller than sending the entire
value.
UPDATE_VALUE_PARAMS_T update_value_params =
update_value_params_base;
update_value_params.data = cbor_buf;
update_value(session, update_value_params);
buf_free(cbor_buf);
cbor_generator_free(cbor_generator);
cJSON_Delete(json_object);
// Sleep for a second
sleep(1);
}
puts("Updater not active, exiting.");
session_close(session, NULL);
session_free(session);
credentials_free(credentials);
hash_free(options, NULL, free);
apr_thread_cond_destroy(cond);
apr_thread_mutex_destroy(mutex);
apr_pool_destroy(pool);
apr_terminate();
return EXIT_SUCCESS;
}
Diffusion | 232
4. Add handlers.
/*
* Handler for add topic feature.
*/
static int
add_topic_callback(SESSION_T *session, const
SVC_ADD_TOPIC_RESPONSE_T *response, void *context)
{
if(response->reason != ADD_TOPIC_FAILURE_REASON_SUCCESS &&
response->reason != ADD_TOPIC_FAILURE_REASON_EXISTS) {
fprintf(stderr, "Failed to add topic\n");
exit(EXIT_FAILURE);
}
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
/*
* Handler for processing updater registration callbacks.
*/
static int
register_updater_callback(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_REGISTRATION_RESPONSE_T
*response,
void *context)
{
apr_thread_mutex_lock(mutex);
switch(response->state) {
case UPDATE_SOURCE_STATE_ACTIVE:
g_active = 1;
break;
default:
g_active = 0;
break;
}
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
/*
* Handlers for update of data.
*/
static int
on_update_success(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_RESPONSE_T *response,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("on_update_success for updater \"%s\"\n", id_str);
free(id_str);
return HANDLER_SUCCESS;
}
static int
on_update_failure(SESSION_T *session,
Diffusion | 233
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_RESPONSE_T *response,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("on_update_failure for updater \"%s\"\n", id_str);
free(id_str);
return HANDLER_SUCCESS;
}
5. Add functions using the cJSON library to process JSON.
/*
* This function takes a JSON token, and writes an equivalent CBOR
token to
* the supplied CBOR generator.
*/
static void
cbor_write_json_token(cJSON *item, CBOR_GENERATOR_T
*cbor_generator)
{
if(item->string) {
// The item is a JSON key/value pair; write out the
key.
cbor_write_text_string(cbor_generator, item>string, strlen(item->string));
}
switch(item->type) {
case cJSON_False:
cbor_write_false(cbor_generator);
break;
case cJSON_True:
cbor_write_true(cbor_generator);
break;
case cJSON_NULL:
cbor_write_null(cbor_generator);
break;
case cJSON_Number:
if(floor(item->valuedouble) == item->valuedouble) {
cbor_write_uint(cbor_generator, item>valueint);
}
else {
cbor_write_float(cbor_generator, item>valuedouble);
}
break;
case cJSON_String:
cbor_write_text_string(cbor_generator, item>valuestring, strlen(item->valuestring));
break;
case cJSON_Array:
cbor_write_array(cbor_generator,
cJSON_GetArraySize(item));
break;
case cJSON_Object:
cbor_write_map(cbor_generator,
cJSON_GetArraySize(item));
break;
default:
printf("Unknown type\n");
break;
Diffusion | 234
}
}
/*
* Iterate/recurse through a JSON object, building up a stream of
CBOR tokens
* inside a CBOR generator.
*/
static void
json_to_cbor(cJSON *item, CBOR_GENERATOR_T *cbor_generator)
{
while(item) {
cbor_write_json_token(item, cbor_generator);
if(item->child) {
json_to_cbor(item->child, cbor_generator);
}
item = item->next;
}
}
6. Build your C client.
a) Create a Makefile in the same directory as your C file.
An example Makefile is provided after the steps.
b) Ensure that your Makefile links to the include and lib directory of the Diffusion C library.
DIFFUSION_C_CLIENT_INCDIR = ../path-to-client/include
DIFFUSION_C_CLIENT_LIBDIR = ../path-to-client/lib
c) Run the make command to build the example.
The cjson-publishing-example binary is created in the target/bin directory.
7. Run your C client from the command line.
Results
The client updates the value of the processes topic. You can see the value of the processes topic by
creating a subscribing client to subscribe to the topic. For more information, see Start subscribing with
C on page 220.
Example
The completed cjson-publishing-example file contains the following code:
/**
* Copyright © 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS"
BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
Diffusion | 235
* limitations under the License.
*
* This example is written in C99. Please use an appropriate C99
capable compiler
*
* @author Push Technology Limited
* @since 6.0
*/
/*
* The purpose of this example is to show how to update a JSON
topic.
*
* It also shows:
* 1. Use of a third-party JSON library (cJSON) to build the JSON
structure.
* 2. Reading JSON tokens and translating those to CBOR
equivalent.
*
* The example reads a list of processes running on the system and
uses it as
* a source of data to be published into a JSON topic.
*
* cJSON can be downloaded from https://github.com/DaveGamble/
cJSON/
*/
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<string.h>
<math.h>
<time.h>
#ifdef WIN32
#define sleep(x) Sleep(1000 * x)
#endif
#include <apr.h>
#include "diffusion.h"
#include "args.h"
#include "cJSON.h"
// 5 seconds
#define SYNC_DEFAULT_TIMEOUT 5000 * 1000
static void json_to_cbor(cJSON *item, CBOR_GENERATOR_T
*cbor_generator);
int volatile g_active = 0;
CONVERSATION_ID_T volatile *g_updater_id;
apr_pool_t *pool = NULL;
apr_thread_mutex_t *mutex = NULL;
apr_thread_cond_t *cond = NULL;
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'p', "principal", "Principal (username) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, "control" },
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, "password" },
Diffusion | 236
{'t', "topic", "Topic name to create and update",
ARG_OPTIONAL, ARG_HAS_VALUE, "time" },
END_OF_ARG_OPTS
};
/*
* Handler for add topic feature.
*/
static int
add_topic_callback(SESSION_T *session, const
SVC_ADD_TOPIC_RESPONSE_T *response, void *context)
{
if(response->reason != ADD_TOPIC_FAILURE_REASON_SUCCESS &&
response->reason != ADD_TOPIC_FAILURE_REASON_EXISTS) {
fprintf(stderr, "Failed to add topic\n");
exit(EXIT_FAILURE);
}
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
/*
* Handler for processing updater registration callbacks.
*/
static int
register_updater_callback(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_REGISTRATION_RESPONSE_T
*response,
void *context)
{
apr_thread_mutex_lock(mutex);
switch(response->state) {
case UPDATE_SOURCE_STATE_ACTIVE:
g_active = 1;
break;
default:
g_active = 0;
break;
}
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
/*
* Handlers for update of data.
*/
static int
on_update_success(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_RESPONSE_T *response,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("on_update_success for updater \"%s\"\n", id_str);
free(id_str);
return HANDLER_SUCCESS;
}
Diffusion | 237
static int
on_update_failure(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_RESPONSE_T *response,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("on_update_failure for updater \"%s\"\n", id_str);
free(id_str);
return HANDLER_SUCCESS;
}
int
main(int argc, char **argv)
{
/*
* Standard command-line parsing.
*/
HASH_T *options = parse_cmdline(argc, argv, arg_opts);
if(options == NULL || hash_get(options, "help") !=
NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
const char *url = hash_get(options, "url");
const char *principal = hash_get(options, "principal");
CREDENTIALS_T *credentials = NULL;
const char *password = hash_get(options, "credentials");
if(password != NULL) {
credentials =
credentials_create_password(password);
}
const char *topic_name = hash_get(options, "topic");
/*
* Setup mutex and condition variable.
*/
apr_initialize();
apr_pool_create(&pool, NULL);
apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_UNNESTED,
pool);
apr_thread_cond_create(&cond, pool);
/*
* Create a session with the Diffusion server.
*/
DIFFUSION_ERROR_T error = { 0 };
SESSION_T *session = session_create(url, principal,
credentials, NULL, NULL, &error);
if(session == NULL) {
fprintf(stderr, "Failed to create session: %s\n",
error.message);
return EXIT_FAILURE;
}
/*
* Synchronously create a topic holding JSON content.
*/
TOPIC_DETAILS_T *topic_details =
create_topic_details_json();
const ADD_TOPIC_PARAMS_T add_topic_params = {
.topic_path = topic_name,
Diffusion | 238
.details = topic_details,
.on_topic_added = add_topic_callback,
.on_topic_add_failed = add_topic_callback
};
apr_thread_mutex_lock(mutex);
add_topic(session, add_topic_params);
apr_status_t rc = apr_thread_cond_timedwait(cond, mutex,
SYNC_DEFAULT_TIMEOUT);
apr_thread_mutex_unlock(mutex);
topic_details_free(topic_details);
if(rc != APR_SUCCESS) {
fprintf(stderr, "Timed out while waiting for topic
to be created\n");
return EXIT_FAILURE;
}
/*
* Register an updater for the topic.
*/
const UPDATE_SOURCE_REGISTRATION_PARAMS_T
update_reg_params = {
.topic_path = topic_name,
.on_active = register_updater_callback,
.on_standby = register_updater_callback,
.on_close = register_updater_callback
};
apr_thread_mutex_lock(mutex);
g_updater_id = register_update_source(session,
update_reg_params);
rc = apr_thread_cond_timedwait(cond, mutex,
SYNC_DEFAULT_TIMEOUT);
apr_thread_mutex_unlock(mutex);
if(rc != APR_SUCCESS) {
fprintf(stderr, "Timed out while waiting to
register an updater\n");
return EXIT_FAILURE;
}
/*
* Define default parameters for an update source.
*/
UPDATE_VALUE_PARAMS_T update_value_params_base = {
.updater_id = (CONVERSATION_ID_T *)g_updater_id,
.topic_path = (char *)topic_name,
.on_success = on_update_success,
.on_failure = on_update_failure
};
time_t current_time;
struct tm *time_info;
char json[255];
char format_string[] = "{\"day\":\"%d\",\"month\":\"%m\",
\"year\":\"%Y\",\"hour\":\"%H\",\"minute\":\"%M\",\"second\":\"%S
\"}";
/*
* Forever, until deactivated.
*/
while(g_active) {
// Get current time
current_time = time(NULL);
if(current_time == ((time_t)-1)) {
Diffusion | 239
fprintf(stderr, "Failure to obtain the
current time\n");
return EXIT_FAILURE;
}
// Get UTC time info
time_info = gmtime( &current_time );
if(time_info == NULL) {
fprintf(stderr, "Failure to obtain UTC
time info\n");
return EXIT_FAILURE;
}
// Construct JSON string based on current time
if(strftime(json, sizeof(json), format_string,
time_info) == 0) {
fprintf(stderr, "Failure to construct JSON
value\n");
return EXIT_FAILURE;
}
printf("Updated value: %s\n", json);
// Convert JSON to CBOR
cJSON *json_object = cJSON_Parse(json);
CBOR_GENERATOR_T *cbor_generator =
cbor_generator_create();
json_to_cbor(json_object, cbor_generator);
// Extract the CBOR-encoded data and wrap it in a
BUF_T structure.
BUF_T *cbor_buf = buf_create();
buf_write_bytes(cbor_buf, cbor_generator->data,
cbor_generator->len);
// Issue an update request to Diffusion. Under the
covers,
// this transmits a binary delta of changes,
assuming those
// changes are smaller than sending the entire
value.
UPDATE_VALUE_PARAMS_T update_value_params =
update_value_params_base;
update_value_params.data = cbor_buf;
update_value(session, update_value_params);
buf_free(cbor_buf);
cbor_generator_free(cbor_generator);
cJSON_Delete(json_object);
// Sleep for a second
sleep(1);
}
puts("Updater not active, exiting.");
session_close(session, NULL);
session_free(session);
credentials_free(credentials);
hash_free(options, NULL, free);
apr_thread_cond_destroy(cond);
Diffusion | 240
apr_thread_mutex_destroy(mutex);
apr_pool_destroy(pool);
apr_terminate();
return EXIT_SUCCESS;
}
/*
* This function takes a JSON token, and writes an equivalent CBOR
token to
* the supplied CBOR generator.
*/
static void
cbor_write_json_token(cJSON *item, CBOR_GENERATOR_T
*cbor_generator)
{
if(item->string) {
// The item is a JSON key/value pair; write out
the key.
cbor_write_text_string(cbor_generator, item>string, strlen(item->string));
}
switch(item->type) {
case cJSON_False:
cbor_write_false(cbor_generator);
break;
case cJSON_True:
cbor_write_true(cbor_generator);
break;
case cJSON_NULL:
cbor_write_null(cbor_generator);
break;
case cJSON_Number:
if(floor(item->valuedouble) == item->valuedouble)
{
cbor_write_uint(cbor_generator, item>valueint);
}
else {
cbor_write_float(cbor_generator, item>valuedouble);
}
break;
case cJSON_String:
cbor_write_text_string(cbor_generator, item>valuestring, strlen(item->valuestring));
break;
case cJSON_Array:
cbor_write_array(cbor_generator,
cJSON_GetArraySize(item));
break;
case cJSON_Object:
cbor_write_map(cbor_generator,
cJSON_GetArraySize(item));
break;
default:
printf("Unknown type\n");
break;
}
}
/*
Diffusion | 241
* Iterate/recurse through a JSON object, building up a stream of
CBOR tokens
* inside a CBOR generator.
*/
static void
json_to_cbor(cJSON *item, CBOR_GENERATOR_T *cbor_generator)
{
while(item) {
cbor_write_json_token(item, cbor_generator);
if(item->child) {
json_to_cbor(item->child, cbor_generator);
}
item = item->next;
}
}
The Makefile contains the following code:
#
#
#
#
#
#
#
The following two variables must be set.
Directory containing the C client include files.
DIFFUSION_C_CLIENT_INCDIR =
Directory containing libdiffusion.a
DIFFUSION_C_CLIENT_LIBDIR =
ifndef DIFFUSION_C_CLIENT_INCDIR
$(error DIFFUSION_C_CLIENT_INCDIR is not set)
endif
ifndef DIFFUSION_C_CLIENT_LIBDIR
$(error DIFFUSION_C_CLIENT_LIBDIR is not set)
endif
CC
= gcc
# Extra definitions from parent directory, if they exist.
-include ../makefile.defs
CFLAGS
+= -g -Wall -Werror -std=c99 -D_POSIX_C_SOURCE=200112L
-D_XOPEN_SOURCE=700 -I$(DIFFUSION_C_CLIENT_INCDIR)
LDFLAGS
+= $(DIFFUSION_C_CLIENT_LIBDIR)/libdiffusion.a lpthread -lpcre -lz $(LIBS)
# If you have openssl installed then you can uncomment these.
ifdef HAVE_OPEN_SSL
LDFLAGS
+= -lssl -lcrypto
endif
ARFLAGS
+=
SOURCES = json/cjson-publishing-example.c
TARGETDIR
OBJDIR
BINDIR
OBJECTS
TARGETS
=
=
=
=
=
target
$(TARGETDIR)/objs
$(TARGETDIR)/bin
$(SOURCES:.c=.o)
cjson-publishing-example
all:
.PHONY:
prepare $(TARGETS)
all
prepare:
Diffusion | 242
mkdir -p $(OBJDIR) $(BINDIR)
$(OBJDIR)/%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
cjson-publishing-example:
json/cjson-publishing-example.c json/
cJSON.c
$(CC) $^ $(CFLAGS) $(LDFLAGS) -lm -o $(BINDIR)/$@
clean:
rm -rf $(TARGETS) $(OBJECTS) $(TARGETDIR) core a.out
Connecting to the Diffusion server
One of the first actions your Diffusion client takes is to connect to the Diffusion server. Clients connect
to the Diffusion server by opening a session. A session represents a logical context between a client
and the Diffusion server. All interactions with the Diffusion server happen within a session.
Sessions
The act of opening the session establishes a connection with the the Diffusion server. When a session
is opened it is assigned a unique session identifier by the Diffusion server, which identifies the session
even if it becomes connected to another server.
The session does not receive input from the Diffusion server until it is started, but can be used to
obtain features and perform certain setup actions before it is started.
Session state
The session between a client and the Diffusion server can be in one of a number of states.
The following diagram shows the session state model:
Diffusion | 243
Figure 22: Session state model
CONNECTING
The client session is in this state while it attempts to connect to the Diffusion server. If
the connection attempt is successful, the session changes to CONNECTED_ACTIVE
state. If the connection is not successful, the session changes to one of the closed
states.
CONNECTED_ACTIVE
The client session is in this state while it is connected to the Diffusion server.
The session spends the majority of its lifetime in this state. If the session
becomes disconnected and reconnect is enabled, the session changes to
RECOVERING_CONNECT state. If the session closes, it changes to one of the closed
states.
RECOVERING_CONNECT
The client session is in this state while it attempts to reconnect to the Diffusion server
after a disconnection. If the reconnection attempt is successful, the session changes
back to CONNECTED_ACTIVE state. If the reconnection attempt is not successful,
the session changes to one of the closed states.
CLOSED_BY_CLIENT
The client session is in this state when it is closed by the client. If a session is in closed
state, it cannot be reopened. A new session must be established.
CLOSED_BY_SERVER
The client session is in this state when it is closed by the Diffusion server. If a session
is in closed state, it cannot be reopened. A new session must be established.
CLOSED_FAILED
Diffusion | 244
The client session is in this state when it is closed for any reason other than a close
by the client or by the Diffusion server. If a session is in closed state, it cannot be
reopened. A new session must be established.
For more information, see Managing your session on page 249.
Session properties
When you connect to the Diffusion server by opening a session, the session is assigned a set of
properties. These properties are assigned by either the Diffusion server or an authentication handler
and can be used by clients to filter the set of connected client sessions to take actions on.
For more information, see Session properties on page 275.
Session roles
When a session authenticates with the Diffusion server, the session is assigned a set of roles. These
roles are assigned by either the Diffusion server or an authentication handler and define the set of
permissions a client session has.
For more information, see Role-based authorization on page 124.
Connecting basics
To make a connection to the Diffusion server the client must specify the host name and port number of
the Diffusion server, the transport to use to connect, and whether that connection is secure.
The Diffusion API is an asynchronous API. As such, the client APIs for all languages provide
asynchronous connect methods.
A subset of the Diffusion APIs also provide synchronous connect methods: the Android, Java, .NET,
and C APIs. In the following sections, all examples for these APIs are synchronous for simplicity. For
asynchronous examples for these APIs, see Asynchronous connections on page 247.
Connection parameters
host
The host name or IP address of the system on which the Diffusion server is located.
port
The port on which the Diffusion server accepts connections from clients using
the Diffusion API. You can configure which ports to provide connectors for in the
Connectors.xml configuration file. For more information, see .
transport
The transport used to make the connection. For example, WebSocket (ws). The
transports your client can use to make a connection depend on the client library
capabilities. For more information, see Platform support for the Diffusion API libraries
on page 35.
secure
Whether the connection is made over SSL.
Connecting
In JavaScript, Android and Java, you can define each of these parameters individually:
Diffusion | 245
JavaScript
diffusion.connect({
host : 'host_name',
port : 'port', // If not specified, port defaults to 80 for
standard connections or 443 for secure connections
transports : 'transport', // If not specified, transports
defaults to 'WS' and the client uses a WebSocket connection
secure : false // If not specified, secure defaults to false
}).then(function(session) { ... } );
Java and Android
final Session session = Diffusion
.sessions()
.serverHost("host_name")
// If no port is specified, the port defaults to 80 for standard
connections or 443 for secure connections
.serverPort(port)
// If no transports are specified, the connection defaults to
use the WebSocket transport
.transports(transport)
// If not specified, secure transport defaults to false
.secureTransport(false)
.open();
In Apple, Android, Java, .NET and C, composite the host, port, transport, and whether the connection
is secure into a single URL-style string of the following form: transport[s]://host:port.
For example, ws://diffusion.example.com:8080.
Use this URL to open the connection to the Diffusion server:
Apple
[PTDiffusionSession openWithURL:[NSURL URLWithString:@"url"]
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
Session session = Diffusion.sessions().open("url");
.NET
var session = Diffusion.Sessions.Open( "url" );
C
SESSION_T *session = session_create(url, NULL, NULL,
&session_listener, NULL, NULL);
Diffusion | 246
Connecting with multiple transports
In JavaScript, you can specify a list of transports. The client uses these transports to provide a
transport cascading capability.
JavaScript
diffusion.connect({
host : 'host_name',
transports : ['transport','transport','transport']
}).then(function(session) { ... } );
Java and Android
final Session session = Diffusion
.sessions()
.serverHost("host_name")
.serverPort(port)
.transports(transport, transport, transport)
.open();
1. The client attempts to connect using the first transport listed.
2. If the connection is unsuccessful, the client attempts to connect using the next transport listed.
3. This continues until the one of the following events happens:
•
•
The client makes a connection
The client has attempted to make a connection using every listed transport. If this happens, the
connection fails.
You can use specify that a client attempt to connect with a transport more than once. This enables you
to define retry behavior. For example:
JavaScript
transports: ['WS', 'XHR', 'WS']
Java and Android
.transports(WEBSOCKET, WEBSOCKET, WEBSOCKET)
Transport cascading is useful to specify in your clients as it enables them to connect from many
different environments. Factors such as the firewall in use, your end-user's mobile provider, or your
end-user's browser can affect which transports can successfully make a connection to the Diffusion
server.
Asynchronous connections
All Diffusion APIs can connect asynchronously to the Diffusion server:
JavaScript
diffusion.connect({
host : 'host_name',
port : 'port'
}).then(function(session) { ... } );
Apple
// Excluding the port from the URL defaults to 80, or 443 for secure
connections
[PTDiffusionSession openWithURL:[NSURL URLWithString:@"url"]
Diffusion | 247
completionHandler:^(PTDiffusionSession * newSession,
NSError * error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
// openAsync returns a CompletableFuture that completes with a
Session when a response is received from the server
Diffusion.sessions().openAsync("url").thenApply(session -> { ... });
.NET
// Define a callback that implements ISessionOpenCallback and pass
this to the open method
Diffusion.Sessions.Open("url", callback );
C
/*
* Asynchronous connections have callbacks for notifying that
* a connection has been made, or that an error occurred.
*/
SESSION_CREATE_CALLBACK_T *callbacks = calloc(1,
sizeof(SESSION_CREATE_CALLBACK_T));
callbacks->on_connected = &on_connected;
callbacks->on_error = &on_error;
session_create_async(url, principal, credentials,
&session_listener, reconnection_strategy, callbacks, &error);
Synchronous connections
The following APIs can connect synchronously to the Diffusion server:
Java and Android
Session session = Diffusion.sessions().open("url");
.NET
var session = Diffusion.Sessions.Open( "url" );
C
SESSION_T *session = session_create(url, NULL, NULL,
&session_listener, NULL, NULL);
When connecting to the Diffusion server using the Android API, prefer the asynchronous open()
method with a callback. Using the synchronous open() method might open a connection on the
same thread as the UI and cause a runtime exception. However, the synchronous open() method can
be used in any thread that is not the UI thread.
Diffusion | 248
Managing your session
When your client has opened a session with the Diffusion server, you can listen for session events to be
notified when the session state changes. For more information about session states, see Session state
on page 243.
JavaScript
In JavaScript, listen for the following events on the session:
•
disconnect: The session has lost connection to the Diffusion server.
•
The session state changes from CONNECTED_ACTIVE to RECOVERING_RECONNECT. This event
is only emitted if reconnect is enabled.
reconnect: The session has re-established connection to the Diffusion server.
•
•
The session state changes from RECOVERING_RECONNECT to CONNECTED_ACTIVE.
close: The session has closed. The provided close reason indicates whether this was caused by
the client, the Diffusion server, a failure to connect, or an error.
The session state changes to one of CLOSED_FAILED, CLOSED_BY_SERVER, or
CLOSED_BY_CLIENT.
error: A session error occurs.
JavaScript
session.on('disconnect', function() {
console.log('Lost connection to the server.');
});
session.on('reconnect', function() {
console.log('Reconnected to the session on the server.');
});
session.on('close', function() {
console.log('Session is closed.');
});
session.on('error', function() {
console.log('A session error occurred.');
});
Apple
In Apple, the following boolean properties are available on the states that are broadcast through the
default notification center for the application process and posted on the main dispatch queue:
•
•
•
isConnected: If true, the state is equivalent to the CONNECTED_ACTIVE state.
isRecovering: If true, the state is equivalent to the RECOVERING_RECONNECT state.
isClosed: If true, the state is one of CLOSED_FAILED, CLOSED_BY_SERVER, or
CLOSED_BY_CLIENT.
The broadcast includes both the old state and new state of the session. It also includes an error
property that is nil unless the session closure was caused by a failure.
Apple
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
[nc addObserverForName:PTDiffusionSessionStateDidChangeNotification
object:session
queue:nil
usingBlock:^(NSNotification * note)
{
PTDiffusionSessionStateChange * change =
note.userInfo[PTDiffusionSessionStateChangeUserInfoKey];
NSLog(@"Session state change: %@", change);
Diffusion | 249
}];
Other SDKs
In Android, Java, .NET, and C listen for changes to the session state. The listener provides both the old
state and new state of the session. The states provided are those listed in the session state diagram.
For more information, see Session state on page 243.
Java and Android
// Add the listener to the session
session.addListener(new Listener() {
@Override
public void onSessionStateChanged(Session session, State
oldState, State newState) {
System.out.println("Session state changed from " +
oldState.toString() + " to " + newState.toString());
}
});
.NET
// Add the listener to the session factory you will use to create the
session
var sessionFactory =
Diffusion.Sessions.SessionStateChangedHandler( ( sender, args ) => {
Console.WriteLine( "Session state changed from " +
args.OldState.ToString() + " to " + args.NewState.ToString() );
} );
C
// Define a session listener
static void
on_session_state_changed(SESSION_T *session,
const SESSION_STATE_T old_state,
const SESSION_STATE_T new_state)
{
printf("Session state changed from %s (%d) to %s (%d)\n",
session_state_as_string(old_state), old_state,
session_state_as_string(new_state), new_state);
}
// ...
// Use the session listener when opening your session
SESSION_LISTENER_T session_listener = { 0 };
session_listener.on_state_changed =
&on_session_state_changed;
session_create_async(url, principal, credentials,
&session_listener, &reconnection_strategy, callbacks, &error);
Diffusion | 250
Connecting securely
A Diffusion client can make secure connections to the Diffusion server over TLS. All supported
transports can connect securely.
To connect securely do one of the following:
•
•
•
In JavaScript, set the secure parameter to true
In Android and Java, when specifying parameters individually, pass true to the
secureTransport() method.
If using a URL to connect, insert an “s” after the transport value in the url parameter. For example,
wss://diffusion.example.com:443.
Configure the SSL context or behavior
A secure connection to the Diffusion server uses SSL to secure the communication.
When connecting over SSL, you might need to configure SSL.
•
•
•
In JavaScript, the SSL context is provided by the browser.
In Android, Java, and .NET, you can provide an SSL context when creating the session.
In Apple, you can use the sslOptions property of the to provide a dictionary of values that
specify the SSL behavior. For more information, see the CFStreamConstants documentation.
Java and Android
Session session =
Diffusion.sessions().sslContext(ssl_context).open("secure_url");
.NET
var session =
Diffusion.Sessions.SslContext(ssl_context).Open( "secure_url" );
If no SSL context or behavior is specified, the client uses the default context or configuration.
Validating server-side certificates
Diffusion clients that connect over a secure transport use certificates to validate the security of their
connection to the Diffusion server. These certificates are validated against any certificates in the set
trusted by the framework, runtime, or platform that the client library runs on.
If the client does not trust the certificate provided by a CA, you can configure the client to add
certificates to its trust store:
•
•
For Java, see Certificates on page 192
For .NET, see Certificates on page 203
You can also write a trust manager that explicitly allows the CA's certificates.
Disabling certificate validation on the client
You can disable client validation of the server-side certificates.
Note: We do not recommend disabling this validation on your production clients. However, it
can be useful for testing.
Certificates can only be strictly validated if they have been issue by an appropriate Certificate
Authority (CA) and if the CA's certificates are also known to your client.
Diffusion | 251
Since certificates are specific to the domain name that the server is deployed on, Diffusion ships
with demo certificates and these cannot be strictly validated. To test against a server with demo
certificates, disable client-side SSL certificate validation as shown in the following examples:
Apple
// Create a session configuration with non-standard SSL options...
PTDiffusionMutableSessionConfiguration *const configuration =
[PTDiffusionMutableSessionConfiguration new];
configuration.sslOptions = @{
(__bridge id)kCFStreamSSLValidatesCertificateChain
: (__bridge id)kCFBooleanFalse
};
// Use the configuration to open a new session...
[PTDiffusionSession openWithURL:[NSURL URLWithString:@"wss://
TestServer"]
configuration:configuration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[0];
}
};
final SSLContext context = SSLContext.getInstance("TLS");
context.init( null, new TrustManager[] { tm }, null );
Session session =
Diffusion.sessions().sslContext(context).open("secure_url");
Diffusion | 252
Connecting to the Diffusion server with a security principal and credentials
The Diffusion server can accept anonymous connections. However, if your clients specify a security
principal (for example, a username) and its associated credentials (for example, a password) when
they connect, these client sessions can be authenticated and authorized in a more granular way.
Authentication parameters
principal
A string that contains the name of the principal or identity that is connecting to the
Diffusion server. If a value is not specified when connecting, the principal defaults to
ANONYMOUS.
credentials
Credentials are a piece of information that authenticates the principal. This can be
empty or contain a password, a cryptographic key, an image, or any other piece of
information.
If you connect to the Diffusion server using a principal and credentials, connect over SSL to ensure that
these details are encrypted.
Connecting using any type of credentials
In JavaScript and C the method that opens a connection to the Diffusion server takes principal and
credentials as parameters:
JavaScript
diffusion.connect({
host : 'host_name',
port : 'port',
principal: 'principal',
credentials: 'credentials'
});
C
SESSION_T *session = session_create(url, principal, credentials,
&session_listener, NULL, NULL);
Any form of credentials can be wrapped in a credentials object. This can be empty or contain a
password, a cryptographic key, an image, or any other piece of information. The authentication
handler is responsible for interpreting the bytes.
In the Apple, Android, Java, and .NET API specify the credentials as a credentials object. The principal
and credentials are specified when configuring the session before opening it:
Apple
PTDiffusionCredentials *const credentials =
[[PTDiffusionCredentials alloc] initWithData:data];
PTDiffusionSessionConfiguration *const configuration =
[[PTDiffusionSessionConfiguration alloc]
initWithPrincipal:@"principal"
credentials:credentials];
[PTDiffusionSession openWithURL:[NSURL URLWithString:@"wss://
push.example.com"]
configuration:configuration
Diffusion | 253
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
Session session = Diffusion.sessions()
.principal("principal")
.credentials("credentials")
.open("url");
.NET
var session = Diffusion.Sessions
.Principal("principal")
.Credentials("credentials")
.Open( "url" );
Connecting using a string password as credentials
A string password is the most commonly used type of credentials. The Apple, Android, Java, and .NET
API provide a convenience method that enables you to specify credentials as a string password. The
principal and credentials are specified when configuring the session before opening it:
Apple
// Create a credentials object encapsulating a string password.
PTDiffusionCredentials *const credentials =
[[PTDiffusionCredentials alloc] initWithPassword:@"password"];
PTDiffusionSessionConfiguration *const configuration =
[[PTDiffusionSessionConfiguration alloc]
initWithPrincipal:@"principal"
credentials:credentials];
[PTDiffusionSession openWithURL:[NSURL URLWithString:@"url"]
configuration:configuration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
Session session = Diffusion.sessions()
.principal("principal")
.password("credentials")
.open("url");
Diffusion | 254
.NET
var session = Diffusion.Sessions
.Principal("principal")
.Password("credentials")
.Open( "url" );
Connecting using a byte array as credentials
The Android, Java, and .NET API provide a convenience method that enables you to specify credentials
as a byte array. The principal and credentials are specified when configuring the session before
opening it:
Java and Android
Session session = Diffusion.sessions()
.principal("principal")
.customCredentials(byte_credentials)
.open("url");
.NET
var session = Diffusion.Sessions
.Principal("principal")
.CustomCredentials(byte_credentials)
.Open( "url" );
Changing the principal and credentials a session uses
The client session can change the principal and credentials it uses to connect to the Diffusion server
at any time. For more information, see Change the security principal and credentials associated with
your client session on page 274.
Connecting through an HTTP proxy
Clients can connect to the Diffusion server through an HTTP proxy by using the HTTP CONNECT verb to
create the connection and tunneling any of the supported transports through that connection.
Figure 23: Flow of requests and responses when connecting to Diffusion through a proxy.
Apple, Android, Java, and .NET clients can connect to the Diffusion server through an HTTP proxy by
specifying additional information on connection.
Diffusion | 255
With no authentication at the proxy
When creating your session, add an HTTP proxy to the session by passing in the host and port number
of the proxy.
Apple
// Create a mutable session configuration.
PTDiffusionMutableSessionConfiguration *const configuration =
[PTDiffusionMutableSessionConfiguration new];
// Create an unauthenticated HTTP proxy configuration.
PTDiffusionHTTPProxyConfiguration *const proxyConfiguration =
[[PTDiffusionHTTPProxyConfiguration alloc] initWithHost:@"proxy"
port:80];
// Specify the proxy configuration.
configuration.httpProxyConfiguration = proxyConfiguration;
// Open the session, specifying this configuration.
[PTDiffusionSession openWithURL:[NSURL URLWithString:@"wss://
push.example.com"]
configuration:configuration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
Diffusion.sessions().httpProxy(host, port)
.NET
var session = Diffusion.Sessions
.HttpProxy( host, port )
.Open( diffusionUrl );
With basic authentication at the proxy
If the proxy requires basic authentication, the client can use the implementation in the Diffusion API to
authenticate.
When creating your session, add an HTTP proxy to the session by passing in the host and port
number of the proxy and a proxy authentication object that provides the challenge handler for basic
authentication.
Apple
// Create a mutable session configuration.
PTDiffusionMutableSessionConfiguration *const configuration =
[PTDiffusionMutableSessionConfiguration new];
// Create an authentication provider for the HTTP proxy.
const id<PTDiffusionHTTPAuthentication> authentication =
[[PTDiffusionBasicHTTPProxyAuthentication alloc]
initWithUsername:@"user"
Diffusion | 256
password:@"pass"];
// Create an authenticated HTTP proxy configuration using the
provider.
PTDiffusionHTTPProxyConfiguration *const proxyConfiguration =
[[PTDiffusionHTTPProxyConfiguration alloc] initWithHost:@"proxy"
port:80
authentication:authentication];
// Specify the proxy configuration.
configuration.httpProxyConfiguration = proxyConfiguration;
// Open the session, specifying this configuration.
[PTDiffusionSession openWithURL:[NSURL URLWithString:@"wss://
push.example.com"]
configuration:configuration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
HTTPProxyAuthentication auth =
Diffusion.proxyAuthentication().basic(username, password);
Diffusion.sessions().httpProxy(host, port, auth);
.NET
var clientAuth = Diffusion.ProxyAuthentication.Basic( username,
password );
var session = Diffusion.Sessions
.HttpProxy( host, port, clientAuth )
.Open( diffusionUrl );
With another form of authentication at the proxy
If the proxy requires another form of authentication, the client can implement a challenge handler that
the client uses to authenticate.
Implement the HTTPProxyAuthentication interface to provide a challenge handler that can
handle the type of authentication your proxy uses. When creating your session, add an HTTP proxy to
the session by passing in the host and port number of the proxy and a proxy authentication object that
provides your challenge handler.
Note: The proxy authentication mechanism is separate from the client authentication
mechanism and is transparent to the Diffusion server.
Connecting through a load balancer
Connections between Diffusion clients and Diffusion servers can be routed through a load balancer.
Some clients can pass additional information to a load balancer in the request path of their URL.
Supported in: JavaScript, Apple, Android, and Java APIs
Diffusion | 257
An additional request path can be specified to define the connection URL context.
JavaScript
diffusion.connect({
host : 'host_name',
port : 'port',
transports : 'transport',
secure : false,
path: '/path/diffusion'
}).then(function(session) { ... } );
Apple
NSString *const host = @"host";
NSString *const additionalInformationAsPath = @"path";
// Formulate the URL string for connection via secure WebSocket,
// appending the required '/diffusion' suffix.
NSString *const url = [NSString stringWithFormat:@"wss://%@/%@/
diffusion",
host, additionalInformationAsPath];
// Open the session, using the formulated URL.
[PTDiffusionSession openWithURL:[NSURL URLWithString:url]
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
final Session session = Diffusion
.sessions()
.serverHost("host_name")
.serverPort(port)
.transports(transport)
.secureTransport(false)
.requestPath("/path/diffusion");
.open();
Specify a request path that begins with / and ends with /diffusion. The default value is /
diffusion.
Load balancer configuration
Connections between Diffusion clients and Diffusion servers have specific requirements. If your load
balancer handles Diffusion connections incorrectly, for example by routing subsequent client requests
to different backend Diffusion servers, this can cause problems for your solution.
For more information about how to configure your load balancers to work with Diffusion, see Load
balancers on page 739.
Diffusion | 258
Reconnect to the Diffusion server
When clients connect to the Diffusion server over unreliable networks these connections can be lost.
Clients can attempt to reconnect to the Diffusion server after they lose connection.
Diffusion keeps client sessions in the DISCONNECTED state for a period of time, during which the client
can reconnect to the same session. The length of time the Diffusion server keeps a client session in the
DISCONNECTED state for is configured for the connector that the client uses. For more information,
see Configuring connectors on page 532.
Configuring reconnection on the client
Clients have reconnection enabled by default.
You can configure a reconnection timeout that restricts the amount of time the client can be
disconnected and still reconnect to its session on the Diffusion server. The period of time that the
Diffusion server keeps the session available for reconnect is the lowest of the following values:
•
•
The reconnection timeout configured by the client when it creates its session
The reconnection timeout configured on the Diffusion server for the connector that the client
connects on
When the reconnection timeout period configured by the client ends, the client stops attempting to
reconnect and closes its session.
JavaScript
diffusion.connect({
host : 'url',
reconnect : {
// Specify the timeout in milliseconds
timeout : reconnection_time
}
})
Apple
PTDiffusionMutableSessionConfiguration *const configuration =
[PTDiffusionMutableSessionConfiguration new];
// Specify the timeout in seconds
configuration.reconnectionTimeout = @10;
[PTDiffusionSession openWithURL:[NSURL URLWithString:@"url"]
configuration:configuration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
final Session session = Diffusion
.sessions()
// Specify the timeout in milliseconds
Diffusion | 259
.reconnectionTimeout(reconnection_time)
.open("url");
.NET
var session = Diffusion.Sessions
// Specify the timeout in milliseconds
.ReconnectionTimeout(reconnection_time)
.Open("url");
C
reconnection_strategy_set_timeout(&reconnection_strategy, reconnection_time);
SESSION_T *session = session_create(url, NULL, NULL, NULL,
&reconnection_strategy, NULL);
Set the value of the reconnection timeout to zero to disable reconnection. If no reconnection timeout
is specified, a default of 60 seconds (60000 ms) is used.
You can also define your own custom reconnection behavior using reconnection strategies. For more
information, see Specifying a reconnection strategy on page 262.
If no custom reconnection strategy is defined, the client attempts to reconnect at five second intervals
until the reconnection timeout is reached.
Reliable reconnection
If a client loses connection to the Diffusion server, data sent between the client and the Diffusion
server in either direction might be lost in transmission. If this happens and the client reconnects to its
session on the Diffusion server, lost data might cause the client state or topic data to be incorrect.
To prevent any data being lost, the reconnection process re-synchronizes the streams of messages
from the client session to the Diffusion server and from the Diffusion server to the client session. When
reconnecting, the client notifies the Diffusion server of the last message received and the earliest
message it can send again. The Diffusion server resends any missing messages and instructs the client
session to resume from the appropriate message.
To be able to send messages again, the Diffusion server maintains a recovery buffer of sent messages.
Some types of client also maintain a recovery buffer of sent messages that can be sent again if
necessary.
If a message has been lost and is no longer present in the recovery buffer, the server will abort the
reconnection. If reconnection succeeds, delivery of all messages is assured.
Configuring the recovery buffer on the client
All Diffusion clients can retain a buffer of messages that they have sent to the Diffusion server.
If messages from the client are lost in transmission during a disconnection and subsequent
reconnection, the client can resend the missing messages to the Diffusion server.
In the Apple, Java, Android and .NET APIs, you can configure the size of this buffer, in messages, when
creating your session on the Diffusion server:
Apple
PTDiffusionMutableSessionConfiguration *const configuration =
[PTDiffusionMutableSessionConfiguration new];
configuration.recoveryBufferSize = 1000;
[PTDiffusionSession openWithURL:[NSURL URLWithString:@"url"]
Diffusion | 260
configuration:configuration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
// Check error is `nil`, then use session as required.
// Ensure to maintain a strong reference to the session beyond
the lifetime
// of this callback, for example by assigning it to an instance
variable.
}];
Java and Android
final Session session = Diffusion
.sessions()
.recoveryBufferSize(number_of_messages)
.open("url");
.NET
var session =
Diffusion.Sessions.RecoveryBufferSize( number_of_messages
).Open("url");
The default size of the recovery buffer is 128 messages.
The larger this buffer is, the greater the chance of successful reconnection. However, a larger buffer of
messages increases the memory footprint of a client.
Configuring the recovery buffer on the Diffusion server
The recovery buffers on the Diffusion server can be configured on a per-connector basis in the
Connectors.xml configuration file. For more information, see Configuring connectors on page
532.
Related concepts
Session reconnection on page 606
You can configure the session reconnection feature by configuring the connectors at the Diffusion
server to keep the client session in a disconnected state for a period before closing the session.
Detecting connection problems
A client can automatically detect if there are problems with its connection to the Diffusion server and
take action to handle any disconnection.
When a client detects that it has become disconnected from the Diffusion server, the session state
changes from CONNECTED to one of the following states:
•
•
If reconnection is enabled at the client and at the Diffusion server, the session state changes to
RECOVERING.
If reconnection is not enabled, the session state changes to DISCONNECTED.
The client can detect that it has become disconnected from the Diffusion server using the following
methods:
Monitoring the connection activity
The client automatically monitors the activity between the client and the Diffusion server and uses this
information to quickly discover any connection problems.
Diffusion | 261
Using TCP state
Depending on the transport the client uses to connect to the Diffusion server, the client can use
the TCP state to detect whether to change its state from CONNECTED to one of RECOVERING or
DISCONNECTED.
•
•
WebSocket: The client uses the TCP state to detect whether to trigger a state change.
HTTP Polling: The client uses the TCP state at certain points during an HTTP request to detect
whether to trigger a state change.
Specifying a reconnection strategy
Reconnection behavior can be configured using custom reconnection strategies.
The reconnection behavior of a client session can be configured using reconnection strategies.
A reconnection strategy is applied when the session enters the RECOVERING_RECONNECT state,
enabling the session to attempt to reconnect and recover its previous state.
Reconnection can only succeed if the client session is still available on the Diffusion server. The
maximum time that the Diffusion server keeps client sessions in the DISCONNECTED state before
closing them can be configured using the Connectors.xml configuration file. For more information,
see Configuring connectors on page 532.
Individual client sessions can request a shorter reconnection timeout for their sessions or request to
disable reconnection when they first connect to the Diffusion server
Examples
JavaScript
// When establishing a session, it is possible to specify whether
reconnection
// should be attempted in the event of an unexpected disconnection.
This allows
// the session to recover its previous state.
// Set the maximum amount of time we'll try and reconnect for to 10
minutes
var maximumTimeoutDuration = 1000 * 60 * 10;
// Set the maximum interval between reconnect attempts to 60 seconds
var maximumAttemptInterval = 1000 * 60;
// Set an upper limit to the number of times we'll try to reconnect
for
var maximumAttempts = 25;
// Count the number of reconnection attempts we've made
var attempts = 0;
// Create a reconnection strategy that applies an exponential backoff
// The strategy will be called with two arguments, start & abort.
Both
// of these are functions, which allow the strategy to either start a
// reconnection attempt, or to abort reconnection (which will close
the session)
var reconnectionStrategy = function(start, abort) {
if (attempts > maximumAttempts) {
abort();
} else {
Diffusion | 262
var wait = Math.min(Math.pow(2, attempts++) * 100,
maximumAttemptInterval);
// Wait the specified time period, and then start the
reconnection attempt
setTimeout(start, wait);
}
};
// Connect to the server.
diffusion.connect({
host : 'diffusion.example.com',
port : 443,
secure : true,
principal : 'control',
credentials : 'password',
reconnect : {
timeout : maximumTimeoutDuration,
strategy : reconnectionStrategy
}
}).then(function(session) {
session.on('disconnect', function() {
// This will be called when we lose connection. Because we've
specified the
// reconnection strategy, it will be called automatically
when this event
// is dispatched
});
session.on('reconnect', function() {
// If the session is able to reconnect within the reconnect
timeout, this
// event will be dispatched to notify that normal operations
may resume
attempts = 0;
});
session.on('close', function() {
// If the session is closed normally, or the session is
unable to reconnect,
// this event will be dispatched to notify that the session
is no longer
// operational.
});
});
Apple
@import Diffusion;
@interface ExponentialBackoffReconnectionStrategy : NSObject
<PTDiffusionSessionReconnectionStrategy>
@end
@implementation CustomReconnectionStrategyExample {
PTDiffusionSession* _session;
}
-(void)startWithURL:(NSURL*)url {
NSLog(@"Connecting...");
Diffusion | 263
PTDiffusionMutableSessionConfiguration *const
sessionConfiguration =
[PTDiffusionMutableSessionConfiguration new];
// Set the maximum amount of time we'll try and reconnect for to
10 minutes.
sessionConfiguration.reconnectionTimeout = @(10.0 * 60.0); //
seconds
// Set the reconnection strategy to be used.
sessionConfiguration.reconnectionStrategy =
[ExponentialBackoffReconnectionStrategy new];
// Start connecting asynchronously.
[PTDiffusionSession openWithURL:url
configuration:sessionConfiguration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
if (!session) {
NSLog(@"Failed to open session: %@", error);
return;
}
// At this point we now have a connected session.
NSLog(@"Connected.");
// Set ivar to maintain a strong reference to the session.
_session = session;
}];
}
@end
@implementation ExponentialBackoffReconnectionStrategy {
NSUInteger _attemptCount;
}
-(void)
diffusionSession:(PTDiffusionSession *const)session
wishesToReconnectWithAttempt:
(PTDiffusionSessionReconnectionAttempt *const)attempt {
// Limit the maximum time to delay between reconnection attempts
to 60 seconds.
const NSTimeInterval maximumAttemptInterval = 60.0;
// Compute delay for exponential backoff based on the number of
attempts so far.
const NSTimeInterval delay = MIN(pow(2.0, _attemptCount++) * 0.1,
maximumAttemptInterval);
// Schedule asynchronous execution.
NSLog(@"Reconnection attempt scheduled for %.2fs", delay);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay *
NSEC_PER_SEC)),
dispatch_get_main_queue(), ^
{
NSLog(@"Attempting reconnection.");
[attempt start];
});
}
@end
Diffusion | 264
Java and Android
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.Session.Listener;
import com.pushtechnology.diffusion.client.session.Session.State;
import
com.pushtechnology.diffusion.client.session.reconnect.ReconnectionStrategy;
/**
* This example class demonstrates the ability to set a custom {@link
ReconnectionStrategy}
* when creating sessions.
*
* @author Push Technology Limited
* @since 5.5
*/
public class ClientWithReconnectionStrategy {
private volatile int retries = 0;
/**
* Constructor.
*/
public ClientWithReconnectionStrategy() {
// Set the maximum amount of time we'll try and reconnect for
to 10 minutes.
final int maximumTimeoutDuration = 1000 * 60 * 10;
// Set the maximum interval between reconnect attempts to 60
seconds.
final long maximumAttemptInterval = 1000 * 60;
// Create a new reconnection strategy that applies an
exponential backoff
final ReconnectionStrategy reconnectionStrategy = new
ReconnectionStrategy() {
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
@Override
public void performReconnection(final ReconnectionAttempt
reconnection) {
final long exponentialWaitTime =
Math.min((long) Math.pow(2, retries++) * 100L,
maximumAttemptInterval);
scheduler.schedule(new Runnable() {
@Override
public void run() {
reconnection.start();
}
}, exponentialWaitTime, TimeUnit.MILLISECONDS);
}
};
Diffusion | 265
final Session session =
Diffusion.sessions().reconnectionTimeout(maximumTimeoutDuration)
.reconnectionStrategy(reconnectionStrategy)
.open("ws://
diffusion.example.com:80");
session.addListener(new Listener() {
@Override
public void onSessionStateChanged(Session session, State
oldState, State newState) {
if (newState == State.RECOVERING_RECONNECT) {
// The session has been disconnected, and has
entered recovery state. It is during this state that
// the reconnect strategy will be called
}
if (newState == State.CONNECTED_ACTIVE) {
// The session has connected for the first time,
or it has been reconnected.
retries = 0;
}
if (oldState == State.RECOVERING_RECONNECT) {
// The session has left recovery state. It may
either be attempting to reconnect, or the attempt has
// been aborted; this will be reflected in the
newState.
}
}
});
}
}
.NET
/**
* Copyright © 2015, 2016 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using
using
using
using
using
System;
System.Threading.Tasks;
PushTechnology.ClientInterface.Client.Factories;
PushTechnology.ClientInterface.Client.Session;
PushTechnology.ClientInterface.Client.Session.Reconnection;
Diffusion | 266
namespace Examples {
/// <summary>
/// These examples show how to configure and enable the
reconnection feature of the API.
/// Every method represents a different selfcontained example.
/// </summary>
public class ClientReconnection {
private static int counter = 0;
/// <summary>
/// Sets the reconnection timeout that represents the
duration in which the client is trying to reconnect to
/// the server.
/// If we are not reconnected after the timeout, the client
will close the session.
/// </summary>
public void SetReconnectionTimeout() {
// The timeout is set in milliseconds and should be high
enough to
// account for actual reconnection time
var sessionFactory =
Diffusion.Sessions.ReconnectionTimeout( 60000 );
}
/// <summary>
/// Disables the reconnection feature.
/// </summary>
public void DisableReconnection() {
// This will disable the reconnection feature completely
and instead of switching to the RECOVERING_RECONNECT
// session state it will switch straight to
CLOSED_BY_SERVER.
var sessionFactoryNoReconnection =
Diffusion.Sessions.NoReconnection();
// This call has exactly the same effect as the above
statement.
var sessionFactoryNoTimeout =
Diffusion.Sessions.ReconnectionTimeout( 0 );
}
/// <summary>
/// This is a custom reconnection strategy that will try to
reconnect
/// to the server up to 3 times and then abort.
/// </summary>
public class MyReconnectionStrategy : IReconnectionStrategy {
/// <summary>
/// Here we put our actual reconnection logic. The async
keyword should always be added since it makes
/// things easier for a void return type.
/// </summary>
/// <param name="reconnectionAttempt">The reconnection
attempt wil be given by the session.</param>
public async Task
PerformReconnection( IReconnectionAttempt reconnectionAttempt ) {
++counter;
if ( counter <= 3 ) {
// We start the next reconnection attempt
reconnectionAttempt.Start();
} else {
counter = 0;
Diffusion | 267
// We abort any other reconnection attempt and
let the session switch to CLOSED_BY_SERVER.
reconnectionAttempt.Abort();
}
}
}
/// <summary>
/// This applies the custom reconnection strategy.
/// </summary>
public void SetCustomReconnectionStrategy() {
// We don't need to hold a reference to the reconnection
strategy
var sessionFactoryWithCustomStrategy
= Diffusion.Sessions.ReconnectionStrategy( new
MyReconnectionStrategy() );
}
/// <summary>
/// Reconnection can be observed via session state changes
within the SessionStateChangeHandler.
/// </summary>
public void ObserveReconnection() {
var sessionFactory =
Diffusion.Sessions.SessionStateChangedHandler( ( sender, args ) => {
if
( args.NewState.Equals( SessionState.RECOVERING_RECONNECT ) ) {
// This will be set on a connection loss and
indicates a reconnection attempt.
// Unless reconnection is disabled, at which
point the session never gets switched to this state.
Console.WriteLine( "We are in the process of
reconnecting." );
} else if
( args.NewState.Equals( SessionState.CONNECTION_ATTEMPT_FAILED ) ) {
// If a reconnection attempt fails because the
server session timed out, we won't be able
// to reconnect anymore. At which point the
session will switch to this state.
Console.WriteLine( "We couldn't connect." );
} else if
( args.NewState.Equals( SessionState.CLOSED_BY_SERVER ) ) {
// If the reconnection timeout is over, we will
switch to this state. In case of disabled
// reconnection we will switch directly to this
state on a connection loss.
Console.WriteLine( "We lost connection." );
} else if
( args.NewState.Equals( SessionState.CONNECTED_ACTIVE ) ) {
// This is the obvious state on our first
connection. It is also the state to which we switch
// after a successful reconnection attempt.
Console.WriteLine( "We are connected." );
counter = 0;
}
} );
}
}
}
Diffusion | 268
C
/*
* This example shows how to make a synchronous connection to
* Diffusion, with user-provided reconnection logic.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <apr_time.h>
#include "diffusion.h"
#include "args.h"
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'p', "principal", "Principal (username) for the connection",
ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
{'s', "sleep", "Time to sleep before disconnecting (in
seconds).", ARG_OPTIONAL, ARG_HAS_VALUE, "5" },
END_OF_ARG_OPTS
};
/*
* This callback is used when the session state changes, e.g. when a
session
* moves from a "connecting" to a "connected" state, or from
"connected" to
* "closed".
*/
static void
on_session_state_changed(SESSION_T *session,
const SESSION_STATE_T old_state,
const SESSION_STATE_T new_state)
{
printf("Session state changed from %s (%d) to %s (%d)\n",
session_state_as_string(old_state), old_state,
session_state_as_string(new_state), new_state);
}
typedef struct {
long current_wait;
long max_wait;
} BACKOFF_STRATEGY_ARGS_T;
static RECONNECTION_ATTEMPT_ACTION_T
backoff_reconnection_strategy(SESSION_T *session, void *args)
{
BACKOFF_STRATEGY_ARGS_T *backoff_args = args;
printf("Waiting for %ld ms\n", backoff_args->current_wait);
apr_sleep(backoff_args->current_wait * 1000); // µs -> ms
// But only up to some maximum time.
if(backoff_args->current_wait > backoff_args->max_wait) {
backoff_args->current_wait = backoff_args->max_wait;
Diffusion | 269
}
return RECONNECTION_ATTEMPT_ACTION_START;
}
static void
backoff_success(SESSION_T *session, void *args)
{
printf("Reconnection successful\n");
BACKOFF_STRATEGY_ARGS_T *backoff_args = args;
backoff_args->current_wait = 0; // Reset wait.
}
static void
backoff_failure(SESSION_T *session, void *args)
{
printf("Reconnection failed (%s)\n",
session_state_as_string(session->state));
BACKOFF_STRATEGY_ARGS_T *backoff_args = args;
// Exponential backoff.
if(backoff_args->current_wait == 0) {
backoff_args->current_wait = 1;
}
else {
backoff_args->current_wait *= 2;
}
}
/*
* Entry point for the example.
*/
int
main(int argc, char **argv)
{
/*
* Standard command-line parsing.
*/
HASH_T *options = parse_cmdline(argc, argv, arg_opts);
if(options == NULL || hash_get(options, "help") != NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
const char *url = hash_get(options, "url");
const char *principal = hash_get(options, "principal");
CREDENTIALS_T *credentials = NULL;
const char *password = hash_get(options, "credentials");
if(password != NULL) {
credentials = credentials_create_password(password);
}
const unsigned int sleep_time = atol(hash_get(options,
"sleep"));
SESSION_T *session;
DIFFUSION_ERROR_T error = { 0 };
SESSION_LISTENER_T session_listener = { 0 };
session_listener.on_state_changed =
&on_session_state_changed;
Diffusion | 270
/*
* Set the arguments to our exponential backoff strategy.
*/
BACKOFF_STRATEGY_ARGS_T *backoff_args = calloc(1,
sizeof(BACKOFF_STRATEGY_ARGS_T));
backoff_args->current_wait = 0;
backoff_args->max_wait = 5000;
/*
* Create the backoff strategy.
*/
RECONNECTION_STRATEGY_T *reconnection_strategy =
make_reconnection_strategy_user_function(backoff_reconnection_strategy,
backoff_args,
backoff_success,
backoff_failure,
NULL);
/*
* Only ever retry for 30 seconds.
*/
reconnection_strategy_set_timeout(reconnection_strategy, 30 *
1000);
/*
* Create a session, synchronously.
*/
session = session_create(url, principal, credentials,
&session_listener, reconnection_strategy, &error);
if(session != NULL) {
char *sid_str = session_id_to_string(session->id);
printf("Session created (state=%d, id=%s)\n",
session_state_get(session), sid_str);
free(sid_str);
}
else {
printf("Failed to create session: %s\n",
error.message);
free(error.message);
}
// With the exception of backoff_args, the reconnection
strategy is
// copied withing session_create() and may be freed now.
free(reconnection_strategy);
/*
* Sleep for a while.
*/
sleep(sleep_time);
/*
* Close the session, and release resources and memory.
*/
session_close(session, NULL);
session_free(session);
free(backoff_args);
Diffusion | 271
credentials_free(credentials);
hash_free(options, NULL, free);
return EXIT_SUCCESS;
}
Related concepts
Session reconnection on page 606
You can configure the session reconnection feature by configuring the connectors at the Diffusion
server to keep the client session in a disconnected state for a period before closing the session.
Session failover
Session failover occurs when a client that disconnects from a Diffusion server attempts to connect to a
different Diffusion server that also has information about that client's session.
For session failover to occur, session replication must be configured for a cluster of Diffusion servers.
For more information, see Configuring replication on page 559.
Differences between session reconnection and session failover
When a client loses a load-balanced connection to Diffusion, one of the following things can occur
when the client attempts to reconnect through the load balancer:
Session reconnection
The load balancer forwards the client connection to the Diffusion server it was
previously connected to, if that server is still available. For more information, see
Reconnect to the Diffusion server on page 259.
Session failover
The load balancer forwards the client connection to a different Diffusion server
that shares information about the client's session, if session replication is enabled
between the servers.
Prefer session reconnection to session failover wherever possible by ensuring that the load balancer is
configured to route all connections from a specific client to the same server if that server is available.
Session reconnection is more efficient as less data must be sent to the client and has less risk of data
loss, as sent messages can be recovered, in-flight requests are not lost, and handlers do not need to be
registered again.
For more information, see Routing strategies at your load balancer on page 740.
To a client the process of disconnection and subsequent reconnection has the following differences for
session reconnection or session failover.
Session reconnection
Session failover
The client connects to the same Diffusion server
it was previously connected to.
The client connects to a Diffusion server different
to the one it was previously connected to.
The client sends its last session token to the server.
The server authenticates the client connection or validates its session token.
Diffusion | 272
Session reconnection
Session failover
The server uses the session token to
resynchronize the streams of messages between
the server and client by resending any messages
that were lost in transmission from a buffer of
sent messages.
The server uses the session token to retrieve
the session state and topic selections from the
datagrid.
If lost messages cannot be recovered because
they are no longer present in a buffer, the server
aborts the reconnection.
The server sends any messages that have been
queued since the session disconnected.
The server uses the state to recover the session,
uses the topic selections to match the subscribed
topics, and sends the session the current topic
value for each subscribed topic.
Any in-flight requests made by the client session
to the previous server are cancelled and the
client session is notified by a callback. All
handlers, including authentication handlers
and update sources, that the client session had
registered with the previous server are closed
and receive a callback to notify them of the
closure.
Pinging the Diffusion server
Ping the Diffusion server from your client. If the ping is successful it reports the round-trip time
between your client and the Diffusion server.
The Diffusion client libraries and the Diffusion server include capabilities that automatically check
whether the connection is active. However, there might be times when you want to check the
connection from within your client code. For example, if the client is aware that the device it is hosted
on has recently changed from a 3G connection to a WiFi connection.
Use the pings capability to asynchronously ping the Diffusion server.
JavaScript
session.pingServer().then(function(pingResult) {
// Take action based on ping details.
});
Apple
[session.pings pingServerWithCompletionHandler:
^(PTDiffusionPingDetails *details, NSError *error)
{
// Check error is `nil`, indicating success.
}];
Java and Android
Pings pings = session.feature(Pings.class);
pings.pingServer(context, callback);
Diffusion | 273
.NET
IPings pings = session.Ping;
pings.PingServer( context, callback );
C
PING_USER_PARAMS_T params = {
.on_ping_response = on_ping_response_user
};
ping_user(session, params);
Change the security principal and credentials associated with your
client session
A client session can change the credentials it uses to authenticate with the Diffusion server at any time.
JavaScript
session.security.changePrincipal('principal',
'password').then(function() {
console.log('Authenticated as admin');
});
Apple
// Create a credentials object encapsulating a string password.
PTDiffusionCredentials *const credentials =
[[PTDiffusionCredentials alloc] initWithPassword:@"password"];
// Use the Security feature from your session...
[session.security changePrincipal:@"principal"
credentials:credentials
completionHandler:^(NSError *error)
{
// Check error is `nil`, indicating success.
}];
Java and Android
security = session.feature(Security.class);
security.changePrincipal(
principal,
Diffusion.credentials().password(password),
callback);
.NET
security = session.Security;
security.ChangePrincipal( principal,
Diffusion.Credentials.Password( password ), callback );
C
// Specify callbacks for the change_principal request.
CHANGE_PRINCIPAL_PARAMS_T params = {
.principal = hash_get(options, "principal"),
.credentials = credentials,
Diffusion | 274
.on_change_principal = on_change_principal,
.on_change_principal_failure =
on_change_principal_failure
};
// Do the change.
change_principal(session, params);
When the principal associated with a session changes, the following happens:
•
•
•
The $Principal session property is updated to contain the new principal.
The roles associated with the old principal are removed from the session and those roles
associated with the new principal are assigned to the session.
Topic subscriptions made with the old principal are not re-evaluated. The session remains
subscribed to any topics the new principal does not have permissions for.
Session properties
A client session has a number of properties associated with it. Properties are key-value pairs. Both the
key and the value are case sensitive.
Session properties provide a powerful way for client sessions to target actions at a specific session or
set of sessions whose session properties match a given criteria. Client sessions can use session filtering
to select a set of client sessions upon which to perform one of the following actions:
•
Send messages directly to that session or set of sessions.
•
For more information, see Sending request messages to a session filter on page 408.
Subscribe that session or set of sessions to a topic.
•
For more information, see Managing subscriptions on page 382.
Unsubscribe that session or set of sessions from a topic.
For more information, see Managing subscriptions on page 382.
For more information, see Session filtering on page 276.
A client session with the appropriate permissions can also view all of the session properties and
modify the user-defined session properties. For more information, see Working with session properties
on page 452.
Fixed properties
Fixed properties are set by the Diffusion server when a client opens a session with it. Fixed property
keys are prefixed by a dollar sign ($). The fixed session properties are:
$SessionId
The session identifier.
$Principal
The security principal the session uses to connect to the Diffusion server.
$ClientType
The client type of the session. For more information, see Client types on page 108.
$Transport
The transport the client session uses to connect to the Diffusion server. For more
information, see Client types on page 108.
$ServerName
The name of the Diffusion server that the client connects to.
Diffusion | 275
$Connector
The name of the connector on which the client connected to the Diffusion server.
$Country
The two letter country code for the country where the client's internet address is
located. The value is uppercase.
$Language
The two letter language code for the most common language of the country where
the client's internet address is located. The value is lowercase.
$ClientIP
The session's IP address represented as a string.
$Latitude
The client session's geographic latitude, if this can be ascertained.
$Longitude
The client session's geographic longitude, if this can be ascertained.
$StartTime
The client session's start time in milliseconds since the epoch.
User-defined properties
An authentication handler that allows the client session to connect can assign additional properties to
the session. This is achieved by supplying an AuthenticationResult to callback.allow().
The keys of these properties are case sensitive, must begin with an alphabetic character, must be
alphanumeric, and must not include any whitespace.
Related concepts
Session filtering on page 276
Session filters enable you to query the set of connected client sessions on the Diffusion server based
on their session properties.
Managing subscriptions on page 382
A client can use the SubscriptionControl feature to subscribe other client sessions to topics that they
have not requested subscription to themselves and also to unsubscribe clients from topics. It also
enables the client to register as the handler for routing topic subscriptions.
Managing sessions on page 452
A client session with the appropriate permissions can receive notifications and information about
other client sessions. A client session with the appropriate permissions can also manage other client
sessions.
Session filtering
Session filters enable you to query the set of connected client sessions on the Diffusion server based
on their session properties.
To perform an action on a subset of the connected client sessions, you can create a query expression
that filters the set of connected client sessions by the values of their session properties. Filter query
expressions are parsed and evaluated by the Diffusion server.
The query expression used to filter the session is made up of one or more search clauses chained
together by boolean operators.
Diffusion | 276
Creating a single search clause
Search clauses have the following form:
key operator 'value'
key
The key name of the session property to be tested. The key name is case sensitive.
operator
The operator that defines the test to be performed. The operator is not case sensitive.
value
The test value to be compared to the session property value. This value is a string and
must be contained in single or double quotation marks. Any special characters must
be escaped with Java escaping. The value is case sensitive.
Table 29: Session filter search clause operators
Operator
Description
IS
Tests whether the session property value
associated with the property key matches the
test value.
EQ
Equals. Tests whether the session property value
associated with the property key matches the
test value. Equivalent to 'IS'.
NE
Not equal. Tests whether the session property
value associated with the key is not equal to the
test value.
Examples: single search clause
Filter by clients that connect with the principal Ellington:
$Principal IS 'Ellington'
Filter by clients that connect to the Diffusion server using WebSocket:
$Transport EQ 'WEBSOCKET'
Filter by clients that are not located in the United Kingdom:
$Country NE 'GB'
Filter by clients that have the user-defined property Location set to San Jose:
Location IS "San Jose"
Filter by clients that have the user-defined property Status set to Active:
Status EQ 'Active'
Filter by clients that do not have the user-defined property Tier set to Premium:
Tier NE 'Premium'
Diffusion | 277
Chaining multiple search clauses
Chain individual search clauses together using boolean operator or use the NOT operator to negate a
search clause. Boolean operators are not case sensitive.
Table 30: Session filter boolean operators
Operator
Description
AND
Specifies that both joined search clauses must be
true.
OR
Specifies that at least one of the joined search
clauses must be true.
NOT
Specifies that the following search clause or set
of search clauses must not be true.
Use parentheses to group sets of search clauses and indicate the order of precedence for evaluation. If
no order of precedence is explicitly defined, the AND operator takes precedence over the OR operator.
Examples: multiple search clauses
Filter by clients that connect with one of the principals Fitzgerald, Gillespie, or Hancock:
$Principal IS 'Fitzgerald' OR $Principal IS 'Gillespie' OR $Principal
IS 'Hancock'
Filter by clients that connect to the Diffusion server using WebSocket and are located in France and
have the user-defined property Status set to Active:
$Transport EQ 'WEBSOCKET' AND $Country IS 'FR' AND Status EQ 'Active'
Filter by clients that are located in the United States, but do not connect with either of the principals
Monk or Peterson:
$Country EQ 'US' AND NOT ($Principal IS 'Monk' OR $Principal IS
'Peterson')
Filter by clients excluding those that have both the user-defined property Status set to Inactive and the
user-defined property Tier set to Free:
NOT (Status IS 'Inactive' AND Tier IS 'Free')
Related concepts
Session properties on page 275
A client session has a number of properties associated with it. Properties are key-value pairs. Both the
key and the value are case sensitive.
Managing subscriptions on page 382
A client can use the SubscriptionControl feature to subscribe other client sessions to topics that they
have not requested subscription to themselves and also to unsubscribe clients from topics. It also
enables the client to register as the handler for routing topic subscriptions.
Managing sessions on page 452
Diffusion | 278
A client session with the appropriate permissions can receive notifications and information about
other client sessions. A client session with the appropriate permissions can also manage other client
sessions.
Receiving data from topics
A client can subscribe to a topic to receive a stream of updates or can fetch the current state of a topic.
Topics
A topic is a logical channel through which data can be distributed to clients. Topics provide a logical
link between publishing clients and subscribing clients.
Diffusion provides different types of topic that can be used to stream diffent data formats or can be
used for special purposes. For more information about topic types and uses, see Topics on page 57.
Subscribing
To receive data published to a topic as a stream of updates, a client takes the following actions:
•
•
Subscribe to a topic or set of topics.
Register a stream that matches the topic or set of topics, or register a matching fallback stream.
For topics that exist and that the client is subscribed to, data is received through a stream that is
registered against that topic - if one exists and is of the appropriate type.
Note: The subscription flow is fully described in the design section of the documentation. For
more information, see Subscribing to topics on page 83.
Fetching
To fetch the current value of a topic or set of topics, a client makes a fetch request, passing in a fetch
stream, and uses the fetch stream to receive the data.
Streams
Clients use streams to receive data from topics.
Diffusion | 279
Figure 24: A stream
Streams are API objects that can receive multiple calls from the Diffusion server on the same method
while the stream is open. After the stream is closed or discarded, it can no longer receive responses on
any of its other methods.
Subscribing to topics
Subscribe to topics with topic selectors. When topics exist that match the selections made by the
client, data from those topics is sent to the client from the Diffusion server.
The client must register a stream to access topic data that has been sent from the Diffusion server. For
more information, see Subscribing to topics on page 83.
Required permissions: select_topic and read_topic permissions for the specified topics
Subscribing to topics
A client can subscribe to a topic to receive updates that are published to the topic. If the topic has
state, when the client subscribes to that topic the Diffusion server sends the topic state as a full value.
Subsequent updates to the data on the topic are sent as deltas or as full values depending on the type
of the topic and the structure of its data.
JavaScript
session.subscribe('topic_selector');
Apple
[session.topics subscribeWithTopicSelectorExpression:topic_selector
completionHandler:^(NSError *
const error)
{
if (error) {
NSLog(@"Subscribe request failed. Error: %@", error);
} else {
NSLog(@"Subscribe request succeeded.");
}
}];
Diffusion | 280
Java and Android
topics.subscribe(topic_selector).whenComplete((voidResult, exception)
-> {
//Do something
});
.NET
topics.Subscribe( topic, new TopicsCompletionCallbackDefault() );
C
// Define the required callbacks elsewhere
subscribe(session, (SUBSCRIPTION_PARAMS_T) { .topic_selector
= topic_selector,
.on_topic_message =
on_topic_message,
.on_subscribe =
on_subscribe });
A client can subscribe to multiple topics in a single request by using topic selectors. Topic selectors
enable you to select whole branches of the topic tree or use regular expressions to select topics based
on the names in the topic path.
For more information, see Topic selectors on page 50.
Unsubscribing from topics
To stop receiving updates from a topic or set of topics, unsubscribe from the topic or topics:
JavaScript
session.unsubscribe('topic_selector');
Apple
[session.topics unsubscribeFromTopicSelectorExpression:topic_selector
completionHandler:^(NSError *
const error)
{
if (error) {
NSLog(@"Unsubscribe request failed. Error: %@", error);
} else {
NSLog(@"Unsubscribe request succeeded.");
}
}];
Java and Android
topics.unsubscribe(topic_selector).whenComplete((voidResult,
exception) -> {
//Do something
});
.NET
topics.Unsubscribe( topic, new TopicsCompletionCallbackDefault() );
Diffusion | 281
C
// Define an on_unsubscribe callback elsewhere
unsubscribe(session, (UNSUBSCRIPTION_PARAMS_T) {.topic_selector
= topic_selector,
.on_unsubscribe =
on_unsubscribe} );
Using streams for subscription
Register a stream against a set of topics to access values published to those topics. For a registered
stream to access the value of a topic, the topic type must match the stream and the client must be
subscribed to the topic.
Subscribing to a topic causes the value of the topic to be sent from the Diffusion server to the client.
Registering a stream that matches the topic enables the client to access these values. For more
information, see Subscribing to topics on page 83
Two kinds of stream are provided to receive updates from subscribed topics: value streams and topic
streams.
Value streams
Value streams are typed. Register value streams against a set of topics by using a topic selector. A
value stream receives updates for any subscribed topics that match the value stream's type and the
topic selector used when registering the value stream.
A value stream can have one of the following types:
JSON
JSON topics are routed to this type of stream.
Binary
Binary topics are routed to this type of stream.
String
String topics are routed to this type of stream.
Int64
Int64 topics are routed to this type of stream.
Double
Double topics are routed to this type of stream.
RecordV2
RecordV2 topics are routed to this type of stream.
Content
JSON, binary, string, int64, double, recordV2 and single value topics are routed to this
type of stream.
If a value stream receives a delta update, this delta is automatically applied to a locally cached value
so that the stream always receives full values.
Using a value stream
Register the typed stream against the topic or topics that you want the stream to receive updates
from:
Diffusion | 282
JavaScript
// Register a JSON value stream
session.stream('topic_selector')
.asType(diffusion.datatypes.json())
.on('value', function(path, specification, newValue, oldValue)
{
// Action to take when update is received
});
// Register a binary value stream
session.stream('topic_selector')
.asType(diffusion.datatypes.binary())
.on('value', function(path, specification, newValue, oldValue)
{
// Action to take when update is received
});
Apple
// Register a JSON value stream
PTDiffusionValueStream *const jsonValueStream = [PTDiffusionJSON
valueStreamWithDelegate:self];
[session.topics addStream : jsonValueStream,
withSelector : topic_selector];
// Register a binary value stream
PTDiffusionValueStream *const binaryValueStream = [PTDiffusionBinary
valueStreamWithDelegate:self];
[session.topics addStream : binaryValueStream,
withSelector : topic_selector];
Java and Android
final Topics topics = session.feature(Topics.class);
// Register a JSON value stream
topics.addStream(topic_selector, JSON.class, new
Topics.ValueStream.Default<JSON>());
// Register a binary value stream
topics.addStream(topic_selector, Binary.class, new
Topics.ValueStream.Default<Binary>());
.NET
var topics = session.Topics;
// Register a JSON value stream
topics.AddStream( "topic_selector", new
Topics.DefaultValueStream<IJSON>() );
// Register a binary value stream
topics.AddStream( "topic_selector", new
Topics.DefaultValueStream<IBinary>() );
Use topic selectors to register the stream against multiple topics. For more information, see Topic
selectors on page 50.
The examples above show how to register a default or no-op value stream against a set of topics. The
stream receives values from any topic in the set whose topic data type matches the stream data type.
Diffusion | 283
To make use of the values sent to your client, implement a value stream that takes the required action
when an update is received from a subscribed topic that matches the type of the stream:
JavaScript
session.stream('topic_selector')
.asType(diffusion.datatypes.json())
.on({
update : function(value, topic) {
console.log('Update from: ', topic, value);
},
subscribe : function(details, topic) {
console.log('Subscribed to: ', topic);
},
unsubscribe : function(reason, topic) {
console.log('Unsubscribed from: ', topic);
}});
Apple
@implementation JSONSubscribeExample
(PTDiffusionJSONValueStreamDelegate)
-(void)
diffusionStream:(PTDiffusionStream *const)stream
didSubscribeToTopicPath:(NSString *const)topicPath
specification:(PTDiffusionTopicSpecification
*const)specification {
NSLog(@"Subscribed: %@", topicPath);
}
-(void)diffusionStream:(PTDiffusionValueStream *const)stream
didUpdateTopicPath:(NSString *const)topicPath
specification:(PTDiffusionTopicSpecification
*const)specification
oldJSON:(PTDiffusionJSON *const)oldJSON
newJSON:(PTDiffusionJSON *const)newJSON {
NSError * error;
NSDictionary *const map = [newJSON objectWithError:&error];
if (!map) {
NSLog(@"Failed to create map from received JSON. Error: %@",
error);
return;
}
// For the purposes of a meaningful example, only emit a log line
if we
// have a rate for GBP to USD.
if ([currency isEqualToString:@"GBP"]) {
const id rate = map[@"USD"];
if (rate) {
NSLog(@"Rate for GBP to USD: %@", rate);
}
}
}
-(void)
diffusionStream:(PTDiffusionStream *const)stream
didUnsubscribeFromTopicPath:(NSString *const)topicPath
specification:(PTDiffusionTopicSpecification
*const)specification
Diffusion | 284
reason:(const
PTDiffusionTopicUnsubscriptionReason)reason {
NSLog(@"Unsubscribed: %@", topicPath);
}
@end
Java and Android
private class JSONStream extends ValueStream.Default<JSON> {
@Override
public void onValue(
String topicPath,
TopicSpecification specification,
JSON oldValue,
JSON newValue) {
LOG.info(newValue.toJsonString());
}
}
.NET
/// Basic implementation of IValueStream<TValue> for JSON topics.
internal sealed class JSONStream : IValueStream<IJSON> {
/// Notification of stream being closed normally.
public void OnClose() {
Console.WriteLine( "The subscrption stream is now closed." );
}
/// Notification of a contextual error related to this callback.
public void OnError( ErrorReason errorReason ) {
Console.WriteLine( "An error has occured : {0}",
errorReason );
}
/// Notification of a successful subscription.
public void OnSubscription( string topicPath, ITopicSpecification
specification ) {
Console.WriteLine( "Client subscribed to {0} ", topicPath );
}
/// Notification of a successful unsubscription.
public void OnUnsubscription( string topicPath,
ITopicSpecification specification, TopicUnsubscribeReason reason ) {
Console.WriteLine( "Client unsubscribed from {0} : {1}",
topicPath, reason );
}
/// Topic update received.
public void OnValue( string topicPath, ITopicSpecification
specification, IJSON oldValue, IJSON newValue ) {
Console.WriteLine( "New value of {0} is {1}", topicPath,
newValue.ToJSONString() );
}
}
Topic streams
Note: Where a value stream is available for your topic type, we recommend you use a value
stream instead of a topic stream.
Diffusion | 285
Topic streams are not typed and are used to receive value and delta updates for all subscribed topics
that match the topic selectors used when registering the value stream.
This type of stream provides the value and the deltas but relies upon the application to apply the
deltas to a client-maintained current value. It is important, when using a topic stream with a record
topic, to register the stream before subscribing to the topic. This ensures that a full value is received by
the subscribing client.
Using a topic stream
Register the stream against the topic or topics that you want the stream to receive updates from:
JavaScript
session.stream('topic_selector')
.on('update', function(update, topic) {
// Do something
});
Apple
// Register self as the handler for topic updates on a set of topics.
[session.topics addTopicStreamWithSelector : topic_selector,
delegate : self];
Java and Android
Topics topics = session.feature(Topics.class);
// Add a topic stream that you implemented elsewhere
topics.addTopicStream(topic_selector, new myTopicStream(data));
.NET
var topics = session.Topics;
// Add a topic stream that you implemented elsewhere
topics.AddTopicStream( topic_selector, myTopicStream );
Use topic selectors to register the stream against multiple topics. For more information, see Topic
selectors on page 50.
Registering a fallback stream
You can register one or more fallback streams to receive updates to subscribed topics that do not have
a value stream or topic stream registered against them:
JavaScript
session.stream()
.asType(diffusion.datatypes.json())
.on('value', function(topic, specification, newValue, oldValue)
{
// Do something
});
Apple
// Register self as the fallback handler for JSON value updates.
PTDiffusionValueStream *const valueStream = [PTDiffusionJSON
valueStreamWithDelegate:self];
Diffusion | 286
[session.topics addFallbackStream:valueStream];
Java and Android
final Topics topics = session.feature(Topics.class);
topics.addFallbackStream(topic_selector, JSON.class, new
Topics.ValueStream.Default());
.NET
var topics = session.Topics;
topics.AddFallbackStream<IJSON>( new
Topics.DefaultValueStream<IJSON>() );
C
/*
* Install a global topic handler to capture messages for
* topics we haven't explicitly subscribed to, and therefore
* don't have a specific handler for.
*/
session->global_topic_handler = on_unexpected_topic_message;
A fallback value stream receives all updates for topics of the matching type that do not have a stream
already registered against them.
A fallback topic stream receives all updates for topics of any type that do not have a stream already
registered against them.
Fetching the current value of a topic
A client can send a fetch request for the state of a topic. If the topic is of a type that maintains its state,
the Diffusion server provides the current state of that topic to the client.
Required permissions: select_topic and read_topic permissions for the specified topics
A fetch request enables you to get the current value of a topic without subscribing to the topic.
The value fetched from a topic are received through a fetch stream, not through any value streams or
topic streams registered against the topic.
Fetch streams are not typed and are used to receive responses to fetch requests for all topics that
match the topic selectors used when registering the fetch stream.
Fetch the value of a topic or set of topics by using a topic selector to make a fetch request:
JavaScript
session.fetch("topic_selector").on('value', function(value, path) {
// Do something
});
Apple
[session.topics fetchWithTopicSelectorExpression:@"topic_selector"
delegate:self];
Diffusion | 287
Java and Android
Topics topics = session.feature(Topics.class);
topics.fetch(topic_selector, new FetchStream.Default);
.NET
var topics = session.Topics;
topics.Fetch( topic_selector, new FetchStreamDefault );
C
FETCH_PARAMS_T params = {
.selector = topic_selector,
.on_topic_message = on_topic_message,
.on_fetch = on_fetch,
.on_status_message = on_fetch_status_message
};
/*
* Issue the fetch request.
*/
fetch(session, params);
These examples pass in a default or no-op fetch stream.
To make use of the fetched values, implement a fetch stream to handle the values:
JavaScript
session.fetch("topic_selector").on({
value : function(value, path) {
console.log("Value for topic '" + path + "' is: " +
value);
},
error : function(error) {
console.log("Error on fetch: '" + error);
},
close : function() {
console.log("Fetch stream closed.");
}
});
Apple
@implementation FetchExample (PTDiffusionFetchStreamDelegate)
-(void)diffusionStream:(PTDiffusionStream * const)stream
didFetchTopicPath:(NSString * const)topicPath
content:(PTDiffusionContent * const)content {
NSLog(@"Fetch Result: %@ = \"%@\"", topicPath, content);
}
-(void)diffusionDidCloseStream:(PTDiffusionStream * const)stream {
NSLog(@"Fetch stream finished.");
}
-(void)diffusionStream:(PTDiffusionStream * const)stream
didFailWithError:(NSError * const)error {
NSLog(@"Fetch stream failed error: %@", error);
Diffusion | 288
}
@end
Java and Android
private final class myFetchStream implements Topics.FetchStream {
@Override
public void onClose() {
LOG.info("Fetch stream closed.");
}
@Override
public void onDiscard() {
LOG.info("Fetch stream discarded.");
}
@Override
public void onFetchReply(String topicPath, Content content) {
LOG.info("Fetched value " + content.asString() +" from topic " +
topicPath);
}
}
.NET
internal sealed class myFetchStream : IFetchStream {
public void OnClose() {
Console.WriteLine( "The fetch stream is now closed." );
}
public void OnDiscard() {
Console.WriteLine( "The fetch stream was discarded." );
}
public void OnFetchReply( string topicPath, IContent content ) {
Console.WriteLine( "Fetched value {0} from topic {1}",
topicPath, content.AsString() );
}
}
C
/*
* When a fetched message is received, this callback in invoked.
*/
static int
on_topic_message(SESSION_T *session, const TOPIC_MESSAGE_T *msg)
{
printf("Received message for topic %s\n", msg->name);
printf("Payload: %.*s\n", (int)msg->payload->len, msg>payload->data);
#ifdef DEBUG
topic_message_debug(response->payload);
#endif
return HANDLER_SUCCESS;
Diffusion | 289
}
Receiving topic notifications
Receive topic notifications using topic selectors. This enables a client to receive updates when topics
are added or removed, without the topic values.
Note: Topic notifications are supported by the Android API, Java API and JavaScript API.
The client must register a listener object to receive notifications about selected topics. Use a topic
selector to specify the topics.
For more details about topic notifications, see Topic notifications on page 86.
Required permissions: select_topic and read_topic permissions for the specified topics
Receiving topic notifications
A client can register to receive notifications about a set of topics via a listener object.
JavaScript
var listener = {
onDescendantNotification: function(topicPath, type) {},
onTopicNotification: function(topicPath, topicSpecification,
type) {},
onClose: function() {},
onError: function(error) {}
};
session.notifications.addListener(listener).then(function(reg) {
reg.select("foo");
});
Java and Android
final TopicNotifications notifications =
session.feature(TopicNotifications.class);
final TopicNotificationListener listener = new
TopicNotificationListener() {
@Override
public void onTopicNotification(String topicPath,
TopicSpecification specification, NotificationType type) {
// Handle notifications for selected/deselected topics
}
@Override
public void onDescendantNotification(String topicPath,
NotificationType type) {
// Handle notifications for immediate descendants
}
@Override
public void onClose() {
// The listener has been closed
}
@Override
public void onError(ErrorReason error) {
Diffusion | 290
// The listener has encountered an error
}
};
final CompletableFuture<NotificationRegistration> future =
notifications.addListener(listener);
final NotificationRegistration registration = future.get();
registration.select("foo");
Managing topics
A client can to add and remove topics at the Diffusion server.
Required permissions: modify_topic
Adding topics is an asynchronous operation and calls back to notify of either successful creation of the
topic or failure to create the topic.
If the topic add fails at the Diffusion server, the reason for failure is returned. Possible reasons for
failure include the following:
•
•
•
•
•
The topic already exists at the Diffusion server
The name of the supplied topic is not valid
The supplied details are not valid. This can occur only if properties are supplied.
Permission to create the topic was denied
An error occurred trying to initialize the newly created topic with the supplied content, possibly
because it was not validly formatted
Note: Sometimes you may wish to "add or update" a topic: add it if it does not exist, but
update if it does. There is no single method for this. Instead, the updating client can try to add
the topic first, and if it receives a response that the topic exists, can then update it.
A client can create topics subordinate to topics created by another client.
Note:
It is not currently possible to add new topics under branches of the topic tree that have been
created by internal publishers..
Currently all topics created using a client have a lifespan the same as the Diffusion server (unless
persistence is enabled). The topics remain at the Diffusion server even after the client session that
created them has closed unless you explicitly specify that the topic is removed with the session.
Adding topics with topic specifications
The recommended way to add topics is to use a topic specification. Some deprecated topic types use
topic details.
Adding topics with topic specifications
Required permissions: modify_topic
To create most topic types, you can either create the topic by defining just the topic type or use the
more complex topic specification to specify other properties of the topic.
Diffusion | 291
A topic is specified in terms of its type and a map of optional property settings which can alter the
default behavior of the topic.
You can use the same instance of topic specification to create many topics.
Adding topics with topic details (deprecated)
Required permissions: modify_topic
Only use topic details if you are creating the deprecated record or single value topic types.
Clients can use full topic details to describe a topic when creating it. Builders (and convenience
methods) are available for creating details relating to all the different topic types.
You can use the same instance of topic details to create many topics. This is recommended when
many topics with the same definition are to be created, because caching optimizations occur that
prevent complex definitions from being transmitted to the Diffusion server many times.
For some types of topic, setting up metadata is part of the task of describing the topic.
DEPRECATED: Add a topic using an initial value
A client can add a topic and define its type by providing an initial value, but this is now deprecated. It is
better to create a topic, then separately update it with an initial value.
Required permissions: modify_topic
Supported platforms: JavaScript, Android, Java, .NET
Add a topic, providing a value to the method from which the Diffusion server can derive the type of the
topic:
JavaScript
// Create a topic with string values, and an initial value of "xyz".
session.topics.add('topic/string', 'xyz');
// Create a topic with integer values, and an initial value of 123.
session.topics.add('topic/integer', 123);
// Create a topic with decimal values, with an implicit scale of 2
and an initial value of 1.23.
session.topics.add('topic/decimal', 1.23);
// Create record content from previously defined metadata
var builder = metadata.builder();
// Values must be set before a value can be created
builder.add('game', { title : 'Planet Express!', count : 3 });
builder.add('player', { name : 'Fry', score : 0 });
builder.add('player', { name : 'Amy', score : 0 });
builder.add('player', { name : 'Kif', score : 0 });
// Build a content instance
var initial_value = builder.build();
// Create a topic with record content. The metadata structure is
derived from the structure of the initial value.
session.topics.add('games/game', initial_value).then(function() {
console.log('Topic was added with initial value');
});
Diffusion | 292
Java and Android
// Create a topic with string values, and an initial value of "xyz".
topicControl.addTopicFromValue( "topic/string", "xyz", callback);
// Create a topic with integer values, and an initial value of 42.
topicControl.addTopicFromValue( "topic/integer", "42", callback);
// Create a topic with decimal values, with an implicit scale of 3
and an initial value of 2.718.
topicControl.addTopicFromValue( "topic/decimal", "2.718", callback);
// Create a topic with record content. Use RecordCOntentBuilder and
its methods to construct the content.
// The metadata structure is derived from the structure of the
initial value.
topicControl.addTopicFromValue( "topic/record",
Diffusion.content().newBuilder(RecordContentBuilder.class)
.putFields(initialValues).build(),
callback);
.NET
var topicControl = session.TopicControl;
var callback = new TopicControlAddCallbackDefault();
// Create a topic with string values and an initial value of 'xyz'.
topicControl.AddTopicFromValue( "topic/string", "xyz", callback );
// Create a topic with integer values and an initial value of 42.
topicControl.AddTopicFromValue( "topic/integer", "42", callback );
// Create a topic with decimal values with an implicit scale of 3 and
an initial value of 2.718.
topicControl.AddTopicFromValue( "topic/decimal", "2.718", callback );
// Create a topic with record content. Use IRecordContentBuilder and
its methods to construct the content.
// The metadata structure is derived from the structure of the
initial value.
var initialValues = new[]{"1","2","3"};
topicControl.AddTopicFromValue(
"topic/record",
Diffusion.Content.NewBuilder<IRecordContentBuilder>().PutFields( initialValues
callback );
The value used to create the topic is set as the initial value of the topic and sent to any subscribed
clients.
Derived topic types
The following table lists the topic types derived from different types of provided values:
Value type
Topic type
Metadata
Initial value
JSON
JSON
Not applicable
The supplied value
Binary
Binary
Not applicable
The supplied value
Diffusion | 293
Value type
Topic type
Content created using a Record
builder method
Metadata
Initial value
The metadata of the
The supplied content
content is derived from
the records and fields
in the content with the
following assumptions:
•
•
•
Numeric values
are assumed to be
MIntegerString
Numeric values
that contain a
decimal point (.)
are assumed to be
MDecimalString
with a scale equal
to the number of
places after the
decimal point
All other values
are assumed to be
MString
Content not created
using a builder method
Single value
MString
The supplied content as
a string
Integer, Long, Short,
Byte, BigInteger,
AtomicInteger,
AtomicLong
Single value
MIntegerString
A value derived
from the string
representation of the
supplied value
BigDecimal
Single value
MDecimalString
with scale from
supplied value
A value derived
from the string
representation of the
supplied value
Double, Float
Single value
MDecimalString
with scale 2
A value derived
from the string
representation of the
supplied value
Note: We
do not
recommend
using floating
point numbers.
If used, the
number is
converted
to decimal
using half even
rounding.
Other
Single value
MString
A string representation
of the supplied value
Diffusion | 294
Note: Ensure that you correctly format the data provided as an initial value according to the
standards of the language you are using. Incorrectly formatted data can cause errors when
using the data to add topics.
For more information about the format of the initial value in each API and how metadata structure and
data types are derived from that value, see the API documentation for that API.
Record topics
If the topic type is a record topic, Diffusion derives the structure of the metadata of the topic from the
provided initial value. This metadata structure or data type must be adhered to by all subsequent
updates to the topic.
Note: Using this method to define the metadata structure for record topics has limitations.
•
•
•
Field types are derived from the values provided. Avoid providing initial values that are
ambiguous. For example, if you intend that a field have a string data type, do not provide
an initial value that is numeric. In this case a decimal or integer type is derived.
It is not possible for Diffusion to detect the intent to use repeating records or fields. If your
metadata structure is to contain repeating records or fields, you cannot define your topic
using a value. Instead create your metadata definition explicitly, using the provided builder
methods.
In some APIs (for example, JavaScript you must have defined the metadata in order to
create record content. In many cases, it is better when adding a topic to use this metadata
definition in addition to providing an initial value.
Example: Create a JSON topic
The following examples create a JSON topic and receive a stream of values from the topic.
JavaScript
diffusion.connect({
host
: 'diffusion.example.com',
port
: 443,
secure : true,
principal : 'control',
credentials : 'password'
}).then(function(session) {
// 1. Data Types are exposed from the top level Diffusion
namespace. It is often easier
// to assign these directly to a local variable.
var jsonDataType = diffusion.datatypes.json();
// 2. Data Types are currently provided for JSON and Binary topic
types.
session.topics.add('topic/json',
diffusion.topics.TopicType.JSON);
// 3. Values can be created directly from the data type.
var jsonValue = jsonDataType.from({
"foo" : "bar"
});
// Topics are updated using the standard update mechanisms
session.topics.update('topic/json', jsonValue);
// Subscriptions are performed normally
session.subscribe('topic/json');
Diffusion | 295
// 4. Streams can be specialised to provide values from a
specific datatype.
session.stream('topic/json').asType(jsonDataType).on('value',
function(topic, specification, newValue, oldValue) {
// When a JSON or Binary topic is updated, any value handlers
on a subscription will be called with both the
// new value, and the old value.
// The oldValue parameter will be undefined if this is the
first value received for a topic.
// For JSON topics, value#get returns a JavaScript object
// For Binary topics, value#get returns a Buffer instance
console.log("Update for " + topic, newValue.get());
});
// 5. Raw values of an appropriate type can also be used for JSON
and Binary topics.
// For example, plain JSON objects can be used to update JSON
topics.
session.topics.update('topic/json', {
"foo" : "baz",
"numbers" : [1, 2, 3]
});
});
Java and Android
package com.pushtechnology.diffusion.examples;
import static java.util.Objects.requireNonNull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
import com.fasterxml.jackson.dataformat.cbor.CBORGenerator;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.Registration;
import
com.pushtechnology.diffusion.client.callbacks.TopicTreeHandler;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl.AddCon
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl.Remova
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl.
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl.
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl.
import com.pushtechnology.diffusion.client.session.Session;
import
com.pushtechnology.diffusion.client.session.SessionClosedException;
import com.pushtechnology.diffusion.client.topics.details.TopicType;
Diffusion | 296
import com.pushtechnology.diffusion.datatype.json.JSON;
import com.pushtechnology.diffusion.datatype.json.JSONDataType;
/**
* This example shows a control client creating a JSON topic and
sending updates
* to it.
* <P>
* There will be a topic for each currency for which rates are
provided. The
* topic will be created under the FX topic - so, for example FX/GBP
will
* contain a map of all rate conversions from the base GBP currency.
The rates
* are represented as string decimal values (e.g. "12.457").
* <P>
* The {@code addRates} method shows how to create a new rates topic,
specifying
* its initial map of values.
* <P>
* The {@code changeRates} method which takes a map shows how to
completely
* replace the set of rates for a currency with a new map of rates.
* <P>
* The {@code changeRates} method which takes a string shows an
alternative
* mechanism where the new rates are simply supplied as a JSON
string.
* <P>
* Either of the changeRates methods could be used and after the
first usage for
* any topic the values is cached, and so subsequent set calls can
compare with
* the last value and send only the differences to the server.
*
* @author Push Technology Limited
* @since 5.7
* @see ClientConsumingJSONTopics
*/
public final class ControlClientUpdatingJSONTopics {
private static final String ROOT_TOPIC = "FX";
private final Session session;
private final TopicControl topicControl;
private volatile TopicUpdateControl.ValueUpdater<JSON>
valueUpdater;
private volatile Registration updateSourceRegistration;
private final CBORFactory cborFactory = new CBORFactory();
private final JSONDataType jsonDataType =
Diffusion.dataTypes().json();
/**
* Constructor.
*
* @param serverUrl for example "ws://diffusion.example.com:80"
*/
public ControlClientUpdatingJSONTopics(String serverUrl) {
cborFactory.setCodec(new ObjectMapper());
session =
Diffusion | 297
Diffusion.sessions().principal("control").password("password")
.open(serverUrl);
topicControl = session.feature(TopicControl.class);
// Register as an updater for all topics under the root and
request
// that all topics created are removed when the session
closes
session.feature(TopicUpdateControl.class).registerUpdateSource(
ROOT_TOPIC,
new UpdateSource.Default() {
@Override
public void onRegistered(
String topicPath,
Registration registration) {
updateSourceRegistration = registration;
}
@Override
public void onActive(String topicPath, Updater
updater) {
topicControl.removeTopicsWithSession(
ROOT_TOPIC,
new TopicTreeHandler.Default());
valueUpdater = updater.valueUpdater(JSON.class);
}
@Override
public void onClose(String topicPath) {
session.close();
}
});
}
/**
* Add a new rates topic.
*
* @param currency the base currency
* @param values the full map of initial rates values
* @param callback reports outcome
* @throws IOException if unable to convert rates map
*/
public void addRates(
String currency,
Map<String, String> values,
AddContextCallback<String> callback) throws IOException {
topicControl.addTopic(
rateTopicName(currency),
TopicType.JSON,
mapToJSON(values),
currency,
callback);
}
/**
* Update an existing rates topic, replacing the rates mappings
with a new
* set of mappings.
Diffusion | 298
*
* @param currency the base currency
* @param values the new rates values
* @param callback reports outcome
* @throws IOException if unable to convert rates map
*/
public void changeRates(
String currency,
Map<String, String> values,
UpdateContextCallback<String> callback) throws IOException {
if (valueUpdater == null) {
throw new IllegalStateException("Not registered as
updater");
}
valueUpdater.update(
rateTopicName(currency),
mapToJSON(values),
currency,
callback);
}
/**
* Update an existing rates topic, replacing the rates mappings
with a new
* set of mappings specified as a JSON string, for example
* {"USD":"123.45","HKD":"456.3"}.
*
* @param currency the base currency
* @param jsonString a JSON string specifying the map of currency
rates
* @param callback reports the outcome
* @throws IOException if unable to convert string
*/
public void changeRates(
String currency,
String jsonString,
UpdateContextCallback<String> callback) throws
SessionClosedException,
IllegalArgumentException, IOException {
if (valueUpdater == null) {
throw new IllegalStateException("Not registered as
updater");
}
valueUpdater.update(
rateTopicName(currency),
jsonDataType.fromJsonString(jsonString),
currency,
callback);
}
/**
* Convert a given map to a JSON object.
*/
private JSON mapToJSON(Map<String, String> values) throws
IOException {
// Use the third-party Jackson library to write out the
values map as a
// CBOR-format binary.
Diffusion | 299
final ByteArrayOutputStream baos = new
ByteArrayOutputStream();
final CBORGenerator generator =
cborFactory.createGenerator(baos);
generator.writeObject(values);
return jsonDataType.readValue(baos.toByteArray());
}
/**
* Remove a rates entry (removes its topic) and clear cached
value for the
* topic.
*
* @param currency the currency
*
* @param callback reports the outcome
*/
public void removeRates(
String currency,
RemovalContextCallback<String> callback) {
final String topicName = rateTopicName(currency);
if (valueUpdater != null) {
valueUpdater.removeCachedValues(topicName);
}
topicControl.remove(topicName, currency, callback);
}
/**
* Close the session.
*/
public void close() {
updateSourceRegistration.close();
}
/**
* Generate a hierarchical topic name for a rates topic.
* <P>
* e.g. for currency=GBP would return "FX/GBP".
*
* @param currency the currency
* @return the topic name
*/
private static String rateTopicName(String currency) {
return String.format("%s/%s", ROOT_TOPIC,
requireNonNull(currency));
}
}
.NET
*
*
*
*
*
*
/**
Copyright © 2016, 2017 Push Technology Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Diffusion | 300
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using
using
using
using
using
using
using
using
System;
System.Threading;
System.Threading.Tasks;
PushTechnology.ClientInterface.Client.Callbacks;
PushTechnology.ClientInterface.Client.Factories;
PushTechnology.ClientInterface.Client.Features.Control.Topics;
PushTechnology.ClientInterface.Data.JSON;
PushTechnology.ClientInterface.Examples.Runner;
using static System.Console;
namespace PushTechnology.ClientInterface.Examples.Control {
/// <summary>
/// Control Client implementation that Adds and Updates a JSON
topic.
/// </summary>
public sealed class UpdatingJSONTopics : IExample {
/// <summary>
/// Runs the JSON topic Control Client example.
/// </summary>
/// <param name="cancellationToken">A token used to end the
client example.</param>
/// <param name="args">A single string should be used for the
server url.</param>
public async Task Run( CancellationToken cancellationToken,
string[] args ) {
var serverUrl = args[ 0 ];
var session =
Diffusion.Sessions.Principal( "control" ).Password( "password" ).Open( serverUr
var topicControl = session.TopicControl;
var updateControl = session.TopicUpdateControl;
// Create a JSON topic 'random/JSON'
var topic = "random/JSON";
var addCallback = new AddCallback();
topicControl.AddTopicFromValue(
topic,
Diffusion.DataTypes.JSON.FromJSONString( "{\"date\":
\"To be updated\",\"time\":\"To be updated\"}" ),
addCallback );
// Wait for the OnTopicAdded callback, or a failure
if ( !addCallback.Wait( TimeSpan.FromSeconds( 5 ) ) ) {
WriteLine( "Callback not received within timeout." );
session.Close();
return;
}
if ( addCallback.Error != null ) {
WriteLine( $"Error : {addCallback.Error}" );
session.Close();
Diffusion | 301
return;
}
// Update topic every 300 ms until user requests
cancellation of enxample
var updateCallback = new UpdateCallback( topic );
while ( !cancellationToken.IsCancellationRequested ) {
var newValue =
Diffusion.DataTypes.JSON.FromJSONString(
"{\"date\":\"" +
DateTime.Today.Date.ToString( "D" ) + "\"," +
"\"time\":\"" +
DateTime.Now.TimeOfDay.ToString( "g" ) + "\"}" );
updateControl.Updater.ValueUpdater<IJSON>().Update( topic, newValue,
updateCallback );
Thread.Sleep( 300 );
}
// Remove the JSON topic 'random/JSON'
var removeCallback = new RemoveCallback( topic );
topicControl.Remove( topic, removeCallback );
if ( !removeCallback.Wait( TimeSpan.FromSeconds( 5 ) ) )
{
WriteLine( "Callback not received within timeout." );
} else if ( removeCallback.Error != null ) {
WriteLine( $"Error : {removeCallback.Error}" );
}
// Close the session
session.Close();
}
/// <summary>
/// Basic implementation of the ITopicControlAddCallback.
/// </summary>
private sealed class AddCallback : ITopicControlAddCallback {
private readonly AutoResetEvent resetEvent = new
AutoResetEvent( false );
/// <summary>
/// Any error from this AddCallback will be stored here.
/// </summary>
public Exception Error { get; private set; }
/// <summary>
/// This is called to notify that a call context was
closed prematurely, typically due to a timeout or the
/// session being closed.
/// </summary>
/// <remarks>
/// No further calls will be made for the context.
/// </remarks>
public void OnDiscard() {
Error = new Exception( "This context was closed
prematurely." );
resetEvent.Set();
}
Diffusion | 302
/// <summary>
/// This is called to notify that the topic has been
added.
/// </summary>
/// <param name="topicPath">The full path of the topic
that was added.</param>
public void OnTopicAdded( string topicPath ) {
WriteLine( $"Topic {topicPath} added." );
resetEvent.Set();
}
/// <summary>
/// This is called to notify that an attempt to add a
topic has failed.
/// </summary>
/// <param name="topicPath">The topic path as supplied to
the add request.</param>
/// <param name="reason">The reason for failure.</param>
public void OnTopicAddFailed( string topicPath,
TopicAddFailReason reason ) {
Error = new Exception( $"Failed to add topic
{topicPath} : {reason}" );
resetEvent.Set();
}
/// <summary>
/// Wait for one of the callbacks for a given time.
/// </summary>
/// <param name="timeout">Time to wait for the
callback.</param>
/// <returns><c>true</c> if either of the callbacks has
been triggered. Otherwise <c>false</c>.</returns>
public bool Wait( TimeSpan timeout ) =>
resetEvent.WaitOne( timeout );
}
/// <summary>
/// A simple ITopicUpdaterUpdateCallback implementation that
prints confimation of the actions completed.
/// </summary>
private sealed class UpdateCallback :
ITopicUpdaterUpdateCallback {
private readonly string topicPath;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="topicPath">The topic path.</param>
public UpdateCallback( string topicPath ) {
this.topicPath = topicPath;
}
/// <summary>
/// Notification of a contextual error related to this
callback.
/// </summary>
/// <remarks>
/// Situations in which <code>OnError</code> is called
include the session being closed, a communication
/// timeout, or a problem with the provided parameters.
No further calls will be made to this callback.
/// </remarks>
Diffusion | 303
/// <param name="errorReason">A value representing the
error.</param>
public void OnError( ErrorReason errorReason )
=> WriteLine( $"Topic {topicPath} could not be updated :
{errorReason}" );
/// <summary>
/// Indicates a successful update.
/// </summary>
public void OnSuccess() => WriteLine( $"Topic {topicPath}
updated successfully." );
}
/// <summary>
/// Basic implementation of the ITopicRemovalCallback.
/// </summary>
private sealed class RemoveCallback :
ITopicControlRemovalCallback {
private readonly AutoResetEvent resetEvent = new
AutoResetEvent( false );
private readonly string topicPath;
/// <summary>
/// Any error from this AddCallback will be stored here.
/// </summary>
public Exception Error { get; private set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="topicPath">The topic path.</param>
public RemoveCallback( string topicPath ) {
this.topicPath = topicPath;
}
/// <summary>
/// Notification that a call context was closed
prematurely, typically due to a timeout or
/// the session being closed.
/// </summary>
/// <param name="errorReason">The error reason.</param>
/// <remarks>
/// No further calls will be made for the context.
/// </remarks>
public void OnError( ErrorReason errorReason ) {
Error = new Exception( "This context was closed
prematurely. Reason=" + errorReason );
resetEvent.Set();
}
/// <summary>
/// Topic(s) have been removed.
/// </summary>
public void OnTopicsRemoved() {
WriteLine( $"Topic {topicPath} removed" );
resetEvent.Set();
}
/// <summary>
/// Wait for one of the callbacks for a given time.
/// </summary>
/// <param name="timeout">Time to wait for the
callback.</param>
Diffusion | 304
/// <returns><c>true</c> if either of the callbacks has
been triggered, and <c>false</c> otherwise.</returns>
public bool Wait( TimeSpan timeout ) =>
resetEvent.WaitOne( timeout );
}
}
}
Change the URL from that provided in the example to the URL of the Diffusion server.
Handling subscriptions to missing topics
A client can handle subscription or fetch requests for topics that do not exist and can act on those
notifications, for example, by creating a topic on demand that matches the request.
Required permissions: modify_topic, register_handler
The client can register itself as a handler for missing topics for any branch of the topic tree. The client
is notified of attempts to subscribe to or fetch topics that are subordinate to that topic and that do
not exist. This enables the client to create the topics and notify the Diffusion server that the client
operation subscribe or fetch can proceed.
Note: The handler is called only when a client attempts to subscribe or fetch using a single
topic path. If another type of selector is used to subscribe to or fetch the topic, the handler is
not notified.
Figure 25: Flow from a subscribing client to the client that handles a missing topic subscription
The missing topic handler is removed when the registering session is closed. If the registering session
loses connection, it goes into DISCONNECTED state. When in DISCONNECTED state the handler
remains active but cannot pass on the notifications to the client. In this case, cancel or proceed
callbacks for these notifications might not function as expected because of timeouts. If the client then
closes, these notifications are discarded.
Diffusion | 305
To ensure that missing topic notifications are always received by your solution, you can use multiple
clients to register missing topic handlers. Ensure that if any of these clients lose connection they go
straight to CLOSED state by setting the reconnection timeout to zero. When the client loses connect it
closes straight away, the handler is registered is removed, and further missing topic notifications are
routed to a handler registered by another client.
Registering a missing topic notification handler
Register a handler against a branch of the topic tree:
JavaScript
session.topics.addMissingTopicHandler("topic_branch", {
// Implement handling code
});
Apple
-(void)registerAsMissingTopicHandlerForSession:(PTDiffusionSession
*const)session {
[session.topicControl addMissingTopicHandler:self
forTopicPath:@"topic_branch"
completionHandler:^(PTDiffusionTopicTreeRegistration *const
registration, NSError *const error)
{
if (registration) {
NSLog(@"Registered as missing topic handler.");
} else {
NSLog(@"Failed to register as missing topic handler.
Error: %@", error);
}
}];
}
Java and Android
TopicControl topicControl = session.feature(TopicControl.class);
topicControl.addMissingTopicHandler("topic_branch", new
MissingTopicNotificationHandler());
.NET
ITopicControl topicControl = session.TopicControl;
var missingTopicHandler = new MissingTopicHandler();
topicControl.AddMissingTopicHandler( topic_branch,
missingTopicHandler );
C
MISSING_TOPIC_PARAMS_T handler = {
.on_missing_topic = on_missing_topic,
.topic_path = topic_branch,
.context = NULL
};
missing_topic_register_handler(session, handler);
Diffusion | 306
Implementing a missing topic handler
The sample registration code shows default or no-op missing topic handlers being registered. To take
the required action when a missing topic notification is received, implement a missing topic handler:
JavaScript
session.topics.addMissingTopicHandler("topic_branch", {
onRegister : function(path, close) {
console.log("Registered missing topic handler on path: " + path);
},
onClose : function(path) {
console.log("Missing topic handler on path '" + path + "' has been
closed");
},
onError : function(path, error) {
console.log("Error on missing topic handler");
},
onMissingTopic : function(notification) {
console.log("Received missing topic notification with selector: " +
notification.selector);
// Action to take in response to the notification, for example,
creating a topic.
// If the action is successful, you can indicate that by calling
proceed
notification.proceed();
}
});
Apple
@implementation MissingTopicHandlerExample
(PTDiffusionMissingTopicHandler)
-(void)diffusionTopicTreeRegistration:
(PTDiffusionTopicTreeRegistration *const)registration
hadMissingTopicNotification:
(PTDiffusionMissingTopicNotification *const)notification {
NSString *const expression =
notification.topicSelectorExpression;
NSLog(@"Received Missing Topic Notification: %@", expression);
// Action to take in response to the notification, for example,
creating a topic.
// If the action is successful, you can indicate that by calling
proceed
[notification proceed];
}
@end
Java and Android
private final class MissingTopicNotificationHandler implements
MissingTopicHandler {
Diffusion | 307
/**
* @param topicPath
*
- the path that the handler is active for
* @param registeredHandler
*
- allows the handler to be closed
*/
@Override
public void onActive(String topicPath, RegisteredHandler
registeredHandler) {
}
/**
* @param topicPath
*
- the branch of the topic tree for which the
handler was
*
registered
*/
@Override
public void onClose(String topicPath) {
}
/**
* @param notification
*
- the missing topic details
*/
@Override
public void onMissingTopic(MissingTopicNotification notification)
{
// Action to take in response to the notification, for
example, creating a topic.
// If the action is successful, you can indicate that by
calling proceed
notification.proceed();
}
}
.NET
private class MissingTopicHandler : IMissingTopicHandler {
private readonly TaskCompletionSource<IRegisteredHandler>
onActive =
new TaskCompletionSource<IRegisteredHandler>();
private readonly
TaskCompletionSource<IMissingTopicNotification> onMissingTopic =
new
TaskCompletionSource<IMissingTopicNotification>();
private readonly TaskCompletionSource<bool> onClose = new
TaskCompletionSource<bool>();
public Task<IRegisteredHandler> OnActiveCalled {
get {
return onActive.Task;
}
}
public Task<IMissingTopicNotification>
OnMissingTopicCalled {
get {
Diffusion | 308
return onMissingTopic.Task;
}
}
public Task OnCloseCalled {
get {
return onClose.Task;
}
}
void
IMissingTopicHandler.OnMissingTopic( IMissingTopicNotification
notification ) {
onMissingTopic.SetResult( notification );
}
void ITopicTreeHandler.OnActive( string topicPath,
IRegisteredHandler registeredHandler ) {
onActive.SetResult( registeredHandler );
}
void ITopicTreeHandler.OnClose( string topicPath ) {
onClose.TrySetResult( false );
}
}
}
C
static int
on_missing_topic(SESSION_T *session, const
SVC_MISSING_TOPIC_REQUEST_T *request, void *context)
{
printf("Missing topic: %s\n", request->topic_selector);
// Action to take in response to the notification, for
example, creating a topic.
// If the action is successful, you can indicate that by
calling proceed
missing_topic_proceed(session, (SVC_MISSING_TOPIC_REQUEST_T
*) request);
return HANDLER_SUCCESS;
}
Related concepts
Using missing topic notifications with fan-out on page 97
Diffusion | 309
Missing topic notifications generated by subscription or fetch requests to a secondary server are
propagated to missing topic handlers registered against the primary servers.
Example: Receive missing topic notifications
The following examples use the TopicControl feature in the Diffusion API to register a missing topic
notification handler.
JavaScript
var diffusion = require('diffusion');
// Connect to the server. Change these options to suit your own
environment.
// Node.js will not accept self-signed certificates by default. If
you have
// one of these, set the environment variable
NODE_TLS_REJECT_UNAUTHORIZED=0
// before running this example.
diffusion.connect({
host
: 'diffusion.example.com',
port
: 443,
secure : true
}).then(function(session) {
// Register a missing topic handler on the "example" root topic
// Any subscriptions to missing topics along this path will invoke
this handler
session.topics.addMissingTopicHandler("example", {
// Called when a handler is successfully registered
onRegister : function(path, close) {
console.log("Registered missing topic handler on path: " + path);
// Once we've registered the handler, we initiate a subscription
with the selector "?example/topic/.*"
// This will invoke the handler.
session.subscribe("?example/topic/.*").on('subscribe',
function(type, path) {
console.log("Subscribed to topic: " + path);
});
},
// Called when the handler is closed
onClose : function(path) {
console.log("Missing topic handler on path '" + path + "' has been
closed");
},
// Called if there is an error on the handler
onError : function(path, error) {
console.log("Error on missing topic handler");
},
// Called when we've received a missing topic notification on our
registered handler path
onMissingTopic : function(notification) {
console.log("Received missing topic notification with selector: "
+ notification.selector);
// Once we've received the missing topic notification initiated
from subscribing to "?example/topic/.*",
// we add a topic that will match the selector
var topic = "example/topic/foo";
Diffusion | 310
session.topics.add(topic).then(function(result) {
console.log("Topic add success: " + topic);
// If the topic addition is successful, we proceed() with the
session's subscription.
// The client will now be subscribed to the topic
notification.proceed();
}, function(reason) {
console.log("Topic add failed: " + reason);
// If the topic addition fails, we cancel() the session's
subscription request.
notification.cancel();
});
}
});
});
Apple
@import Diffusion;
@interface MissingTopicHandlerExample
(PTDiffusionMissingTopicHandler) <PTDiffusionMissingTopicHandler>
@end
@implementation MissingTopicHandlerExample {
PTDiffusionSession* _session;
}
-(void)startWithURL:(NSURL*)url {
PTDiffusionCredentials *const credentials =
[[PTDiffusionCredentials alloc]
initWithPassword:@"password"];
PTDiffusionSessionConfiguration *const sessionConfiguration =
[[PTDiffusionSessionConfiguration alloc]
initWithPrincipal:@"control"
credentials:credentials];
NSLog(@"Connecting...");
[PTDiffusionSession openWithURL:url
configuration:sessionConfiguration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
if (!session) {
NSLog(@"Failed to open session: %@", error);
return;
}
// At this point we now have a connected session.
NSLog(@"Connected.");
// Set ivar to maintain a strong reference to the session.
_session = session;
// Register as missing topic handler for a branch of the
topic tree.
[self registerAsMissingTopicHandlerForSession:session];
}];
Diffusion | 311
}
-(void)registerAsMissingTopicHandlerForSession:(PTDiffusionSession
*const)session {
[session.topicControl addMissingTopicHandler:self
forTopicPath:@"Example/Control
Client Handler"
completionHandler:^(PTDiffusionTopicTreeRegistration *const
registration, NSError *const error)
{
if (registration) {
NSLog(@"Registered as missing topic handler.");
} else {
NSLog(@"Failed to register as missing topic handler.
Error: %@", error);
}
}];
}
@end
@implementation MissingTopicHandlerExample
(PTDiffusionMissingTopicHandler)
-(void)diffusionTopicTreeRegistration:
(PTDiffusionTopicTreeRegistration *const)registration
hadMissingTopicNotification:
(PTDiffusionMissingTopicNotification *const)notification {
NSString *const expression =
notification.topicSelectorExpression;
NSLog(@"Received Missing Topic Notification: %@", expression);
// Expect a path pattern expression.
if (![expression hasPrefix:@">"]) {
NSLog(@"Topic selector expression is not a path pattern.");
return;
}
// Extract topic path from path pattern expression.
NSString *const topicPath = [expression substringFromIndex:1];
// Add a stateless topic at this topic path.
[_session.topicControl addWithTopicPath:topicPath
type:PTDiffusionTopicType_Stateless
value:nil
completionHandler:^(NSError *const error)
{
if (error) {
NSLog(@"Failed to add topic.");
return;
}
// Topic added so allow subscriber to proceed.
[notification proceed];
}];
}
@end
Diffusion | 312
Java and Android
/
*******************************************************************************
* Copyright (C) 2014, 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*******************************************************************************
package com.pushtechnology.diffusion.examples;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl.Missin
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl.Missin
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.topics.details.TopicType;
/**
* An example of registering a missing topic notification handler and
processing
* notifications using a control client.
*
* @author Push Technology Limited
*/
public final class ControlClientHandlingMissingTopicNotification {
// UCI features
private final Session session;
private final TopicControl topicControl;
/**
* Constructor.
*/
public ControlClientHandlingMissingTopicNotification()
throws InterruptedException, ExecutionException,
TimeoutException {
// Create a session
session =
Diffusion.sessions().password("password").principal("admin")
.open("ws://diffusion.example.com:8080");
topicControl = session.feature(TopicControl.class);
Diffusion | 313
// Registers a missing topic notification on a topic path
topicControl.addMissingTopicHandler(
"A",
new NotificationStream()).get(5, TimeUnit.SECONDS);
}
private final class NotificationStream implements
MissingTopicNotificationStream {
@Override
public void onClose() {
}
@Override
public void onError(ErrorReason errorReason) {
}
@Override
public void onMissingTopic(MissingTopicNotification
notification) {
topicControl.addTopic(
notification.getTopicPath(),
TopicType.STRING).whenComplete((result, ex) -> {
if (ex == null) {
notification.proceed();
}
else {
notification.cancel();
}
});
}
}
}
.NET
/**
* Copyright © 2014, 2016 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using
using
using
using
System.Threading.Tasks;
PushTechnology.ClientInterface.Client.Factories;
PushTechnology.ClientInterface.Client.Features;
PushTechnology.ClientInterface.Client.Features.Control.Topics;
Diffusion | 314
using PushTechnology.ClientInterface.Client.Session;
namespace Examples {
public class ControlClientMissingTopicNotification {
private readonly ISession clientSession;
private readonly ITopicControl topicControl;
public ControlClientMissingTopicNotification() {
clientSession =
Diffusion.Sessions.Principal( "client" ).Password( "password" )
.Open( "ws://diffusion.example.com:80" );
topicControl =
Diffusion.Sessions.Principal( "control" ).Password( "password" )
.Open( "ws://
diffusion.example.com:80" ).TopicControl;
Subscribe( "some/path10" );
}
/// <summary>
/// Subscribes to a topic which may or may not be missing.
/// </summary>
/// <param name="topicPath">The path of the topic to
subscribe to.</param>
public async void Subscribe( string topicPath ) {
var missingTopicHandler = new MissingTopicHandler();
// Add the 'missing topic handler' to the topic control
object
topicControl.AddMissingTopicHandler( topicPath,
missingTopicHandler );
// Wait for the successful registration of the handler
var registeredHandler = await
missingTopicHandler.OnActiveCalled;
var topics = clientSession.Topics;
var topicCompletion = new TaskCompletionSource<bool>();
// Attempt to subscribe to the topic
topics.Subscribe( topicPath, new
TopicsCompletionCallback( topicCompletion ) );
await topicCompletion.Task;
// Wait and see if a missing topic notification is
generated
var request = await
missingTopicHandler.OnMissingTopicCalled;
// Cancel the client request on the server
request.Cancel();
// Close the registered handler
registeredHandler.Close();
// All events in Diffusion are asynchronous, so we must
wait for the close to happen
await missingTopicHandler.OnCloseCalled;
}
Diffusion | 315
private class TopicsCompletionCallback :
ITopicsCompletionCallback {
private readonly TaskCompletionSource<bool>
theCompletionSource;
public
TopicsCompletionCallback( TaskCompletionSource<bool> source ) {
theCompletionSource = source;
}
/// <summary>
/// This is called to notify that a call context was
closed prematurely, typically due to a timeout or the
/// session being closed. No further calls will be made
for the context.
/// </summary>
public void OnDiscard() {
theCompletionSource.SetResult( false );
}
/// <summary>
/// Called to indicate that the requested operation has
been processed by the server.
/// </summary>
public void OnComplete() {
theCompletionSource.SetResult( true );
}
}
/// <summary>
/// Asynchronous helper class for handling missing topic
notifications.
/// </summary>
private class MissingTopicHandler : IMissingTopicHandler {
private readonly TaskCompletionSource<IRegisteredHandler>
onActive =
new TaskCompletionSource<IRegisteredHandler>();
private readonly
TaskCompletionSource<IMissingTopicNotification> onMissingTopic =
new
TaskCompletionSource<IMissingTopicNotification>();
private readonly TaskCompletionSource<bool> onClose = new
TaskCompletionSource<bool>();
/// <summary>
/// Waits for the 'OnActive' event to be called.
/// </summary>
public Task<IRegisteredHandler> OnActiveCalled {
get {
return onActive.Task;
}
}
/// <summary>
/// Waits for the 'OnMissingTopic' event to be called.
/// </summary>
public Task<IMissingTopicNotification>
OnMissingTopicCalled {
get {
return onMissingTopic.Task;
}
Diffusion | 316
}
public Task OnCloseCalled {
get {
return onClose.Task;
}
}
/// <summary>
/// Called when a client session requests a topic that
does not exist, and the topic path belongs to part of
/// the topic tree for which this handler was registered.
///
/// The handler implementation should take the
appropriate action (for example, create the topic), and then
/// call IMissingTopicNotification.Proceed on the
supplied notification. This allows the client request to
/// continue and successfully resolve against the topic
if it was created.
///
/// A handler should always call Proceed() otherwise
resources will continue to be reserved on the server
/// and the client's request will not complete.
/// </summary>
/// <param name="notification">The client notification
object.</param>
void
IMissingTopicHandler.OnMissingTopic( IMissingTopicNotification
notification ) {
onMissingTopic.SetResult( notification );
}
/// <summary>
/// Called when the handler has been successfully
registered with the server.
///
/// A session can register a single handler of each type
for a given branch of the topic tree. If there is
/// already a handler registered for the topic path the
operation will fail, <c>registeredHandler</c> will
/// be closed, and the session error handler will be
notified. To change the handler, first close the
/// previous handler.
/// </summary>
/// <param name="topicPath">The path that the handler is
active for.</param>
/// <param name="registeredHandler">Allows the handler to
be closed.</param>
void ITopicTreeHandler.OnActive( string topicPath,
IRegisteredHandler registeredHandler ) {
onActive.SetResult( registeredHandler );
}
/// <summary>
/// Called if the handler is closed. This happens if the
call to register the handler fails, or the handler
/// is unregistered.
/// </summary>
/// <param name="topicPath">The branch of the topic tree
for which the handler was registered.</param>
void ITopicTreeHandler.OnClose( string topicPath ) {
onClose.TrySetResult( false );
}
Diffusion | 317
}
}
}
C
/*
* This example shows how to register a missing topic notification
* handler and return a missing topic notification response - calling
* missing_topic_proceed() once we've created the topic.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <apr.h>
#include <apr_thread_mutex.h>
#include <apr_thread_cond.h>
#include "diffusion.h"
#include "args.h"
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'p', "principal", "Principal (username) for the connection",
ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
{'r', "topic_root", "Topic root to process missing topic
notifications on", ARG_OPTIONAL, ARG_HAS_VALUE, "foo"},
END_OF_ARG_OPTS
};
static int
on_topic_added(SESSION_T *session, const SVC_ADD_TOPIC_RESPONSE_T
*response, void *context)
{
puts("Topic added");
return HANDLER_SUCCESS;
}
static int
on_topic_add_failed(SESSION_T *session, const
SVC_ADD_TOPIC_RESPONSE_T *response, void *context)
{
puts("Topic add failed");
printf("Reason code: %d\n", response->reason);
return HANDLER_SUCCESS;
}
static int
on_topic_add_discard(SESSION_T *session, void *context)
{
puts("Topic add discarded");
return HANDLER_SUCCESS;
}
/*
Diffusion | 318
* A request has been made for a topic that doesn't exist; create it
* and inform Diffusion that the client's subcription request can
* proceed.
*/
static int
on_missing_topic(SESSION_T *session, const
SVC_MISSING_TOPIC_REQUEST_T *request, void *context)
{
printf("Missing topic: %s\n", request->topic_selector);
BUF_T *sample_data_buf = buf_create();
buf_write_string(sample_data_buf, "Hello, world");
// Add the missing topic.
ADD_TOPIC_PARAMS_T topic_params = {
.on_topic_added = on_topic_added,
.on_topic_add_failed = on_topic_add_failed,
.on_discard = on_topic_add_discard,
.topic_path = strdup(request->topic_selector+1),
.details =
create_topic_details_single_value(M_DATA_TYPE_STRING),
.content = content_create(CONTENT_ENCODING_NONE,
sample_data_buf)
};
add_topic(session, topic_params);
// Proceed with the client's subscription to the topic
missing_topic_proceed(session, (SVC_MISSING_TOPIC_REQUEST_T
*) request);
return HANDLER_SUCCESS;
}
/*
* Entry point for the example.
*/
int
main(int argc, char **argv)
{
/*
* Standard command-line parsing.
*/
HASH_T *options = parse_cmdline(argc, argv, arg_opts);
if(options == NULL || hash_get(options, "help") != NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
const char *url = hash_get(options, "url");
const char *principal = hash_get(options, "principal");
const char *topic_root = hash_get(options, "topic_root");
CREDENTIALS_T *credentials = NULL;
const char *password = hash_get(options, "credentials");
if(password != NULL) {
credentials = credentials_create_password(password);
}
SESSION_T *session;
DIFFUSION_ERROR_T error = { 0 };
Diffusion | 319
session = session_create(url, principal, credentials, NULL,
NULL, &error);
if(session != NULL) {
printf("Session created (state=%d, id=%s)\n",
session_state_get(session),
session_id_to_string(session->id));
}
else {
printf("Failed to create session: %s\n",
error.message);
free(error.message);
return EXIT_FAILURE;
}
/*
* Register the missing topic handler
*/
MISSING_TOPIC_PARAMS_T handler = {
.on_missing_topic = on_missing_topic,
.topic_path = topic_root,
.context = NULL
};
missing_topic_register_handler(session, handler);
/*
* Run for 5 minutes.
*/
sleep(5 * 60);
/*
* Close session and clean up.
*/
session_close(session, NULL);
session_free(session);
hash_free(options, NULL, free);
return EXIT_SUCCESS;
}
Change the URL from that provided in the example to the URL of the Diffusion server.
Defining a recordV2 schema
You can use the API to specify a schema that defines the content of a recordV2 topic.
About this task
Publishing clients can define an optional schema for a recordV2 topic. The topic value must conform to
the schema.
No client session is required to create a schema.
The Diffusion API for the following platforms provides builder methods that enable you to define a
schema:
•
•
Java
Android
The following example demonstrates how to create a schema using the Java API.
Diffusion | 320
Procedure
1. Import the required classes.
import com.pushtechnology.diffusion.client.Diffusion;
import
com.pushtechnology.diffusion.datatype.recordv2.RecordV2DataType;
import
com.pushtechnology.diffusion.datatype.recordv2.schema.Schema;
import
com.pushtechnology.diffusion.datatype.recordv2.schema.SchemaBuilder;
2. Create an example class with a recordV2 datatype and a constructor.
public final class ClientCreatingRecordV2Schema {
private final RecordV2DataType dataType =
Diffusion.dataTypes().recordV2();
/**
* Constructor.
*/
public ClientCreatingRecordV2Schema() {
}
}
3. Create a schema.
This schema specifies that the topic will contain a record containing a field that can occur from two
to five times, and a different record with a field that can occur unlimited times.
public Schema createVariableRepeatingFieldsSchema() {
final SchemaBuilder builder = dataType.schemaBuilder();
return builder
.record("A").string("repeatingField", 2, 5)
.record("B").string("repeatingFieldUnlimited", 1, -1)
.build();
}
See the full example code below for how to construct a variety of different schemas.
For more information, see Java API documentation.
Example: A class that shows how to create different schemas.
/
*******************************************************************************
* Copyright (C) 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS"
BASIS,
Diffusion | 321
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
******************************************************************************
package com.pushtechnology.diffusion.examples;
import com.pushtechnology.diffusion.client.Diffusion;
import
com.pushtechnology.diffusion.datatype.recordv2.RecordV2DataType;
import
com.pushtechnology.diffusion.datatype.recordv2.schema.Schema;
import
com.pushtechnology.diffusion.datatype.recordv2.schema.SchemaBuilder;
/**
* This example class has a number of methods that demonstrate the
creation of
* schemas for RECORD_V2 topics, using the Diffusion Client API.
* <P>
* Note that no client session is required in order to create a
schema.
*
* @author Push Technology Limited
* @since 6.0
*/
public final class ClientCreatingRecordV2Schema {
private final RecordV2DataType dataType =
Diffusion.dataTypes().recordV2();
/**
* Constructor.
*/
public ClientCreatingRecordV2Schema() {
}
/**
* Example of a schema consisting of a single record with
three fields each
* of s different data type.
*
* @return a schema
*/
public Schema createSimpleSchema() {
final SchemaBuilder builder = dataType.schemaBuilder();
return builder
.record("Record")
.string("string").integer("integer").decimal("decimal", 3)
.build();
}
/**
* Example of a schema consisting of multiple records, each
record with a
* single field of a specific type.
*
* @return a schema
*/
public Schema createMultipleRecordsSchema() {
final SchemaBuilder builder = dataType.schemaBuilder();
Diffusion | 322
return builder
.record("StringRecord").string("string")
.record("IntegerRecord").integer("integer")
.record("DecimalRecord").decimal("decimal", 3)
.build();
}
/**
* Example of a schema consisting of a record (with a single
string field)
* repeating exactly 10 times.
*
* @return a schema
*/
public Schema createFixedRepeatingRecordsSchema() {
final SchemaBuilder builder = dataType.schemaBuilder();
return builder
.record("RepeatingRecord", 10).string("string")
.build();
}
/**
* Example of a schema consisting of 2 record types.
"FixedRecord" is a
* record that occurs 5 times. "RepeatingRecord" is an
optional record that
* can be repeated as many times as required (unlimited).
*
* @return a schema
*/
public Schema createVariableRepeatingRecordsSchema() {
final SchemaBuilder builder = dataType.schemaBuilder();
return builder
.record("FixedRecord", 5).string("a")
.record("RepeatingRecord", 0, -1).string("b")
.build();
}
/**
* Example of a schema consisting of a single record with a
string field
* that occurs exactly 10 times.
*
* @return a schema
*/
public Schema createFixedRepeatingFieldsSchema() {
final SchemaBuilder builder = dataType.schemaBuilder();
return builder
.record("Record").string("repeatingString", 10)
.build();
}
/**
* Example of a schema consisting of two records. The first
record (A) has a
* field, "repeatingField", which can occur between 2 and 5
times. The
* second record (B) has a field, "repeatingFieldUnlimited",
which can occur
* as many times as required but at least once.
*
* @return a schema
*/
Diffusion | 323
public Schema createVariableRepeatingFieldsSchema() {
final SchemaBuilder builder = dataType.schemaBuilder();
return builder
.record("A").string("repeatingField", 2, 5)
.record("B").string("repeatingFieldUnlimited", 1, -1)
.build();
}
/**
* Example of a schema consisting of a single record and
multiple fields
* encapsulating a person's name and address.
*
* @return a schema
*/
public Schema createNameAndAddressSchema() {
final SchemaBuilder builder = dataType.schemaBuilder();
return builder
.record("nameAndAddress")
.string("firstName")
.string("surname")
.integer("houseNumber")
.string("street")
.string("town")
.string("state")
.string("postCode")
.build();
}
}
Related concepts
RecordV2 topics on page 72
A topic that streams data in recordV2 format, where the data is divided into multiple records, each of
which can contain multiple fields. RecordV2 topics are stateful: each topic stores a value consisting of
one or more records on the Diffusion server.
RecordV2 schema on page 74
A schema is an optional way to define how data is formatted when it is published on a recordV2 topic.
A schema defines and names the permitted records and fields within the topic, and enables direct
access to the fields.
Update recordV2 with a schema on page 324
The following example demonstrates how to create and update recordV2 topics, including the use of a
schema.
Subscribe to recordV2 with a schema on page 330
The following example demonstrates how to process information from subscribed recordV2 topics,
including the use of a schema.
Update recordV2 with a schema
The following example demonstrates how to create and update recordV2 topics, including the use of a
schema.
This example demonstrates a Java control client updating recordV2 topics containing currency
exchange rate information.
Diffusion | 324
Each topic contains a record with two decimal fields, representing the buy and sell rates between a
pair of currencies.
The example can be run either with or without a schema.
/
*******************************************************************************
* Copyright (C) 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*******************************************************************************
package com.pushtechnology.diffusion.examples;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.Registration;
import
com.pushtechnology.diffusion.client.callbacks.TopicTreeHandler;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl.
import com.pushtechnology.diffusion.client.session.Session;
import
com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.client.topics.details.TopicType;
import com.pushtechnology.diffusion.datatype.recordv2.RecordV2;
import
com.pushtechnology.diffusion.datatype.recordv2.RecordV2DataType;
import
com.pushtechnology.diffusion.datatype.recordv2.model.MutableRecordModel;
import com.pushtechnology.diffusion.datatype.recordv2.schema.Schema;
/**
* An example of using a control client to create and update a
RecordV2 topic in
* exclusive mode.
* <P>
* This uses the 'TopicControl' feature to create a topic and the
* 'TopicUpdateControl' feature to send updates to it.
* <P>
* To send updates to a topic, the client session requires the
'update_topic'
* permission for that branch of the topic tree.
* <P>
Diffusion | 325
* The example can be used with or without the use of a schema. This
is simply
* to demonstrate the different mechanisms and is not necessarily
demonstrating
* the most efficient way to update such a topic.
*
* @author Push Technology Limited
* @since 6.0
* @see ClientConsumingRecordV2Topics
*/
public final class ControlClientUpdatingRecordV2Topics {
private static final String ROOT_TOPIC = "FX";
private
private
private
private
private
private
final Session session;
final TopicControl topicControl;
final TopicSpecification topicSpecification;
volatile Registration updateSourceRegistration;
final Schema schema;
final RecordV2DataType dataType;
private volatile TopicUpdateControl.ValueUpdater<RecordV2>
valueUpdater;
/**
* Constructor.
*
* @param serverUrl for example "ws://diffusion.example.com:80"
*/
public ControlClientUpdatingRecordV2Topics(
String serverUrl,
boolean withSchema) {
session =
Diffusion.sessions().principal("control").password("password")
.open(serverUrl);
topicControl = session.feature(TopicControl.class);
dataType = Diffusion.dataTypes().recordV2();
if (withSchema) {
schema = dataType.schemaBuilder()
.record("Rates").decimal("Bid", 5).decimal("Ask",
5).build();
// Create the topic specification to be used for all
rates topics
topicSpecification =
topicControl.newSpecification(TopicType.RECORD_V2)
.withProperty(
TopicSpecification.SCHEMA,
schema.asJSONString());
}
else {
schema = null;
// Create the topic specification to be used for all
rates topics
topicSpecification =
topicControl.newSpecification(TopicType.RECORD_V2);
}
final TopicUpdateControl updateControl =
Diffusion | 326
session.feature(TopicUpdateControl.class);
// Register as an updater for all topics under the root
updateControl.registerUpdateSource(
ROOT_TOPIC,
new TopicUpdateControl.UpdateSource.Default() {
@Override
public void onRegistered(
String topicPath,
Registration registration) {
topicControl.removeTopicsWithSession(
ROOT_TOPIC,
new TopicTreeHandler.Default());
updateSourceRegistration = registration;
}
@Override
public void onActive(
String topicPath,
TopicUpdateControl.Updater updater) {
valueUpdater =
updater.valueUpdater(RecordV2.class);
}
});
}
/**
* Adds a new conversion rate in terms of base currency and
target currency.
*
* The bid and ask rates are entered as strings which may be a
decimal value
* which will be parsed and validated, rounding to 5 decimal
places.
*
* @param currency the base currency (e.g. GBP)
*
* @param targetCurrency the target currency (e.g. USD)
*/
public void addRateTopic(
String currency,
String targetCurrency)
throws InterruptedException, ExecutionException,
TimeoutException {
topicControl.addTopic(
rateTopicName(currency, targetCurrency),
topicSpecification).get(5, TimeUnit.SECONDS);
}
/**
* Set a rate.
* <P>
* The rate topic in question must have been added first using
* {@link #addRateTopic} otherwise this will fail.
*
* @param currency the base currency
*
* @param targetCurrency the target currency
*
* @param bid the new bid rate
*
Diffusion | 327
* @param ask the new ask rate
*
* @param callback a callback which will be called to report the
outcome.
*
The context in the callback will be currency/
targetCurrency (e.g.
*
"GBP/USD")
*/
public void setRate(
String currency,
String targetCurrency,
String bid,
String ask,
UpdateContextCallback<String> callback) {
if (valueUpdater == null) {
throw new IllegalStateException("Not registered as
updater");
}
final RecordV2 value;
if (schema == null) {
value = dataType.valueBuilder().addFields(bid,
ask).build();
}
else {
// Mutable models could be kept and reused but for this
simple
// example one is created every time
final MutableRecordModel model =
schema.createMutableModel();
model.set("Bid", bid);
model.set("Ask", ask);
value = model.asValue();
}
valueUpdater.update(
rateTopicName(currency, targetCurrency),
value,
String.format("%s/%s", currency, targetCurrency),
callback);
}
/**
* Remove a rate (removes its topic).
*
* @param currency the base currency
*
* @param targetCurrency the target currency
*/
public void removeRate(
String currency,
String targetCurrency)
throws InterruptedException, ExecutionException,
TimeoutException {
topicControl.removeTopics(
rateTopicName(currency, targetCurrency))
.get(5, TimeUnit.SECONDS);
}
/**
Diffusion | 328
* Removes a currency (removes its topic and all subordinate rate
topics).
*
* @param currency the base currency
*/
public void removeCurrency(String currency)
throws InterruptedException, ExecutionException,
TimeoutException {
topicControl
.removeTopics(String.format("?%s/%s//", ROOT_TOPIC,
currency))
.get(5, TimeUnit.SECONDS);
}
/**
* Close the session.
*/
public void close() throws InterruptedException {
// Close the registered update source
final Registration registration =
this.updateSourceRegistration;
if (registration != null) {
registration.close();
}
session.close();
}
/**
* Generates a hierarchical topic name for a rate topic.
* <P>
* e.g. for currency=GBP and targetCurrency=USD would return "FX/
GBP/USD".
*
* @param currency the base currency
* @param targetCurrency the target currency
* @return the topic name
*/
private static String rateTopicName(String currency,
String targetCurrency) {
return String.format("%s/%s/%s", ROOT_TOPIC, currency,
targetCurrency);
}
}
Related concepts
RecordV2 topics on page 72
A topic that streams data in recordV2 format, where the data is divided into multiple records, each of
which can contain multiple fields. RecordV2 topics are stateful: each topic stores a value consisting of
one or more records on the Diffusion server.
RecordV2 schema on page 74
A schema is an optional way to define how data is formatted when it is published on a recordV2 topic.
A schema defines and names the permitted records and fields within the topic, and enables direct
access to the fields.
Subscribe to recordV2 with a schema on page 330
Diffusion | 329
The following example demonstrates how to process information from subscribed recordV2 topics,
including the use of a schema.
Related tasks
Defining a recordV2 schema on page 320
You can use the API to specify a schema that defines the content of a recordV2 topic.
Subscribe to recordV2 with a schema
The following example demonstrates how to process information from subscribed recordV2 topics,
including the use of a schema.
This example demonstrates a Java client consuming recordV2 topics which contain currency
conversion rates.
Each topic contains a record with two decimal fields, representing the buy and sell rates between a
pair of currencies.
The example can be run either with or without a schema.
/
*******************************************************************************
* Copyright (C) 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*******************************************************************************
package com.pushtechnology.diffusion.examples;
import static java.util.Objects.requireNonNull;
import
import
import
import
java.util.HashMap;
java.util.List;
java.util.Map;
java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.features.Topics;
import
com.pushtechnology.diffusion.client.features.Topics.UnsubscribeReason;
import
com.pushtechnology.diffusion.client.features.Topics.ValueStream;
import com.pushtechnology.diffusion.client.session.Session;
import
com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.datatype.recordv2.RecordV2;
import com.pushtechnology.diffusion.datatype.recordv2.RecordV2Delta;
Diffusion | 330
import
com.pushtechnology.diffusion.datatype.recordv2.RecordV2Delta.Change;
import
com.pushtechnology.diffusion.datatype.recordv2.model.RecordModel;
import com.pushtechnology.diffusion.datatype.recordv2.schema.Schema;
import
com.pushtechnology.diffusion.datatype.recordv2.schema.SchemaParseException;
/**
* This demonstrates a client consuming RecordV2 topics.
* <P>
* It has been contrived to demonstrate the various techniques for
Diffusion
* record topics and is not necessarily realistic or efficient in its
* processing.
* <P>
* It can be run using a schema or not using a schema and
demonstrates how the
* processing could be done in both cases.
* <P>
* This makes use of the 'Topics' feature only.
* <P>
* To subscribe to a topic, the client session must have the
'select_topic' and
* 'read_topic' permissions for that branch of the topic tree.
* <P>
* This example receives updates to currency conversion rates via a
branch of
* the topic tree where the root topic is called "FX" which under it
has a topic
* for each base currency and under each of those is a topic for each
target
* currency which contains the bid and ask rates. So a topic FX/GBP/
USD would
* contain the rates for GBP to USD.
* <P>
* This example maintains a local map of the rates and also notifies
a listener
* of any rates changes.
*
* @author Push Technology Limited
* @since 6.0
* @see ControlClientUpdatingRecordV2Topics
*/
public final class ClientConsumingRecordV2Topics {
private static final Logger LOG =
LoggerFactory.getLogger(ClientConsumingRecordV2Topics.class);
private static final String ROOT_TOPIC = "FX";
/**
* The map of currency codes to currency objects which each
maintain rates
* for each target currency.
*/
private final Map<String, Currency> currencies = new
ConcurrentHashMap<>();
private Schema schema;
private final RatesListener listener;
Diffusion | 331
private final Session session;
/**
* Constructor.
*
* @param serverUrl for example "ws://diffusion.example.com:80"
* @param listener a listener that will be notified of all rates
and rate
*
changes
*/
public ClientConsumingRecordV2Topics(String serverUrl,
RatesListener listener) {
this.listener = requireNonNull(listener);
session =
Diffusion.sessions().principal("client").password("password")
.open(serverUrl);
// Use the Topics feature to add a record value stream and
subscribe to
// all topics under the root.
final Topics topics = session.feature(Topics.class);
final String topicSelector = String.format("?%s//",
ROOT_TOPIC);
topics.addStream(
topicSelector,
RecordV2.class,
new RatesValueStream());
topics.subscribe(topicSelector)
.whenComplete((voidResult, exception) -> {
if (exception != null) {
LOG.info("subscription failed", exception);
}
});
}
/**
* Returns the rates for a given base and target currency.
*
* @param currency the base currency
* @param targetCurrency the target currency
* @return the rates or null if there is no such base or target
currency
*/
public Rates getRates(String currency, String targetCurrency) {
final Currency currencyObject = currencies.get(currency);
if (currencyObject != null) {
return currencyObject.getRates(targetCurrency);
}
return null;
}
/**
* This is used to apply topic stream updates to the local map
and notify
* listener of changes.
*/
private void applyUpdate(
String currency,
Diffusion | 332
String targetCurrency,
RecordV2 oldValue,
RecordV2 newValue) {
Currency currencyObject = currencies.get(currency);
if (currencyObject == null) {
currencyObject = new Currency();
currencies.put(currency, currencyObject);
}
if (schema == null) {
updateWithoutSchema(
currency,
targetCurrency,
oldValue,
newValue,
currencyObject);
}
else {
updateWithSchema(
currency,
targetCurrency,
oldValue,
newValue,
currencyObject);
}
}
private void updateWithSchema(
String currency,
String targetCurrency,
RecordV2 oldValue,
RecordV2 newValue,
Currency currencyObject) {
// A data model is generated using the schema allowing direct
access to
// the fields within it
final RecordModel model = newValue.asModel(schema);
final String bid = model.get("Bid");
final String ask = model.get("Ask");
currencyObject.setRate(targetCurrency, bid, ask);
if (oldValue == null) {
listener.onNewRate(currency, targetCurrency, bid, ask);
}
else {
// Generate a structural delta to determine what has
changed
final RecordV2Delta delta = newValue.diff(oldValue);
for (Change change : delta.changes(schema)) {
final String fieldName = change.fieldName();
listener.onRateChange(
currency,
targetCurrency,
fieldName,
model.get(fieldName));
}
}
}
private void updateWithoutSchema(
Diffusion | 333
String currency,
String targetCurrency,
RecordV2 oldValue,
RecordV2 newValue,
Currency currencyObject) {
// All of the fields in the value are obtained.
final List<String> fields = newValue.asFields();
final String bid = fields.get(0);
final String ask = fields.get(1);
currencyObject.setRate(targetCurrency, bid, ask);
if (oldValue == null) {
listener.onNewRate(currency, targetCurrency, bid, ask);
}
else {
// Fields in the old value are obtained to determine what
has
// changed
final List<String> oldfields = oldValue.asFields();
final String oldBid = oldfields.get(0);
final String oldAsk = oldfields.get(1);
if (!bid.equals(oldBid)) {
listener.onRateChange(currency, targetCurrency,
"Bid", bid);
}
if (!ask.equals(oldAsk)) {
listener.onRateChange(currency, targetCurrency,
"Ask", ask);
}
}
}
private void removeCurrency(String currency) {
final Currency oldCurrency = currencies.remove(currency);
for (String targetCurrency : oldCurrency.rates.keySet()) {
listener.onRateRemoved(currency, targetCurrency);
}
}
private void removeRate(
String currency,
String targetCurrency) {
final Currency currencyObject = currencies.get(currency);
if (currencyObject != null) {
if (currencyObject.rates.remove(targetCurrency) != null)
{
listener.onRateRemoved(currency, targetCurrency);
}
}
}
/**
* Close session.
*/
public void close() {
currencies.clear();
session.close();
}
/**
Diffusion | 334
* Encapsulates a base currency and all of its known rates.
*/
private static class Currency {
private final Map<String, Rates> rates = new HashMap<>();
private Rates getRates(String currency) {
return rates.get(currency);
}
private void setRate(String currency, String bid, String ask)
{
rates.put(currency, new Rates(bid, ask));
}
}
/**
* Encapsulates the rates for a particular base/target currency
pair.
*/
public static final class Rates {
private final String bidRate;
private final String askRate;
/**
* Constructor.
*
* @param bid the bid rate or ""
* @param ask the ask rate or ""
*/
private Rates(String bid, String ask) {
bidRate = bid;
askRate = ask;
}
/**
* Returns the bid rate.
*
* @return bid rate or "" if not available
*/
public String getBidRate() {
return bidRate;
}
/**
* Returns the ask rate.
*
* @return ask rate or "" if not available
*/
public String getAskRate() {
return askRate;
}
}
/**
* A listener for Rates updates.
*/
public interface RatesListener {
/**
Diffusion | 335
* Notification of a new rate or rate update.
*
* @param currency the base currency
* @param targetCurrency the target currency
* @param bid rate
* @param ask rate
*/
void onNewRate(String currency, String targetCurrency, String
bid,
String ask);
/**
* Notification of a change to the bid or ask value for a
rate.
*
* @param currency the base currency
* @param targetCurrency the target currency
* @param bidOrAsk "Bid" or "Ask"
* @param rate the new rate
*/
void onRateChange(String currency, String targetCurrency,
String bidOrAsk, String rate);
/**
* Notification of a rate being removed.
*
* @param currency the base currency
* @param targetCurrency the target currency
*/
void onRateRemoved(String currency, String targetCurrency);
}
private final class RatesValueStream
extends ValueStream.Default<RecordV2> {
@Override
public void onSubscription(String topicPath,
TopicSpecification specification) {
final boolean isRatesTopic =
Diffusion.topicSelectors().parse("?FX/.*/.*").selects(topicPath);
// Only retrieve a schema when subscribing to a rates
topic
if (isRatesTopic) {
final String schemaString =
specification.getProperties().get(TopicSpecification.SCHEMA);
// If a schema is provided on subscription, retrieve
it and set it once
// All schemas are identical for rates topics.
if (schemaString != null && schema == null) {
try {
schema =
Diffusion.dataTypes().recordV2().parseSchema(schemaString);
}
catch (SchemaParseException e) {
LOG.error("Unable to parse recordV2 schema",
e);
}
}
}
}
@Override
Diffusion | 336
public void onValue(String topicPath, TopicSpecification
specification,
RecordV2 oldValue, RecordV2 newValue) {
final String[] topicElements = elements(topicPath);
// It is only a rate update if topic has 2 elements below
root path
if (topicElements.length == 2) {
applyUpdate(
topicElements[0], // The base currency
topicElements[1], // The target currency
oldValue,
newValue);
}
}
@Override
public void onUnsubscription(String topicPath,
TopicSpecification specification, UnsubscribeReason
reason) {
final String[] topicElements = elements(topicPath);
if (topicElements.length == 2) {
removeRate(topicElements[0], topicElements[1]);
}
else if (topicElements.length == 1) {
removeCurrency(topicElements[0]);
}
}
private String[] elements(String topicPath) {
final String subPath =
topicPath.replaceFirst("^" + ROOT_TOPIC + "/", "");
return subPath.split("/");
}
}
}
Related concepts
RecordV2 topics on page 72
A topic that streams data in recordV2 format, where the data is divided into multiple records, each of
which can contain multiple fields. RecordV2 topics are stateful: each topic stores a value consisting of
one or more records on the Diffusion server.
RecordV2 schema on page 74
A schema is an optional way to define how data is formatted when it is published on a recordV2 topic.
A schema defines and names the permitted records and fields within the topic, and enables direct
access to the fields.
Update recordV2 with a schema on page 324
The following example demonstrates how to create and update recordV2 topics, including the use of a
schema.
Related tasks
Defining a recordV2 schema on page 320
Diffusion | 337
You can use the API to specify a schema that defines the content of a recordV2 topic.
Creating a metadata definition
You can use the API to specify the metadata structure that describes the byte data content of a
message.
About this task
Publishing clients define the metadata structure for messages. This metadata structure can be used
when defining a topic. All messages placed on the topic must conform to the metadata structure.
The Diffusion API for the following platforms provides builder methods that enable you to define the
metadata structure:
•
•
•
JavaScript
Java
.NET
The C API does not provide builder methods, but instead takes the definition of the metadata as XML.
For more information, see C API overview.
The following example demonstrates how to define the metadata structure using the Java API.
Note: Where there notation c.p.d is used in class or package names, it indicates
com.pushtechnology.diffusion.
Procedure
1. Define the metadata structure.
a) Use the Diffusion.metadata method to get a MetadataFactory.
private final MetadataFactory factory = Diffusion.metadata();
b) Use the methods on the MetadataFactory to specify the content, record, and field
definitions that make up the metadata structure.
For example, the following code uses a content builder to create content metadata with a single
record type that can occur zero to n times.
public MContent createContentRepeating() {
return
factory.contentBuilder("Content").
add(
factory.record(
"Rec1",
factory.string("A"),
factory.string("B")),
0,
-1).
build();
}
For more information, see Java API documentation.
2. Create a record topic and apply the metadata definition to it.
Diffusion | 338
a) Import the TopicControl feature, Session class, and RecordTopicDetails class.
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import com.pushtechnology.diffusion.client.session.Session;
import
com.pushtechnology.diffusion.client.topics.details.RecordTopicDetails;
b) Create a Session instance and use it to get the TopicControl feature.
final Session session = Diffusion.sessions().open("ws://
diffusion.example.com:80");
final TopicControl tc = session.feature(TopicControl.class);
c) Get a topic builder for a record topic from the TopicControl feature.
final RecordTopicDetails.Builder builder =
tc.newDetailsBuilder(RecordTopicDetails.Builder.class);
d) Use the metadata method of the topic builder to create the topic definition.
tc.addTopic(
TOPIC_NAME,
builder.metadata(metadata).build(),
new TopicControl.AddCallback.Default() {
@Override
public void onTopicAdded(String topic) {
theTopic = topic;
}
});
Example: A client that creates a metadata definition and uses it when creating a topic.
package com.example.metadata;
import com.pushtechnology.diffusion.client.Diffusion;
import
com.pushtechnology.diffusion.client.content.metadata.MContent;
import
com.pushtechnology.diffusion.client.content.metadata.MDecimalString;
import
com.pushtechnology.diffusion.client.content.metadata.MField;
import
com.pushtechnology.diffusion.client.content.metadata.MRecord;
import
com.pushtechnology.diffusion.client.content.metadata.MetadataFactory;
import
com.pushtechnology.diffusion.client.topics.details.TopicType;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import com.pushtechnology.diffusion.client.session.Session;
import
com.pushtechnology.diffusion.client.topics.details.RecordTopicDetails;
import com.pushtechnology.diffusion.client.types.UpdateOptions;
/**
* An example of a client creating metadata definition and using
it when creating a
Diffusion | 339
* topic definition.
*/
public class ControlClient {
private final static String TOPIC_NAME = "foo/record";
private String theTopic;
private final MetadataFactory factory = Diffusion.metadata();
/**
* Creates control client instance.
*/
public ControlClient() {
// Create the Session
final Session session = Diffusion.sessions()
.open("ws://diffusion.example.com:80");
// Add the TopicControl feature
final TopicControl tc =
session.feature(TopicControl.class);
// Create metadata for the topic
final MContent metadata = defineMetadata();
// Get a topic builder
final RecordTopicDetails.Builder builder =
tc.newDetailsBuilder(RecordTopicDetails.Builder.class);
// Create the topic with metadata
tc.addTopic(
TOPIC_NAME,
builder.metadata(metadata).build(),
new TopicControl.AddCallback.Default() {
@Override
public void onTopicAdded(String topic) {
theTopic = topic;
}
});
}
/**
* A simple example of creating content metadata with two
records.
*
* @return content metadata
*/
public MContent defineMetadata() {
return factory.content(
"Content",
createNameAndAddressRecord(),
createMultipleRateCurrencyRecord("Exchange Rates",
5));
}
/**
* Creates a simple name and address record with fixed name
single
* multiplicity fields.
*
Diffusion | 340
* @return record definition.
*/
public MRecord createNameAndAddressRecord() {
return factory.record(
"NameAndAddress",
factory.string("FirstName"),
factory.string("Surname"),
factory.string("HouseNumber"),
factory.string("Street"),
factory.string("Town"),
factory.string("State"),
factory.string("PostCode"));
}
/**
* This creates a record with two fields, a string called
"Currency" and a
* decimal string called "Rate" with a default value of 1.00
which repeats a
* specified number of times.
*
* @param name the record name
* @param occurs the number of occurrences of the "Rate" field
* @return the metadata record
*/
public MRecord createMultipleRateCurrencyRecord(String name,
int occurs) {
return factory.recordBuilder(name).
add(factory.string("Currency")).
add(factory.decimal("Rate", "1.00"), occurs).
build();
}
}
Removing topics
A client can use the TopicControl feature of the Diffusion API to add and remove topics at the server.
Required permissions: modify_topic
Currently all topics created using a client have a lifespan the same as the Diffusion server. The topics
remain at the Diffusion server even after the client session that created them has closed unless you
explicitly specify that the topic is removed with the session.
A client can remove topics anywhere in the topic tree. The remove operation takes a topic selector,
which enables the client to remove many topics at once.
You can also remove all topics beneath a selected topic path by appending the descendant pattern
qualifiers, / and //.
Only topics for which the client has modify_topic permission are removed.
If there are topics for which the client does not have modify_topic permission, they are unaffected and
the operation completes without throwing an exception.
A client cannot remove topics created by a publisher.
A publisher cannot remove topics created by a client.
For more information, see Topic selectors on page 50.
Diffusion | 341
JavaScript
session.topics.removeSelector('topic_selector'))
.then(function() {
console.log('Removed all topics that match the selector.');
});
Apple
// Remove topic.
[session.topicControl
removeDiscreteWithTopicSelectorExpression: topic_selector
completionHandler:^(NSError *const error)
{
if (error) {
NSLog(@"Failed to remove topic. Error: %@", error);
} else {
NSLog(@"Topic removal request succeeded.");
}
}];
Java and Android
TopicControl topicControl = session.feature(TopicControl.class);
topicControl.remove(topic_selector, callback);
.NET
ITopicControl topicControl = session.GetTopicControlFeature();
topicControl.RemoveTopics( topic_selector, callback );
C
// Define callbacks elsewhere
REMOVE_TOPICS_PARAMS_T remove_params = {
.on_removed = on_topic_removed,
.on_discard = on_topic_remove_discard,
.topic_selector = "topic_selector"
};
remove_topics(session, remove_params);
Removing topics with sessions
A client can specify that the Diffusion server removes a topic or topics after the client session closes or
fails.
Required permissions: modify_topic
Register a branch of the topic tree to be removed when the session closes:
JavaScript
// Remove all topics under a topic path
session.topics.removeWithSession('topic_path').then(
function(registration) {
// Registration complete
// Deregister this action
registration.deregister().then(
Diffusion | 342
function() {
// Deregistration complete
},
function(err) {
// Failure while deregistering
}
);
},
function(err) {
// Could not register
}
);
Apple
PTDiffusionTopicControlFeature *const tc = session.topicControl;
// Register to remove the Example topic tree when the session closes.
[tc removeTopicsWithSessionForTopicPath:@"topic_path"
delegate:self
completionHandler:^(PTDiffusionTopicTreeRegistration *const
registration, NSError *const error)
{
if (registration) {
NSLog(@"Registered.");
} else {
NSLog(@"Registration failed. Error: %@", error);
}
}];
Java and Android
TopicControl topicControl = session.feature(TopicControl.class);
topicControl.removeTopicsWithSession(topic_path, new
TopicTreeHandler.Default());
.NET
ITopicControl topicControl = session.TopicControl;
topicControl.RemoveTopicsWithSession( topic_path, new
DefaultTopicTreeHandler() );
C
SESSION_WILLS_REMOVE_TOPIC_PARAMS_T params = {
.topic_path = topic_path,
.on_registered = on_will_registered,
.on_close = on_will_closed
};
session_wills_remove_topics(session, params);
Note: Only topics for which the client has modify_topic permission when the session closes
will be removed. Topics in the registered branch for which the client does not have permission
will not be removed.
Diffusion | 343
How topic removal with the session works
When a client registers that branch of the topic tree to be removed when its session closes, the
following events occur:
1. The client registers the removal request on a branch of the topic tree and passes in a topic tree
handler.
•
The removal request is registered against a topic path. This is a path that identifies a branch
of the topic tree, for example foo/bar. The removal request is registered for the branch of the
topic tree, for example the topics foo/bar/baz and foo/bar/fred/qux are included in the specified
branch of the topic tree.
• You cannot register a removal request above or below an existing removal request. For
example, if a client has registered a removal request against foo/bar/fred another client cannot
register a removal request against foo/bar or foo/bar/fred/qux.
2. The server validates the request and gives one of the following responses:
•
If the request is not valid, the Diffusion server calls the onError callback of the topic tree
handler.
For example, a registration request is not valid if it registers against a topic branch that is above
or below a branch where an existing removal request is registered.
• If the request is valid, the Diffusion server calls the OnActive callback of the topic tree handler
and provides a Registration object to the client.
3. If the client wants to deregister a removal request, it can call the onClose method of the
Registration object for that removal request.
4. Other clients can register removal requests against a topic that already has a removal request
registered against it. For example, if one session on the Diffusion server has registered a removal
request against foo/bar/baz, another session on the Diffusion server can also register a removal
request against foo/bar/baz.
5. When a client session closes or fails, if it has registered removal requests, one of the following
things happens:
•
•
If there are still open sessions that have removal requests for the same branch of the topic tree,
the Diffusion server takes no action.
If there are no open sessions that have removal requests for that branch of the topic tree, the
Diffusion server removes all topics in that branch of the topic tree where the client has the
required modify_topic permission.
Note: The client session must be in a closed state for a removal request to be acted
upon. If a client becomes disconnected, the removal request is not acted upon until the
reconnection timeout has elapsed and the client session is closed.
Remove topic requests and topic replication
If all sessions on a Diffusion server that have a removal request for a branch of the topic tree close,
the topics are removed even if that topic is replicated and sessions on other Diffusion servers have
removal requests registered against that part of the tree. When the topics are removed on the server,
that change is replicated to all other servers that participate in replication for these topics.
Handling subscriptions to missing topics
A client can handle subscription or fetch requests for topics that do not exist and can act on those
notifications, for example, by creating a topic on demand that matches the request.
Required permissions: modify_topic, register_handler
Diffusion | 344
The client can register itself as a handler for missing topics for any branch of the topic tree. The client
is notified of attempts to subscribe to or fetch topics that are subordinate to that topic and that do
not exist. This enables the client to create the topics and notify the Diffusion server that the client
operation subscribe or fetch can proceed.
Note: The handler is called only when a client attempts to subscribe or fetch using a single
topic path. If another type of selector is used to subscribe to or fetch the topic, the handler is
not notified.
Figure 26: Flow from a subscribing client to the client that handles a missing topic subscription
The missing topic handler is removed when the registering session is closed. If the registering session
loses connection, it goes into DISCONNECTED state. When in DISCONNECTED state the handler
remains active but cannot pass on the notifications to the client. In this case, cancel or proceed
callbacks for these notifications might not function as expected because of timeouts. If the client then
closes, these notifications are discarded.
To ensure that missing topic notifications are always received by your solution, you can use multiple
clients to register missing topic handlers. Ensure that if any of these clients lose connection they go
straight to CLOSED state by setting the reconnection timeout to zero. When the client loses connect it
closes straight away, the handler is registered is removed, and further missing topic notifications are
routed to a handler registered by another client.
Registering a missing topic notification handler
Register a handler against a branch of the topic tree:
JavaScript
session.topics.addMissingTopicHandler("topic_branch", {
// Implement handling code
});
Diffusion | 345
Apple
-(void)registerAsMissingTopicHandlerForSession:(PTDiffusionSession
*const)session {
[session.topicControl addMissingTopicHandler:self
forTopicPath:@"topic_branch"
completionHandler:^(PTDiffusionTopicTreeRegistration *const
registration, NSError *const error)
{
if (registration) {
NSLog(@"Registered as missing topic handler.");
} else {
NSLog(@"Failed to register as missing topic handler.
Error: %@", error);
}
}];
}
Java and Android
TopicControl topicControl = session.feature(TopicControl.class);
topicControl.addMissingTopicHandler("topic_branch", new
MissingTopicNotificationHandler());
.NET
ITopicControl topicControl = session.TopicControl;
var missingTopicHandler = new MissingTopicHandler();
topicControl.AddMissingTopicHandler( topic_branch,
missingTopicHandler );
C
MISSING_TOPIC_PARAMS_T handler = {
.on_missing_topic = on_missing_topic,
.topic_path = topic_branch,
.context = NULL
};
missing_topic_register_handler(session, handler);
Implementing a missing topic handler
The sample registration code shows default or no-op missing topic handlers being registered. To take
the required action when a missing topic notification is received, implement a missing topic handler:
JavaScript
session.topics.addMissingTopicHandler("topic_branch", {
onRegister : function(path, close) {
console.log("Registered missing topic handler on path: " + path);
},
onClose : function(path) {
console.log("Missing topic handler on path '" + path + "' has been
closed");
},
Diffusion | 346
onError : function(path, error) {
console.log("Error on missing topic handler");
},
onMissingTopic : function(notification) {
console.log("Received missing topic notification with selector: " +
notification.selector);
// Action to take in response to the notification, for example,
creating a topic.
// If the action is successful, you can indicate that by calling
proceed
notification.proceed();
}
});
Apple
@implementation MissingTopicHandlerExample
(PTDiffusionMissingTopicHandler)
-(void)diffusionTopicTreeRegistration:
(PTDiffusionTopicTreeRegistration *const)registration
hadMissingTopicNotification:
(PTDiffusionMissingTopicNotification *const)notification {
NSString *const expression =
notification.topicSelectorExpression;
NSLog(@"Received Missing Topic Notification: %@", expression);
// Action to take in response to the notification, for example,
creating a topic.
// If the action is successful, you can indicate that by calling
proceed
[notification proceed];
}
@end
Java and Android
private final class MissingTopicNotificationHandler implements
MissingTopicHandler {
/**
* @param topicPath
*
- the path that the handler is active for
* @param registeredHandler
*
- allows the handler to be closed
*/
@Override
public void onActive(String topicPath, RegisteredHandler
registeredHandler) {
}
/**
* @param topicPath
*
- the branch of the topic tree for which the
handler was
*
registered
*/
@Override
Diffusion | 347
public void onClose(String topicPath) {
}
/**
* @param notification
*
- the missing topic details
*/
@Override
public void onMissingTopic(MissingTopicNotification notification)
{
// Action to take in response to the notification, for
example, creating a topic.
// If the action is successful, you can indicate that by
calling proceed
notification.proceed();
}
}
.NET
private class MissingTopicHandler : IMissingTopicHandler {
private readonly TaskCompletionSource<IRegisteredHandler>
onActive =
new TaskCompletionSource<IRegisteredHandler>();
private readonly
TaskCompletionSource<IMissingTopicNotification> onMissingTopic =
new
TaskCompletionSource<IMissingTopicNotification>();
private readonly TaskCompletionSource<bool> onClose = new
TaskCompletionSource<bool>();
public Task<IRegisteredHandler> OnActiveCalled {
get {
return onActive.Task;
}
}
public Task<IMissingTopicNotification>
OnMissingTopicCalled {
get {
return onMissingTopic.Task;
}
}
public Task OnCloseCalled {
get {
return onClose.Task;
}
}
void
IMissingTopicHandler.OnMissingTopic( IMissingTopicNotification
notification ) {
onMissingTopic.SetResult( notification );
}
void ITopicTreeHandler.OnActive( string topicPath,
IRegisteredHandler registeredHandler ) {
Diffusion | 348
onActive.SetResult( registeredHandler );
}
void ITopicTreeHandler.OnClose( string topicPath ) {
onClose.TrySetResult( false );
}
}
}
C
static int
on_missing_topic(SESSION_T *session, const
SVC_MISSING_TOPIC_REQUEST_T *request, void *context)
{
printf("Missing topic: %s\n", request->topic_selector);
// Action to take in response to the notification, for
example, creating a topic.
// If the action is successful, you can indicate that by
calling proceed
missing_topic_proceed(session, (SVC_MISSING_TOPIC_REQUEST_T
*) request);
return HANDLER_SUCCESS;
}
Related concepts
Using missing topic notifications with fan-out on page 97
Missing topic notifications generated by subscription or fetch requests to a secondary server are
propagated to missing topic handlers registered against the primary servers.
Listening for topic events
A client can listen for events that happen on topics in a specific topic branch.
Registering a topic event listener
Required permissions: register_handler
You can use the TopicControl feature to receive a notification whenever one of the following topic
events occurs:
•
•
A topic that previously had zero subscribers gains one or more subscribers
A topic that previously had one or more subscribers goes to zero subscribers
Note: Subscriber numbers also include indirect subscriptions, for example: subscriptions via a
slave topic or routing topic; subscriptions by another Diffusion server for fan-out replication; or
replication of the topic using high-availability replication.
A client can register a topic event listener against any branch of the topic tree. When a topic event
occurs on one of the topics in that branch of the topic tree the listening client receives a notification.
If multiple clients register listeners against the same branch of the topic tree, all receive notifications.
However, if a client registers a listener at a more specific branch of the topic tree, the most specific
listener or listeners receive a notification and any less specific listeners within that same branch do not
receive a notification.
Diffusion | 349
For example: Client One registers a topic event listener against A, Client Two registers a topic event
listener against A, and Client Three registers a topic event listener against A/B/C. If a topic event occurs
on A/B, both Client One and Client Two receive notifications. If a topic event occurs on A/B/C/D, only
Client Three receives a notification.
Receiving topic notifications
Receive topic notifications using topic selectors. This enables a client to receive updates when topics
are added or removed, without the topic values.
Note: Topic notifications are supported by the Android API, Java API and JavaScript API.
The client must register a listener object to receive notifications about selected topics. Use a topic
selector to specify the topics.
For more details about topic notifications, see Topic notifications on page 86.
Required permissions: select_topic and read_topic permissions for the specified topics
Receiving topic notifications
A client can register to receive notifications about a set of topics via a listener object.
JavaScript
var listener = {
onDescendantNotification: function(topicPath, type) {},
onTopicNotification: function(topicPath, topicSpecification,
type) {},
onClose: function() {},
onError: function(error) {}
};
session.notifications.addListener(listener).then(function(reg) {
reg.select("foo");
});
Java and Android
final TopicNotifications notifications =
session.feature(TopicNotifications.class);
final TopicNotificationListener listener = new
TopicNotificationListener() {
@Override
public void onTopicNotification(String topicPath,
TopicSpecification specification, NotificationType type) {
// Handle notifications for selected/deselected topics
}
@Override
public void onDescendantNotification(String topicPath,
NotificationType type) {
// Handle notifications for immediate descendants
}
@Override
public void onClose() {
// The listener has been closed
}
Diffusion | 350
@Override
public void onError(ErrorReason error) {
// The listener has encountered an error
}
};
final CompletableFuture<NotificationRegistration> future =
notifications.addListener(listener);
final NotificationRegistration registration = future.get();
registration.select("foo");
Updating topics
A client can use the TopicUpdateControl feature to update topics.
A client can update a topic in one of the following ways:
Exclusive updating
By registering with the Diffusion server as an update source for the branch of the
topic tree that contains the topic to be updated.
If a client is registered as the active update source for a branch of the topic tree, no
other clients can update topics in that branch of the topic tree.
Non-exclusive updating
By getting a non-exclusive updater from the TopicUpdateControl feature. This
updater can be used to update any topic that does not already have an active update
source registered against it.
Registering as an exclusive update source
Required permissions: update_topic, register_handler
A client must register as an update source for a branch of the topic tree to be able to exclusively
publish content to topics in that branch. This locks the branch of the topic tree and prevents other
clients from publishing updates to topics in the branch.
When a client registers as an update source the following events occur:
1. The client requests to register as an update source on a branch of the topic tree.
•
The update source is registered against a topic path. This is a path that identifies a branch of
the topic tree, for example foo/bar. The update source is registered as a source for that branch
of the topic tree, for example the topics foo/bar/baz and foo/bar/fred/qux are included in the
specified branch of the topic tree.
• You cannot register an update source above or below an existing update source. For example, if
a client has registered an update source against foo/bar/fred another client cannot register an
update source against foo/bar or foo/bar/fred/qux.
• You can register an update source against a topic owned by an existing publisher or a topic that
has an update source created by the server that is used for topic failover.
2. The server validates the registration request and returns one of the following responses:
•
•
If the request is valid, the Diffusion server calls the OnRegister callback of the update source
and passes a RegisteredHandler that you can use to deregister the update source.
If the request is not valid, the Diffusion server calls the onClose callback of the update source.
Diffusion | 351
For example, a registration request is not valid if it registers against a topic branch that is above
or below a branch where an existing update source is registered.
3. When the update source is registered, the Diffusion server calls one of the following callbacks:
•
If the update source is the primary update source, the Diffusion server calls the onActive
callback of the update source.
• If another update source is already the primary source for this branch of the topic tree, the
Diffusion server calls the onStandby callback of the update source.
4. If an update source is on standby, the update source cannot update the topics it is registered
against. If the active update source for a branch of the topic tree closes or becomes inactive, a
standby update source can then become active and become the primary update source for that
branch of the topic tree.
5. If an update source is active, the Diffusion server provides the update source with an Updater.
The update source can use the Updater to update the topics it is registered against.
6. If an active update source exists for a branch of the topic tree, no other clients can update topics in
that branch of the topic tree.
Updating a topic non-exclusively
Required permissions: update_topic
To non-exclusively update topics, a client must get a non-exclusive updater from the
TopicUpdateControl feature. This updater can be used to update any topic under the following
conditions:
•
•
The topic does not already have an active update source registered against it
The client has the update_topic permission for the topic
Types of updater
Updater type
Description
Value updater
Use a value updater to update one of the following topic types: JSON,
binary. In future releases, more topic types will use the value updaters.
When a topic is updated with a value updater, the value is cached.
Subsequent updates can use the cached value to calculate a delta of
change between the two values and just send that to the Diffusion server,
thus reducing the data volume to the Diffusion server.
Updater
Use an updater to update one of the following topic types: single value,
record, stateless.
Using a value updater to stream values through topics
Required permissions: update_topic
A client uses a value updater to publish a value to a topic. Value updaters are typed and can only be
used to update topics whose data type matches the data type of the value updater.
Value updaters can be used for exclusive or non-exclusive updating, depending on how the value
updater is acquired.
When used exclusively, value updaters cache the values that are passed to them. When a value is
passed to a value updater, the value updater compared that value with the previously cached value. If
it is more efficient to do so, the value updater publishes a delta of changes between the previous value
and the new value instead of publishing the full new value.
Diffusion | 352
For non-exclusive updating, the complete value is always sent to the server and the value is not
cached.
When the client uses a value updater method to publish values, it passes in the following parameters:
Topic path
The path to the topic to be updated.
If the value updater is an exclusive updater, this topic must be in the branch of the
topic tree that the client is the active update source for and that the updater is
associated with.
Value
The value to use to update the topic. This value is of the data type that matches the
data type of the topic being updated.
Context
OPTIONAL: A context object can be passed in to the update method that provides
application state information.
Callback
The server uses the callback to return the result of the update. If the update
completes successfully, the Diffusion server calls the callback's onComplete
method. Otherwise, the Diffusion server calls the callback's onError method.
Using an updater to publish content to topics
Required permissions: update_topic
A client uses an updater to publish content to topics. Updaters can be used for exclusive or nonexclusive updating, depending on how the updater is acquired. When the client uses an updater
method to publish content, it passes in the following parameters:
Topic path
The path to the topic to be updated.
If the updater is an exclusive updater, this topic must be in the branch of the topic
tree that the client is the active update source for and that the updater is associated
with.
Content
The information about the update can be provided as either a simple Content
object or as a more complex Update object.
The content that is to be published to the topic. The client must use the appropriate
content type when formatting the content. If the content uses the wrong content type
for the topic, it can cause an error.
Update
The information about the update can be provided as either a simple Content
object or as a more complex Update object.
An update that contains the content that is to be published to the topic and other
information about the update, such as its type.
Use the Builder methods provided in the Diffusion API to build your Update
objects.
Context
OPTIONAL: A context object can be passed in to the update method that provides
application state information.
Diffusion | 353
Callback
The server uses the callback to return the result of the update. If the update
completes successfully, the Diffusion server calls the callback's onComplete
method. Otherwise, the Diffusion server calls the callback's onError method.
Related reference
Failover of active update sources on page 106
You can use failover of active update sources to ensure that when a server that is the active update
source for a section of the topic tree becomes unavailable, an update source on another server is
assigned to be the active update source for that section of the topic tree. Failover of active update
sources is enabled for any sections of the topic tree that have topic replication enabled.
Example: Make exclusive updates to a topic
The following examples use the Diffusion API to register as the update source of a topic and to update
that topic with content. A client that updates a topic using this method locks the topic and prevents
other clients from updating the topic.
JavaScript
diffusion.connect({
host
: 'diffusion.example.com',
port
: 443,
secure : true,
principal : 'control',
credentials : 'password'
}).then(function(session) {
// A session may establish an exclusive update source. Once
active, only this session may update topics at or
// under the registration branch.
session.topics.registerUpdateSource('exclusive/topic', {
onRegister : function(topic, deregister) {
// The handler provides a deregistration function to
remove this registration and allow other sessions to
// update topics under the registered path.
},
onActive : function(topic, updater) {
// Once active, a handler may use the provided updater to
update any topics at or under the registered path
updater.update('exclusive/topic/bar',
123).then(function() {
// The update was successful.
}, function(err) {
// There was an error updating the topic
});
},
onStandBy : function(topic) {
// If there is another update source registered for the
same topic path, any subsequent registrations will
// be put into a standby state. The registration is still
held by the server, and the 'onActive' function
// will be called if the pre-existing registration is
closed at a later point in time
},
onClose : function(topic, err) {
// The 'onClose' function will be called once the
registration is closed, either by the session being closed
Diffusion | 354
// or the 'deregister' function being called.
}
});
});
Apple
@import Diffusion;
@interface TopicUpdateSourceExample (PTDiffusionTopicUpdateSource)
<PTDiffusionTopicUpdateSource>
@end
@implementation TopicUpdateSourceExample {
PTDiffusionSession* _session;
}
-(void)startWithURL:(NSURL*)url {
PTDiffusionCredentials *const credentials =
[[PTDiffusionCredentials alloc]
initWithPassword:@"password"];
PTDiffusionSessionConfiguration *const sessionConfiguration =
[[PTDiffusionSessionConfiguration alloc]
initWithPrincipal:@"control"
credentials:credentials];
NSLog(@"Connecting...");
[PTDiffusionSession openWithURL:url
configuration:sessionConfiguration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
if (!session) {
NSLog(@"Failed to open session: %@", error);
return;
}
// At this point we now have a connected session.
NSLog(@"Connected.");
// Set ivar to maintain a strong reference to the session.
_session = session;
// Add topic.
[self addTopicForSession:session];
}];
}
static NSString *const _TopicPath = @"Example/Exclusively Updating";
-(void)addTopicForSession:(PTDiffusionSession *const)session {
// Add a single value topic without an initial value.
[session.topicControl addWithTopicPath:_TopicPath
type:PTDiffusionTopicType_SingleValue
value:nil
completionHandler:^(NSError * _Nullable
error)
{
Diffusion | 355
if (error) {
NSLog(@"Failed to add topic. Error: %@", error);
} else {
NSLog(@"Topic created.");
// Register as an exclusive update source.
[self registerAsUpdateSourceForSession:session];
}
}];
}
-(void)registerAsUpdateSourceForSession:(PTDiffusionSession
*const)session {
[session.topicUpdateControl registerUpdateSource:self
forTopicPath:_TopicPath
completionHandler:^(PTDiffusionTopicTreeRegistration *const
registration, NSError *const error)
{
if (registration) {
NSLog(@"Registered as an update source.");
} else {
NSLog(@"Failed to register as an update source. Error:
%@", error);
}
}];
}
-(void)updateTopicWithUpdater:(PTDiffusionTopicUpdater *const)updater
value:(const NSUInteger)value {
// Prepare data to update topic with.
NSString *const string =
[NSString stringWithFormat:@"Update #%lu", (unsigned
long)value];
NSData *const data = [string
dataUsingEncoding:NSUTF8StringEncoding];
PTDiffusionContent *const content =
[[PTDiffusionContent alloc] initWithData:data];
// Update the topic.
[updater updateWithTopicPath:_TopicPath
value:content
completionHandler:^(NSError *const error)
{
if (error) {
NSLog(@"Failed to update topic. Error: %@", error);
} else {
NSLog(@"Topic updated to \"%@\"", string);
// Update topic after a short wait.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)
(1.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^
{
[self updateTopicWithUpdater:updater value:value +
1];
});
}
}];
}
@end
Diffusion | 356
@implementation TopicUpdateSourceExample
(PTDiffusionTopicUpdateSource)
-(void)diffusionTopicTreeRegistration:
(PTDiffusionTopicTreeRegistration *const)registration
isActiveWithUpdater:(PTDiffusionTopicUpdater
*const)updater {
NSLog(@"Registration is active.");
// Start updating.
[self updateTopicWithUpdater:updater value:1];
}
@end
Java and Android
/
*******************************************************************************
* Copyright (C) 2014, 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*******************************************************************************
package com.pushtechnology.diffusion.examples;
import
import
import
import
import
java.util.concurrent.ExecutionException;
java.util.concurrent.ScheduledExecutorService;
java.util.concurrent.ScheduledFuture;
java.util.concurrent.TimeUnit;
java.util.concurrent.TimeoutException;
import com.pushtechnology.diffusion.client.Diffusion;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl.
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl.
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl.
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl.
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.topics.details.TopicType;
/**
* An example of using a control client as an event feed to a topic.
Diffusion | 357
* <P>
* This uses the 'TopicControl' feature to create a topic and the
* 'TopicUpdateControl' feature to send updates to it.
* <P>
* To send updates to a topic, the client session requires the
'update_topic'
* permission for that branch of the topic tree.
*
* @author Push Technology Limited
* @since 5.0
*/
public class ControlClientAsUpdateSource {
private static final String TOPIC_NAME = "Feeder";
private final Session session;
private final UpdateCallback updateCallback;
/**
* Constructor.
*
* @param callback for updates
*/
public ControlClientAsUpdateSource(UpdateCallback callback) {
updateCallback = callback;
session =
Diffusion.sessions().principal("control").password("password")
.open("ws://diffusion.example.com:80");
}
/**
* Start the feed.
*
* @param provider the provider of prices
* @param scheduler a scheduler service to schedule a periodic
feeder task
* @throws TimeoutException if the topic was not created within 5
seconds
* @throws ExecutionException if topic creation failed
* @throws InterruptedException if the current thread was
interrupted whilst
*
waiting for the topic to be created
*/
public void start(
final PriceProvider provider,
final ScheduledExecutorService scheduler)
throws InterruptedException, ExecutionException,
TimeoutException {
// Add the topic and when created notify the server that the
topic
// should be removed when the session closes.
final TopicControl topicControl =
session.feature(TopicControl.class);
topicControl.addTopic(
TOPIC_NAME,
TopicType.STRING).get(5, TimeUnit.SECONDS);
topicControl.removeTopicsWithSession(TOPIC_NAME);
Diffusion | 358
// Declare a custom update source implementation. When the
source is set
// as active start a periodic task to poll the provider every
second and
// update the topic. When the source is closed, stop the
scheduled task.
final UpdateSource source = new UpdateSource.Default() {
private ScheduledFuture<?> theFeeder;
@Override
public void onActive(String topicPath, Updater updater) {
theFeeder =
scheduler.scheduleAtFixedRate(
new FeederTask(provider,
updater.valueUpdater(String.class)),
1, 1, TimeUnit.SECONDS);
}
@Override
public void onClose(String topicPath) {
if (theFeeder != null) {
theFeeder.cancel(true);
}
}
};
final TopicUpdateControl updateControl =
session.feature(TopicUpdateControl.class);
updateControl.registerUpdateSource(TOPIC_NAME, source);
}
/**
* Close the session.
*/
public void close() {
session.close();
}
/**
* Periodic task to poll from provider and send update to server.
*/
private final class FeederTask implements Runnable {
private final PriceProvider priceProvider;
private final ValueUpdater<String> priceUpdater;
private FeederTask(PriceProvider provider,
ValueUpdater<String> updater) {
priceProvider = provider;
priceUpdater = updater;
}
@Override
public void run() {
priceUpdater.update(
TOPIC_NAME,
priceProvider.getPrice(),
updateCallback);
}
Diffusion | 359
}
/**
* Interface of a price provider that can periodically be polled
for a
* price.
*/
public interface PriceProvider {
/**
* Get the current price.
*
* @return current price as a decimal string
*/
String getPrice();
}
}
.NET
/**
* Copyright © 2014, 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using
using
using
using
using
using
System.Threading;
PushTechnology.ClientInterface.Client.Callbacks;
PushTechnology.ClientInterface.Client.Factories;
PushTechnology.ClientInterface.Client.Features.Control.Topics;
PushTechnology.ClientInterface.Client.Session;
PushTechnology.ClientInterface.Client.Topics;
namespace Examples {
/// <summary>
/// An example of using a control client as an event feed to a
topic.
///
/// This uses the <see cref="ITopicControl"/> feature to create a
topic and the <see cref="ITopicUpdateControl"/>
/// feature to send updates to it.
///
/// To send updates to a topic, the client session requires the
<see cref="TopicPermission.UPDATE_TOPIC"/>
/// permission for that branch of the topic tree.
/// </summary>
public class ControlClientAsUpdateSource {
private const string TopicName = "Feeder";
private readonly ISession session;
Diffusion | 360
private readonly ITopicControl topicControl;
private readonly ITopicUpdateControl updateControl;
private readonly ITopicUpdaterUpdateCallback updateCallback;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="callback">The callback for updates.</param>
public
ControlClientAsUpdateSource( ITopicUpdaterUpdateCallback callback )
{
updateCallback = callback;
session =
Diffusion.Sessions.Principal( "control" ).Password( "password" )
.Open( "ws://diffusion.example.com;80" );
topicControl = session.TopicControl;
updateControl = session.TopicUpdateControl;
}
public void Start( IPriceProvider provider ) {
// Set up topic details
var builder =
topicControl.CreateDetailsBuilder<ISingleValueTopicDetailsBuilder>();
var details =
builder.Metadata( Diffusion.Metadata.Decimal( "Price" ) ).Build();
// Declare a custom update source implementation. When
the source is set as active, start a periodic task
// to poll the provider every second and update the
topic. When the source is closed, stop the scheduled
// task.
var source = new UpdateSource( provider,
updateCallback );
// Create the topic. When the callback indicates that the
topic has been created, register the topic
// source for the topic.
topicControl.AddTopicFromValue( TopicName, details, new
AddCallback( updateControl, source ) );
}
public void Close() {
// Remove our topic and close the session when done.
topicControl.Remove( "?" + TopicName + "//", new
RemoveCallback( session ) );
}
private class RemoveCallback :
TopicControlRemovalCallbackDefault {
private readonly ISession theSession;
public RemoveCallback( ISession session ) {
theSession = session;
}
/// <summary>
/// Notification that a call context was closed
prematurely, typically due to a timeout or the session being
/// closed. No further calls will be made for the
context.
/// </summary>
Diffusion | 361
public override void OnError( ErrorReason error ) {
theSession.Close();
}
/// <summary>
/// Topic(s) have been removed.
/// </summary>
public override void OnTopicsRemoved() {
theSession.Close();
}
}
private class AddCallback : TopicControlAddCallbackDefault {
private readonly ITopicUpdateControl updateControl;
private readonly UpdateSource updateSource;
public AddCallback( ITopicUpdateControl updater,
UpdateSource source ) {
updateControl = updater;
updateSource = source;
}
/// <summary>
/// Topic has been added.
/// </summary>
/// <param name="topicPath">The full path of the topic
that was added.</param>
public override void OnTopicAdded( string topicPath ) {
updateControl.RegisterUpdateSource( topicPath,
updateSource );
}
}
private class UpdateSource : TopicUpdateSourceDefault {
private readonly IPriceProvider thePriceProvider;
private readonly ITopicUpdaterUpdateCallback
theUpdateCallback;
private readonly CancellationTokenSource
cancellationToken = new CancellationTokenSource();
public UpdateSource( IPriceProvider provider,
ITopicUpdaterUpdateCallback callback ) {
thePriceProvider = provider;
theUpdateCallback = callback;
}
/// <summary>
/// State notification that this source is now active for
the specified topic path, and is therefore in a
/// valid state to send updates on topics at or below the
registered topic path.
/// </summary>
/// <param name="topicPath">The registration path.</
param>
/// <param name="updater">An updater that may be used to
update topics at or below the registered path.</param>
public override void OnActive( string topicPath,
ITopicUpdater updater ) {
PeriodicTaskFactory.Start( () => {
updater.Update(
TopicName,
Diffusion.Content.NewContent( thePriceProvider.Price ),
theUpdateCallback );
Diffusion | 362
}, 1000, cancelToken: cancellationToken.Token );
}
/// <summary>
/// Called if the handler is closed. The handler will be
closed if the
/// session is closed after the handler has been
registered, or if the
/// handler is unregistered using <see
cref="IRegistration.Close">close</see>.
///
/// No further calls will be made for the handler.
/// </summary>
/// <param name="topicPath">the branch of the topic tree
for which the handler was registered</param>
public override void OnClose( string topicPath ) {
cancellationToken.Cancel();
}
}
public interface IPriceProvider {
/// <summary>
/// Get the current price as a decimal string.
/// </summary>
string Price {
get;
}
}
}
}
C
/**
* Copyright © 2014, 2016 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*
* This example is written in C99. Please use an appropriate C99
capable compiler
*
* @author Push Technology Limited
* @since 5.0
*/
/*
* This example creates a simple single-value topic and periodically
updates
* the data it contains.
Diffusion | 363
*/
#include
#include
#include
#include
&lt;stdio.h>
&lt;stdlib.h>
&lt;time.h>
&lt;unistd.h>
#include &lt;apr.h>
#include &lt;apr_thread_mutex.h>
#include &lt;apr_thread_cond.h>
#include
#include
#include
#include
"diffusion.h"
"args.h"
"conversation.h"
"service/svc-update.h"
int active = 0;
apr_pool_t *pool = NULL;
apr_thread_mutex_t *mutex = NULL;
apr_thread_cond_t *cond = NULL;
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'p', "principal", "Principal (username) for the connection",
ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
{'t', "topic", "Topic name to create and update",
ARG_OPTIONAL, ARG_HAS_VALUE, "time"},
{'s', "seconds", "Number of seconds to run for before
exiting", ARG_OPTIONAL, ARG_HAS_VALUE, "30"},
END_OF_ARG_OPTS
};
/*
* Handlers for add topic feature.
*/
static int
on_topic_added(SESSION_T *session, const SVC_ADD_TOPIC_RESPONSE_T
*response, void *context)
{
printf("Added topic\n");
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
static int
on_topic_add_failed(SESSION_T *session, const
SVC_ADD_TOPIC_RESPONSE_T *response, void *context)
{
printf("Failed to add topic (%d)\n", response>response_code);
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
static int
Diffusion | 364
on_topic_add_discard(SESSION_T *session, void *context)
{
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
/*
* Handlers for registration of update source feature
*/
static int
on_update_source_init(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_REGISTRATION_RESPONSE_T
*response,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("Topic source \"%s\" in init state\n", id_str);
free(id_str);
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
static int
on_update_source_registered(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_REGISTRATION_RESPONSE_T
*response,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("Registered update source \"%s\"\n", id_str);
free(id_str);
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
static int
on_update_source_deregistered(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("Deregistered update source \"%s\"\n", id_str);
free(id_str);
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;}
static int
on_update_source_active(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_REGISTRATION_RESPONSE_T
*response,
void *context)
Diffusion | 365
{
char *id_str = conversation_id_to_string(*updater_id);
printf("Topic source \"%s\" active\n", id_str);
free(id_str);
active = 1;
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
static int
on_update_source_standby(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_REGISTRATION_RESPONSE_T
*response,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("Topic source \"%s\" on standby\n", id_str);
free(id_str);
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
static int
on_update_source_closed(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_REGISTRATION_RESPONSE_T
*response,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("Topic source \"%s\" closed\n", id_str);
free(id_str);
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
/*
* Handlers for update of data.
*/
static int
on_update_success(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_RESPONSE_T *response,
void *context)
{
char *id_str = conversation_id_to_string(*updater_id);
printf("on_update_success for updater \"%s\"\n", id_str);
free(id_str);
return HANDLER_SUCCESS;
}
static int
on_update_failure(SESSION_T *session,
const CONVERSATION_ID_T *updater_id,
const SVC_UPDATE_RESPONSE_T *response,
void *context)
Diffusion | 366
{
char *id_str = conversation_id_to_string(*updater_id);
printf("on_update_failure for updater \"%s\"\n", id_str);
free(id_str);
return HANDLER_SUCCESS;
}
/*
* Program entry point.
*/
int
main(int argc, char** argv)
{
/*
* Standard command-line parsing.
*/
const HASH_T *options = parse_cmdline(argc, argv, arg_opts);
if(options == NULL || hash_get(options, "help") != NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
const char *url = hash_get(options, "url");
const char *principal = hash_get(options, "principal");
CREDENTIALS_T *credentials = NULL;
const char *password = hash_get(options, "credentials");
if(password != NULL) {
credentials = credentials_create_password(password);
}
const char *topic_name = hash_get(options, "topic");
const long seconds = atol(hash_get(options, "seconds"));
/*
* Setup for condition variable.
*/
apr_initialize();
apr_pool_create(&pool, NULL);
apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_UNNESTED,
pool);
apr_thread_cond_create(&cond, pool);
/*
* Create a session with the Diffusion server.
*/
SESSION_T *session;
DIFFUSION_ERROR_T error = { 0 };
session = session_create(url, principal, credentials, NULL,
NULL, &error);
if(session == NULL) {
fprintf(stderr, "TEST: Failed to create session\n");
fprintf(stderr, "ERR : %s\n", error.message);
return EXIT_FAILURE;
}
/*
* Create a topic holding simple string content.
*/
TOPIC_DETAILS_T *string_topic_details =
create_topic_details_single_value(M_DATA_TYPE_STRING);
const ADD_TOPIC_PARAMS_T add_topic_params = {
.topic_path = topic_name,
.details = string_topic_details,
.on_topic_added = on_topic_added,
Diffusion | 367
.on_topic_add_failed = on_topic_add_failed,
.on_discard = on_topic_add_discard,
};
apr_thread_mutex_lock(mutex);
add_topic(session, add_topic_params);
apr_thread_cond_wait(cond, mutex);
apr_thread_mutex_unlock(mutex);
topic_details_free(string_topic_details);
/*
* Define the handlers for add_update_source()
*/
const UPDATE_SOURCE_REGISTRATION_PARAMS_T update_reg_params =
{
.topic_path = topic_name,
.on_init = on_update_source_init,
.on_registered = on_update_source_registered,
.on_active = on_update_source_active,
.on_standby = on_update_source_standby,
.on_close = on_update_source_closed
};
/*
* Register an updater.
*/
apr_thread_mutex_lock(mutex);
CONVERSATION_ID_T *updater_id =
register_update_source(session, update_reg_params);
apr_thread_cond_wait(cond, mutex);
apr_thread_mutex_unlock(mutex);
/*
* Define default parameters for an update source.
*/
UPDATE_SOURCE_PARAMS_T update_source_params_base = {
.updater_id = updater_id,
.topic_path = topic_name,
.on_success = on_update_success,
.on_failure = on_update_failure
};
time_t end_time = time(NULL) + seconds;
while(time(NULL) &lt; end_time) {
if(active) {
/*
* Create an update structure containing the
current time.
*/
BUF_T *buf = buf_create();
const time_t time_now = time(NULL);
buf_write_string(buf, ctime(&time_now));
CONTENT_T *content =
content_create(CONTENT_ENCODING_NONE, buf);
UPDATE_T *upd =
update_create(UPDATE_ACTION_REFRESH,
UPDATE_TYPE_CONTENT,
Diffusion | 368
content);
UPDATE_SOURCE_PARAMS_T update_source_params =
update_source_params_base;
update_source_params.update = upd;
/*
* Update the topic.
*/
update(session, update_source_params);
content_free(content);
update_free(upd);
buf_free(buf);
}
sleep(1);
}
if(active) {
UPDATE_SOURCE_DEREGISTRATION_PARAMS_T
update_dereg_params = {
.updater_id = updater_id,
.on_deregistered =
on_update_source_deregistered
};
apr_thread_mutex_lock(mutex);
deregister_update_source(session,
update_dereg_params);
apr_thread_cond_wait(cond, mutex);
apr_thread_mutex_unlock(mutex);
}
/*
* Close session and free resources.
*/
session_close(session, NULL);
session_free(session);
conversation_id_free(updater_id);
credentials_free(credentials);
apr_thread_mutex_destroy(mutex);
apr_thread_cond_destroy(cond);
apr_pool_destroy(pool);
apr_terminate();
return EXIT_SUCCESS;
}
Change the URL from that provided in the example to the URL of the Diffusion server.
Diffusion | 369
Example: Make non-exclusive updates to a topic
The following examples use the Diffusion API to update a topic with content. Updating a topic this way
does not prevent other clients from updating the topic.
JavaScript
// 1. A session may update any existing topic. Update values must be
of the same type as the topic being updated.
// Add a topic first with a string type
session.topics.add('foo', '').then(function() {
// Update the topic
return session.topics.update('foo', 'hello');
}).then(function() {
// Update the topic again
return session.topics.update('foo', 'world');
});
// 2. If using RecordContent metadata, update values are
constructed from the metadata
// Create a new metadata instance
var meta = new diffusion.metadata.RecordContent();
meta.addRecord('record', 1, {
'field' : meta.integer()
});
// Create a builder to set values
var builder = meta.builder();
builder.add('record', {
field : 123
});
// Update the topic with the new value
session.topics.add('topic', '').then(function() {
session.topics.update('topic', builder.build());
});
Apple
@import Diffusion;
@implementation TopicUpdateExample {
PTDiffusionSession* _session;
}
-(void)startWithURL:(NSURL*)url {
PTDiffusionCredentials *const credentials =
[[PTDiffusionCredentials alloc]
initWithPassword:@"password"];
PTDiffusionSessionConfiguration *const sessionConfiguration =
[[PTDiffusionSessionConfiguration alloc]
initWithPrincipal:@"control"
credentials:credentials];
Diffusion | 370
NSLog(@"Connecting...");
[PTDiffusionSession openWithURL:url
configuration:sessionConfiguration
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
if (!session) {
NSLog(@"Failed to open session: %@", error);
return;
}
// At this point we now have a connected session.
NSLog(@"Connected.");
// Set ivar to maintain a strong reference to the session.
_session = session;
// Add topic.
[self addTopicForSession:session];
}];
}
static NSString *const _TopicPath = @"Example/Updating";
-(void)addTopicForSession:(PTDiffusionSession *const)session {
// Add a single value topic without an initial value.
[session.topicControl addWithTopicPath:_TopicPath
type:PTDiffusionTopicType_SingleValue
value:nil
completionHandler:^(NSError * _Nullable
error)
{
if (error) {
NSLog(@"Failed to add topic. Error: %@", error);
} else {
NSLog(@"Topic created.");
// Update topic after a short wait.
[self updateTopicForSession:session withValue:1];
}
}];
}
-(void)updateTopicForSession:(PTDiffusionSession *const)session
withValue:(const NSUInteger)value {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 *
NSEC_PER_SEC)),
dispatch_get_main_queue(), ^
{
// Get the non-exclusive updater.
PTDiffusionTopicUpdater *const updater =
session.topicUpdateControl.updater;
// Prepare data to update topic with.
NSString *const string =
[NSString stringWithFormat:@"Update #%lu", (unsigned
long)value];
NSData *const data = [string
dataUsingEncoding:NSUTF8StringEncoding];
PTDiffusionContent *const content =
[[PTDiffusionContent alloc] initWithData:data];
Diffusion | 371
// Update the topic.
[updater updateWithTopicPath:_TopicPath
value:content
completionHandler:^(NSError *const error)
{
if (error) {
NSLog(@"Failed to update topic. Error: %@", error);
} else {
NSLog(@"Topic updated to \"%@\"", string);
// Update topic after a short wait.
[self updateTopicForSession:session withValue:value +
1];
}
}];
});
}
@end
Java and Android
import com.pushtechnology.diffusion.client.Diffusion;
import
com.pushtechnology.diffusion.client.callbacks.TopicTreeHandler;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl.AddCal
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicUpdateControl.
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.topics.details.TopicType;
/**
* An example of using a control client to create and update a topic
in non
* exclusive mode (as opposed to acting as an exclusive update
source). In this
* mode other clients could update the same topic (on a last update
wins basis).
* <P>
* This uses the 'TopicControl' feature to create a topic and the
* 'TopicUpdateControl' feature to send updates to it.
* <P>
* To send updates to a topic, the client session requires the
'update_topic'
* permission for that branch of the topic tree.
*
* @author Push Technology Limited
* @since 5.3
*/
public final class ControlClientUpdatingSingleValueTopic {
private static final String TOPIC = "MyTopic";
private final Session session;
private final TopicControl topicControl;
private final TopicUpdateControl updateControl;
Diffusion | 372
/**
* Constructor.
*/
public ControlClientUpdatingSingleValueTopic() {
session =
Diffusion.sessions().principal("control").password("password")
.open("ws://diffusion.example.com:80");
topicControl = session.feature(TopicControl.class);
updateControl = session.feature(TopicUpdateControl.class);
// Create the topic and request that it is removed when the
session
// closes
topicControl.addTopic(
TOPIC,
TopicType.SINGLE_VALUE,
new AddCallback.Default() {
@Override
public void onTopicAdded(String topicPath) {
topicControl.removeTopicsWithSession(
TOPIC,
new TopicTreeHandler.Default());
}
});
}
/**
* Update the topic with a string value.
*
* @param value the update value
* @param callback the update callback
*/
public void update(String value, UpdateCallback callback) {
updateControl.updater().update(TOPIC, value, callback);
}
/**
* Close the session.
*/
public void close() {
session.close();
}
}
.NET
/**
* Copyright © 2014, 2016 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
Diffusion | 373
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using
using
using
using
using
PushTechnology.ClientInterface.Client.Callbacks;
PushTechnology.ClientInterface.Client.Factories;
PushTechnology.ClientInterface.Client.Features.Control.Topics;
PushTechnology.ClientInterface.Client.Session;
PushTechnology.ClientInterface.Client.Topics;
namespace Examples {
/// <summary>
/// An example of using a control client to create and update a
topic in non-exclusive mode (as opposed to acting
/// as an exclusive update source). In this mode other clients
could update the same topic (on a 'last update wins'
/// basis).
///
/// This uses the <see cref="ITopicControl"/> feature to create a
topic and the <see cref="ITopicUpdateControl"/>
/// feature to send updates to it.
///
/// To send updates to a topic, the client session requires the
'update_topic' permission for that branch of the
/// topic tree.
/// </summary>
public class ControlClientUpdatingTopic {
private const string Topic = "MyTopic";
private readonly ISession session;
private readonly ITopicControl topicControl;
private readonly ITopicUpdateControl updateControl;
/// <summary>
/// Constructor.
/// </summary>
public ControlClientUpdatingTopic() {
session =
Diffusion.Sessions.Principal( "control" ).Password( "password" )
.Open( "ws://diffusion.example.com:80" );
topicControl = session.TopicControl;
updateControl = session.TopicUpdateControl;
// Create a single-value topic.
topicControl.AddTopicFromValue( Topic,
TopicType.SINGLE_VALUE, new TopicControlAddCallbackDefault() );
}
/// <summary>
/// Update the topic with a string value.
/// </summary>
/// <param name="value">The update value.</param>
/// <param name="callback">The update callback.</param>
public void Update( string value, ITopicUpdaterUpdateCallback
callback ) {
updateControl.Updater.Update( Topic, value, callback );
}
/// <summary>
Diffusion | 374
/// Close the session.
/// </summary>
public void Close() {
// Remove our topic and close session when done.
topicControl.Remove( "?" + Topic + "//", new
RemoveCallback( session ) );
}
private class RemoveCallback :
TopicControlRemovalCallbackDefault {
private readonly ISession theSession;
public RemoveCallback( ISession session ) {
theSession = session;
}
/// <summary>
/// Notification that a call context was closed
prematurely, typically due to a timeout or the session being
/// closed. No further calls will be made for the
context.
/// </summary>
public override void OnError(ErrorReason errorReason) {
theSession.Close();
}
/// <summary>
/// Topic(s) have been removed.
/// </summary>
public override void OnTopicsRemoved() {
theSession.Close();
}
}
}
}
Change the URL from that provided in the example to the URL of the Diffusion server.
Using time series topics
A client can subscribe to a time series topic using a value stream, query to retrieve values within a
range, append new values, or apply an edit event to override the value of an earlier event.
Note: Time series topics are supported by the JavaScript, Java, Android and Apple APIs.
A time series topic stores an ordered series of events.
Each event stores a value, and has associated metadata.
An event value can be binary, double, int64, JSON, string or recordV2 value. Every event within a given
time series has values with the same data type.
Table 31: Time series event metadata
Sequence number
Timestamp
Author
Unique number within time
series, assigned when event
Timestamp for event creation.
Not guaranteed unique.
Principal that created the event.
May be ANONYMOUS if session
was not authorised.
Diffusion | 375
Sequence number
Timestamp
Author
created. Number increased by
one with each new event.
Subscribing to a time series topic
Required permissions: read_topic
A client session can subscribe to a time series topic using a value stream to receive the latest events.
On subscribing to a time series topic, a session receives a set of the most recent events. By default, the
latest event is sent. You can configure the TIME_SERIES_SUBSCRIPTION_RANGE property to determine
how many recent events a new subscriber will receive.
Setting the DONT_RETAIN_VALUE property to true will prevent an initial event being sent, unless you
have configured a subscription range.
Appending to a time series topic
Required permissions: update_topic
A session can append a value to a time series. The server will assign metadata to the event. The
timestamp will be set to the current server time. The author will be set to the authenticated principal
of the client session. The sequence number will be one higher than the previous event in the time
series.
Editing a time series topic
Required permissions: update_topic, edit_time_series_events or edit_own_time_series_events
A client session can edit a time series. This provides a new value for an existing event.
The server retains both the original event and the edit event.
Subscribers receive two sets of metadata: the metadata for the edit event, and the metadata of the
original event that was replaced.
Consider this example time series containing two events:
Sequence
Value
Type
0
A
original event
1
B
original event
Now an edit event is applied to the event with sequence number 0, changing the value to X. This
information is now stored on the server:
Sequence
Value
Type
0
A
original event
1
B
original event
2
X
edit of sequence 0
The edit event is assigned a sequence number like a normal event. Both the original event 0 and the
edit event are retained on the server.
If an original event has several edit events, the latest edit event (the one with the highest sequence
number) determines its current value. Each edit event refers to an original event, never to another edit
event.
Diffusion | 376
For example, suppose another edit event is applied to change the value of the first event from X to Y.
Now the information stored looks like this:
Sequence
Value
Type
0
A
original event
1
B
original event
2
X
edit of sequence 0
3
Y
edit of sequence 0
Querying a time series topic
There are two ways to query a time series topic and select a range of events, which differ only in how
they handle edit events.
Value range query
Required permissions: read_topic
A value range query returns part of a time series, using the latest available value for
each event.
Events are returned in order of the original sequence number. If an event has never
been edited, it is simply returned. If it has been edited, the most recent edit event is
returned instead.
For example, consider the example time series above after the two edits have been
applied. A value range query which selected the whole topic would return:
Sequence
Value
Original event sequence
3
Y
0
1
B
-
The original value of the first event is not returned. The fact that the metadata of the
original event is provided tells you that the event was edited.
A value range query is suitable for most use cases. If the client only needs the most
recent value, or your application is not using edit events at all, use a value range
query.
Edit range query
Required permissions: read_topic, query_obsolete_time_series_events
An edit range query can provide the history of values for events that have
been edited. You are only likely to use this if you are implementing auditing or
administrative features.
Because the history of edits is potentially sensitive information, an edit range query
requires the additional query_obsolete_time_series_events permission.
There are two types of edit range query:
All edits
An all edits query returns all original events selected by the query, and all subsequent
edit events that affect the originals. The results are provided in time series order.
An all edits query which selected all of the example above would return:
Diffusion | 377
Sequence
Value
Original event sequence
0
A
-
1
B
-
2
X
0
3
Y
0
Both of the edit events for the original event 0 are returned.
This sort of query provides the maximum amount of information about the edit
history of event values.
Latest edits
A latest edits query returns all original events selected by the query, plus the most
recent edit event for each original event. The results are provided in time series order.
A latest edits query which selected all of the example above would return:
Sequence
Value
Original event sequence
0
A
N/A
1
B
N/A
3
Y
0
There were two edit events applied to the original event 0, but only the most recent
edit event is returned in the query result.
This sort of query is useful if you need the original and latest value for an event, but
not any intermediate values.
Note: Time series topics only retain a range of the most recent events (configured with the
TIME_SERIES_RETAINED_RANGE property). By default, only the ten most recent events are
retained, counting both original and edit events. Range queries only return edit events if the
original event is selected. If you find that your queries do not return the results you expect, you
may need to increase the retained range.
Related concepts
Time series topics on page 67
A time series topic holds a sequence of events.
Example: Publish a time series
The following example uses the Diffusion API to create and update a time series topic.
This example creates a time series topic at foo/timeseries. It demonstrates how to append and edit
values.
/
*******************************************************************************
* Copyright (C) 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Diffusion | 378
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*******************************************************************************
package com.pushtechnology.diffusion.examples;
import static
com.pushtechnology.diffusion.datatype.DataTypes.INT64_DATATYPE_NAME;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.features.TimeSeries;
import
com.pushtechnology.diffusion.client.features.TimeSeries.EventMetadata;
import
com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import com.pushtechnology.diffusion.client.session.Session;
import
com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.client.topics.details.TopicType;
/**
* This example shows a control client creating a {@link TimeSeries}
topic.
* Values can be appended to the topic using {@link
#appendValue(Long)}, and
* the last value of the topic can be edited using {@link
#editLast(Long)}.
*
* @author Push Technology Limited
* @since 6.0
* @see ClientConsumingTimeSeriesTopics
* @see TimeSeriesQueryExample
*/
public class ControlClientUpdatingTimeSeriesTopics {
private static final String TOPIC_PATH = "foo/timeseries";
private static final Logger LOG =
LoggerFactory.getLogger(ControlClientUpdatingTimeSeriesTopics.class);
private final Session session;
private final TimeSeries timeSeries;
private final TopicControl topicControl;
/**
* Constructor.
*
* @param serverUrl server URL to connect to example "ws://
diffusion.example.com:80"
Diffusion | 379
*/
public ControlClientUpdatingTimeSeriesTopics(String serverUrl)
throws InterruptedException, ExecutionException,
TimeoutException {
session =
Diffusion.sessions().principal("control").password("password")
.open(serverUrl);
timeSeries = session.feature(TimeSeries.class);
topicControl = session.feature(TopicControl.class);
final TopicSpecification spec =
topicControl.newSpecification(TopicType.TIME_SERIES)
.withProperty(TopicSpecification.TIME_SERIES_EVENT_VALUE_TYPE,
INT64_DATATYPE_NAME);
topicControl.addTopic(TOPIC_PATH, spec)
.thenAccept(result -> LOG.info("Add topic result: {}",
result)).get(5, TimeUnit.SECONDS);
}
/**
* Appends a value to the time series topic.
*
* @param value value to append
* @return the event metadata from the successful append
*/
public EventMetadata appendValue(long value)
throws IllegalArgumentException, InterruptedException,
ExecutionException, TimeoutException {
return timeSeries.append(TOPIC_PATH, Long.class,
value).get(5, TimeUnit.SECONDS);
}
/**
* Close the session and remove the time series topic.
*/
public void close()
throws IllegalArgumentException, InterruptedException,
ExecutionException, TimeoutException {
topicControl.removeTopics("?foo//").get(5, TimeUnit.SECONDS);
session.close();
}
/**
* Edit the last value in a time series topic.
*
* @param value value to edit with
*/
public void editLast(long value) {
//Obtain the last value in the time series topic
timeSeries.rangeQuery().fromLast(1).as(Long.class).selectFrom(TOPIC_PATH)
.whenComplete((query, ex) -> {
if (ex != null) {
LOG.error("Error obtaining the range query: {}",
ex);
return;
}
//Perform the value edit
query.stream().forEach(event -> {
Diffusion | 380
timeSeries.edit(TOPIC_PATH, event.sequence(),
Long.class, value)
.whenComplete((metadata, e) -> {
if (e != null) {
LOG.error("Error editing topic: {}",
e);
return;
}
LOG.info("EventMetadata from edit: {}",
metadata);
});
});
});
}
}
Example: Subscribe to a time series
The following example uses Diffusion API to subscribe to a time series topic.
This example demonstrates subscribing to a time series topic at foo/timeseries.
/
*******************************************************************************
* Copyright (C) 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*******************************************************************************
package com.pushtechnology.diffusion.examples;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.features.TimeSeries;
import com.pushtechnology.diffusion.client.features.TimeSeries.Event;
import com.pushtechnology.diffusion.client.features.Topics;
import
com.pushtechnology.diffusion.client.features.Topics.ValueStream;
import com.pushtechnology.diffusion.client.session.Session;
/**
* This demonstrates a client session subscribing to a
* {@link TimeSeries} topic.
*
* @author Push Technology Limited
* @since 6.0
Diffusion | 381
* @see ControlClientUpdatingTimeSeriesTopics
* @see TimeSeriesQueryExample
*/
public class ClientConsumingTimeSeriesTopics {
private static final String TOPIC_PATH = "foo/timeseries";
private Session session;
/**
* Constructor.
*
* @param serverUrl for example "ws://diffusion.example.com:80"
* @param valueStream value stream to receive time series topic
events
*/
public ClientConsumingTimeSeriesTopics(String serverUrl,
ValueStream<Event<Long>> valueStream)
throws InterruptedException, ExecutionException,
TimeoutException {
session =
Diffusion.sessions().principal("client").password("password")
.open(serverUrl);
final Topics topics = session.feature(Topics.class);
topics.addTimeSeriesStream(TOPIC_PATH, Long.class,
valueStream);
topics.subscribe(TOPIC_PATH).get(5, TimeUnit.SECONDS);
}
/**
* Close the session.
*/
public void close() {
session.close();
}
}
Managing subscriptions
A client can use the SubscriptionControl feature to subscribe other client sessions to topics that they
have not requested subscription to themselves and also to unsubscribe clients from topics. It also
enables the client to register as the handler for routing topic subscriptions.
Subscribing and unsubscribing clients
Required permissions: modify_session, select_topic permission for the topics being subscribed to
A client can subscribe client sessions that it knows about to topics that those clients have not explicitly
requested. It can also unsubscribe clients from topics.
A session identifier is required to specify the client session that is to be subscribed or unsubscribed.
Use the ClientControl feature to get the identifiers for connected client sessions.
The SubscriptionControl feature uses topic selectors to specify topics for subscription and
unsubscription. Many topics can be specified in a single operation.
The client being subscribed to topics must have read_topic permission for the topics it is being
subscribed to.
Diffusion | 382
Using session properties to select clients to subscribe and unsubscribe
Required permissions: view_session, modify_session, select_topic permission for the topics being
subscribed to
When managing client subscriptions, a client can specify a filter for which client sessions it subscribes
to topics or unsubscribes from topics. The filter is a query expression on the values of session
properties.
The managing client defines a filter and sends a subscription request with this filter to the Diffusion
server. The Diffusion server evaluates the query and subscribes those currently connected client
sessions whose session properties match the filter to the topic or topics.
The filter is evaluated only once. Clients that subsequently connect or clients whose properties change
are do not cause the subscription request to be reevaluated. Even if these clients match the filter, they
are not subscribed.
Managing all subscriptions from a separate control session
You can prevent client sessions from subscribing themselves to topics and control all subscriptions
from a separate control client session that uses SubscriptionControl feature to subscribe clients to
topics.
To restrict subscription capability to control sessions, configure the following permissions:
Control session:
•
•
Grant the modify_session permission
Grant the select_topic permission
This can either be granted for the default path scope or more selectively to restrict the topic
selectors the control session can use.
Other sessions:
•
•
•
Grant read_topic to the appropriate topics.
Deny the select_topic permission by default.
Do not assign the session a role that has the select_topic permission for the default path scope.
This prevents the session from subscribing to all topics using a wildcard selector.
Optionally, grant the select_topic permission to specific branches of the topic tree to which the
session can subscribe freely.
Acting as a routing subscription handler
Required permissions: view_session, modify_session, register_handler
Routing topics can be created with a server-side handler that assigns clients to real topics. However,
you can omit the server-side handler such that subscriptions to routing topics are directed at a client
acting as a routing subscription handler.
A client can register a routing subscription handler for a branch of the topic tree. Any subscription
requests to routing topics in that branch that do not have server-side handlers are passed to the client
for action.
On receipt of a routing subscription request the client can respond with a route request that specifies
the path of the actual topic that the routing topic maps to for the requesting client. This subscription
fails if the target topic does not already exist or if the requesting client does not have read_topic
permission for the routing topic or target topic.
The client can complete other actions before calling back to route. For example, it could use the
TopicControl feature to create the topic that the client is to map to.
Diffusion | 383
Alternatively, the client can defer the routing subscription request in which case the requesting client
remains unsubscribed. This is similar to denying it from an authorization point of view.
The client must reply with a route or defer for all routing requests.
Related concepts
Topic selectors on page 50
A topic selector identifies one or more topics. You can create a topic selector object from a pattern
expression.
Session properties on page 275
A client session has a number of properties associated with it. Properties are key-value pairs. Both the
key and the value are case sensitive.
Session filtering on page 276
Session filters enable you to query the set of connected client sessions on the Diffusion server based
on their session properties.
Example: Subscribe other clients to topics
The following examples use the SubscriptionControl feature in the Diffusion API to subscribe other
client sessions to topics.
Java and Android
package com.pushtechnology.diffusion.examples;
import com.pushtechnology.diffusion.client.Diffusion;
import
com.pushtechnology.diffusion.client.features.control.topics.SubscriptionControl
import
com.pushtechnology.diffusion.client.features.control.topics.SubscriptionControl
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionId;
/**
* This demonstrates using a client to subscribe and unsubscribe
other clients
* to topics.
* <P>
* This uses the 'SubscriptionControl' feature.
*
* @author Push Technology Limited
* @since 5.0
*/
public class ControlClientSubscriptionControl {
private final Session session;
private final SubscriptionControl subscriptionControl;
/**
* Constructor.
*/
public ControlClientSubscriptionControl() {
session =
Diffusion.sessions().principal("control").password("password")
Diffusion | 384
.open("ws://diffusion.example.com:80");
subscriptionControl =
session.feature(SubscriptionControl.class);
}
/**
* Subscribe a client to topics.
*
* @param sessionId client to subscribe
* @param topicSelector topic selector expression
* @param callback for subscription result
*/
public void subscribe(
SessionId sessionId,
String topicSelector,
SubscriptionCallback callback) {
// To subscribe a client to a topic, this client session
// must have the 'modify_session' permission.
subscriptionControl.subscribe(
sessionId,
topicSelector,
callback);
}
/**
* Unsubscribe a client from topics.
*
* @param sessionId client to unsubscribe
* @param topicSelector topic selector expression
* @param callback for unsubscription result
*/
public void unsubscribe(
SessionId sessionId,
String topicSelector,
SubscriptionCallback callback) {
// To unsubscribe a client from a topic, this client session
// must have the 'modify_session' permission.
subscriptionControl.unsubscribe(
sessionId,
topicSelector,
callback);
}
/**
* Close the session.
*/
public void close() {
session.close();
}
}
.NET
/**
* Copyright © 2014, 2016 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Diffusion | 385
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using PushTechnology.ClientInterface.Client.Factories;
using PushTechnology.ClientInterface.Client.Features.Control.Topics;
using PushTechnology.ClientInterface.Client.Session;
namespace Examples {
/// <summary>
/// This demonstrates using a client to subscribe and unsubscribe
other clients to topics.
///
/// This uses the <see cref="ISubscriptionControl"/> feature.
/// </summary>
public class ControlClientSubscriptionControl {
private readonly ISession session;
private readonly ISubscriptionControl subscriptionControl;
/// <summary>
/// Constructor.
/// </summary>
public ControlClientSubscriptionControl() {
session =
Diffusion.Sessions.Principal( "control" ).Password( "password" )
.Open( "ws://diffusion.example.com:80" );
subscriptionControl = session.SubscriptionControl;
}
/// <summary>
/// Subscribe a client to topics.
/// </summary>
/// <param name="sessionId">The session id of the client to
subscribe.</param>
/// <param name="topicSelector">The topic selector
expression.</param>
/// <param name="callback">The callback for the subscription
result.</param>
public void Subscribe( SessionId sessionId, string
topicSelector, ISubscriptionCallback callback ) {
// To subscribe a client to a topic, this client session
must have the MODIFY_SESSION permission.
subscriptionControl.Subscribe( sessionId, topicSelector,
callback );
}
/// <summary>
/// Unsubscribe a client from topics.
/// </summary>
/// <param name="sessionId">The session id of the client to
unsubscribe.</param>
/// <param name="topicSelector">The topic selector
expression.</param>
Diffusion | 386
/// <param name="callback">The callback for the
unsubscription result.</param>
public void Unsubscribe( SessionId sessionId, string
topicSelector, ISubscriptionCallback callback ) {
subscriptionControl.Unsubscribe( sessionId,
topicSelector, callback );
}
/// <summary>
/// Close the session.
/// </summary>
public void Close() {
session.Close();
}
}
}
C
/*
* This example waits to be notified of a client connection, and then
* subscribes that client to a named topic.
*/
#include <stdio.h>
#include <unistd.h>
#include <apr.h>
#include <apr_thread_mutex.h>
#include <apr_thread_cond.h>
#include "diffusion.h"
#include "args.h"
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'p', "principal", "Principal (username) for the connection",
ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
{'t', "topic_selector", "Topic selector to subscribe/
unsubscribe clients from", ARG_OPTIONAL, ARG_HAS_VALUE, ">foo"},
END_OF_ARG_OPTS
};
HASH_T *options = NULL;
/*
* Callback invoked when a client has been successfully subscribed to
* a topic.
*/
static int
on_subscription_complete(SESSION_T *session, void *context)
{
printf("Subscription complete\n");
return HANDLER_SUCCESS;
}
/*
* Callback invoked when a client session has been opened.
Diffusion | 387
*/
static int
on_session_open(SESSION_T *session, const SESSION_PROPERTIES_EVENT_T
*request, void *context)
{
if(session_id_cmp(*session->id, request->session_id) == 0) {
// It's our own session, ignore.
return HANDLER_SUCCESS;
}
char *topic_selector = hash_get(options, "topic_selector");
char *sid_str = session_id_to_string(&request->session_id);
printf("Subscribing session %s to topic selector %s\n",
sid_str, topic_selector);
free(sid_str);
/*
* Subscribe the client session to the topic.
*/
SUBSCRIPTION_CONTROL_PARAMS_T subscribe_params = {
.session_id = request->session_id,
.topic_selector = topic_selector,
.on_complete = on_subscription_complete
};
subscribe_client(session, subscribe_params);
return HANDLER_SUCCESS;
}
int
main(int argc, char **argv)
{
/*
* Standard command-line parsing.
*/
options = parse_cmdline(argc, argv, arg_opts);
if(options == NULL || hash_get(options, "help") != NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
const char *url = hash_get(options, "url");
const char *principal = hash_get(options, "principal");
CREDENTIALS_T *credentials = NULL;
const char *password = hash_get(options, "credentials");
if(password != NULL) {
credentials = credentials_create_password(password);
}
/*
* Create a session with Diffusion.
*/
DIFFUSION_ERROR_T error = { 0 };
SESSION_T *session = session_create(url, principal,
credentials, NULL, NULL, &error);
if(session == NULL) {
fprintf(stderr, "Failed to create session: %s\n",
error.message);
return EXIT_FAILURE;
}
/*
Diffusion | 388
* Register a session properties listener, so we are notified
* of new client connections.
* In the callback, we will subscribe the client to topics
* according to the topic_selector argument.
*/
SET_T *required_properties = set_new_string(1);
set_add(required_properties,
PROPERTIES_SELECTOR_ALL_FIXED_PROPERTIES);
SESSION_PROPERTIES_REGISTRATION_PARAMS_T params = {
.on_session_open = on_session_open,
.required_properties = required_properties
};
session_properties_listener_register(session, params);
set_free(required_properties);
/*
* Pretend to do some work.
*/
sleep(10);
/*
* Close session and tidy up.
*/
session_close(session, NULL);
session_free(session);
return EXIT_SUCCESS;
}
Change the URL from that provided in the example to the URL of the Diffusion server.
Example: Receive notifications when a client subscribes to a routing
topic
The following examples use the SubscriptionControl feature in the Diffusion API to listen for
notifications of when a client subscribes to a routing topic.
Java and Android
package com.pushtechnology.diffusion.examples;
import com.pushtechnology.diffusion.client.Diffusion;
import
com.pushtechnology.diffusion.client.features.control.topics.SubscriptionControl
import
com.pushtechnology.diffusion.client.features.control.topics.SubscriptionControl
import
com.pushtechnology.diffusion.client.features.control.topics.SubscriptionControl
import com.pushtechnology.diffusion.client.session.Session;
/**
* This demonstrates using a control client to be notified of
subscription
* requests to routing topics.
* <P>
* This uses the 'SubscriptionControl' feature.
*
* @author Push Technology Limited
* @since 5.0
*/
Diffusion | 389
public class ControlClientSubscriptionControlRouting {
private final Session session;
/**
* Constructor.
*
* @param routingCallback for routing subscription requests
*/
public ControlClientSubscriptionControlRouting(
final SubscriptionCallback routingCallback) {
session =
Diffusion.sessions().principal("control").password("password")
.open("ws://diffusion.example.com:80");
final SubscriptionControl subscriptionControl =
session.feature(SubscriptionControl.class);
// Sets up a handler so that all subscriptions to topic a/b
are routed
// to routing/target/topic
// To do this, the client session requires the
'view_session',
// 'modify_session', and 'register_handler' permissions.
subscriptionControl.addRoutingSubscriptionHandler(
"a/b",
new
SubscriptionControl.RoutingSubscriptionRequest.Handler
.Default() {
@Override
public void onSubscriptionRequest(
final RoutingSubscriptionRequest request) {
request.route(
"routing/target/topic",
routingCallback);
}
});
}
/**
* Close the session.
*/
public void close() {
session.close();
}
}
.NET
*
*
*
*
*
*
*
/**
Copyright © 2014, 2016 Push Technology Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Diffusion | 390
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using PushTechnology.ClientInterface.Client.Factories;
using PushTechnology.ClientInterface.Client.Features.Control.Topics;
using PushTechnology.ClientInterface.Client.Session;
namespace Examples {
/// <summary>
/// This demonstrates using a control client to be notified of
subscription requests to routing topics.
///
/// This uses the <see cref="ISubscriptionControl"/> feature.
/// </summary>
public class ControlClientSubscriptionControlRouting {
private readonly ISession session;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="routingCallback">The callback for routing
subscription requests.</param>
public
ControlClientSubscriptionControlRouting( ISubscriptionCallback
routingCallback ) {
session =
Diffusion.Sessions.Principal( "control" ).Password( "password" )
.Open( "ws://diffusion.example.com:80" );
var subscriptionControl = session.SubscriptionControl;
// Sets up a handler so that all subscriptions to topic
'a/b' are routed to the routing/target topic.
// To do this, the client session requires the
VIEW_SESSION, MODIFY_SESSION and REGISTER_HANDLER
// permissions.
subscriptionControl.AddRoutingSubscriptionHandler( "a/b",
new SubscriptionHandler( routingCallback ) );
}
/// <summary>
/// Close the session.
/// </summary>
public void Close() {
session.Close();
}
private class SubscriptionHandler :
RoutingSubscriptionRequestHandlerDefault {
private readonly ISubscriptionCallback
theRoutingCallback;
public SubscriptionHandler( ISubscriptionCallback
callback ) {
theRoutingCallback = callback;
}
Diffusion | 391
/// <summary>
/// A request to subscribe to a specific routing topic.
/// </summary>
/// <param name="request"></param>
public override void
OnSubscriptionRequest( IRoutingSubscriptionRequest request ) {
request.Route( "routing/target/topic",
theRoutingCallback );
}
}
}
}
Change the URL from that provided in the example to the URL of the Diffusion server.
Using request-response messaging
You can send request messages directly to a client session, a set of client sessions, or a message path.
The recipient of a message can respond to the request.
Typed requests and responses
Each request and response messages has a data type. The data type can be one of the following types:
•
•
•
•
•
JSON
Binary
String
Int64
Double
The data type of the response is not required to be the same as the data type of the request it responds
to.
When you send a request, you specify the data type of the request message and the data type of the
response message it expects. When you register a handler or a stream to receive requests and respond
to them, you specify the data type of the requests it receives and the data type of the responses it
sends.
Data types are organized in a hierarchy of compatibility.
Diffusion | 392
Figure 27: Data type hierarchy
A request or response with a data type at a lower (more specific) level of the hierarchy can be received
by a stream, handler, or requester that is expecting a message with a data type at a higher (more
general) level of the hierarchy. For example, a request message with a string data type, can be received
by a stream or handler that specifies string, JSON, or bytes as the request type.
Message path
The message path is made up of path segments separated by the slash character (/). Each path
segment can be made up of one or more Unicode characters. The slash character (/) is not permitted in
any path segment. The restrictions for message paths are the same as those for paths at which topics
can be bound. For more information, see Topic naming on page 49.
However, messaging is entirely separate from streaming data through topics:
•
•
•
•
Message paths are unrelated topic paths. Sending a message does not change the state of any
topic and does not publish the message to topic subscribers.
An application can bind a topic to a topic path and use the same path as a message path. This is a
useful convention where the messages are related to the topic in some way. The messages sent to
the message path do not interact with the topic in any way.
If a topic is bound to the path used by messaging, the data type of the topic does not affect the
data type of any messages sent using the message path.
The security permissions required to use a path for messaging are separate from those required to
use a topic bound to that path to stream data.
One-way messaging
Diffusion also provides a capability to send one-way messages to a client session, a set of client
sessions, or a message path. These messages cannot be responded to directly.
In one-way messaging, the message data is not typed. Applications are responsible for serializing
messages to and from a binary format.
Diffusion | 393
Messages sent using one-way messaging can include additional options, such as headers and a
message priority. These additional options are provided to allow compatibility with messaging to
publishers.
One-way messaging provides the following guarantees:
•
•
If sending to a message path completes successfully, the message was definitely passed to a
publisher or a message handler registered by a client session.
If sending to a session completes successfully, the message was definitely passed to a message
stream registered by the session.
When sending to a filter completes successfully and returns the number of sessions that match the
filter, one-way messaging cannot guarantee that the message has been delivered to those sessions.
Sending request messages to a message path
A client session can send a request message containing typed data to a message path. One or more
client sessions can register to handle messages sent to that message path. The handling client session
can then send a response message containing typed data. The response message is sent to the
requesting client session directly, through the same message path.
When a request message is sent to a message path and a client session that handlers that message
path responds, the following events occur:
1.
2.
3.
4.
A client session sends a request message to a message path.
The control client session receives the request message through a request handler.
The session client session uses sends a response to the request message.
The client session receives the response.
Both the request message and the response message contain typed values. The messages can contain
data of one of the following types: JSON, binary, string, 64-bit integer, or double. The response
message is not required to be the same data type as the request it responds to.
Sending to a message path
Required permissions: send_to_handler permission for the specified message path
Send the request message specifying the following information:
Diffusion | 394
•
•
•
•
The message path to send the request to and receive the response through
The request message
The datatype of the request message
The datatype of the response message
JavaScript
// Example with json topic type.
var jsonType = diffusion.datatypes.json();
var requestJson = jsonType.from({ "foo": "bar"});
var responseJson = jsonType.from({ "ying": "yang"});
var handler = {
onRequest: function(request, context, responder) {
responder.respond(responseJson, jsonType);
},
onError: function() {},
onClose: function() {}
};
control.messages.addRequestHandler('test/topic',
handler).then(function(reg) {
session.messages.sendRequest('test/topic', requestJson,
jsonType).then(function(response) {
console.log(response.get());
}, function(error) {});
});
Apple
[session.messaging sendRequest:[PTDiffusionPrimitive
requestWithLongLong:42]
toPath:message_path
int64NumberCompletionHandler:^(NSNumber *response, NSError *error)
{
if (error) {
NSLog(@"Failed to send to %@. Error: %@", message_path,
error);
} else {
NSLog(@"Received response: %@", response);
}
}];
Java and Android
//Establish client sesssion
final Session session =
Diffusion.sessions().principal("client").password("password").open("ws://
localhost:8080");
//Obtain the Messaging feature
final Messaging messaging = session.feature(Messaging.class);
//Create a JSON object to send as a request
final JSON request =
Diffusion.dataTypes().json().fromJsonString("\"hello\"");
//Send the request to a message path "foo" and wait for (at most)
5 seconds until the response is received.
final JSON response = messaging.sendRequest("foo", request,
JSON.class, JSON.class).get(5, TimeUnit.SECONDS);
Diffusion | 395
Responding to request messages sent to a message path
Required permissions: send_to_session permission for the specified message path, register_handler
permission, and view_session permission to register to receive session property values with the
request message
Define a request handler to receive and respond to request messages that have a specific data type.
JavaScript
var jsonType = diffusion.datatypes.json();
var requestJson = jsonType.from({ "foo": "bar"});
var responseJson = jsonType.from({ "ying": "yang"});
// Define a request handler for json topic type
var handler = {
onRequest: function(request, context, responder) {
responder.respond(responseJson, jsonType);
},
onError: function() {},
onClose: function() {}
};
Apple
@interface NumberRequestDelegate :
NSObject<PTDiffusionNumberRequestDelegate>
@end
@implementation NumberRequestDelegate
-(void)diffusionTopicTreeRegistration:
(PTDiffusionTopicTreeRegistration *)registration
didReceiveRequestWithNumber:(nullable NSNumber *)number
context:(PTDiffusionRequestContext
*)context
responder:(PTDiffusionResponder
*)responder;
{
// Do something when a request is received.
}
- (void)diffusionTopicTreeRegistration:(nonnull
PTDiffusionTopicTreeRegistration *)registration
didFailWithError:(nonnull NSError *)error
{
// Do something if the registration fails.
}
- (void)diffusionTopicTreeRegistrationDidClose:(nonnull
PTDiffusionTopicTreeRegistration *)registration
{
// Do something if the registration closes.
}
Java and Android
private final class JSONRequestHandler implements
MessagingControl.RequestHandler<JSON, JSON> {
@Override
public void onClose() {
....
Diffusion | 396
}
@Override
public void onError(ErrorReason errorReason) {
....
}
@Override
public void onRequest(JSON request, RequestContext context,
Responder<JSON> responder) {
....
responder.respond(response);
}
}
Register the request handler against a message path. You can only register one request handler
against each message path.
JavaScript
var handler = {
onRequest: function(request, context, responder) {},
onError: function() {},
onClose: function() {}
};
session.messages.addRequestHandler('topic', handler);
Apple
// Ensure to maintain a strong reference to your delegate as it
// is referenced weakly by the Diffusion client library.
NumberRequestDelegate *const delegate = [NumberRequestDelegate new];
PTDiffusionRequestHandler *const handler = [PTDiffusionPrimitive
int64RequestHandlerWithDelegate:delegate];
[session.messagingControl addRequestHandler:handler
forPath:path
completionHandler:^(PTDiffusionTopicTreeRegistration *registration,
NSError *error)
{
// Check error is `nil`, indicating success.
}];
Java and Android
messagingControl.addRequestHandler(messagePath, JSON.class,
JSON.class, new JSONRequestHandler());
One-way messaging to a path
Diffusion also provides a capability to send one-way messages to a message path. This message is not
typed. The handling session cannot respond directly to this message.
Send a one-way message specifying the following information:
•
•
•
The message path to send the message through
The request message
Any additional options, such as headers or a message priority
Diffusion | 397
JavaScript
session.messages.send('message_path', content);
Apple
PTDiffusionContent *const content = [[PTDiffusionContent alloc]
initWithData:data];
[session.messaging sendWithPath:message_path
value:content
options:[PTDiffusionSendOptions new]
completionHandler:^(NSError *const error)
{
if (error) {
NSLog(@"Failed to send. Error: %@", error);
} else {
NSLog(@"Sent");
}
}];
Java and Android
messaging = session.feature(Messaging.class);
messaging.send(message_path, message_content,
messaging.sendOptionsBuilder().headers(headers).build());
.NET
var messaging = session.Messaging;
messaging.Send( message_path, message_content,
messaging.CreateSendOptionsBuilder().SetHeaders( headers ).Build() );
C
SEND_MSG_PARAMS_T params = {
.topic_path = message_path,
.payload = *content,
.headers = headers,
.priority = CLIENT_SEND_PRIORITY_NORMAL,
.on_send = on_send,
.context = context
};
/*
* Send the message and wait for the callback to acknowledge
* delivery.
*/
send_msg(session, params);
To receive a message sent to a message path, a client session must define a message handler:
JavaScript
// Create a message handler
var handler = {
onMessage : function(message) {
console.log(message); // Log the received message
},
onActive : function(unregister) {
Diffusion | 398
},
onClose : function() {
}
};
Apple
@interface MessageDelegate : NSObject <PTDiffusionMessageDelegate>
@end
@implementation MessageDelegate
-(void)diffusionTopicTreeRegistration:
(PTDiffusionTopicTreeRegistration *)registration
hadMessageFromSessionId:(PTDiffusionSessionId
*)sessionId
path:(NSString *)path
content:(PTDiffusionContent *)content
context:(PTDiffusionReceiveContext
*)context
{
// Do something when a message is received.
}
-(void)diffusionTopicTreeRegistrationDidClose:
(PTDiffusionTopicTreeRegistration *)registration
{
// Do something if the registration closes.
}
-(void)diffusionTopicTreeRegistration:
(PTDiffusionTopicTreeRegistration *)registration didFailWithError:
(NSError *)error
{
// Do something if the registration fails.
}
Java and Android
private class MyHandler extends MessageHandler.Default {
@Override
public void onMessage(
SessionId sessionId,
String topicPath,
Content content,
ReceiveContext context) {
// Do something when a message is received.
}
}
.NET
private class MyHandler : MessageReceiverDefault {
public override void OnMessage(
SessionId sessionId,
string topicPath,
IContent content,
IReceiveContext context) {
// Take action when a message is received.
}
Diffusion | 399
}
C
/*
* Function called on receipt of a message from a client.
*
* We print the following information:
*
1. The message path on which the message was received.
*
2. A hexdump of the message content.
*
3. The headers associated with the message.
*
4. The session properties that were requested when the handler
was
*
registered.
*
5. The user context, as a string.
*/
int
on_msg(SESSION_T *session, const SVC_SEND_RECEIVER_CLIENT_REQUEST_T
*request, void *context)
{
printf("Received message on path %s\n", request->topic_path);
hexdump_buf(request->content->data);
printf("Headers:\n");
if(request->send_options.headers->first == NULL) {
printf(" No headers\n");
}
else {
for(LIST_NODE_T *node = request>send_options.headers->first;
node != NULL;
node = node->next) {
printf(" Header: %s\n", (char *)node->data);
}
}
printf("Session properties:\n");
char **keys = hash_keys(request->session_properties);
if(keys == NULL || *keys == NULL) {
printf(" No properties\n");
}
else {
for(char **k = keys; *k != NULL; k++) {
char *v = hash_get(request>session_properties, *k);
printf(" %s=%s\n", *k, v);
}
}
free(keys);
if(context != NULL) {
printf("Context: %s\n", (char *)context);
}
return HANDLER_SUCCESS;
}
Register the message handler against a message path and its descendants:
JavaScript
// Register the handler
Diffusion | 400
session.messages.addHandler('message_path', handler).then(function()
{
// Registration happened successfully
}, function(error) {
// Registration failed
});
Apple
// Ensure to maintain a strong reference to your message delegate as
it is
// referenced weakly by the Diffusion client library.
MessageDelegate *const messageDelegate = [MessageDelegate new];
// Use the Messaging Control feature to send a registration request
to the
// server.
[session.messagingControl addMessageHandlerForPath:message_path
withDelegate:messageDelegate
completionHandler:
^(PTDiffusionTopicTreeRegistration* registration, NSError* error)
{
// Check error is `nil`, indicating success.
// Optionally store a strong reference to registration in order
to allow the
// handler to be unregistered at the server.
}];
Java and Android
session.feature(MessagingControl.class).addMessageHandler(message_path,
new MyHandler());
.NET
session.MessagingControl.AddMessageHandler( message_path, new
MyHandler() );
C
/*
* Register a message handler, and for each message ask for
* the $Principal property to be provided.
*/
LIST_T *requested_properties = list_create();
list_append_last(requested_properties, "$Principal");
MSG_RECEIVER_REGISTRATION_PARAMS_T params = {
.on_registered = on_registered,
.topic_path = topic,
.on_message = on_msg,
.session_properties = requested_properties
};
list_free(requested_properties, free);
register_msg_handler(session, params);
To respond to this message, the receiving client session sends a separate message to the session that
sent the message. For more information, see One-way messaging to a session on page 405.
Diffusion | 401
Sending request messages to a session
A client session can send a request message containing typed data directly to a client session. The
receiving client session can then send a response message containing typed data. The request and
response messages are addressed through the same message path.
When a request message is sent to a specific client session and that session responds, the following
events occur:
1. A control client session sends a request message to a client session, specifying the message path to
send the message through and the session ID of the client session to send the request message to.
2. The client session receives the request message through a request stream.
3. The client session uses a responder to send a response to the request message.
4. The control client session receives the response.
Both the request message and the response message contain typed values. The messages can contain
data of one of the following types: JSON, binary, string, 64-bit integer, or double. The response
message is not required to be the same data type as the request it responds to.
Sending a request to a session
Required permissions: send_to_session permission for the specified message path and
register_handler permission
Usually, it is a control client session in your organization's backend that sends messages directly to
other sessions.
Send the request message specifying the following information:
•
•
•
•
•
The session ID of the client session to send the request to
The message path to send the request and receive the response through
The request message
The datatype of the request message
The datatype of the response message
Diffusion | 402
JavaScript
control.messages.sendRequest('foo', 'Hello client', session_id,
diffusion.datatypes.json(), diffusion.datatypes.json())
Apple
[session.messagingControl sendRequest:[PTDiffusionPrimitive
requestWithLongLong:42]
toSessionId:sessionId
path:message_path
int64NumberCompletionHandler:^(NSNumber *response, NSError*
error)
{
if (error) {
NSLog(@"Failed to send to %@. Error: %@", message_path,
error);
} else {
NSLog(@"Received response: %@", response);
}
}];
Java and Android
//Establish client session and control session
final Session control =
Diffusion.sessions().principal("control").password("password").open("ws://
localhost:8080");
final Session client =
Diffusion.sessions().principal("client").password("password").open("ws://
localhost:8080");
//Obtain the Messaging and MessagingControl features
final MessagingControl messagingControl =
control.feature("MessagingControl.class");
final Messaging messaging = client.feature(Messaging.class);
//Create a JSON object to send as a request
final JSON request =
Diffusion.dataTypes().json().fromJsonString("\"hello\"");
//Create a local request stream for the client to receive direct
requests from the control session
messaging.setRequestStream("foo", JSON.class, JSON.class,
requestStream);
//Send the request to a message path "foo" and wait for (at most)
5 seconds until the response is received.
final JSON response =
messagingControl.sendRequest(client.getSessionId(), "foo", request,
JSON.class, JSON.class).get(5, TimeUnit.SECONDS);
Responding to messages sent to a session
Required permissions: send_to_message_handler for the specified message path
Define a request stream to receive and respond to request messages that have a specific data type.
JavaScript
var handler = {
Diffusion | 403
onRequest : function(request, context, responder) {
....
responder.respond(response);
},
onError : function(error) {},
onClose : function() {}
}
Apple
@interface NumberRequestStreamDelegate :
NSObject<PTDiffusionNumberRequestStreamDelegate>
@end
@implementation NumberRequestStreamDelegate
- (void)
diffusionStream:(nonnull PTDiffusionStream *)stream
didReceiveRequestWithNumber:(nullable NSNumber *)number
responder:(nonnull PTDiffusionResponder *)responder
{
// Do something when a request is received.
}
- (void)diffusionStream:(nonnull PTDiffusionStream *)stream
didFailWithError:(nonnull NSError *)error
{
// Do something if the stream fails.
}
- (void)diffusionDidCloseStream:(nonnull PTDiffusionStream *)stream
{
// Do something if the stream closes.
}
Java and Android
private final class JSONRequestStream implements
Messaging.RequestStream<JSON, JSON> {
@Override
public void onClose() {
....
}
@Override
public void onError(ErrorReason errorReason) {
....
}
@Override
public void onRequest(String path, JSON request, Responder<JSON>
responder) {
....
}
}
Add the request stream against a message path. You can only add one request stream for each
message path.
Diffusion | 404
JavaScript
control.messages.setRequestStream("foo", diffusion.datatypes.json(),
diffusion.datatypes.json(), request_stream);
Apple
// Ensure to maintain a strong reference to your request stream as it
// is referenced weakly by the Diffusion client library.
NumberRequestStreamDelegate *delegate = [NumberRequestStreamDelegate
new];
PTDiffusionRequestStream *requestStream = [PTDiffusionPrimitive
int64RequestStreamWithDelegate:delegate];
[session.messaging setRequestStream:requestStream
forPath:message_path];
Java and Android
messaging.setRequestStream("foo", JSON.class, JSON.class,
requestStream);
One-way messaging to a session
Diffusion also provides a legacy capability to send one-way messages to a session. This message is not
typed. The receiving session cannot respond directly to this message.
Send a one-way message specifying the following information:
•
•
•
•
The session ID of the client session to send the message to
The message path to send the message through
The message content
Any additional options, such as headers or a message priority
JavaScript
session.messages.send('message_path', content, session_id);
Apple
[session.messagingControl sendToSessionId:sessionId
path:message_path
message:[[PTDiffusionBytes alloc]
initWithData:data]
completionHandler:^(NSError* error)
{
if (error) {
NSLog(@"Failed to send to %@. Error: %@", message_path,
error);
} else {
NSLog(@"Sent");
}
}];
Java and Android
session.feature(MessagingControl.class).send(session_id, message_path, content,
Diffusion | 405
.NET
session.MessagingControl.Send( session_id, message_path, content,
send_callback );
C
SEND_MSG_TO_SESSION_PARAMS_T params = {
.topic_path = message_path,
.session_id = *session_id,
.content = *content,
.options.headers = headers,
.options.priority = CLIENT_SEND_PRIORITY_NORMAL,
.on_send = on_send,
.context = context
};
/*
* Send the message and wait for the callback to acknowledge
* delivery.
*/
send_msg_to_session(session, params);
To receive a message sent directly to a client session, that client session must add a message stream to
receive messages sent through that message path:
JavaScript
// Create with a default listener function
session.messages.listen('message_path', function(message) {
// Do something with the message
});
Apple
@interface MessageStreamDelegate : NSObject
<PTDiffusionMessageStreamDelegate>
@end
@implementation MessageStreamDelegate
-(void)
diffusionStream:(PTDiffusionStream *)stream
didReceiveMessageOnTopicPath:(NSString *)path
content:(PTDiffusionContent *)content
context:(PTDiffusionReceiveContext *)context
{
// Do something when a message is received.
}
-(void)diffusionDidCloseStream:(PTDiffusionStream *)stream
{
// Do something if the stream closes.
}
-(void)diffusionStream:(PTDiffusionStream *)stream didFailWithError:
(NSError *)error
{
// Do something if the stream fails.
}
// ... in later code
Diffusion | 406
// Ensure to maintain a strong reference to your message stream
delegate as it
// is referenced weakly by the Diffusion client library.
MessageStreamDelegate *const delegate = [MessageStreamDelegate new];
// Create a locally evaluated topic selector specifying the messaging
paths that
// should be captured by the stream.
PTDiffusionTopicSelector *const topicSelector =
[PTDiffusionTopicSelector
topicSelectorWithExpression:message_path];
// Use the Messaging feature to add a local stream using your
delegate against
// the topic selector.
[session.messaging addMessageStreamWithSelector:topicSelector
delegate:delegate];
Java and Android
session.feature(Messaging.class).addMessageStream(message_path, stream);
.NET
session.Messaging.AddMessageStream( message_path, stream );
C
/*
* Register a listener for messages on the given path.
*/
MSG_LISTENER_REGISTRATION_PARAMS_T listener_params = {
.topic_path = message_path,
.listener = on_stream_message,
.context = context
};
register_msg_listener(session, listener_params);
You can also add a fallback message stream to receive messages sent through any message path that
does not have a stream add against it:
Apple
// Ensure to maintain a strong reference to your message stream
delegate as it
// is referenced weakly by the Diffusion client library.
MessageStreamDelegate *const delegate = [MessageStreamDelegate new];
// Use the Messaging feature to add a local fallback stream using
your delegate.
[session.messaging addFallbackMessageStreamWithDelegate:delegate];
Java and Android
messaging.addFallbackMessageStream(message_stream);
.NET
session.Messaging.AddFallbackMessageStream( stream );
Diffusion | 407
C
/*
* Register a listener for any other messages.
* (.topic_path is NULL).
*/
MSG_LISTENER_REGISTRATION_PARAMS_T global_listener_params = {
.listener = on_stream_message,
.context = context
};
register_msg_listener(session, global_listener_params);
To respond to this message, the receiving client session sends a separate message to the message
path through which the received message was sent. For more information, see One-way messaging
to a path on page 397. However, if multiple client sessions have added messages handlers on this
message path, the one-way message sent in response is not guaranteed to be received by the client
session that sent the original one-way message.
Sending request messages to a session filter
A client session can send a request message containing typed data directly to each client session in
the set of connected client sessions that match a specified session properties filter. The receiving
client sessions can then send a response message containing typed data. The request and response
messages are addressed through the same message path.
Note: Sending messages to a set of client sessions defined by a filter is not intended for high
throughput of data. If you have a lot of data to send or want to send data to a lot of client
sessions, use the pub-sub capabilities of Diffusion. Subscribe the set of client sessions to a
topic and publish the data as updates through that topic.
For more information about session properties and how to filter connected client sessions using their
properties, see Session properties on page 275 and Session filtering on page 276.
When a request message is sent to a set of client sessions and those sessions respond, the following
events occur:
1. A control client session sends a request message, specifying the filter that selects the client
sessions to receive the request and specifying the message path to send the message through.
Diffusion | 408
2. The Diffusion server evaluates the query and sends the message on to connected client sessions
whose session properties match the filter
3. The client sessions in the filtered set each receive the request message through a request stream.
4. Each client session uses a responder to send a response to the request message.
5. The control client session receives responses from each of the clients sessions specified by the
filter.
The request messages and the response messages contain typed values. The messages can contain
data of one of the following types: JSON, binary, string, 64-bit integer, or double. The response
messages are not required to be the same data type as the request or as the response messages from
other client sessions.
Sending a request message to a filter
Required permissions: send_to_session permission for the specified message path and
register_handler permission
Usually, it is a control client session in your organization's backend that sends messages to a filter. For
more information about defining a session filter, see Session filtering on page 276.
Send the request message specifying the following information:
•
•
•
•
•
The query to use to filter which client sessions to send the requests to
The message path to send the request and receive the responses through
The request message
The datatype of the request message
The datatype of the response message
JavaScript
var handler = {
onResponse : function(sessionID, response) {},
onResponseError : function(sessionID, error) {},
onError : function(error) {}
}
control.messages.sendRequestToFilter(filter, 'foo', 'Hello clients',
handler, diffusion.datatypes.json(), diffusion.datatypes.json());
Java and Android
//Establish control sesssion
final Session control =
Diffusion.sessions().principal("control").password("password").open("ws://
localhost:8080");
//Obtain the MessagingControl feature
final MessagingControl messagingControl =
control.feature(MessagingControl.class);
//Create a JSON object to send as a request
final JSON request =
Diffusion.dataTypes().json().fromJsonString("\"hello\"");
//Send the request to a message path "foo", to all sessions which
do not have a 'control' principal and wait for (at most) 5 seconds
until the response (number of responses) is received.
final int numberOfResponses =
messagingControl.sendRequestToFilter("$Principal NE 'control'",
"foo", request, JSON.class, JSON.class).get(5, TimeUnit.SECONDS);
Diffusion | 409
Responding to messages sent to a filter
Required permissions: send_to_message_handler for the specified message path
To the receiving client session, a request message sent to a filter is the same as a request message sent
directly to the session. The receiving client session responds in the same way.
See Responding to messages sent to a session for details.
One-way messaging
Diffusion also provides a capability to send one-way messages to a filter. This message is not typed.
The specified sessions cannot respond directly to this message.
Send a one-way message specifying the following information:
•
•
•
•
The query to use to filter which client sessions to send the message to
The message path to send the messages through
The message content
Any additional options, such as headers or a message priority
JavaScript
session.messages.send('message_path', filter_query);
Apple
[session.messagingControl sendToFilter:filter
path:message_path
message:[[PTDiffusionBytes alloc]
initWithData:data]
completionHandler:^(NSUInteger count, NSError*
error)
{
if (error) {
NSLog(@"Failed to send to %@. Error: %@", message_path,
error);
} else {
NSLog(@"Sent to %lu sessions", (unsigned long)count);
}
}];
Java and Android
.NET
var options =
session.MessagingControl.CreateSendOptionsBuilder().SetHeaders( headers ).Build
session.MessagingControl.SendToFilter( filter_query, message_path,
content, options, send_callback );
C
/*
* Parameters for send_msg_to_session() call.
*/
SEND_MSG_TO_FILTER_PARAMS_T params = {
.topic_path = message_path,
.filter = filter,
.content = message_content,
Diffusion | 410
.options.headers = headers,
.options.priority = CLIENT_SEND_PRIORITY_NORMAL,
.on_send = on_send_callback,
.context = context
};
/*
* Send the message and wait for the callback to acknowledge
delivery.
*/
send_msg_to_filter(session, params);
To the receiving client session, a one-way message sent to a filter is the same as a one-way message
sent directly to that session. The receiving client session receives the message in the same way.
To receive a message sent directly to a client session, that client session must add a message stream to
receive messages sent through that message path:
JavaScript
// Create with a default listener function
session.messages.listen('message_path', function(message) {
// Do something with the message
});
Apple
@interface MessageStreamDelegate : NSObject
<PTDiffusionMessageStreamDelegate>
@end
@implementation MessageStreamDelegate
-(void)
diffusionStream:(PTDiffusionStream *)stream
didReceiveMessageOnTopicPath:(NSString *)path
content:(PTDiffusionContent *)content
context:(PTDiffusionReceiveContext *)context
{
// Do something when a message is received.
}
-(void)diffusionDidCloseStream:(PTDiffusionStream *)stream
{
// Do something if the stream closes.
}
-(void)diffusionStream:(PTDiffusionStream *)stream didFailWithError:
(NSError *)error
{
// Do something if the stream fails.
}
// ... in later code
// Ensure to maintain a strong reference to your message stream
delegate as it
// is referenced weakly by the Diffusion client library.
MessageStreamDelegate *const delegate = [MessageStreamDelegate new];
// Create a locally evaluated topic selector specifying the messaging
paths that
// should be captured by the stream.
Diffusion | 411
PTDiffusionTopicSelector *const topicSelector =
[PTDiffusionTopicSelector
topicSelectorWithExpression:message_path];
// Use the Messaging feature to add a local stream using your
delegate against
// the topic selector.
[session.messaging addMessageStreamWithSelector:topicSelector
delegate:delegate];
Java and Android
session.feature(Messaging.class).addMessageStream(message_path, stream);
.NET
session.Messaging.AddMessageStream( message_path, stream );
C
/*
* Register a listener for messages on the given path.
*/
MSG_LISTENER_REGISTRATION_PARAMS_T listener_params = {
.topic_path = message_path,
.listener = on_stream_message,
.context = context
};
register_msg_listener(session, listener_params);
You can also add a fallback message stream to receive messages sent through any message path that
does not have a stream add against it:
Apple
// Ensure to maintain a strong reference to your message stream
delegate as it
// is referenced weakly by the Diffusion client library.
MessageStreamDelegate *const delegate = [MessageStreamDelegate new];
// Use the Messaging feature to add a local fallback stream using
your delegate.
[session.messaging addFallbackMessageStreamWithDelegate:delegate];
Java and Android
messaging.addFallbackMessageStream(message_stream);
.NET
session.Messaging.AddFallbackMessageStream( stream );
C
/*
* Register a listener for any other messages.
* (.topic_path is NULL).
*/
MSG_LISTENER_REGISTRATION_PARAMS_T global_listener_params = {
.listener = on_stream_message,
Diffusion | 412
.context = context
};
register_msg_listener(session, global_listener_params);
To respond to this message, the receiving client session sends a separate message to the message
path through which the received message was sent. For more information, see One-way messaging
to a path on page 397. However, if multiple client sessions have added messages handlers on this
message path, the one-way message sent in response is not guaranteed to be received by the client
session that sent the original one-way message.
Authenticating new sessions
A client session can use the AuthenticationControl feature to authenticate other client sessions.
Registering a control authentication handler
Required permissions: authenticate, register_handler
A client can register an authentication handler that can be called when a client connects to the
Diffusion server or changes the principal and credentials it is connected with.
The authentication handler can decide whether a client's authentication request is allowed or denied,
or the authentication handler can abstain from the decision. In which case the next configured
authentication handler is called.
If the authentication handler allows a client's authentication request, it can assign roles to that client's
session.
For more information about authentication and role-based security, see Authentication on page 136.
Related concepts
Configuring authentication handlers on page 529
Authentication handlers and the order that the Diffusion server calls them in are configured in the
Server.xml configuration file.
Example: Register an authentication handler
The following examples use the Diffusion API to register a control authentication handler with the
Diffusion server. The examples also include a simple or empty authentication handler.
The name by which the control authentication handler is registered must be configured in the
Server.xml configuration file of the Diffusion server for the control authentication handler to be
called to handle authentication requests.
Java and Android
package com.pushtechnology.diffusion.examples;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.EnumSet;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.details.SessionDetails;
import
com.pushtechnology.diffusion.client.details.SessionDetails.DetailType;
Diffusion | 413
import com.pushtechnology.diffusion.client.features.ServerHandler;
import
com.pushtechnology.diffusion.client.features.control.clients.AuthenticationCont
import
com.pushtechnology.diffusion.client.features.control.clients.AuthenticationCont
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.types.Credentials;
/**
* This demonstrates the use of a control client to authenticate
client
* connections.
* <P>
* This uses the 'AuthenticationControl' feature.
*
* @author Push Technology Limited
* @since 5.0
*/
public class ControlClientIdentityChecks {
private final Session session;
/**
* Constructor.
*/
public ControlClientIdentityChecks() {
session =
Diffusion.sessions().principal("control").password("password")
.open("ws://diffusion.example.com:80");
final AuthenticationControl authenticationControl =
session.feature(AuthenticationControl.class);
// To register the authentication handler, this client
session must
// have the 'authenticate' and 'register_handler'
permissions.
authenticationControl.setAuthenticationHandler(
"example-handler",
EnumSet.allOf(DetailType.class),
new Handler());
}
/**
* Authentication handler.
*/
private static class Handler extends ServerHandler.Default
implements ControlAuthenticationHandler {
@Override
public void authenticate(
final String principal,
final Credentials credentials,
final SessionDetails sessionDetails,
final Callback callback) {
final byte[] passwordBytes =
"password".getBytes(Charset.forName("UTF-8"));
if ("admin".equals(principal) &&
credentials.getType() ==
Credentials.Type.PLAIN_PASSWORD &&
Diffusion | 414
Arrays.equals(credentials.toBytes(), passwordBytes))
{
callback.allow();
}
else {
callback.deny();
}
}
}
/**
* Close the session.
*/
public void close() {
session.close();
}
}
.NET
/**
* Copyright © 2014, 2016 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using
using
using
using
using
System;
System.Linq;
System.Threading;
PushTechnology.ClientInterface.Client.Details;
PushTechnology.ClientInterface.Client.Factories;
namespace Examples {
/// <summary>
/// This is a control client which registers an authentication
handler with a server.
/// </summary>
public class ControlAuthenticationClient {
/// <summary>
/// Main entry point.
/// </summary>
public static void Run() {
var session =
Diffusion.Sessions.Principal( "auth" ).Password( "auth_secret" )
.Open( "ws://diffusion.example.com:80" );
session.AuthenticationControl.SetAuthenticationHandler( "controlclient-auth-handler-example",
Diffusion | 415
Enum.GetValues( typeof( DetailType ) ).OfType<DetailType>().ToList(),
new ExampleControlAuthenticationHandler() );
while ( true ) {
Thread.Sleep( 60000 );
}
}
}
}
/**
* Copyright © 2014, 2016 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using
using
using
using
using
using
System;
PushTechnology.ClientInterface.Client.Details;
PushTechnology.ClientInterface.Client.Features;
PushTechnology.ClientInterface.Client.Features.Control.Clients;
PushTechnology.ClientInterface.Client.Security.Authentication;
PushTechnology.DiffusionCore.Client.Types;
namespace Examples {
/// <summary>
/// Implementation of <see cref="IControlAuthenticationHandler"/
>.
/// </summary>
public class ExampleControlAuthenticationHandler :
IControlAuthenticationHandler {
/// <summary>
/// Request authentication.
///
/// The server calls this to authenticate new sessions, and
when a client requests the session principal is
/// changed.
///
/// For each call to Authenticate, the authentication handler
should respond by calling one of the methods of
/// the provided callback. The handler may return immediately
and process the authentication request
/// asynchronously. The client session will be blocked until
a callback method is called.
/// </summary>
/// <param name="principal"></param>
/// <param name="credentials"></param>
/// <param name="sessionDetails"></param>
/// <param name="callback"></param>
public void Authenticate( string principal, ICredentials
credentials, ISessionDetails sessionDetails,
IAuthenticationHandlerCallback callback ) {
Diffusion | 416
var output =
string.Format( "ExampleControlAuthenticationHandler asked to
authenticate: Principal {0}," +
"Credentials {1} -> {2}, SessionDetails {3}: ",
principal, credentials,
System.Text.Encoding.UTF8.GetString( credentials.ToBytes() ),
sessionDetails );
Console.WriteLine( output );
callback.Abstain();
}
/// <summary>
/// Called when the handler has been registered at the server
and is now active.
///
/// A session can register at most one a single handler of
each type. If there is already a handler registered
/// for the topic path the operation will fail, the
registered handler will be closed, and the session error
/// handler will be notified. To change the handler, first
close the previous handler.
/// </summary>
public void OnActive( IRegisteredHandler registeredHandler )
{
}
/// <summary>
/// Called if the handler is closed. This happens if the call
to register the handler fails, or the handler is
/// unregistered.
/// </summary>
public void OnClose() {
}
}
}
C
/*
* Diffusion can be configured to delegate authentication requests to
* an external handler. This program provides an authentication
* handler to demonstrate this feature. A detailed description of
* security and authentication handlers can be found in the Diffusion
* user manual.
*
* Authentication handlers are registered with a name, which is
typically specified in
* Server.xml
*
* Two handler names are provided by default;
* before-system-handler and after-system-handler, and additional
* handlers may be specified for Diffusion through the Server.xml
file
* and an accompanying Java class that implements the
* AuthenticationHandler interface.
*
* This control authentication handler connects to Diffusion and
attempts
Diffusion | 417
* to register itself with a user-supplied name, which should match
the name
* configured in Server.xml.
*
* The default behavior is to install as the "before-system-handler",
* which means that it will intercept authentication requests before
* Diffusion has a chance to act on them.
*
* It will:
* <ul>
* <li>Deny all anonymous connections</li>
* <li>Allow connections where the principal and credentials (i.e.,
username and password) match some hardcoded values</li>
* <li>Abstain from all other decisions, thereby letting Diffusion
and other authentication handlers decide what to do.</li>
* </ul>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "diffusion.h"
#include "args.h"
#include "conversation.h"
struct user_credentials_s {
const char *username;
const char *password;
};
/*
* Username/password pairs that this handler accepts.
*/
static const struct user_credentials_s USERS[] = {
{ "fish", "chips" },
{ "ham", "eggs" },
{ NULL, NULL }
};
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'n', "name", "Name under which to register the
authentication handler", ARG_OPTIONAL, ARG_HAS_VALUE, "beforesystem-handler"},
{'p', "principal", "Principal (username) for the connection",
ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
END_OF_ARG_OPTS
};
/*
* When the authentication service has been registered, this function
will be
* called.
*/
static int
on_registration(SESSION_T *session, void *context)
{
printf("Registered authentication handler\n");
Diffusion | 418
return HANDLER_SUCCESS;
}
/*
* When the authentication service has be deregistered, this function
will be
* called.
*/
static int
on_deregistration(SESSION_T *session, void *context)
{
printf("Deregistered authentication handler\n");
return HANDLER_SUCCESS;
}
/*
* This is the function that is called when authentication has been
delegated
* from Diffusion.
*
* The response may return one of three values via the response
parameter:
* ALLOW:
The user is authenticated.
* ALLOW_WITH_RESULT: The user is authenticated, and additional roles
are
*
to be applied to the user.
* DENY:
The user is NOT authenticated.
* ABSTAIN: Allow another handler to make the decision.
*
* The handler should return HANDLER_SUCCESS in all cases, unless an
actual
* error occurs during the authentication process (in which case,
* HANDLER_FAILURE is appropriate).
*/
static int
on_authentication(SESSION_T *session,
const SVC_AUTHENTICATION_REQUEST_T *request,
SVC_AUTHENTICATION_RESPONSE_T *response,
void *context)
{
// No credentials, or not password type. We're not an
authority for
// this type of authentication so abstain in case some other
registered
// authentication handler can deal with the request.
if(request->credentials == NULL) {
printf("No credentials specified, abstaining\n");
response->value = AUTHENTICATION_ABSTAIN;
return HANDLER_SUCCESS;
}
if(request->credentials->type != PLAIN_PASSWORD) {
printf("Credentials are not PLAIN_PASSWORD,
abstaining\n");
response->value = AUTHENTICATION_ABSTAIN;
return HANDLER_SUCCESS;
}
printf("principal = %s\n", request->principal);
printf("credentials = %*s\n",
(int)request->credentials->data->len,
request->credentials->data->data);
Diffusion | 419
if(request->principal == NULL || strlen(request->principal)
== 0) {
printf("Denying anonymous connection (no
principal)\n");
response->value = AUTHENTICATION_DENY; // Deny anon
connections
return HANDLER_SUCCESS;
}
char *password = malloc(request->credentials->data->len + 1);
memmove(password, request->credentials->data->data, request>credentials->data->len);
password[request->credentials->data->len] = '\0';
int auth_decided = 0;
int i = 0;
while(USERS[i].username != NULL) {
printf("Checking username %s vs %s\n", request>principal, USERS[i].username);
printf("
and password %s vs %s\n", password,
USERS[i].password);
if(strcmp(USERS[i].username, request->principal) == 0
&&
strcmp(USERS[i].password, password) == 0) {
puts("Allowed");
response->value = AUTHENTICATION_ALLOW;
auth_decided = 1;
break;
}
i++;
}
free(password);
if(auth_decided == 0) {
puts("Abstained");
response->value = AUTHENTICATION_ABSTAIN;
}
return HANDLER_SUCCESS;
}
int
main(int argc, char** argv)
{
HASH_T *options = parse_cmdline(argc, argv, arg_opts);
if (options == NULL || hash_get(options, "help") != NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
char
char
char
char
*url = hash_get(options, "url");
*name = hash_get(options, "name");
*principal = hash_get(options, "principal");
*credentials = hash_get(options, "credentials");
/*
* Create a session with Diffusion.
*/
Diffusion | 420
puts("Creating session");
DIFFUSION_ERROR_T error = { 0 };
SESSION_T *session = session_create(url,
principal,
credentials != NULL ?
credentials_create_password(credentials) : NULL,
NULL, NULL,
&error);
if (session == NULL) {
fprintf(stderr, "TEST: Failed to create session\n");
fprintf(stderr, "ERR : %s\n", error.message);
return EXIT_FAILURE;
}
/*
* Provide a set (via a hash map containing keys and NULL
* values) to indicate what information about the connecting
* client that we'd like Diffusion to send us.
*/
HASH_T *detail_set = hash_new(5);
char buf[2];
sprintf(buf, "%d", SESSION_DETAIL_SUMMARY);
hash_add(detail_set, strdup(buf), NULL);
sprintf(buf, "%d", SESSION_DETAIL_LOCATION);
hash_add(detail_set, strdup(buf), NULL);
sprintf(buf, "%d", SESSION_DETAIL_CONNECTOR_NAME);
hash_add(detail_set, strdup(buf), NULL);
/*
* Register the authentication handler.
*/
AUTHENTICATION_REGISTRATION_PARAMS_T auth_registration_params
= {
.name = name,
.detail_set = detail_set,
.on_registration = on_registration,
.authentication_handlers.on_authentication =
on_authentication
};
puts("Sending registration request");
SVC_AUTHENTICATION_REGISTER_REQUEST_T *reg_request =
authentication_register(session,
auth_registration_params);
/*
* Wait a while before moving on to deregistration.
*/
sleep(30);
AUTHENTICATION_DEREGISTRATION_PARAMS_T
auth_deregistration_params = {
.on_deregistration = on_deregistration,
.original_request = reg_request
};
/*
* Deregister the authentication handler.
*/
printf("Deregistering authentication handler\n");
authentication_deregister(session,
auth_deregistration_params);
Diffusion | 421
session_close(session, NULL);
session_free(session);
return EXIT_SUCCESS;
}
Change the URL from that provided in the example to the URL of the Diffusion server.
Related concepts
Configuring authentication handlers on page 529
Authentication handlers and the order that the Diffusion server calls them in are configured in the
Server.xml configuration file.
Developing a control authentication handler
Implement the ControlAuthenticationHandler interface to create a control authentication
handler.
About this task
A control authentication handler can be implemented in any language where the Diffusion API
includes the AuthenticationControl feature.
For more information, see .
This example demonstrates how to implement a control authentication handler in Java.
Note: Where c.p.d is used in package names, it indicates
com.pushtechnology.diffusion.
Procedure
1. Edit the etc/Server.xml configuration file to include a name that the control authentication
handler can register with.
Include the control-authentication-handler element in the list of authentication
handlers. The order of the list defines the order in which the authentication handlers are called.
The value of the handler-name attribute is the name that your control authentication handler
registers as. For example:
<security>
<authentication-handlers>
<-- Include a local authentication handler that can
authenticate the control client -->
<authentication-handler class="com.example.LocalHandler" />
<-- Register your control authentication handler -->
<control-authentication-handler handler-name="before-systemhandler" />
</authentication-handlers>
</security>
The client that registers your control authentication handler must first authenticate with the
Diffusion server. Configure a local authentication handler that allows the client to connect.
2. Start the Diffusion server.
Diffusion | 422
On UNIX®-based systems, run the diffusion.sh command in the
diffusion_installation_dir/bin directory.
• On Windows systems, run the diffusion.bat command in the
diffusion_installation_dir\bin directory.
3. Create a Java class that implements ControlAuthenticationHandler.
•
package com.example.client;
import com.pushtechnology.diffusion.client.details.SessionDetails;
import
com.pushtechnology.diffusion.client.features.control.clients.AuthenticationCo
import com.pushtechnology.diffusion.client.types.Credentials;
public class ExampleControlAuthenticationHandler implements
ControlAuthenticationHandler{
public void authenticate(String principal, Credentials
credentials,
SessionDetails sessionDetails, Callback callback) {
// Logic to make the authentication decision.
// Authentication decision
callback.abstain();
// callback.deny();
// callback.allow();
}
@Override
public void onActive(RegisteredHandler handler) {
}
@Override
public void onClose() {
}
}
a) Implement the authenticate method.
b) Use the allow, deny, or abstain method on the Callback object to respond with the
authentication decision.
c) You can override the onActive and onClose to include actions the control authentication
handler performs when the client opens its connection to the Diffusion server and when the
client closes its session with the Diffusion server.
For example, when the client session becomes active, the control authentication handler uses
the onActive method to open a connection to a database. When the client session is closed,
the control authentication handler uses the onClose method to close the connection to the
database.
4. Create a simple client that registers your control authentication handler with the Diffusion server.
package com.example.client;
import com.example.client.ExampleControlAuthenticationHandler;
import com.pushtechnology.diffusion.client.Diffusion;
Diffusion | 423
import
com.pushtechnology.diffusion.client.details.SessionDetails.DetailType;
import
com.pushtechnology.diffusion.client.features.control.clients.AuthenticationCo
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionFactory;
import java.util.EnumSet;
public class ExampleControlClient {
public static void main(String[] args) {
final Session session;
// Create the client session
SessionFactory sf = Diffusion.sessions();
session = sf.principal("ControlClient1")
.passwordCredentials("Passw0rd")
.open("ws://diffusion.example.com:80");
// Get the AuthenticationControl feature
AuthenticationControl authControl =
session.feature(AuthenticationControl.class);
// Use the AuthenticationControl feature to register your
control authentication
// handler with the name that you configured in Server.xml
authControl.setAuthenticationHandler("before-systemhandler",
EnumSet.allOf(DetailType.class), new
ExampleControlAuthenticationHandler());
}
}
a) Create a session.
Change the URL from that provided in the example to the URL of the Diffusion server.
b) Use the session to get the AuthenticationControl feature.
c) Use the AuthenticationControl feature to register your control authentication handler,
ExampleControlAuthenticationHandler, using the name that you configured in the
etc/Server.xml configuration file, before-system-handler.
5. Start your client.
It connects to the Diffusion server and registers the control authentication handler with the name
before-system-handler.
Results
When a client authenticates, the Diffusion server forwards the authentication request to the
authentication handler you have registered. Your authentication handler can ALLOW, DENY, or
ABSTAIN from the authentication decision. If your authentication handler returns an ALLOW or DENY
decision, this decision is used as the response to the authenticating client. If your authentication
handler returns an ABSTAIN decision, the Diffusion server forwards the authentication request to the
next authentication handler. For more information, see Authentication on page 136.
Related concepts
User-written authentication handlers on page 139
Diffusion | 424
You can implement authentication handlers that authenticate clients that connect to the Diffusion
server or perform an action that requires authentication.
Authentication on page 136
You can implement and register handlers to authenticate clients when the clients try to perform
operations that require authentication.
Related tasks
Developing a local authentication handler on page 484
Implement the AuthenticationHandler interface to create a local authentication handler.
Developing a composite authentication handler on page 486
Extend the CompositeAuthenticationHandler class to combine the decisions from multiple
authentication handlers.
Developing a composite control authentication handler on page 425
Extend the CompositeControlAuthenticationHandler class to combine the decisions from
multiple control authentication handlers.
Developing a composite control authentication handler
Extend the CompositeControlAuthenticationHandler class to combine the decisions from
multiple control authentication handlers.
About this task
Using a composite control authentication handler reduces the number of messages that are sent
between the Diffusion server and the client to perform authentication.
This example describes how to use a composite control authentication handler as part of a client
remote from the Diffusion server.
Procedure
1. Edit the etc/Server.xml configuration file to point to your composite control authentication
handler.
Include the control-authentication-handler element in the list of authentication
handlers. The order of the list defines the order in which the authentication handlers are called.
The value of the handler-name attribute is the name that your composite control authentication
handler registers as. For example:
<security>
<authentication-handlers>
<-- Include a local authentication handler that can
authenticate the control client -->
<authentication-handler class="com.example.LocalHandler" />
<-- Register your composite control authentication handler -->
<control-authentication-handler handler-name="examplecomposite-control-authentication-handler" />
</authentication-handlers>
</security>
The client that registers your control authentication handler must first authenticate with the
Diffusion server. Configure a local authentication handler that allows the client to connect.
2. Start the Diffusion server.
Diffusion | 425
•
On UNIX-based systems, run the diffusion.sh command in the
diffusion_installation_dir/bin directory.
• On Windows systems, run the diffusion.bat command in the
diffusion_installation_dir\bin directory.
3. Create the individual control authentication handlers that your composite control authentication
handler calls.
You can follow steps in the task Developing a control authentication handler on page 422.
In this example, the individual control authentication handlers are referred to as HandlerOne,
HandlerTwo, and HandlerThree.
4. Extend the CompositeControlAuthenticationHandler class.
package com.example.client;
import com.example.client.HandlerOne;
import com.example.client.HandlerTwo;
import com.example.client.HandlerThree;
import
com.pushtechnology.diffusion.client.features.control.clients.CompositeControl
public class ExampleHandler extends
CompositeControlAuthenticationHandler {
public ExampleHandler() {
super(new HandlerOne(), new HandlerTwo(), new
HandlerThree());
}
}
a) Import your individual control authentication handlers.
b) Create a no-argument constructor that calls the super class constructor with a list of your
individual handlers.
5. Create a simple client that registers your composite control authentication handler with the
Diffusion server.
You can follow steps in the task Developing a control authentication handler on page 422.
Ensure that you register your composite control authentication handler, ExampleHandler,
using the name that you configured in the etc/Server.xml configuration file, examplecomposite-control-authentication-handler.
6. Start your client.
It connects to the Diffusion server and registers the composite control authentication handler.
Results
When the client session starts, the composite control authentication handler calls the onActive
methods of the individual control authentication handlers in the order in which they are passed in to
the composite handler.
When the composite control authentication handler is called, it calls the individual control
authentication handlers that are passed to it as parameters in the order they are passed in.
•
•
If an individual handler responds with ALLOW, the composite handler responds with that decision
to the Diffusion server and a list of any roles to assign to the authenticated principal.
If an individual handler responds with DENY, the composite handler responds with that decision to
the Diffusion server.
Diffusion | 426
•
•
If an individual handler responds with ABSTAIN, the composite handler calls the next individual
handler in the list.
If all individual handlers respond with ABSTAIN, the composite handler responds to the Diffusion
server with an ABSTAIN decision.
When the client session closes, the composite control authentication handler calls the onClose
methods of the individual control authentication handlers in the order in which they are passed in to
the composite handler.
Related concepts
User-written authentication handlers on page 139
You can implement authentication handlers that authenticate clients that connect to the Diffusion
server or perform an action that requires authentication.
Authentication on page 136
You can implement and register handlers to authenticate clients when the clients try to perform
operations that require authentication.
Related tasks
Developing a local authentication handler on page 484
Implement the AuthenticationHandler interface to create a local authentication handler.
Developing a composite authentication handler on page 486
Extend the CompositeAuthenticationHandler class to combine the decisions from multiple
authentication handlers.
Developing a control authentication handler on page 422
Implement the ControlAuthenticationHandler interface to create a control authentication
handler.
Updating the system authentication store
A client can use the SystemAuthenticationControl feature to update the system authentication store.
The information in the system authentication store is used by the system authentication handler to
authenticate users and assign roles to them.
Querying the store
Required permissions: view_security
The client can get a snapshot of the current information in the system authentication store. This
information is returned as an object model.
Updating the store
Required permissions: modify_security
The client can use a command script to update the system authentication store. The command script
is a string that contains a command on each line. These commands are applied to the current state of
the system authentication store.
The update is transactional. Unless all of the commands in the script can be applied, none of them are.
Using a script builder
You can use a script builder to create the command script used to update the system authentication
store. Use the script builder to create commands for the following actions:
Diffusion | 427
•
•
•
•
•
Set the authentication decision for anonymous principals
Add principals to the store
Delete principals from the store
Change the password of a principal
Assign roles to principals
Related reference
System authentication handler on page 141
Diffusion provides an authentication handler that uses principal, credential, and roles information
stored in the Diffusion server to make its authentication decision.
DSL syntax: system authentication store
The scripts that you can use with the SystemAuthenticationControl feature to update the system
authentication store are formatted according to a domain-specific language (DSL). You can use the
script builders provided in the APIs to create a script to update the system authentication store.
However, if you want to create the script by some other method, ensure that it conforms to the DSL.
The following sections each describe the syntax for a single line of the file.
Adding a principal
Railroad diagram
Backus-Naur form
add principal " principal_name " " password " [ '[' " role " [ , " role " ] ']' ]
Example
add principal "user6" "passw0rd"
add principal "user13" "passw0rd" ["CLIENT", "TOPIC_CONTROL"]
The password is passed in as plain text, but is stored in the system authentication store as a secure
hash.
Removing a principal
Railroad diagram
Backus-Naur form
remove principal " principal_name "
Diffusion | 428
Example
remove principal "user25"
Assigning roles to a principal
Railroad diagram
Backus-Naur form
assign roles " principal_name " '[' " role " [ , " role " ] ']'
Example
assign roles "agent77" ["CLIENT", "CLIENT_CONTROL"]
When you use this command to assign roles to a principal, it overwrites any existing roles assigned to
that principal. Ensure that all the roles you want the principal to have are listed in the command.
Setting the password for a principal
Railroad diagram
Backus-Naur form
set password " principal_name " " password "
Example
set password "user1" "passw0rd"
The password is passed in as plain text, but is stored in the system authentication store as a secure
hash.
Verifying the password for a principal
Railroad diagram
Backus-Naur form
verify password " principal_name " " password "
Example
verify password "user1" "passw0rd"
The password is passed in as plain text, but is stored in the system authentication store as a secure
hash.
Diffusion | 429
Allowing anonymous connections
Railroad diagram
Backus-Naur form
allow anonymous connections [ '[' " role " [ , " role " ] ']' ]
Example
allow anonymous connections [ "CLIENT" ]
Denying anonymous connections
Railroad diagram
Backus-Naur form
deny anonymous connections
Example
deny anonymous connections
Abstaining from providing a decision about anonymous connections
Railroad diagram
Backus-Naur form
abstain anonymous connections
Example
abstain anonymous connections
Example: Update the system authentication store
The following examples use the SystemAuthenticationControl feature in the Diffusion API to update
the system authentication store.
JavaScript
Note: Only steps 4 and 5 deal with the system authentication store.
// Session security allows you to change the principal that a session
is authenticated as. It also allows users to
Diffusion | 430
// query and update server-side security and authentication stores,
which control users, roles and permissions.
// This enables you to manage the capabilities that any logged in
user will have access to.
// Connect to Diffusion with control client credentials
diffusion.connect({
host
: 'diffusion.example.com',
port
: 443,
secure : true,
principal : 'control',
credentials : 'password'
}).then(function(session) {
// 1. A session change their principal by re-authenticating
session.security.changePrincipal('admin',
'password').then(function() {
console.log('Authenticated as admin');
});
// 2. The security configuration provides details about roles and
their assigned permissions
session.security.getSecurityConfiguration().then(function(config)
{
console.log('Roles for anonymous sessions: ',
config.anonymous);
console.log('Roles for named sessions: ', config.named);
console.log('Available roles: ', config.roles);
}, function(error) {
console.log('Unable to fetch security configuration', error);
});
// 3. Changes to the security configuration are done with a
SecurityScriptBuilder
var securityScriptBuilder =
session.security.securityScriptBuilder();
// Set the permissions for a particular role - global and topicscoped
// Each method on a script builder returns a new builder
var setPermissionScript =
securityScriptBuilder.setGlobalPermissions('SUPERUSER',
['REGISTER_HANDLER'])
.setTopicPermissions('SUPERUSER', '/foo', ['UPDATE_TOPIC'])
.build();
// Update the server-side store with the generated script
session.security.updateSecurityStore(setPermissionScript).then(function()
{
console.log('Security configuration updated successfully');
}, function(error) {
console.log('Failed to update security configuration: ',
error);
});
// 4. The system authentication configuration lists all users &
roles
session.security.getSystemAuthenticationConfiguration().then(function(config)
{
console.log('System principals: ', config.principals);
Diffusion | 431
console.log('Anonymous sessions: ', config.anonymous);
}, function(error) {
console.log('Unable to fetch system authentication
configuration', error);
});
// 5. Changes to the system authentication config are done with a
SystemAuthenticationScriptBuilder
var authenticationScriptBuilder =
session.security.authenticationScriptBuilder();
// Add a new user and set password & roles.
var addUserScript =
authenticationScriptBuilder.addPrincipal('Superman',
'correcthorsebatterystapler')
.assignRoles('Superman', ['SUPERUSER'])
.build();
// Update the system authentication store
session.security.updateAuthenticationStore(addUserScript).then(function()
{
console.log('Updated system authentication config');
}, function(error) {
console.log('Failed to update system authentication: ',
error);
});
});
Java and Android
package com.pushtechnology.diffusion.examples;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import
com.pushtechnology.diffusion.client.features.control.clients.SystemAuthenticati
import
com.pushtechnology.diffusion.client.features.control.clients.SystemAuthenticati
import
com.pushtechnology.diffusion.client.features.control.clients.SystemAuthenticati
import
com.pushtechnology.diffusion.client.features.control.clients.SystemAuthenticati
import
com.pushtechnology.diffusion.client.features.control.clients.SystemAuthenticati
import
com.pushtechnology.diffusion.client.features.control.clients.SecurityStoreFeatu
import com.pushtechnology.diffusion.client.session.Session;
/**
* An example of using a control client to alter the system
authentication
* configuration.
* <P>
* This uses the {@link SystemAuthenticationControl} feature only.
Diffusion | 432
*
* @author Push Technology Limited
* @since 5.2
*/
public class ControlClientChangingSystemAuthentication {
private static final Logger LOG =
LoggerFactory.getLogger(
ControlClientChangingSystemAuthentication.class);
private final SystemAuthenticationControl
systemAuthenticationControl;
/**
* Constructor.
*/
public ControlClientChangingSystemAuthentication() {
final Session session = Diffusion.sessions()
// Authenticate with a user that has the VIEW_SECURITY
and
// MODIFY_SECURITY permissions.
.principal("admin").password("password")
// Use a secure channel because we're transferring
sensitive
// information.
.open("wss://diffusion.example.com:80");
systemAuthenticationControl =
session.feature(SystemAuthenticationControl.class);
}
/**
* For all system users, update the assigned roles to replace the
* "SUPERUSER" role and with "ADMINISTRATOR".
*
* @param callback result callback
*/
public void changeSuperUsersToAdministrators(UpdateStoreCallback
callback) {
systemAuthenticationControl.getSystemAuthentication(
new ChangeSuperUsersToAdministrators(callback));
}
private final class ChangeSuperUsersToAdministrators
implements ConfigurationCallback {
private final UpdateStoreCallback callback;
ChangeSuperUsersToAdministrators(UpdateStoreCallback
callback) {
this.callback = callback;
}
@Override
public void onReply(SystemAuthenticationConfiguration
configuration) {
ScriptBuilder builder =
systemAuthenticationControl.scriptBuilder();
// For all system users ...
Diffusion | 433
for (SystemPrincipal principal :
configuration.getPrincipals()) {
final Set<String> assignedRoles =
principal.getAssignedRoles();
// ... that have the SUPERUSER assigned role ...
if (assignedRoles.contains("SUPERUSER")) {
final Set<String> newRoles = new
HashSet<>(assignedRoles);
newRoles.remove("SUPERUSER");
newRoles.add("ADMINISTRATOR");
// ... add a command to the script that updates
the user's
// assigned roles, replacing SUPERUSER with
"ADMINISTRATOR".
builder =
builder.assignRoles(principal.getName(),
newRoles);
}
}
final String script = builder.script();
LOG.info(
"Sending the following script to the server:\n{}",
script);
systemAuthenticationControl.updateStore(
script,
callback);
}
@Override
public void onError(ErrorReason errorReason) {
// This might fail if the session lacks the required
permissions.
callback.onError(errorReason);
}
}
/**
* Close the session.
*/
public void close() {
systemAuthenticationControl.getSession().close();
}
}
.NET
*
*
*
*
*
*
*
/**
Copyright © 2014, 2016 Push Technology Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Diffusion | 434
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using System.Collections.Generic;
using System.Linq;
using PushTechnology.ClientInterface.Client.Callbacks;
using PushTechnology.ClientInterface.Client.Factories;
using PushTechnology.ClientInterface.Client.Features.Control.Clients;
using PushTechnology.ClientInterface.Client.Types;
using IUpdateStoreCallback =
PushTechnology.ClientInterface.Client.Features.Control.Clients.SecurityControl.
namespace Examples {
public class ControlClientChangingSystemAuthentication {
private readonly ISystemAuthenticationControl
theSystemAuthenticationControl;
public ControlClientChangingSystemAuthentication() {
// Authenticate with a user that has the VIEW_SECURITY
and MODIFY_SECURITY permissions
var session =
Diffusion.Sessions.Principal( "control" ).Password( "password" )
// Use a secure channel because we're transferring
sensitive information.
.Open( "wss://localhost:8080" );
theSystemAuthenticationControl =
session.SystemAuthenticationControl;
}
/// <summary>
/// For all system users, update the assigned roles to
replace the 'SUPERUSER' role and with 'ADMINISTRATOR'.
/// </summary>
/// <param name="callback"></param>
public void
ChangeSuperUsersToAdministrators( IUpdateStoreCallback callback ) {
theSystemAuthenticationControl.GetSystemAuthentication(
new
ChangeSuperusersToAdministrators( theSystemAuthenticationControl,
callback ) );
}
private class InternalUpdateStoreCallback :
IUpdateStoreCallback {
/// <summary>
/// The script was applied successfully.
/// </summary>
public void OnSuccess() {
}
/// <summary>
/// The script was rejected. No changes were made to the
system authentication store.
/// </summary>
Diffusion | 435
/// <param name="errors">The details of why the script
was rejected.</param>
public void OnRejected( ICollection<IErrorReport>
errors ) {
}
/// <summary>
/// Notification of a contextual error related to this
callback. This is analogous to an exception being
/// raised. Situations in which <code>OnError</code> is
called include the session being closed, a
/// communication timeout, or a problem with the provided
parameters. No further calls will be made to this
/// callback.
/// </summary>
/// <param name="errorReason">errorReason a value
representing the error; this can be one of constants
/// defined in <see cref="ErrorReason" />, or a featurespecific reason.</param>
public void OnError( ErrorReason errorReason ) {
}
}
private class ChangeSuperusersToAdministrators :
IConfigurationCallback {
private readonly ISystemAuthenticationControl
theSystemAuthenticationControl;
private readonly IUpdateStoreCallback theCallback;
public
ChangeSuperusersToAdministrators( ISystemAuthenticationControl
systemAuthenticationControl,
IUpdateStoreCallback callback ) {
theSystemAuthenticationControl =
systemAuthenticationControl;
theCallback = callback;
}
/// <summary>
/// The configuration callback reply.
/// </summary>
/// <param name="systemAuthenticationConfiguration">The
system authenticationConfiguration stored on the
/// server.</param>
public void OnReply( ISystemAuthenticationConfiguration
systemAuthenticationConfiguration ) {
var builder =
theSystemAuthenticationControl.ScriptBuilder();
// For all system users...
foreach ( var principal in
systemAuthenticationConfiguration.Principals ) {
var assignedRoles = principal.AssignedRoles;
// ...that have the 'SUPERUSER' assigned role...
if ( !assignedRoles.Contains( "SUPERUSER" ) )
continue;
var newRoles = new
HashSet<string>( assignedRoles );
newRoles.Remove( "SUPERUSER" );
newRoles.Add( "ADMINISTRATOR" );
Diffusion | 436
// ...and add a command to the script that
updates the user's assigned roles, replacing 'SUPERUSER'
// with 'ADMINISTRATOR'.
builder = builder.AssignRoles( principal.Name,
newRoles.ToList() );
}
var script = builder.Script();
theSystemAuthenticationControl.UpdateStore( script,
theCallback );
}
/// <summary>
/// Notification of a contextual error related to this
callback. This is analogous to an exception being
/// raised. Situations in which <code>OnError</code> is
called include the session being closed, a
/// communication timeout, or a problem with the provided
parameters. No further calls will be made to this
/// callback.
/// </summary>
/// <param name="errorReason">errorReason a value
representing the error; this can be one of constants
/// defined in <see cref="ErrorReason" />, or a featurespecific reason.</param>
public void OnError( ErrorReason errorReason ) {
theCallback.OnError( errorReason );
}
}
}
}
C
/*
* This examples demonstrates how to interact with the system
* authentication store.
*/
#include <stdio.h>
#include <apr.h>
#include <apr_thread_mutex.h>
#include <apr_thread_cond.h>
#include "diffusion.h"
#include "args.h"
#include "service/svc-system-auth-control.h"
apr_pool_t *pool = NULL;
apr_thread_mutex_t *mutex = NULL;
apr_thread_cond_t *cond = NULL;
ARG_OPTS_T arg_opts[] = {
ARG_OPTS_HELP,
{'u', "url", "Diffusion server URL", ARG_OPTIONAL,
ARG_HAS_VALUE, "ws://localhost:8080"},
{'p', "principal", "Principal (username) for the connection",
ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
Diffusion | 437
{'c', "credentials", "Credentials (password) for the
connection", ARG_OPTIONAL, ARG_HAS_VALUE, NULL},
END_OF_ARG_OPTS
};
/*
* This callback is invoked when the system authentication store is
* received, and prints the contents of the store.
*/
int
on_get_system_authentication_store(SESSION_T *session,
const
SYSTEM_AUTHENTICATION_STORE_T store,
void *context)
{
puts("on_get_system_authentication_store()");
printf("Got %ld principals\n", store.system_principals>size);
char **names = get_principal_names(store);
for(char **name = names; *name != NULL; name++) {
printf("Principal: %s\n", *name);
char **roles = get_roles_for_principal(store, *name);
for(char **role = roles; *role != NULL; role++) {
printf(" |- Role: %s\n", *role);
}
free(roles);
}
free(names);
switch(store.anonymous_connection_action) {
case ANONYMOUS_CONNECTION_ACTION_ALLOW:
puts("Allow anonymous connections");
break;
case ANONYMOUS_CONNECTION_ACTION_DENY:
puts("Deny anonymous connections");
break;
case ANONYMOUS_CONNECTION_ACTION_ABSTAIN:
puts("Abstain from making anonymous connection
decision");
break;
}
puts("Anonymous connection roles:");
char **roles = get_anonymous_roles(store);
for(char **role = roles; *role != NULL; role++) {
printf(" |- Role: %s\n", *role);
}
free(roles);
apr_thread_mutex_lock(mutex);
apr_thread_cond_broadcast(cond);
apr_thread_mutex_unlock(mutex);
return HANDLER_SUCCESS;
}
int
main(int argc, char **argv)
{
/*
Diffusion | 438
* Standard command-line parsing.
*/
const HASH_T *options = parse_cmdline(argc, argv, arg_opts);
if(options == NULL || hash_get(options, "help") != NULL) {
show_usage(argc, argv, arg_opts);
return EXIT_FAILURE;
}
const char *url = hash_get(options, "url");
const char *principal = hash_get(options, "principal");
CREDENTIALS_T *credentials = NULL;
const char *password = hash_get(options, "credentials");
if(password != NULL) {
credentials = credentials_create_password(password);
}
/*
* Setup for condition variable
*/
apr_initialize();
apr_pool_create(&pool, NULL);
apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_UNNESTED,
pool);
apr_thread_cond_create(&cond, pool);
/*
* Create a session with Diffusion.
*/
SESSION_T *session;
DIFFUSION_ERROR_T error = { 0 };
session = session_create(url, principal, credentials, NULL,
NULL, &error);
if(session == NULL) {
fprintf(stderr, "TEST: Failed to create session\n");
fprintf(stderr, "ERR : %s\n", error.message);
return EXIT_FAILURE;
}
/*
* Request the system authentication store.
*/
const GET_SYSTEM_AUTHENTICATION_STORE_PARAMS_T params = {
.on_get = on_get_system_authentication_store
};
apr_thread_mutex_lock(mutex);
get_system_authentication_store(session, params);
apr_thread_cond_wait(cond, mutex);
apr_thread_mutex_unlock(mutex);
/*
* Close the session and tidy up.
*/
session_close(session, NULL);
session_free(session);
apr_thread_mutex_destroy(mutex);
apr_thread_cond_destroy(cond);
apr_pool_destroy(pool);
apr_terminate();
Diffusion | 439
return EXIT_SUCCESS;
}
Change the URL from that provided in the example to the URL of the Diffusion server.
Updating the security store
A client can use the SecurityControl feature to update the security store. The information in the
security store is used by the Diffusion server to define the permissions assigned to roles and the roles
assigned to anonymous sessions and named sessions.
Querying the store
Required permissions: view_security
The client can get a snapshot of the current information in the security store. This information is
returned as an object model.
Updating the store
Required permissions: modify_security
The client can use a command script to update the security store. The command script is a string that
contains a command on each line. These commands are applied to the current state of the security
store.
The update is transactional. Unless all of the commands in the script can be applied, none of them are.
Using a script builder
You can use a script builder to create the command script used to update the security store. Use the
script builder to create commands for the following actions:
•
•
•
Set the global permissions assigned to a named role
Set the default path permissions assigned to a named role
Set the path permissions associated with a specific path assigned to a named role
•
•
•
•
This can include explicitly setting a role to have no permissions at a path.
Remove the path permissions associated with a specific path assigned to a named role
Set the roles included in a named role
Set the roles assigned to sessions authenticated with a named principal
Set the roles assigned to anonymous sessions
DSL syntax: security store
The scripts that you can use with the SecurityControl feature to update the security store are
formatted according to a domain-specific language (DSL). You can use the script builders provided
in the APIs to create a script to update the security store. However, if you want to create the script by
some other method, ensure that it conforms to the DSL.
The following sections each describe the syntax for a single line of the script file.
Note: The path keyword is synonymous with the topic keyword used in previous releases of
Diffusion. Both keywords are accepted. Prefer path.
Diffusion | 440
Assigning global permissions to a role
Railroad diagram
Backus-Naur form
set " role_name " permissions [ '[' global_permission [ , global_permission ] ']' ]
Example
set "ADMINISTRATOR" permissions [CONTROL_SERVER, VIEW_SERVER,
VIEW_SECURITY, MODIFY_SECURITY]
set "CLIENT_CONTROL" permissions [VIEW_SESSION, MODIFY_SESSION,
REGISTER_HANDLER]
Assigning default path permissions to a role
Railroad diagram
Backus-Naur form
set " role_name " default path permissions [ '[' path_permission [ , path_permission ]
']' ]
Example
set "CLIENT" default path permissions [READ_TOPIC ,
SEND_TO_MESSAGE_HANDLER]
Assigning path permissions associated with a specific path to a role
Railroad diagram
Backus-Naur form
set " role_name " path " path " permissions [ '[' path_permission [ , path_permission ] ']' ]
Example
set "CLIENT" path "foo/bar" permissions [READ_TOPIC,
SEND_TO_MESSAGE_HANDLER]
set "ADMINISTRATOR" path "foo" permissions [ MODIFY_TOPIC ]
set "CLIENT_CONTROL" path "foo" permissions [ ]
Diffusion | 441
Removing all path permissions associated with a specific path to a role
Railroad diagram
Backus-Naur form
remove " role_name " permissions for path " path "
Example
remove "CLIENT" permissions for path "foo/bar"
Including roles within another role
Railroad diagram
Backus-Naur form
set " role_name " includes [ '[' " role_name " [ , " role_name " ] ']' ]
Example
set "ADMINISTRATOR" includes ["CLIENT_CONTROL" , "TOPIC_CONTROL"]
set "CLIENT_CONTROL" includes ["CLIENT"]
Assigning roles to a named session
Railroad diagram
Backus-Naur form
set roles for named sessions [ '[' " role_name " [ , " role_name " ] ']' ]
Example
set roles for named sessions ["CLIENT"]
Assigning roles to an anonymous session
Railroad diagram
Diffusion | 442
Backus-Naur form
set roles for anonymous sessions [ '[' " role_name " [ , " role_name " ] ']' ]
Example
set roles for anonymous sessions ["CLIENT"]
Example: Update the security store
The following examples use the SecurityControl feature in the Diffusion API to update the security
store.
JavaScript
Note: Only steps 2 and 3 deal with the security store.
// Session security allows you to change the principal that a session
is authenticated as. It also allows users to
// query and update server-side security and authentication stores,
which control users, roles and permissions.
// This enables you to manage the capabilities that any logged in
user will have access to.
// Connect to Diffusion with control client credentials
diffusion.connect({
host
: 'diffusion.example.com',
port
: 443,
secure : true,
principal : 'control',
credentials : 'password'
}).then(function(session) {
// 1. A session change their principal by re-authenticating
session.security.changePrincipal('admin',
'password').then(function() {
console.log('Authenticated as admin');
});
// 2. The security configuration provides details about roles and
their assigned permissions
session.security.getSecurityConfiguration().then(function(config)
{
console.log('Roles for anonymous sessions: ',
config.anonymous);
console.log('Roles for named sessions: ', config.named);
console.log('Available roles: ', config.roles);
}, function(error) {
console.log('Unable to fetch security configuration', error);
});
// 3. Changes to the security configuration are done with a
SecurityScriptBuilder
var securityScriptBuilder =
session.security.securityScriptBuilder();
// Set the permissions for a particular role - global and topicscoped
// Each method on a script builder returns a new builder
Diffusion | 443
var setPermissionScript =
securityScriptBuilder.setGlobalPermissions('SUPERUSER',
['REGISTER_HANDLER'])
.setTopicPermissions('SUPERUSER', '/foo', ['UPDATE_TOPIC'])
.build();
// Update the server-side store with the generated script
session.security.updateSecurityStore(setPermissionScript).then(function()
{
console.log('Security configuration updated successfully');
}, function(error) {
console.log('Failed to update security configuration: ',
error);
});
// 4. The system authentication configuration lists all users &
roles
session.security.getSystemAuthenticationConfiguration().then(function(config)
{
console.log('System principals: ', config.principals);
console.log('Anonymous sessions: ', config.anonymous);
}, function(error) {
console.log('Unable to fetch system authentication
configuration', error);
});
// 5. Changes to the system authentication config are done with a
SystemAuthenticationScriptBuilder
var authenticationScriptBuilder =
session.security.authenticationScriptBuilder();
// Add a new user and set password & roles.
var addUserScript =
authenticationScriptBuilder.addPrincipal('Superman',
'correcthorsebatterystapler')
.assignRoles('Superman', ['SUPERUSER'])
.build();
// Update the system authentication store
session.security.updateAuthenticationStore(addUserScript).then(function()
{
console.log('Updated system authentication config');
}, function(error) {
console.log('Failed to update system authentication: ',
error);
});
});
Java and Android
package com.pushtechnology.diffusion.examples;
import
import
import
import
java.util.Collections;
java.util.Map;
java.util.Set;
java.util.TreeSet;
Diffusion | 444
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import
com.pushtechnology.diffusion.client.features.control.clients.SecurityControl;
import
com.pushtechnology.diffusion.client.features.control.clients.SecurityControl.Co
import
com.pushtechnology.diffusion.client.features.control.clients.SecurityControl.Ro
import
com.pushtechnology.diffusion.client.features.control.clients.SecurityControl.Sc
import
com.pushtechnology.diffusion.client.features.control.clients.SecurityControl.Se
import
com.pushtechnology.diffusion.client.features.control.clients.SecurityStoreFeatu
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.types.GlobalPermission;
import com.pushtechnology.diffusion.client.types.TopicPermission;
/**
* An example of using a control client to alter the security
configuration.
* <P>
* This uses the {@link SecurityControl} feature only.
*
* @author Push Technology Limited
* @since 5.3
*/
public class ControlClientChangingSecurity {
private static final Logger LOG =
LoggerFactory.getLogger(
ControlClientChangingSecurity.class);
private final SecurityControl securityControl;
/**
* Constructor.
*/
public ControlClientChangingSecurity() {
final Session session = Diffusion.sessions()
// Authenticate with a user that has the VIEW_SECURITY
and
// MODIFY_SECURITY permissions.
.principal("admin").password("password")
// Use a secure channel because we're transferring
sensitive
// information.
.open("wss://diffusion.example.com:80");
securityControl = session.feature(SecurityControl.class);
}
/**
* This will update the security store to ensure that all roles
start with a
* capital letter (note that this does not address changing the
use of the
* roles in the system authentication store).
*
Diffusion | 445
* @param callback result callback
*/
public void capitalizeRoles(UpdateStoreCallback callback) {
securityControl.getSecurity(new CapitalizeRoles(callback));
}
private final class CapitalizeRoles implements
ConfigurationCallback {
private final UpdateStoreCallback callback;
CapitalizeRoles(UpdateStoreCallback callback) {
this.callback = callback;
}
@Override
public void onReply(SecurityConfiguration configuration) {
ScriptBuilder builder =
securityControl.scriptBuilder();
builder = builder.setRolesForAnonymousSessions(
capitalize(configuration.getRolesForAnonymousSessions()));
builder = builder.setRolesForNamedSessions(
capitalize(configuration.getRolesForNamedSessions()));
for (Role role : configuration.getRoles()) {
final String oldName = role.getName();
final String newName = capitalize(oldName);
// Only if new name is different
if (!oldName.equals(newName)) {
// Global Permissions
final Set<GlobalPermission> globalPermissions =
role.getGlobalPermissions();
if (!globalPermissions.isEmpty()) {
// Remove global permissions for old role
builder =
builder.setGlobalPermissions(
oldName,
Collections.<GlobalPermission>emptySet());
// Set global permissions for new role
builder =
builder.setGlobalPermissions(
newName,
role.getGlobalPermissions());
}
final Set<TopicPermission>
defaultTopicPermissions =
role.getDefaultTopicPermissions();
if (!defaultTopicPermissions.isEmpty()) {
// Remove default topic permissions for old
role
builder =
builder.setDefaultTopicPermissions(
oldName,
Diffusion | 446
Collections.<TopicPermission>emptySet());
// Set default topic permissions for new role
builder =
builder.setDefaultTopicPermissions(
newName,
role.getDefaultTopicPermissions());
}
final Map<String, Set<TopicPermission>>
topicPermissions =
role.getTopicPermissions();
if (!topicPermissions.isEmpty()) {
for (Map.Entry<String, Set<TopicPermission>>
entry : topicPermissions
.entrySet()) {
final String topicPath = entry.getKey();
// Remove old topic permissions
builder =
builder.removeTopicPermissions(
oldName,
topicPath);
// Set new topic permissions
builder =
builder.setTopicPermissions(
newName,
topicPath,
entry.getValue());
}
}
}
final Set<String> oldIncludedRoles =
role.getIncludedRoles();
if (!oldIncludedRoles.isEmpty()) {
if (!oldName.equals(newName)) {
// Remove old included roles
builder =
builder.setRoleIncludes(
oldName,
Collections.<String>emptySet());
}
// This is done even if role name did not change
as it is
// possible that roles included may have
final Set<String> newIncludedRoles =
capitalize(oldIncludedRoles);
builder =
builder.setRoleIncludes(
newName,
newIncludedRoles);
}
}
final String script = builder.script();
Diffusion | 447
LOG.info(
"Sending the following script to the server:\n{}",
script);
securityControl.updateStore(
script,
callback);
}
private Set<String> capitalize(Set<String> roles) {
final Set<String> newSet = new TreeSet<>();
for (String role : roles) {
newSet.add(capitalize(role));
}
return newSet;
}
private String capitalize(String role) {
return Character.toUpperCase(role.charAt(0)) +
role.substring(1);
}
@Override
public void onError(ErrorReason errorReason) {
// This might fail if the session lacks the required
permissions.
callback.onError(errorReason);
}
}
/**
* Close the session.
*/
public void close() {
securityControl.getSession().close();
}
}
.NET
/**
* Copyright © 2014, 2016 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*/
using System.Collections.Generic;
using System.Linq;
using PushTechnology.ClientInterface.Client.Callbacks;
Diffusion | 448
using PushTechnology.ClientInterface.Client.Factories;
using
PushTechnology.ClientInterface.Client.Features.Control.Clients.SecurityControl;
using PushTechnology.ClientInterface.Client.Types;
namespace Examples {
/// <summary>
/// An example of using a control client to alter the security
configuration.
///
/// This uses the <see cref="ISecurityControl"/> feature only.
/// </summary>
public class ControlClientChangingSecurity {
private readonly ISecurityControl securityControl;
public ControlClientChangingSecurity() {
// Authenticate with a user that has the VIEW_SECURITY
and MODIFY_SECURITY permissions.
var session =
Diffusion.Sessions.Principal( "admin" ).Password( "password" )
// Use a secure channel because we're transferring
sensitive information.
.Open( "wss://diffusion.example.com:8080" );
securityControl = session.SecurityControl;
}
public void DoCapitalizeRoles( IUpdateStoreCallback
callback ) {
securityControl.GetSecurity( new
CapitalizeRoles( securityControl, callback ) );
}
private class CapitalizeRoles : IConfigurationCallback {
private readonly ISecurityControl theSecurityControl;
private readonly IUpdateStoreCallback theCallback;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="securityControl">The security control
object.</param>
/// <param name="callback">The callback object.</param>
public CapitalizeRoles( ISecurityControl securityControl,
IUpdateStoreCallback callback ) {
theSecurityControl = securityControl;
theCallback = callback;
}
/// <summary>
/// Notification of a contextual error related to this
callback. This is analogous to an exception being
/// raised. Situations in which <code>OnError</code> is
called include the session being closed, a
/// communication timeout, or a problem with the provided
parameters. No further calls will be made to this
/// callback.
/// </summary>
/// <param name="errorReason">errorReason a value
representing the error; this can be one of constants
/// defined in <see cref="ErrorReason" />, or a featurespecific reason.</param>
public void OnError( ErrorReason errorReason ) {
Diffusion | 449
// This might fail if the session lacks the required
permissions.
theCallback.OnError( errorReason );
}
/// <summary>
/// This is called to return the requested security
configuration.
/// </summary>
/// <param name="configuration">The snapshot of
information from the security store.</param>
public void OnReply( ISecurityConfiguration
configuration ) {
var builder = theSecurityControl.ScriptBuilder();
builder = builder.SetRolesForAnonymousSessions(
Capitalize( configuration.RolesForAnonymousSessions ) );
builder = builder.SetRolesForNamedSessions(
Capitalize( configuration.RolesForNamedSessions ) );
foreach ( var role in configuration.Roles ) {
var oldName = role.Name;
var newName = Capitalize( oldName );
// Only if new name is different
if ( !oldName.Equals( newName ) ) {
// Global permissions
var globalPermissions =
role.GlobalPermissions;
if ( globalPermissions.Count > 0 ) {
// Remove global permissions for old role
builder =
builder.SetGlobalPermissions( oldName, new
List<GlobalPermission>() );
// Set global permissions for new role
builder =
builder.SetGlobalPermissions( newName,
new
List<GlobalPermission>( role.GlobalPermissions ) );
}
var defaultTopicPermissions =
role.DefaultTopicPermissions;
if ( defaultTopicPermissions.Count > 0 ) {
// Remove default topic permissions for
old role
builder =
builder.SetDefaultTopicPermissions( oldName, new
List<TopicPermission>() );
// Set default topic permissions for new
role
builder =
builder.SetDefaultTopicPermissions( newName,
new
List<TopicPermission>( role.DefaultTopicPermissions ) );
}
Diffusion | 450
var topicPermissions = role.TopicPermissions;
if ( topicPermissions.Count > 0 ) {
foreach ( var entry in topicPermissions )
{
var topicPath = entry.Key;
// Remove old topic permissions
builder =
builder.RemoveTopicPermissions( oldName, topicPath );
// Set new topic permissions
builder =
builder.SetTopicPermissions( newName, topicPath, entry.Value );
}
}
}
var oldIncludedRoles = role.IncludedRoles;
if ( oldIncludedRoles.Count > 0 ) {
// Remove old included roles
builder = builder.SetRoleIncludes( oldName,
new List<string>() );
}
// This is done even if role name did not change
as it is possible that roles included may have
var newIncludedRoles =
Capitalize( oldIncludedRoles );
builder = builder.SetRoleIncludes( newName,
newIncludedRoles );
}
}
private static List<string>
Capitalize( IEnumerable<string> roles ) {
return roles.Select( Capitalize ).ToList();
}
private static string Capitalize( string role ) {
return char.ToUpper( role[ 0 ] ) +
role.Substring( 1 );
}
}
}
}
Change the URL from that provided in the example to the URL of the Diffusion server.
Diffusion | 451
Managing sessions
A client session with the appropriate permissions can receive notifications and information about
other client sessions. A client session with the appropriate permissions can also manage other client
sessions.
Closing client sessions
Required permissions: view_session, modify_session
A client can close any client session, providing the requesting client knows the session ID of the target
client.
Java and Android
ClientControl clientControl = session.feature(ClientControl.class);
clientControl.close(sessionID,callback);
.NET
var _clientControl = session.ClientControl;
_clientControl.Close( sessionID, callback );
Related concepts
Session properties on page 275
A client session has a number of properties associated with it. Properties are key-value pairs. Both the
key and the value are case sensitive.
Session filtering on page 276
Session filters enable you to query the set of connected client sessions on the Diffusion server based
on their session properties.
Working with session properties
A client session with the appropriate permissions can view, request, or update the session properties
of another client session.
Session properties
Each client session has a number of properties associated with it. Properties are keys and values. Both
the key and the value are case sensitive. These session properties can be used by other clients to select
sets of client session to perform actions on.
For more information, see Session properties on page 275.
Receiving notifications of client session events and their session properties
Required permissions: view_session
To receive notifications when any client session opens, closes, or is updated, register a listener to
listen for these events:
JavaScript
// Register a listener for session properties
Diffusion | 452
session.clients.setSessionPropertiesListener(diffusion.clients.PropertyKeys.ALL_
.then(function() {
var listener =
session.clients.getSessionPropertiesListener();
listener
.on('onSessionOpen', function(event) {
// The action to take on a client session open
notification
})
.on('onSessionUpdate', function(event) {
// The action to take on a client session update
notification
})
.on('onSessionClose', function(event) {
// The action to take on a client session close
notification
});
}, function(err) {
console.log('An error has occurred:', err);
});
Java and Android
ClientControl clientControl = session.feature(ClientControl.class);
clientControl.setSessionPropertiesListener(
new ClientControl.SessionPropertiesListener.Default() {
@Override
public void onSessionOpen(){
// The action to take on a client session open
notification
}
@Override
public void onSessionEvent(){
// The action to take on a client session update
notification
}
@Override
public void onSessionClose(){
// The action to take on a client session close
notification
}
},
// The session properties to receive
"$Country", "$Department");
.NET
var _clientControl = session.ClientControlGet;
// Set up a listener to receive notification of all sessions
_clientControl.SetSessionPropertiesListener( propertyListener,
"$Country", "Department" );
C
/*
* Register a session properties listener.
*
* Requests all "fixed" properties, i.e. those defined by
* Diffusion rather than user-defined properties.
Diffusion | 453
*/
SET_T *required_properties = set_new_string(5);
set_add(required_properties,
PROPERTIES_SELECTOR_ALL_FIXED_PROPERTIES);
// Set the parameters to callbacks previously defined
SESSION_PROPERTIES_REGISTRATION_PARAMS_T params = {
.on_registered = on_registered,
.on_registration_error = on_registration_error,
.on_session_open = on_session_open,
.on_session_close = on_session_close,
.on_session_update = on_session_update,
.on_session_error = on_session_error,
.required_properties = required_properties
};
session_properties_listener_register(session, params);
When registering this listener, specify which session properties to receive for each client session:
JavaScript
// Receive all fixed properties
session.clients.setSessionPropertiesListener(diffusion.clients.PropertyKeys.ALL_
listener)
.then(function() {
});
// OR
// Receive all user-defined properties
session.clients.setSessionPropertiesListener(diffusion.clients.PropertyKeys.ALL_
listener)
.then(function() {
});
// OR
// Receive all properties
session.clients.setSessionPropertiesListener([diffusion.clients.PropertyKeys.ALL
diffusion.clients.PropertyKeys.ALL_USER_PROPERTIES], listener)
.then(function() {
});
Java and Android
// Define individual session properties to receive
clientControl.setSessionPropertiesListener(
new ClientControl.SessionPropertiesListener.Default() {
// Define callbacks
},
"$Country", "$Department");
// OR
// Receive all fixed properties
clientControl.setSessionPropertiesListener(
new ClientControl.SessionPropertiesListener.Default() {
// Define callbacks
},
Session.ALL_FIXED_PROPERTIES);
// OR
// Receive all user-defined properties
clientControl.setSessionPropertiesListener(
new ClientControl.SessionPropertiesListener.Default() {
// Define callbacks
Diffusion | 454
},
Session.ALL_USER_PROPERTIES);
.NET
// Define individual session properties to receive
_clientControl.SetSessionPropertiesListener(propertiesListener,
"$Country", "Department" );
// OR
// Receive all fixed properties
_clientControl.SetSessionPropertiesListener(propertiesListener,
SessionControlConstants.AllFixedProperties );
// OR
// Receive all user-defined properties
_clientControl.SetSessionPropertiesListener(propertiesListener,
SessionControlConstants.AllUserProperties );
C
// Receive all fixed properties
SET_T *required_properties = set_new_string(5);
set_add(required_properties,
PROPERTIES_SELECTOR_ALL_FIXED_PROPERTIES);
SESSION_PROPERTIES_REGISTRATION_PARAMS_T params = {
//Other parameters
.required_properties = required_properties
};
// OR
// Receive all user-defined properties
SET_T *required_properties = set_new_string(5);
set_add(required_properties,
PROPERTIES_SELECTOR_ALL_USER_PROPERTIES);
SESSION_PROPERTIES_REGISTRATION_PARAMS_T params = {
//Other parameters
.required_properties = required_properties
};
// OR
// Receive all properties
SET_T *required_properties = set_new_string(5);
set_add(required_properties,
PROPERTIES_SELECTOR_ALL_FIXED_PROPERTIES);
set_add(required_properties,
PROPERTIES_SELECTOR_ALL_USER_PROPERTIES);
SESSION_PROPERTIES_REGISTRATION_PARAMS_T params = {
//Other parameters
.required_properties = required_properties
};
When the listening client first registers a listener, it receives a notification for every client session that
is currently open. When subsequent client sessions open, the listening client receives a notification for
those clients.
When the listening client is notified of a session event, it receives the requested session properties as a
map of keys and values.
Diffusion | 455
When the listening client is notified of a session closing, it also receives the reason that the session was
closed. If the client session becomes disconnected from the Diffusion server, the listener might not
receive notification of session close immediately. If reconnection is configured for the client, when the
client disconnects, its session goes into reconnecting state for the configured time (the default is 60
seconds) before going into a closed state.
Getting properties of specific client sessions
Required permissions: view_session
A client can make an asynchronous request the session properties of any client session from the
Diffusion server, providing the requesting client knows the session ID of the target client.
JavaScript
// Get fixed session properties
session.clients.getSessionProperties(sessionID,
diffusion.clients.PropertyKeys.ALL_FIXED_PROPERTIES)
.then(function{
});
Java and Android
// Get fixed session properties
ClientControl clientControl = session.feature(ClientControl.class);
clientControl.getSessionProperties(sessionID,
Session.ALL_FIXED_PROPERTIES, sessionPropertiesCallback);
.NET
var _clientControl = session.ClientControl;
_clientControl.GetSessionProperties( sessionID,
SessionControlConstants.AllFixedProperties, sessionPropertiesCallback
);
C
GET_SESSION_PROPERTIES_PARAMS_T params = {
.session_id = session_id,
.required_properties = properties,
.on_session_properties = on_session_properties
};
get_session_properties(session, params);
Update the properties of specific client sessions
Required permissions: view_session, modify_session
A client session with the appropriate permissions can update the value of existing user-defined session
properties or add new user-defined properties for any client session or set of client sessions.
As part of the update session properties request, provide a map of the keys for the session properties
you want to update or add and the new values. If you provide a null value for a session property, that
property is deleted from the session. A successful update session properties request returns a map of
the updated properties and their old values.
Specify a single session to change the user-defined session properties for the session by providing the
session ID.
Diffusion | 456
Java and Android
// Change the session properties of a single session
ClientControl clientControl = session.feature(ClientControl.class);
final CompletableFuture<Map<String, String>> result =
clientControl.setSessionProperties(sessionID, map_of_properties);
Specify a set of client sessions to change the user-defined session properties for by providing a filter
query expression. For more information about filter query expressions, see Session filtering on page
276.
Java and Android
// Change the session properties of set of sessions defined by a
filter expression
ClientControl clientControl = session.feature(ClientControl.class);
final CompletableFuture<Map<String, String>> result =
clientControl.setSessionProperties(filter, map_of_properties);
Handling client queues
Each client session has a queue on the Diffusion server. Messages to be sent to the client are queued
here. You can monitor the state of these queues and set client queue behavior.
Receiving notifications of client queue events
Required permissions: view_session, register_handler
A client can register a handler that is notified when outbound client queues at the Diffusion server
reach pre-configured thresholds.
Java and Android
ClientControl clientControl = session.feature(ClientControl.class);
clientControl.setQueueEventHandler(
new ClientControl. QueueEventHandler.Default {
@Override
public void onUpperThresholdCrossed(
final SessionId client,
final MessageQueuePolicy policy) {
// The action to perform when the queue upper threshold
is crossed.
}
@Override
public void onLowerThresholdCrossed(
final SessionId client,
final MessageQueuePolicy policy) {
// The action to perform when the queue lower threshold
is crossed.
}
}
);
Diffusion | 457
Handling client queue events
Required permissions: view_session, modify_session
A client can respond to a client queue getting full by setting conflation on for the client. Conflation
must be configured at the Diffusion server to have an effect.
A client is also able to set throttling on for specific clients, which also sets conflation. Using throttling
without conflation can result in client queues overflowing.
Always use throttling and conflation in conjunction with a well-designed conflation strategy
configured at the Diffusion server. For more information, see Conflation on page 90 and Configuring
conflation on page 525.
Java and Android
ClientControl clientControl = session.feature(ClientControl.class);
clientControl.setThrottled(client, MESSAGE_INTERVAL, 1000,
clientCallback);
.NET
var clientControl = session.GetClientControlFeature();
clientControl.SetThrottled( client, ThrottlerType.MESSAGE_INTERVAL,
10, theClientCallback );
Flow control
A client application rapidly making thousands of calls to the Diffusion server might overflow
the internal queues for the client, which results in that client session being closed. Flow control
automatically protects against these queues overflowing by progressively delaying messages from the
client to the Diffusion server.
Supported platforms: Android, Java, .NET, and C
The flow control mechanism is a feature of the Diffusion client libraries that works automatically to
protect the following internal queues for an individual client from overflowing:
•
•
The outbound queue on the client where messages are queued to be sent to the Diffusion server
The queue on the Diffusion server where responses to service requests are queued.
If these queues overflow, the client session is terminated.
Flow control is intended to benefit those clients that send a lot of data to the Diffusion server – for
example, updates to publish to topics. Usually, these clients are those located in the back-end of your
solution that perform control functions.
When is flow control enabled?
The client determines whether to enable flow control and the amount of delay to introduce into the
client processing based on a calculated value called back pressure. Back pressure is calculated using
the following criteria:
•
•
•
Depth of the outbound client queue
The number of pending responses to service requests
Whether the current active thread is a callback thread
Back pressure can have a value between 0.0 and 1.0. 1.0 is the maximum amount of back pressure. The
method used to calculate back pressure might be subject to change in future releases.
Diffusion | 458
Flow control introduces sleeps into the client processing. The length of these sleeps depends on the
value of the back pressure. The maximum amount of delay introduced into client processing by flow
control is 100 ms. The amount of delay introduced by flow control might be subject to change in future
releases.
The flow control behavior of a client cannot be configured.
How to tell that flow control is enabled
When flow control is enabled for a Android, Java, or .NET client, the client logs messages at DEBUG
level. The client logs each time a delay is introduced. The log message has the following form:
2016-09-26 11:15:48,344 DEBUG [PushConnectorPool-thread-18]
c.p.d.f.SleepingFlowControl(apply) - pressure=1.0 => sleep for 100
ms
The log message includes the current back pressure and the length of delay introduced.
The C client does not log its flow control behavior.
Actions to take
Diffusion clients can occasionally become flow controlled in response to very heavy load or unusual
network conditions. However, if your clients are constantly being flow controlled, your Diffusion
solution might not be correctly configured for the traffic load.
Consider taking the following actions:
•
•
•
In your client design, ensure that if you have many requests to make to the Diffusion server that
these requests are made from an application thread instead of a callback thread. Less flow control
is applied when the active thread is a callback thread. For more information, see Best practice for
developing clients on page 148.
Ensure that your Diffusion server can handle the incoming messages from the clients. The default
memory configuration might be causing the JVM running the Diffusion server to spend a lot of time
in GC. For more information about tuning your JVM, see Memory considerations.
Increase the maximum queue size on the connector your client uses. This can be configured for
individual connectors in the Connectors.xml configuration file or as a default value for all
connectors in the Server.xml configuration file. For more information, see and .
Logging from the client
Ensuring that your Diffusion client logs messages to inform of events and errors can be a valuable tool
in developing and maintaining your clients.
Logging in JavaScript
The JavaScript client library logs messages to the console.
Log levels
Events are logged at different levels of severity. The log levels, ordered from most severe to least
severe, are as follows:
Diffusion | 459
Table 32: Log levels
Level
Description
error
Events that indicate a failure.
warn
Events that indicate a problem with operation.
info
Significant events.
debug
Verbose logging. Not usually enabled for production.
trace
High-volume logging of interest only to Push Technology Support. Push
Technology Support may occasionally ask you to enable this log level to
diagnose issues.
Configuring logging in the JavaScript client
You can use the JavaScript API to enable and configure logging at runtime.
diffusion.log(level)
To disable logging at runtime, set the level to silent.
diffusion.log('silent')
Note: Do not enable logging in your production clients. Use logging only during development
of your clients.
Logging in Apple
The Apple client logs messages to the Apple system log facility.
Log levels
Events are logged at different levels of severity. The log levels, ordered from most severe to least
severe, are as follows:
Table 33: Log levels
Level
Description
ERROR
Events that indicate a failure.
WARN
Events that indicate a problem with operation.
INFO
Significant events.
DEBUG
Verbose logging. Not usually enabled for production.
TRACE
High-volume logging of interest only to Push Technology Support. Push
Technology Support may occasionally ask you to enable this log level to
diagnose issues.
Configuring logging in the Apple client
You can use the Apple API to enable and configure logging at runtime.
PTDiffusionLogging *const l = [PTDiffusionLogging logging];
Diffusion | 460
// Enable logging in the client library
l.enabled = YES;
// Change the level that the client logs at
l.level = [PTDiffusionLoggingLevel trace];
Note: Do not enable logging in your production clients. Use logging only during development
of your clients.
Logging in Android
The Android client uses slf4j-android-logger to log messages to the Android logging system.
Log levels
Events are logged at different levels of severity. The log levels, ordered from most severe to least
severe, are as follows:
Table 34: Log levels
Level
Description
ERROR
Events that indicate a failure.
WARN
Events that indicate a problem with operation.
INFO
Significant events.
DEBUG
Verbose logging. Not usually enabled for production.
TRACE
High-volume logging of interest only to Push Technology Support. Push
Technology Support may occasionally ask you to enable this log level to
diagnose issues.
Configuring logging in the Android client
The Android JAR, diffusion-android-x.x.x.jar, contains a properties file,
logger.properties. Edit this properties file to configure logging in the Diffusion Android client.
The default logger.properties file contains the following properties:
de.psdev.slf4j.android.logger.logTag=DiffusionAndroidClient
de.psdev.slf4j.android.logger.defaultLogLevel=INFO
For more information about slf4j-android-logger, see https://github.com/PSDev/slf4j-android-logger
Logging in Java
The Java client uses SLF4J to log messages. Provide a bindings library to implement the SLF4J API and
log out the messages.
Log levels
Events are logged at different levels of severity. The log levels, ordered from most severe to least
severe, are as follows:
Diffusion | 461
Table 35: Log levels
Level
Description
ERROR
Events that indicate a failure.
WARN
Events that indicate a problem with operation.
INFO
Significant events.
DEBUG
Verbose logging. Not usually enabled for production.
TRACE
High-volume logging of interest only to Push Technology Support. Push
Technology Support may occasionally ask you to enable this log level to
diagnose issues.
Configuring logging in the Java client
The Java JAR, diffusion-client-x.x.x.jar, uses the SLF4J API to log messages. It does not
include an implementation that outputs the log messages.
Many SLF4J implementations are available.
1. Choose your preferred SLF4J implementation.
2. Ensure that the bindings JAR is on the classpath of your Java client.
3. Configure the SLF4J implementation to provide the logging behavior you require.
Log4j2
Log4j2 is a third-party SLF4J implementation provided by the Apache Software Foundation. For more
information, see http://logging.apache.org/log4j/2.x/.
You can configure your Java clients to use log4j2 by completing the following steps:
1. Get the log4j2 bindings libraries.
The JAR files can be downloaded from https://logging.apache.org/log4j/2.0/download.html.
The log4j2 JAR files are also located in the lib/thirdparty directory of the Diffusion
installation.
2. Ensure that the log4j-api.jar and log4j-core.jar files are on the client classpath.
3. Create a configuration file and ensure that is present on the client classpath.
The following example log4j2.xml file outputs the log messages to a rolling set of files:
<Configuration status="warn" name="DiffusionClient">
<Properties>
<Property name="my.log.dir">../logs</Property>
<!-- The log directory can be be overridden using the
system property 'my.log.dir'. -->
<Property name="log.dir">${sd:my.log.dir}</Property>
<Property name="pattern">%date{yyyy-MM-dd HH:mm:ss.SSS}|
%level|%thread|%marker|%replace{%msg}{\|}{}|%logger%n%xEx
</Property>
</Properties>
<Appenders>
<RollingRandomAccessFile name="file" immediateFlush="false"
fileName="${log.dir}/client.log"
Diffusion | 462
filePattern="${log.dir}/$${date:yyyy-MM}/client-%d{MMdd-yyyy}-%i.log.gz">
<PatternLayout pattern="${pattern}" />
<Policies>
<OnStartupTriggeringPolicy />
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB" />
</Policies>
<DefaultRolloverStrategy max="20" />
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<AsyncRoot level="info" includeLocation="false">
<AppenderRef ref="file" />
</AsyncRoot>
</Loggers>
</Configuration>
For more information about configuring log4j2, see https://logging.apache.org/log4j/2.0/manual/
configuration.html.
Logging in .NET
The .NET API produces logging information. The logging facility uses NLog.
NLog is included in the .NET client assembly. For more information about logging with NLog, see
https://github.com/NLog/NLog/wiki/.
Logging basics
The NLog Logger class acts as the source of the log messages. In general, use one logger per class
and pass in the name of the class as the logger name.
To log a message at a certain level use the write methods provided by the Logger class.
The following log levels are provided:
•
•
•
•
•
•
Fatal
Error
Warn
Info
Debug
Trace
These levels are listed in order from most severe to least severe.
Configuring the log output
You must configure NLog to output the log messages produced by your application to a target or
targets. NLog provides a large number of targets for your output, including File and Console. NLog also
enables you to create your own targets.
In addition to specifying targets for the log output, use NLog configuration to define rules that specify
to which target log messages with particular levels or logger names are directed.
You can configure NLog in the following ways:
Diffusion | 463
•
•
Using a configuration file, NLog.config, that is located in the same directory as your client
application.
For more information, see https://github.com/NLog/NLog/wiki/Configuration-file.
Using the Configuration API to configure NLog in your application code.
For more information, see https://github.com/NLog/NLog/wiki/Configuration-API.
Logging in C
The C API provides no integrated logging feature. You can log out from your client code using your
preferred method or framework.
Developing a publisher
You can develop a publisher in Java by using the Publisher API.
Note: We recommend using a client to create and publish to topics, instead of a publisher.
Publisher basics
A publisher is a user-defined object deployed within a Diffusion server which provides one or more
topics on which it publishes messages to clients.
There can be one or more publishers deployed with a Diffusion server.
Clients connect to the server and subscribe to topics. Messages relate to topics and when a publisher
publishes a message it is broadcast to all clients that are currently subscribed to the message topic. A
publisher can also send messages to individual clients and receive messages sent from clients. Clients
can request (fetch) topic state, even when not subscribed.
A publisher must be written by the user in Java (utilizing the publisher API) and deployed within the
server. This is done by extending a supplied publisher class and implementing methods as required.
Implement the methods relating to the functionality that you require. For more information, see
Writing a publisher on page 471.
Defining publishers
How to define publishers that start with Diffusion.
The Diffusion server is able to start the publishers defined in the etc/Publishers.xml file when
the server starts. The XML file can contain any number of publishers. Each publisher must have at least
a name and a class. The class must implement the publisher by extending the Publisher class For
more information, see Creating a Publisher class on page 472.
<publishers>
<publisher name="Publisher">
<class>com.example.Publisher</class>
</publisher>
...
</publishers>
The name must be unique on the server, and the class must exist on the classpath of the Diffusion
server (For more information, see Classic deployment on page 789). This is sufficient for the publisher
to start when Diffusion does. There are other options, including those that can prevent the publisher
from starting.
Diffusion | 464
When the enabled element is false, the publisher class is not loaded. If the start element is false,
the publisher is not started when the server starts.
You can define properties in the etc/Publishers.xml that can be accessed from the publisher.
For more information, see .
The full configuration file options can be found in the XSD document for the etc/Publishers.xml
or in .
Loading publisher code
This describes how to load publisher classes or code it is dependent upon.
When you write a publisher class (or any other classes it uses), you can deploy them in any folder
as long as it is specified in the configuration (usr-lib in etc/Server.xml ). JAR files can also be
deployed in user libraries and any other software libraries that the publisher requires can be specified
in this way.
Also, when Diffusion starts, the data directory is on the class path. The ext folder, and its subdirectories are scanned for jar files and class loaded. This means that you can easily add new jars to
the Diffusion runtime, without having to edit the startup scripts.
Take care when creating backup jars in the ext folder as anything that ends in .jar is class loaded.
Related tasks
Building a publisher with mvndar on page 498
Use the Maven plugin mvndar to build and deploy your publisher DAR file. This plugin is available from
the Push Public Maven Repository.
Load publishers by using the API
You can configure and load custom publishers using the Diffusion API at any point in the Diffusion
server's lifecycle.
Similarly to loading publishers using configuration files, each publisher must have at least a name
and a class. The class must implement the publisher by extending the Publisher class. For more
information, see Creating a Publisher class on page 472.
PublisherConfig config =
ConfigManager.getServerConfig().addPublisher("MyPublisher",
"com.acme.foo.MyPublisher");
Publisher publisher = Publishers.loadPublisher(config);
The name must be unique on the server, and the class must exist on the classpath of the Diffusion
server. For more information, see Classic deployment on page 789. By default the autostart
property is enabled on the PublisherConfig, so the publisher starts once it is loaded. If this option is
disabled, you can load a publisher and retain a reference to it, to start at a later point in time.
If the default configuration options are suitable for your requirements (as detailed within the API docs
for com.pushtechnology.diffusion.api.config.PublisherConfig) there are several
convenience methods that can be used to load a given publisher and get a reference to it without the
need for construction a specific PublisherConfig instance.
// Create Publisher with classname
Publisher publisher = Publishers.createPublisher("MyPublisher",
"com.acme.foo.MyPublisher");
// Create Publisher with Class
Diffusion | 465
Publisher publisher = Publishers.createPublisher("MyPublisher",
MyPublisher.class);
You can load a default publisher instance. This facilitates programmatic access any features exposed
through the publisher abstract class that do not require method overriding.
Publisher publisher =
Publishers.createPublisher("MyDefaultPublisher");
Starting and stopping publishers
Typically publishers are started when the server starts but you can prevent such automatic start up
and allow publishers to be started using System Management.
Publishers can also be stopped and restarted using System Management functions and are
automatically stopped and removed when the server closes.
In order for a publisher to function properly on being stopped and restarted from System Management
it must be able to cater for the integrity of its data and client connections. For this reason a publisher
cannot be stopped by default and must override the isStoppable method to enable this
functionality.
Publisher startup steps
When a publisher is started it goes through its initial processing in the order shown below:
Table 36: Start publisher
Add initial topics
Initial topics configured for the publisher are added.
initialLoad
The initialLoad notification method is called. This can be
used to perform any initial processing required for the publisher.
Topics can be added here. Other aspects of the publisher, such
as topic loaders and client listeners can also be set up here. If an
exception is thrown by this method, the publisher fails to start.
STARTED
At this point the publisher is considered to have started.
publisherStarted
The publisherStarted notification method is called.
Publisher closedown steps
When a publisher is stopped, either during server closedown or by System Management it goes
through the following steps:
Table 37: Stop publisher
publisherStopping
The publisherStopping notification method is called to allow
the publisher to perform any preliminary close processing.
Remove topics
All topics owned by the publisher are removed.
STOPPED
At this point the publisher is considered to be stopped.
publisherStopped
The publisherStopped notification method is called.
Client events Stopped
Client event notifications are stopped.
Diffusion | 466
Publisher removal
A publisher is removed after it is stopped during server closedown but you can also remove a stopped
publisher at any time using System Management. Once removed a publisher cannot be restarted again
until the server is restarted.
In either case, after removal the publisherRemoved notification method is called.
Publisher topics
Topics are the mechanism by which publishers provide data to clients.
Each publisher can provide one or more topics but each topic must be unique by name within the
server. Topics are hierarchical in nature and so topics can be parents of topics and a tree of topics
can be set up. Using hierarchies allows clients to subscribe to branches of the hierarchy rather than
having to subscribe to individual topics. Only the owner of a topic can create new topics below it in the
hierarchy.
Adding topics
In the simplest case a publisher can name the topics it provides within its configuration. In this case
such topics are automatically added as the publisher is started. These topics can be obtained from
within the publisher using the getInitialTopicSet method.
More typically a publisher adds the topics it requires itself as it starts up. A Publisher can choose to
add some topics at start up and others later. Topics can be added at any time using the publisher's
addTopic or addTopics method. They can be added only if they are added by the owner of the
parent topic.
A topic can be a simple topic where all of the handling of the topic state is provided by the publisher.
Alternatively a topic can be created with topic data which handles the state of the topic automatically.
As soon as a topic has been added clients can subscribe to it.
Loading topics
Simple topic processing involves sending all of the data that defines a topic (the topic load) to a client
when they first subscribe and then subsequently sending deltas (or changes to the data). There are
two mechanisms for performing the topic load:
Send on subscribe
When the publisher is notified of subscription it creates, populates and sends a topic
load message to the client.
Topic loaders
Define a topic loader for the topic which is automatically called to perform the topic
loading when a client subscribes.
If a topic has topic data, the current state is automatically provided to a client when they subscribe.
Subscribing clients to topics
Clients normally request subscription to a topic and if the topic exists the clients become subscribed to
it at that point.
A client can subscribe to a topic that does not exist at that time – this is called pre-emptive
subscription. When the publisher creates a topic, any clients that have pre-emptively subscribed to a
topic are subscribed to that topic automatically.
A publisher can also force all currently connected clients to become subscribed to a topic by calling
subscribeClients with force=true.
Diffusion | 467
Subscribing clients to topics that they were already subscribed to causes the topic load to be
performed again.
A publisher can also cause individual clients to be subscribed to a topic using the client's subscribe
method or unsubscribed using the unsubscribe method.
Providing topic state
A publisher can provide state on request for stateless topics. Implement the publisher method
fetchForClient to respond to fetch requests that clients make to stateless topics. Stateful topics
return values to fetch requests automatically.
Handling topics that do not exist
A topic is an entity that notionally has state but in some circumstances a client might request access
to a topic that does not exist. Client notifications provide a mechanism whereby this situation can be
handled.
Where a client attempts to subscribe to a topic that does not exist, a
clientSubscriptionInvalid notification occurs which gives the publisher the opportunity to
dynamically create the topic (and subscribe the client to it) if that is what is required.
Where a client attempts to fetch the state of a topic that does not exist, a clientFetchInvalid
notification occurs which gives the publisher the opportunity to return a response to the fetch request
(using sendFetchReply) even if the topic does not exist. This can provide an efficient request/response
mechanism without the overhead of actually creating topics.
Removing topics
A publisher can also remove topics at any time using its removeTopic or removeTopics methods.
Removing a topic causes all clients that are subscribed to it to be unsubscribed.
Receiving and maintaining data
A publisher can obtain the data it is to publish and transform that data in any way that is appropriate.
The publisher maintains the state of its own data by updating it whenever any changes are received so
that as a new client subscribes it can be sent the latest state of the data as a whole. As such changes
are received they are also published as deltas to all currently subscribed clients.
Receiving messages from a remote service
Remote service can also provided a data feed into a publisher. The remote service can publish to
topics and the updates are applied to the topics and passed onto subscribed clients.
Publishing and sending messages
Publishing messages to clients and sending messages to clients
Creating messages
Messages can be created using the factory methods on the publisher or on a topic for creating
messages (called createLoadMessage and createDeltaMessage).
If within a class that does not have a direct reference to the publisher or topic objects, the equivalent
static methods in the Publishers class can be used. Messages can be populated with data using the
many variants of the put method.
Diffusion | 468
Publishing messages
Messages (whether load or delta) can be sent to all clients that are subscribed to the message topic.
For stateless topics, use Topic.publishMessage(). For stateful topics (those with topic data),
use PublishingTopicData.publishMessage().
Exclusive publishing
You might want to publish a message to all but a particular client. For example, a message can be sent
to the publisher from a client and the publisher can, publish the message to all of the other subscribed
clients.
This is done using the publisher's publishExclusiveMessage method.
Sending messages to individual clients
To send a message to an individual client the Client.send method can be used.
Publisher notifications
A publisher is notified of certain events by certain methods on it being called. These methods can be
overridden by the user to perform processing at these points as required.
By default these methods (other than those indicated) perform no processing. You do not have to
override any of these methods unless you choose to. The notification methods are:
Table 38: Notification methods
initialLoad
Called when the publisher is first loaded. Is
typically overridden to perform any initial
processing required to prepare the publisher.
publisherStarted
Called after initialLoad (see startup steps).
subscription
Called when a client subscribes to a topic that the
publisher owns. References to the topic and the
client are passed and also a flag to indicate if the
topic has already been loaded by a TopicLoader.
If the topic has not been loaded already, typically
a publisher sends an initial load message to the
client at this point. It might not be necessary to
override this method if topic loaders are in use.
unsubscription
Called when a client unsubscribes from a topic
that the publisher owns.
messageFromClient
Called when a message is received from a client.
References to the message and the client are
passed.
messageFromServer
Called when a message is received from a server
connection. References to the message and the
server connection are passed.
fetchForClient
Called when a client requests a fetch of the topic
state for stateless topics.
messageNotAcknowledged
DEPRECATED: acknowledgements have been
removed and this method will not be called.
Diffusion | 469
publisherStopping
This is called when the publisher has been
requested to stop. It gives the publisher
the opportunity to tidily perform any close
processing.
publisherStopped
This is called after a publisher has stopped.
The publisher can still be restarted (but only if
isStoppable is true).
publisherRemoved
This is called when a publisher is removed and
provides the opportunity for final tidy up. The
publisher cannot be restarted after this is called.
systemStarted
This is called when the Diffusion system has
completed loading and is ready to accept
connections. Publishers are started before
connectors, so this notification is used all
Diffusion sub systems are loaded.
Publisher notification threads
To understand issues of concurrency when writing a publisher it is necessary to understand in which
threads the various publisher notifications occur.
When a message or request is received from a client connection, the inbound thread pool is used to
process it. Depending upon the number of threads in the pool this can mean that the publisher can
receive such notifications concurrently.
Other notifications come from various control threads.
All of the above considerations mean that concurrency must always be taken into account in publisher
code and it must be made thread safe as appropriate.
Client handling
A publisher can receive notifications about and perform actions on individual clients.
Closing/Aborting clients
A publisher can close a client at any time using the close method. This disconnects the client which
might choose to subsequently reconnect.
Alternatively a publisher can use the abort method, which sends an abort notification to the client
before disconnecting it. A client receiving an abort notification must not attempt to reconnect.
Client notifications
A publisher can choose to receive additional client notifications so that it can be informed when clients
connect, disconnect etc.
Client pings
A client ping message is one that can be sent to a client which reflects it back to the server to measure
latency. A publisher can send a ping message to a client using the Client.ping method and
receives a response on the ClientListener.clientPingResponse method within which the
message passed can be queried to establish the round trip time.
Client message filtering
You can filter the messages that get queued for any particular client. For more information, see .
Diffusion | 470
Publisher properties
Properties for a publisher are defined in the etc/Publishers.xml configuration file.
As well as the standard properties a publisher can have user-defined properties. These properties
can be read using convenience methods available on the publisher (for example, getProperty,
getIntegerProperty etc).
Using concurrent threads
Often within a publisher you might have to initiate some processing in a separate thread so that the
publisher itself is not blocked.
For example, a thread can be used to poll data from some data source.
Diffusion provides a mechanism for easily managing concurrent processing using the threads API.
Publisher logging
Every publisher is assigned its own Logger which can be used within the publisher itself for logging
diagnostic messages.
This Logger is obtained using the getLogger method.
The log level of the publisher can be changed dynamically at any time using the setLogLevel
method.
General utilities
General purpose utilities that can be used from within a publisher
There are a number of general purpose utilities available which can aid in the process of writing a
publisher, for example:
Table 39: General publisher utilities
Utils
A set of general purpose utilities which include
file handling, property handling, date and time
formatting and more.
XMLUtils
A set of utilities to aid in the processing of XML.
HTTPUtils
A set of utilities to aid in HTTP processing.
Writing a publisher
How to approach writing a publisher
Note: This section covers only the main aspects of the publisher API. See the API
documentation for full details.
There are demo publishers issued with Diffusion which have the source provided and these act as
examples of working publishers.
In its simplest sense a publisher is responsible for providing topics, and publishing messages relating
to those topics.
Before a publisher is written you need to carefully consider what it needs to do and what methods
need to be implemented. The areas that need to be considered and the methods relating to them are
discussed in the following sections.
Diffusion | 471
Related concepts
Classic deployment on page 789
Installing publishers into a stopped Diffusion instance.
Creating a Publisher class
A publisher is written by extending the abstract Publisher class (see Publisher API) and overriding
any methods that must be implemented to achieve the functionality required by the publisher.
In all but the simplest of publishers it is likely that other classes must be written to perform the
functionality required of the publisher.
The considerations of which methods must be overridden are discussed further within this section.
After the class is written and compiled, you can deploy it in the Diffusion server by specifying its details
in etc/Publishers.xml
Publishers can also be deployed as a DAR file, sidestepping etc/Publishers.xml
See the section on testing for information about how to test the publisher.
Related tasks
Building a publisher with mvndar on page 498
Use the Maven plugin mvndar to build and deploy your publisher DAR file. This plugin is available from
the Push Public Maven Repository.
Publisher startup
When a publisher is first loaded by the Diffusion server it can also be automatically started.
If not automatically started (or if it has been manually stopped), a publisher can be manually started
by using the System Management interface. In either case the publisher processing goes through a
number of startup steps. During these steps the initialLoad and publisherStarted methods
are called and these methods can be implemented by the publisher to perform any initial processing
like setting up the initial data state or adding initial topics.
Data state
A publisher typically holds some data on topics which it updates according to any data update events
it might receive.
The data held by the publisher on the topics it provides is referred to as its state. It is up to the
publisher whether the data state is managed as a whole or on topic by topic basis.
It is the responsibility of the publisher to initialize its state and keep it updated as appropriate. Clients
that subscribe to topics usually want to know the current state of the data relating to that topic and
the publisher provides this as an initial topic load message. Clients are notified of all changes to that
state by the publisher sending out delta messages.
A publisher typically has its own data model represented by classes written to support the data for
the publisher. Ways in which such a data model can be managed are discussed in Designing your data
model on page 47.
Initial state
A publisher's data typically has some initial state which can be updated during the life of the publisher.
The state clearly must be set up before a client requires it but exactly when this is done is up to the
publisher.
Diffusion | 472
The state of the data as a whole can be set up when the publisher starts. This can be done in the
initialLoad method where all topics required can be set up and the data loaded as appropriate.
Alternatively, the state of the data relating to a topic can be initialized when the topic is added, which
is not necessarily when the publisher is started.
Another option is that the initial state is provided by a data feed as it connects (or is connected to). If
data is provided by a server connection, the initial state can be set up when the server connection is
notified to the publisher or more typically the server provides an initial topic load message.
Data integrity
The integrity of the data is also the responsibility of the publisher and care must be taken to ensure
that all updating of data state is thread-safe. For example, it must be borne in mind that a client can
request a load of current state (for example, by subscription) at the same time as the state is being
updated.
Note: The topic data feature automatically handles such data locking and in other cases
topics might be locked as and when required.
Providing data state
If clients are to use the fetch facility to obtain the current state of topics, it will be necessary to
consider the implementation of the fetchForClient method of the publisher.
Stateful and stateless topics
The topics that the publisher provides can store data state, but not all topics store data state. Topics
that store data state are called stateful topics. Topics that do not store data state are called stateless
topics.
The publisher has different mechanisms for publishing data through stateful or stateless topics. For
more information, see Publishing messages on page 475.
Data inputs
For a publisher to be able to publish data to clients it must have a source for that data.
The data can be obtained from some type of feed, perhaps provided by some external API or it can
be from some other application communicating using Diffusion protocols. This is entirely up to the
publisher but Diffusion does offer some mechanisms.
Control clients
A publisher can receive input from a control client.
Control clients can use the TopicUpdateControl feature to publish messages to topics. Where such
topics have topic data the topic state is automatically updated and deltas are published to subscribed
clients. Where topics do not have topic data, published messages are forwarded to subscribed clients
(that is, it is assumed that the control client maintains the data state).
Control clients can also send messages to specific clients and these are forwarded to the clients
automatically.
Diffusion | 473
Handling client subscriptions
Clients subscribe to topics provided by publishers and whenever this occurs the publisher is
notified through its subscription method. The publisher can perform any processing it requires on
subscription.
Performance considerations
Any queries about subscriptions are expensive on resources and time, because these queries are
synchronous and blocking. For example, querying whether a client is subscribed to a topic, what
clients are subscribed to a specific topic, or what topic a specific client subscribes to.
If your publishers respond to add topic notifications or subscription notifications, ensure that these
responses are efficient. These publisher actions are now serialized in a single thread and as a result the
publisher can become a bottleneck and hold up processing.
Using topic data
Where a topic is inherently stateful and has associated data, the use of topic data is recommended.
Topic data automatically handles topic loading.
Topic loading
Typically, on subscription, the publisher provides the client with the current state of the data for the
topic. It can do this by creating a new topic load message and populating it with a representation of
the state. Rather than doing this every time a client subscribes it is generally more efficient for the
publisher to create a topic load message only when the state changes and send this same message out
to every client that subscribes.
This provision of the current state is known as the topic load. This can be done in one of the following
ways:
Topic load in subscription method
If the topic has not already been loaded by a topic loader (see below), the loaded parameter of the
subscription method is false. In this case, the normal action is for the publisher to send a topic load
message to the client (passed as a parameter to subscription) through its send method.
Topic loaders
A topic loader is an object that implements the TopicLoader interface and can be used to perform
any topic load processing that is required for one or more topics. Topic loaders can be declared for
a Publisher using the Publisher.addTopicLoader method. This is typically done in the
initialLoad processing and must be done before any topics that are loaded by the topic loader are
added.
Hierarchic subscription
When a client subscribes to a topic the publisher can choose to subscribe the client to other topics or
to subordinate topics. This can be done using the Client.subscribe methods.
A client itself can request subscription to a hierarchy of topics using topic selectors but this is an
alternative method of handling hierarchies.
Diffusion | 474
Publishing messages
Publishing a message means sending it to all clients subscribed to a topic. The message itself
nominates the topic to which it relates.
A message for publishing can be created and populated by the publisher and then published using
publishing methods on the topic or the publisher itself.
Exclusive messages
To send a message from a publisher to all clients subscribed to a topic except one single client, it can
use the publishExclusiveMessage method. This might be appropriate if the message being published is
a result of receiving a message from a client which you do not want to send back to that client.
Message priority
The priority at which a message is to be sent can be altered from the normal priority. For example, an
urgent message can be sent with high priority causing it to go to the front of the client's queue.
DEPRECATED: Message acknowledgment
Note: Acknowledgements have been removed from the product. The Publisher API methods
and configuration that control acknowledgements now have no effect and are deprecated.
Publishing using stateful topics
Stateful topics are topics that store a current value in the Diffusion server. You can publish using
stateful topics in a simple way or as part of a more complex transactional update.
This section covers working with topics that have associated topic data that extends the
PublishingTopicData interface.
Simple updates to a stateful topic
Use the updateAndPublish or updateAndPublishFromDelta method on the topic data of a
stateful topic to update the topic state. Updating the topic data of a stateful topic publishes a delta to
all subscribed client that includes the changes to the topic data.
topic.getData().updateAndPublish(update);
// OR
topic.getData().updateAndPublishFromDelta(deltaUpdate);
Transactional updates to a stateful topic
Stateful topics can be updated transactionally by bracketing the updates with the startUpdate and
endUpdate methods of the associated topic data.
Combining updates to the topic data as part of a single transaction can be useful when the stateful
topic is a record topic that has multiple records and fields that can be updated from separate sources.
These fields can be updated separately within the transaction, but all updates in the topic state are
published to the subscribing clients at the same time.
// Start the transaction
data.startUpdate();
try {
// Make multiple updates as part of a single transaction
data.update(firstUpdate);
data.update(secondUpdate);
data.update(thirdUpdate);
data.update(fourthUpdate);
data.update(fifthUpdate);
Diffusion | 475
// Publish the updates that have been made in this transaction
if (data.hasChanges()){
data.publishMessage(data.generateDeltaMessage());
}
}
finally {
// Complete the transaction
data.endUpdate()
}
Publishing using stateless topics
Stateless topics are topics that have no associated current value in the Diffusion server. You can
publish using a stateless topic in a simple way or as part of a complex action triggered by a client
subscription to that topic.
Stateless topics have no associated topic data. These topics simply pass through any updates that are
made to them to the subscribing clients.
Simple updates using a stateless topic
Use the publishMessage method on the topic to publish data using the stateless topic at any time.
This data is not stored on the Diffusion server and is sent as-is to all current subscribers to the stateless
topic.
topic.publishMessage(data);
On-subscription updates using a stateless topic
Stateless topics pass through data from the publisher to the subscribing clients. This published data
can be a full update or a delta on previous updates. If a client subscribes to a stateless topic after a full
update and before a delta, the client receives the delta, but does not have the base data to apply it to.
To ensure that a newly subscribing client receives a full update for that topic, the publisher
subscription method — which is called every time a client subscribes to a topic managed by the
publisher — can publish an update that contains all the data required by the subscriber to that topic.
This data is not published until the client subscription to the topic is complete.
Using the subscription method can cause performance issues. For more information, see
Handling client subscriptions on page 474.
To publish a message to the topic whenever a client subscribes to the topic, override the
Publisher.subscription() method in your own publisher class and include in the method a
call to topic.publishMessage() that passes in all the data to publish.
@Override
protected void subscription(final Client client, final Topic
topic, final boolean loaded)
throws APIException {
// Do the required processing to create the full update
message to
// publish for the newly subscribed client.
// Publish that message to the stateless topic
topic.publishMessage(data);
}
Diffusion | 476
Handling clients
Interacting with clients from within a publisher
A publisher is notified when a client subscribes to one of its topics through the subscription
method and when the client unsubscribes the unsubscription method is called.
A publisher can receive message from clients and send messages to clients (see below).
A client can request the state of any topic or topics at any time even if not subscribed to it. This is
referred to as 'fetch' request. Such a request can routed to the publisher's fetchForClient method
if a topic has no topic data.
Other than the above, a publisher is not normally notified of any other client activity. However a
publisher can choose to receive client notifications using the Publishers.addEventListener
method. Using client notifications, a publisher can even handle a fetch request for a topic that does
not exist and return a reply (using Client.sendFetchReply) without the overhead of actually
creating a topic.
A publisher can also choose to close or abort clients.
Sending and receiving client messages
In addition to publishing messages to all clients subscribed to a topic, you can send a message to only
a single client using the Client.send method.
A client can also send messages to the publisher and these are received on the
messageFromClient method which handles them accordingly. Only implement this method if
messages are expected from clients. Alternatively the publisher specifies topic listeners to handle the
messages on a per topic basis.
The message is mapped to a delta TopicMessage.
Publisher closedown
A publisher is stopped and removed when the Diffusion server closes but can also be stopped and
restarted, or stopped and removed by using the System Management interface.
However a publisher is stopped it always goes through a set of closedown steps, during which the
publisherStopping and publisherStopped methods are called. A publisher can implement
these methods if required to perform any special processing such as tidying up resources used.
Publisher removal
When a publisher is finally removed (either during server closedown or by using System Management),
it cannot be restarted again within the same server instance. After removal the publisherRemoved
method is called and this gives the publisher the opportunity to perform any final tidy up processing.
Stopping and restarting using System Management
By default, you cannot stop and restart a publisher using the System Management functions because
in order for this to work the publisher must cater for the integrity of its state when this happens. As
topics are also removed during stopping, the publisher must also be able to restore these topics if it
were restarted.
If a publisher does want to cater for stop and restart using System Management, it must override the
isStoppable method to return true. The publisher code must be able to recover topics and data
state on restart.
Diffusion | 477
Testing a publisher
There are various ways you can test your publishers after you have written them and deployed them
on a Diffusion server instance.
The easiest way to perform some initial tests is to start it and try it out using some of the supplied
testing tools. For example, use the JavaScript test tool provided from the landing page (http://
localhost:8080), connect each to the test server and subscribe to the publisher's topic or topics. The
initial topic load data is displayed and any messages sent as deltas are also displayed in each client.
This tool can also be used to send messages to the publisher from the client.
Test as soon as possible with the actual clients that are going to be used. So, for example, you might
want to develop browser clients.
It can help to diagnose problems with the publisher if it has diagnostic logging encoded within it. Such
logging can be provided only at fine level and this logging level used only during testing.
Client queues
How messages sent to clients are queued and how such queues can be manipulated by publishers
The Diffusion server maintains an outbound queue of messages for each client. Whenever a message
is published to a topic, it is placed in the queue of each client subscribed to that topic as will any
message sent explicitly to the client. Messages are sent to the client strictly in the order that they are
enqueued.
Figure 28: The message queue
A publisher is able to enquire upon the details of a particular client's queue and even to change some
aspects of the queue's behavior.
Queue enquiries
How the publisher can access details of client queues
A publisher can enquire upon the following information about a particular client's queue using the
client interface:
•
•
•
The current queue size
The maximum queue size (The limit the queue can reach before the client is automatically
disconnected.)
The largest queue size (The largest size the client queue has been since the client connected.)
Diffusion | 478
Maximum queue depth
To limit the backlog of messages queued for a client that is not consuming them quickly enough you
can indicate a maximum queue depth for clients.
Choose this size carefully as a large queue size can lead to excessive memory usage and vulnerability
to Denial of Service attacks, whilst a small queue size can lead to slow clients being disconnected too
frequently.
The maximum queue depth for clients can be configured for a client connector in etc/
Connectors.xml. A default value can also be configured in etc/Server.xml for connectors that
do not explicitly specify a value.
These values can be changed dynamically at run time using System Management but they only take
effect for new clients.
Queue notification thresholds
A publisher can receive notifications when a client queue has reached a certain size and use this
information to decide whether or not to act on the situation.
For example, the publisher might want to notify the client so that it can take some action (like
suspending processing). As there is little point in queuing a message to tell the client that their queue
is becoming full, this is probably done using a high priority message which goes to the front of the
queue.
To this end, an upper notification threshold can be set for a client's queue. This is expressed as a
percentage of the maximum queue depth at which a notification is sent to any client listeners that
are declared. A client listener is any object that implements the ClientListener interface and
such a listener can be added from within a publisher using the Publishers.addEventListener
method. Listeners are notified of the limit breach using the clientQueueThresholdReached
method.
In addition a lower notification threshold can be specified. The lower threshold is a percentage of the
maximum queue depth at which a notification occurs when a message is removed from the queue
causing the queue size to reach the threshold if (and only if) the upper threshold has been breached.
When the clientQueueThresholdReached method is called on the client listener it indicates
whether it was the upper or lower threshold that was reached.
The thresholds to use for clients can be configured for a connector in connectors.properties. If not
specified, the default thresholds specified in etc/Server.xml are used.
The thresholds on a client connector can be changed dynamically at run time using System
Management, but the new values only take effect for new clients.
Thresholds can also be set or changed from within the publisher for a connected client using the
Client.setQueueNotificationThresholds method.
Tidy on unsubscribe
When a client unsubscribes from a topic, the topic updates that are already queued for delivery to
the client are delivered. These messages can be cleared from the queue if the client does not want to
receive them.
After a message is queued for a client, it will be delivered. This means that a client can unsubscribe
from a topic but still receive messages queued for it on it on that topic. This is generally what is
required as the messages were sent whilst the client was subscribed.
Diffusion | 479
However, it can be decided that once the client has unsubscribed from a topic then the client no longer
has any interest in any messages for that topic and such messages are removed from the queue. To
achieve this there is an option on a topic (using the setTidyOnUnsubscribe method) to indicate
that messages for the topic must be removed from client queues when the client unsubscribes from
that topic.
Client Geo and WhoIs information
When a client connects to Diffusion, information about that client's geographic location is looked up
and the information is made available to publishers.
When a client first connects to a Diffusion server, its remote IP address is immediately available (using
the Client.getRemoteAddress method) as well as other details obtained from the embedded
GeoIp database. Further host and geographic details about the client are obtained using the Diffusion
"WhoIs service".
GeoIp information
Diffusion ships with a GeoIP database from MaxMind. This provides information about Locale and
geographic co-ordinates. The Java API includes utilities (GeoIPUtils) to make use of this database.
This is a public domain database and is free to use. You can purchase the more accurate database
from MaxMind and change the configuration in the etc/Server.xml properties to use the new
database.
The database can be disabled but its use is mandatory if you are going to use client connection or
subscription validation policies. For more information, see .
WhoIs
The inbuilt WhoIs service can provide additional information about clients, however the lookup of
the WhoIs information might take some time, especially if it is not already cached. This means that
notification of the connection and further processing of the client cannot wait for this information
to become available. For this reason the resolution of the client's WhoIs details is notified to client
listeners separately from client connection on the clientResolved method.
When a client is first connected it is likely that the WhoIs details of the client are not available. This can
be checked using the Client.isResolved method. When the details become available they can be
obtained from the client using the getWhoIsDetails method which returns an object containing
the following information:
Table 40: WhoIs
Address
The client's IP Address – this is the same as that obtained using
Client.getRemoteAddress.
Host
The resolved host name of the client. If the host name cannot be
resolved, the address is returned.
Resolved name
The fully resolved name of the client. Exactly what this means depends
upon the WhoIs provider in use. If a fully resolved name cannot be
obtained, the host name value is returned.
Locale
Returns the result of a geographic lookup of the IP address indicating
where the address was allocated. The country of the locale is set to
the international two-letter code for the country where the internet
Diffusion | 480
address was allocated (for example, NZ for New Zealand). If the internet
address cannot be found in the database, the country and language of
the returned locale are set to empty Strings.
Three country values can be returned that do not exist within the
international standard (ISO 3166). These are EU (for a non-specific
European address), AP (for a non-specific Asia-Pacific address) and **
(an internet address reserved for private use, for example on a corporate
network not available from the public internet).
The language of the returned locale is set to the international two-letter
code for the official language of the country where the internet address
was allocated. Where a country has more than one official language,
the language is set to that which has the majority of native speakers.
For example, the language for Canada is set to English (en) rather
than French (fr). Non-specific addresses (EU and AP), private internet
addresses (**), and addresses not found within the database, all return
an empty string for language.
WhoIsData
This is data extracted from an enquiry upon a 'WhoIs' data provider.
Local
Indicates whether the client address is a local address, in which case no
locale or WhoIsData is available.
Loopback
Indicates whether the client address is a loopback address in which case
no locale or WhoIsData is available.
The Diffusion WhoIs service
The Diffusion WhoIs service runs as a background task in the Diffusion server. It looks up client details
and caches them in case the same client reconnects later.
The behavior of the WhoIs service is configured in etc/Server.xml. This allows the following to be
specified:
Table 41: WhoIs service
The WhoIs provider
This specifies a class to use for WhoIs lookups. A
default WhoIs provider is provided with Diffusion.
Number of threads
The number of background threads that
processes WhoIs resolver requests. More threads
will improve the WhoIs performance. Setting this
to 0 disables WhoIs.
WhoIs Host/Port
These details provide the location of an internet
based WhoIs lookup server that adheres to the
RFC3912 WhoIs protocol. This is used by the
default WhoIs provider. This defaults to using the
RIPE database.
Cache details
Specifying the maximum size of the cache of
details and how long cache entries are retained
before being deleted.
If you envisage large numbers of different clients
connecting over time, it is important to consider
the automatic cache tidying options on the
service.
Diffusion | 481
The WhoIs service can be disabled both by setting the number of threads to zero and removing the
whois configuration element.
WhoIs providers
The Diffusion WhoIs provider is a class which implements the WhoIsProvider interface of the
WhoIs API. This is used by the WhoIs service to lookup WhoIs details for connected clients.
Default provider
A default WhoIsProvider (WhoIsDefaultProvider) is provided with Diffusion.
A connection is made to the WhoIs server specified in etc/Server.xml and returned details are
parsed and used to update the supplied details. Child details objects are added for any separate WhoIs
records found and the type of such objects is the key of the first WhoIs record entry (for example,
person). Where duplicate field names occur then all but the first are suffixed by “_n”, where n is a
number distinguishing the entries.
The netname entry is used as the resolved name if present.
Custom provider
If the behavior of the issued default WhoIs provider is not exactly what is required then users can write
their own WhoIs provider which must implement the WhoIsProvider interface. The name of the
user-written class can be specified in etc/Server.xml and must be deployed on the Diffusion
server's classpath.
Client notifications
A publisher can opt to receive certain notifications regarding clients. It does this by adding a
ClientListener which can be the publisher itself or any other object that implements the
ClientListener interface.
A listener is added using the Publishers.addEventListener method.
All notifications are passed a reference to the client in question which can be interrogated for further
information as required.
Notifications received on the ClientListener interface are as follows:
Table 42: Client listener notifications
clientConnected
This is called whenever a new client connects. It is not
necessarily a client that is subscribing to one of the publisher's
topics.
clientResolved
This is called when a newly connected client is resolved. A
client's full geographical information is not necessarily available
as soon as a client connects and so this method is called
separately after the client has been resolved.
clientSubscriptionInvalid This is called whenever a client attempts to subscribe to a topic
that does not exist. This might be because the topic is not yet
available and this gives a publisher the opportunity to create
the topic and subscribe the client to it.
Diffusion | 482
clientFetchInvalid
This is called whenever a client attempts to fetch a topic that
does not exist. This gives the publisher the opportunity to
respond to fetch request on a non-existent topic. A publisher
can even reply to such a request without having to create a
topic using the sendFetchReply method.
clientSendInvalid
This is called whenever a client attempts to send a message
to a topic that does not exist, or to which the client is not
subscribed. This enables a client to send a message to a topic
and for that topic to be created and subscribed to on demand,
or send data when a response is never expected.
clientQueueThresholdReached
This is called whenever a client's queue breaches an upper
queue notification threshold or returns to a lower queue
notification threshold. Parameters indicate which threshold has
been reached and the threshold value.
clientCredentials
This is called whenever a client supplies new credentials after
connection. It is called after the authentication handlers have
validated the credentials.
clientClosed
This is called whenever a client disconnects. The
reason for disconnection can be obtained using
theClient.getCloseReason method.
Adding a ClientListener
You can add a ClientListener to listen for client notifications.
About this task
Procedure
So a publisher can add itself as a listener for client notifications as follows:
Results
public class MyPublisher extends Publisher implements ClientListener
{
protected void initialLoad() throws APIException {
Publishers.addEventListener(this);
}
Using DefaultClientListener
How to use the default client listener to avoid implementing all methods.
About this task
The publisher must implement all of the ClientListener methods.
Procedure
For convenience, an abstract DefaultClientListener class is provided which has empty
implementations of all methods. This can be extended to produce a class which implements
only the methods you are interested in. Alternatively an anonymous class can be used within the
publisher as follows:
Diffusion | 483
Results
protected void initialLoad() throws APIException {
Publishers.addEventListener(
new DefaultClientListener() {
public void clientConnected(Client client) {
LOG.info("Client {} connected",client);
}
public void clientClosed(Client client) {
LOG.info("Client {} closed",client);
}
});
}
Developing other components
Diffusion provides Java APIs that enable you to customize the behavior of your Diffusion server and
related components.
Local authentication handlers
You can implement authentication handlers that authenticate client connections to the Diffusion
server.
A local authentication handler is an implementation of the AuthenticationHandler interface.
Local authentication handlers can be implemented only in Java. The class file that contains a local
authentication handler must be located on the classpath of the Diffusion server.
Related concepts
Configuring authentication handlers on page 529
Authentication handlers and the order that the Diffusion server calls them in are configured in the
Server.xml configuration file.
Developing a local authentication handler
Implement the AuthenticationHandler interface to create a local authentication handler.
About this task
Local authentication handlers can be implemented only in Java.
Note: Where c.p.d is used in package names, it indicates
com.pushtechnology.diffusion.
Procedure
1. Create a Java class that implements AuthenticationHandler.
package com.example;
import com.pushtechnology.diffusion.client.details.SessionDetails;
import
com.pushtechnology.diffusion.client.security.authentication.AuthenticationHan
import com.pushtechnology.diffusion.client.types.Credentials;
Diffusion | 484
public class ExampleAuthenticationHandler implements
AuthenticationHandler{
public void authenticate(String principal, Credentials
credentials,
SessionDetails sessionDetails, Callback callback) {
// Logic to make the authentication decision.
// Authentication decision
callback.abstain();
// callback.deny();
// callback.allow();
}
}
a) Implement the authenticate method.
b) Use the allow, deny, or abstain method on the Callback object to respond with the
authentication decision.
2. Package your compiled Java class in a JAR file and put the JAR file in the ext directory of your
Diffusion installation.
This includes the authentication handler on the server classpath.
3. Edit the etc/Server.xml configuration file to point to your authentication handler.
Include the authentication-handler element in the list of authentication handlers. The
order of the list defines the order in which the authentication handlers are called. The value of the
class attribute is the fully qualified name of your authentication handler class. For example:
<security>
<authentication-handlers>
<authentication-handler
class="com.example.ExampleAuthenticationHandler" />
</authentication-handlers>
</security>
4. Start or restart the Diffusion server.
•
•
On UNIX-based systems, run the diffusion.sh command in the
diffusion_installation_dir/bin directory.
On Windows systems, run the diffusion.bat command in the
diffusion_installation_dir\bin directory.
Related concepts
User-written authentication handlers on page 139
You can implement authentication handlers that authenticate clients that connect to the Diffusion
server or perform an action that requires authentication.
Authentication on page 136
You can implement and register handlers to authenticate clients when the clients try to perform
operations that require authentication.
Related tasks
Developing a composite authentication handler on page 486
Diffusion | 485
Extend the CompositeAuthenticationHandler class to combine the decisions from multiple
authentication handlers.
Developing a control authentication handler on page 422
Implement the ControlAuthenticationHandler interface to create a control authentication
handler.
Developing a composite control authentication handler on page 425
Extend the CompositeControlAuthenticationHandler class to combine the decisions from
multiple control authentication handlers.
Developing a composite authentication handler
Extend the CompositeAuthenticationHandler class to combine the decisions from multiple
authentication handlers.
About this task
If there are several, discrete authentication steps that must always be performed in the same order,
packaging them as a composite authentication handler simplifies the server configuration.
This example describes how to use a composite authentication handler to call multiple local
authentication handlers in sequence.
Procedure
1. Create the individual authentication handlers that your composite authentication handler calls.
You can follow steps in the task Developing a local authentication handler on page 484.
In this example, the individual authentication handlers are referred to as HandlerA, HandlerB,
and HandlerC.
2. Extend the CompositeAuthenticationHandler class.
package com.example;
import com.example.HandlerA;
import com.example.HandlerB;
import com.example.HandlerC;
import
com.pushtechnology.diffusion.client.security.authentication.CompositeAuthenti
public class CompositeHandler extends
CompositeAuthenticationHandler {
public CompositeHandler() {
super(new HandlerA(), new HandlerB(), new HandlerC());
}
}
a) Import your individual authentication handlers.
b) Create a no-argument constructor that calls the super class constructor with a list of your
individual handlers.
3. Package your compiled Java class in a JAR file and put the JAR file in the ext directory of your
Diffusion installation.
This includes the composite authentication handler on the server classpath.
4. Edit the etc/Server.xml configuration file to point to your composite authentication handler.
Diffusion | 486
Include the authentication-handler element in the list of authentication handlers. The
order of the list defines the order in which the authentication handlers are called. The value of the
class attribute is the fully qualified class name of your composite authentication handler. For
example:
<security>
<authentication-handlers>
<authentication-handler class="com.example.CompositeHandler" />
</authentication-handlers>
</security>
5. Start the Diffusion server.
•
•
On UNIX-based systems, run the diffusion.sh command in the
diffusion_installation_dir/bin directory.
On Windows systems, run the diffusion.bat command in the
diffusion_installation_dir\bin directory.
Results
When the composite authentication handler is called, it calls the individual authentication handlers
that are passed to it as parameters in the order they are passed in.
•
•
•
If an individual handler responds with ALLOW or DENY, the composite handler responds with that
decision to the server.
If an individual handler responds with ABSTAIN, the composite handler calls the next individual
handler in the list.
If all individual handlers respond with ABSTAIN, the composite handler responds to the server with
an ABSTAIN decision.
Related concepts
User-written authentication handlers on page 139
You can implement authentication handlers that authenticate clients that connect to the Diffusion
server or perform an action that requires authentication.
Authentication on page 136
You can implement and register handlers to authenticate clients when the clients try to perform
operations that require authentication.
Related tasks
Developing a local authentication handler on page 484
Implement the AuthenticationHandler interface to create a local authentication handler.
Developing a control authentication handler on page 422
Implement the ControlAuthenticationHandler interface to create a control authentication
handler.
Developing a composite control authentication handler on page 425
Diffusion | 487
Extend the CompositeControlAuthenticationHandler class to combine the decisions from
multiple control authentication handlers.
Push Notification Bridge persistence plugin
The Push Notification Bridge stores subscription information in memory. To persist this information
past the end of the bridge process, implement a persistence plugin.
The persistence API
The Push Notification Bridge persistence API provides the following interfaces for you to use to
develop your persistence plugin:
SaverFactory
Your implementation of this interface is referenced by the bridge configuration and is
called by the bridge to build the Saver object.
Saver
Your implementation of this interface is called by the bridge when push notification
subscriptions and unsubscriptions are made. It uses this information to update the
persisted model of the subscriptions.
Loader
Your implementation of this interface is called by the bridge when it starts and is used
to update the model of subscriptions held in memory by the bridge.
Context
This provides a context for events passed to the Saver interface. It is used for
logging and audit trail purposes.
Full API documentation is available at the following location: Java API documentation.
An example implementation of the persistence API is available on GitHub: https://github.com/
pushtechnology/push-notification-persistence-example. This example is basic and uses Java
serialization to persist the subscription model.
Note: The example persistence plugin is not suitable for production use.
Developing the persistence plugin
A JAR file that contains the persistence API is available on the Push Technology Maven repository.
To use Maven to declare the dependency, first add the Push Technology public repository to your
pom.xml file:
<repositories>
<repository>
<id>push-repository</id>
<url>https://download.pushtechnology.com/maven/</url>
</repository>
</repositories>
Next declare the following dependency in your pom.xml file:
<dependency>
<groupId>com.pushtechnology</groupId>
<artifactId>push-notification-persistence-api</artifactId>
<version>1.0</version>
Diffusion | 488
</dependency>
Using the persistence plugin
1. Compile your persistence code.
2. Ensure that the compiled code is on the classpath of the JVM that runs the bridge.
3. Configure the bridge to use your persistence plugin.
Use the saverFactory attribute of the persistence element to specify the name of the
SaverFactory class in your plugin. For example:
<persistence saverFactory="com.example.pnb.SaverFactory"/>
The content of the persistence element can be text content. This content is passed into the
saver factory as arguments.
For more information, see Configuring your Push Notification Bridge on page 776.
Related concepts
Push notification networks on page 118
Consider whether your solution will interact with push notification networks.
Example: Send a request message to the Push Notification Bridge on page 489
The following examples use the API to send a request message on a topic path to communicate
with the Push Notification Bridge. The request message is in JSON and can be used to subscribe or
unsubscribe from receiving push notifications when specific topics are updated.
Push Notification Bridge on page 773
The Push Notification Bridge is a Diffusion client that subscribes to topics on behalf of other Diffusion
clients and uses a push notification network to relay topic updates to the device where the client
application is located.
Example: Send a request message to the Push Notification Bridge
The following examples use the API to send a request message on a topic path to communicate
with the Push Notification Bridge. The request message is in JSON and can be used to subscribe or
unsubscribe from receiving push notifications when specific topics are updated.
Objective-C
// Diffusion Client Library for iOS,
tvOS and OS X / macOS - Examples
//
// Copyright (C) 2016, 2017 Push Technology Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
// See the License for the specific language governing permissions
and
Diffusion | 489
//
limitations under the License.
//** The default path at which the Push Notification Bridge listens
for messaging
#define SERVICE_PATH @"push/notifications"
#import "MessagingToPushNotificationBridgeExample.h"
@import Diffusion;
@implementation MessagingToPushNotificationBridgeExample {
PTDiffusionSession* _session;
}
-(void)startWithURL:(NSURL*)url {
NSLog(@"Connecting...");
[PTDiffusionSession openWithURL:url
completionHandler:^(PTDiffusionSession *session,
NSError *error)
{
if (!session) {
NSLog(@"Failed to open session: %@", error);
return;
}
// At this point we now have a connected session.
NSLog(@"Connected.");
// Set ivar to maintain a strong reference to the session.
_session = session;
// An example APNs device token
unsigned char tokenBytes[] =
{0x5a, 0x88, 0x3a, 0x57, 0xe2, 0x89, 0x77, 0x84,
0x1d, 0xc8, 0x1a, 0x0a, 0xa1, 0x4e, 0x2f, 0xdf,
0x64, 0xc6, 0x5a, 0x8f, 0x7b, 0xb1, 0x9a, 0xa1,
0x6e, 0xaf, 0xc3, 0x16, 0x13, 0x18, 0x1c, 0x97};
NSData *const deviceToken =
[NSData dataWithBytes:(void *)tokenBytes length:32];
[self doPnSubscribe:@"some/topic/name"
deviceToken:deviceToken];
}];
}
/**
* Compose a URI understood by the Push Notification Bridge from an
APNs device token.
* @param deviceID APNS device token.
* @return string in format expected by the push notification bridge.
*/
-(NSString*)formatAsURI:(NSData*)deviceID {
NSString *const base64 = [deviceID
base64EncodedStringWithOptions:0];
return [NSString stringWithFormat:@"apns://%@", base64];
}
/**
* Compose and send a subscription request to the Push Notification
bridge
* @param topicPath Diffusion topic path subscribed-to by the Push
Notification Bridge.
Diffusion | 490
*/
- (void)doPnSubscribe:(NSString*) topicPath deviceToken:
(NSData*)deviceToken {
// Compose the JSON request from Obj-C literals
NSDictionary *const requestDict = @{
@"pnsub": @{
@"destination": [self formatAsURI:deviceToken],
@"topic": topicPath
}};
// Build a JSON request from that
PTDiffusionJSON *const json =
[[PTDiffusionJSON alloc] initWithObject:requestDict
error:nil];
[_session.messaging sendRequest:json.request
toPath:SERVICE_PATH
JSONCompletionHandler:^(PTDiffusionJSON *json, NSError
*error)
{
if (error) {
NSLog(@"Send to \"%@\" failed: %@", SERVICE_PATH, error);
} else {
NSLog(@"Response: %@", json);
}
}];
}
@end
Swift
// Diffusion Client Library for iOS, tvOS
and OS X / macOS - Examples
//
// Copyright (C) 2017 Push Technology Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
// See the License for the specific language governing permissions
and
// limitations under the License.
import Foundation
import Diffusion
class MessagingToPushNotificationBridgeExample {
static let servicePath = "push/notifications"
var session: PTDiffusionSession?
func startWithURL(url: NSURL) throws {
print("Connecting...")
Diffusion | 491
PTDiffusionSession.open(with: url as URL) { (session, error)
-> Void in
if session == nil {
print("Failed to open session: \(error!)")
return
}
// At this point we now have a connected session.
print("Connected")
// Set ivar to maintain a strong reference to the
session.
self.session = session
// An example APNs device token
let tokenBytes:[UInt8] = [0x5a, 0x88, 0x3a, 0x57, 0xe2,
0x89, 0x77, 0x84,
0x1d, 0xc8, 0x1a, 0x0a, 0xa1, 0x4e,
0x2f, 0xdf,
0x64, 0xc6, 0x5a, 0x8f, 0x7b, 0xb1,
0x9a, 0xa1,
0x6e, 0xaf, 0xc3, 0x16, 0x13, 0x18,
0x1c, 0x97]
let deviceToken = NSData(bytes: tokenBytes, length: 32)
self.doPnSubscribe(topicPath: "some/topic/path",
deviceToken: deviceToken)
}
}
/**
* Compose a URI understood by the Push Notification Bridge from
an APNs device token.
* @param deviceID APNS device token.
* @return string in format expected by the push notification
bridge.
*/
func formatAsURI(token:NSData) -> String {
return String(format:"apns://", token.base64EncodedString())
}
func doPnSubscribe(topicPath: String, deviceToken: NSData) {
// Compose the JSON request from literals
let requestDict = [
"pnsub" : [
"destination": formatAsURI(token: deviceToken),
"topic": topicPath
]
]
// Build a JSON request from that
let json = try! PTDiffusionJSON(object: requestDict)
session?.messaging.send(
json.request,
toPath:
MessagingToPushNotificationBridgeExample.servicePath,
jsonCompletionHandler: {
(json, error) -> Void in
if (nil == json) {
Diffusion | 492
print("Send to
\"\(MessagingToPushNotificationBridgeExample.servicePath)\" failed:
\(error!)")
} else {
print("Response: \(json!)")
}
})
}
}
Android
/
*******************************************************************************
* Copyright (C) 2017 Push Technology Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions
and
* limitations under the License.
*******************************************************************************
package com.pushtechnology.diffusion.examples;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import
import
import
import
com.pushtechnology.diffusion.client.Diffusion;
com.pushtechnology.diffusion.client.features.Messaging;
com.pushtechnology.diffusion.client.session.Session;
com.pushtechnology.diffusion.datatype.json.JSON;
/**
* An example of a client using the 'Messaging' feature to request
the Push
* Notification Bridge subscribe to a topic and relay updates to a
GCM
* registration ID.
*
* @author Push Technology Limited
* @since 5.9
*/
public class ClientSendingPushNotificationSubscription {
private static final Logger LOG = LoggerFactory
.getLogger(ClientSendingPushNotificationSubscription.class);
private final String pushServiceTopicPath;
Diffusion | 493
private final Session session;
private final Messaging messaging;
/**
* Constructs message sending application.
*
* @param pushServiceTopicPath topic path on which the Push
Notification
*
Bridge is taking requests.
*/
public ClientSendingPushNotificationSubscription(
String pushServiceTopicPath) {
this.pushServiceTopicPath = pushServiceTopicPath;
this.session =
Diffusion.sessions().principal("client")
.password("password").open("ws://
diffusion.example.com:80");
this.messaging = session.feature(Messaging.class);
}
/**
* Close the session.
*/
public void close() {
session.close();
}
/**
* Compose & send a subscription request to the Push Notification
Bridge.
*
* @param subscribedTopic topic to which the bridge subscribes.
* @param gcmRegistrationID GCM registration ID to which the
bridge relays
*
updates.
* @throws ExecutionException If the Push Notification Bridge
cannot process
*
the request
* @throws InterruptedException If the current thread was
interrupted while
*
waiting for a response
*/
public void requestPNSubscription(String gcmRegistrationID,
String subscribedTopic)
throws InterruptedException, ExecutionException {
// Compose the request
final String gcmDestination = "gcm://" + gcmRegistrationID;
final JSONObject jsonObject =
buildSubscriptionRequest(gcmDestination,
subscribedTopic);
final JSON request =
Diffusion.dataTypes().json().fromJsonString(jsonObject.toString());
// Send the request
final CompletableFuture<JSON> response =
messaging.sendRequest(
pushServiceTopicPath,
request,
JSON.class,
JSON.class);
Diffusion | 494
LOG.info("Received response from PN Bridge: {}",
response.get().toJsonString());
}
/**
* Compose a subscription request.
* <P>
*
* @param destination The {@code gcm://} or {@code apns://}
destination for
*
any push notifications.
* @param topic Diffusion topic subscribed-to by the Push
Notification
*
Bridge.
* @return a complete request
*/
private static JSONObject buildSubscriptionRequest(
String destination,
String topic) {
final JSONObject subObject = new JSONObject();
subObject
.put("destination", destination)
.put("topic", topic);
final JSONObject contentObj = new JSONObject();
contentObj.put("pnsub", subObject);
return contentObj;
}
}
Related concepts
Push notification networks on page 118
Consider whether your solution will interact with push notification networks.
Push Notification Bridge persistence plugin on page 488
The Push Notification Bridge stores subscription information in memory. To persist this information
past the end of the bridge process, implement a persistence plugin.
Push Notification Bridge on page 773
The Push Notification Bridge is a Diffusion client that subscribes to topics on behalf of other Diffusion
clients and uses a push notification network to relay topic updates to the device where the client
application is located.
Using Maven to build Java Diffusion applications
Apache™ Maven is a popular Java build tool and is well supported by Java IDEs. You can use Apache
Maven to build your Diffusion applications.
The Push Technology public Maven repository
Push Technology publishes Diffusion components and related artifacts to a public Maven repository at
the following location: http://download.pushtechnology.com/maven.
The published artifacts include the following:
Diffusion | 495
Table 43: Artifacts
Artifact Maven coordinates
Description
Diffusion com.pushtechnology.diffusion:diffusionThe Diffusion API interfaces only. Use this artifact
API
api:jar:6.0.0
for compilation only. The JAR includes the source
and Javadoc attachments.
Diffusion com.pushtechnology.diffusion:diffusionThe Diffusion client library.
Clients client:jar:6.0.2
mvndar
com.pushtechnology.tools:dar-A Maven plugin for building DAR files.
maven-plugin:mavenplugin:1.2
To use the Push Technology public Maven repository, add the following repository description to your
pom.xml file:
<repositories>
<repository>
<id>push-repository</id>
<url>https://download.pushtechnology.com/maven/</url>
</repository>
</repositories>
Build client applications
You can build and run Diffusion Java client applications without installing the Diffusion product. The
Diffusion client JAR is all you need.
The following pom.xml shows how to declare the appropriate dependencies:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://
www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://
maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/
maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myclient</artifactId>
<version>1.0-SNAPSHOT</version>
<repositories>
<repository>
<id>push-repository</id>
<url>https://download.pushtechnology.com/maven/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.pushtechnology.diffusion</groupId>
<artifactId>diffusion-client</artifactId>
<version>6.0.2</version>
</dependency>
</dependencies>
</project>
Diffusion | 496
Build publishers with Maven
The Diffusion API for publishers is not available in the Push Technology public Maven repository. To
build publishers, you must install the product locally and depend on diffusion.jar using a Maven
system scope.
DAR files
The preferred way to deploy publishers is to build them into a DAR. DARs are JAR format files that
contain compiled code, libraries, and configuration. They have a similar purpose to Java EE EAR or
WAR files, and can be dynamically deployed to and undeployed from a running Diffusion server
Figure 29: Example folder structure inside a DAR file
The root folder name is the name of the publisher. For example, MyPublisher.
•
The META-INF directory contains the MANIFEST.MF file.
This file contains an attribute, Diffusion-Version, which specifies the minimum version
number of Diffusion on which this publisher runs. This prevents deployment of publishers to
Diffusion instances which might not support features of the publisher or have different API
signatures.
Manifest-Version: 1.0
Diffusion-Version: 6.0.2
•
The etc directory can contain the following files.
etc/Publishers.xml
You must include this file.
The Publishers.xml file has the same structure and the one in a Diffusion
installation's etc directory. For more information, see .
For example:
<publishers>
<publisher name="MyPublisher">
<class>com.pushtechnology.diffusion.test.publisher.MyPublisher</
class>
<start>true</start>
<enabled>true</enabled>
</publisher>
Diffusion | 497
</publishers>
etc/Aliases.xml (optional)
Include this file if there are associated HTML files.
etc/SubscriptionValidationPolicy.xml
Include this file if it is referenced from the etc/Publishers.xml file.
•
These files are normally found in the Diffusion server installation's etc directory, but contain only
information relating to the publisher being deployed. Files that affect the operation of the Diffusion
server and have no relationship to the publisher are not loaded.
The ext directory contains all Java code required by your publisher.
•
You can also include any required third-party JAR files or resources in this folder.
The html is optional and can contain any HTML files or web assets required by the publisher.
mvndar
The preferred way to build a DAR mvndar is a Maven plugin for creating DAR files. More information
about mvndar is available at the following locations:
•
http://pushtechnology.github.io/mvndar/index.html
Example: Using Maven to build the demo applications
If you selected the demo applications when you installed Diffusion, the source files and an example
Maven pom.xml can be found in the directories beneath the demos/src directory. The example uses
mvndar, and depends on diffusion.jar using system scope.
For more information, see .
Related concepts
Classic deployment on page 789
Installing publishers into a stopped Diffusion instance.
Hot deployment on page 790
Installing publishers into a running Diffusion instance.
Deployment methods on page 790
There are two ways to deploy a DAR file: file copy or HTTP.
Building a publisher with mvndar
Use the Maven plugin mvndar to build and deploy your publisher DAR file. This plugin is available from
the Push Public Maven Repository.
Before you begin
This task describes how to build existing publisher code into a DAR file for deployment on the Diffusion
server. Develop your publisher code before beginning this task. For more information, see Writing a
publisher on page 471.
You must have an installation of the Diffusion server on the system you use to build the DAR file.
Diffusion | 498
Procedure
1. Create a Maven project.
mvn archetype:generate -DgroupId=group_id -DartifactId=publisher_id
-DarchetypeArtifactId=maven-archetype-quickstart DinteractiveMode=false
Replace group_id with the group identifier for your publisher, for example, com.example.
Replace publisher_id with the artifact name for your publisher, for example, my-firstpublisher.
This command creates a publisher_id directory that contains a pom.xml and sample files.
2. Replace the sample code in the new publisher_id Maven project with your publisher code.
Put your Java code in the publisher_id/src/main/java/ directory.
3. Add a Publishers.xml file to your Maven project.
Create the file at publisher_id/src/main/diffusion/etc/Publishers.xml:
<publishers>
<publisher name="publisher_name">
<class>fq_publisher_name</class>
<start>true</start>
</publisher>
</publishers>
Replace publisher_name with the name of the class that extends the Publisher
class, for example, MyFirstPublisher. Replace fq_publisher_name with the
fully qualified name of the class that extends the Publisher class, for example,
com.example.publish.MyFirstPublisher.
4. Edit the pom.xml file in the publisher_id directory:
a) Remove the boilerplate code and ensure that the group and artifact IDs are set to the correct
values.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>group_id</groupId>
<artifactId>publisher_id</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>publisher_id</name>
</project>
b) Create a system-scoped dependency on the Diffusion JAR.
<dependencies>
<dependency>
<groupId>com.pushtechnology</groupId>
<artifactId>diffusion</artifactId>
<version>6.0.2</version>
<scope>system</scope>
<systemPath>diffusion_installation_directory/lib/
diffusion.jar</systemPath>
<optional>true</optional>
</dependency>
Diffusion | 499
</dependencies>
c) Add the Push Public Maven Repository as a repository that plugins can be fetched from
<pluginRepositories>
<pluginRepository>
<id>push-repository</id>
<url>http://download.pushtechnology.com/maven/</url>
</pluginRepository>
</pluginRepositories>
d) Add mvndar as configured plugin
<build>
<plugins>
<plugin>
<groupId>com.pushtechnology.tools</groupId>
<artifactId>dar-maven-plugin</artifactId>
<version>1.2</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
e) Set the packaging method to dar instead of jar:
<packaging>dar</packaging>
5. Build your DAR file.
Run the mvn clean package command in the publisher_id directory.
Results
A DAR file is created in the publisher_id/target directory. This DAR file is ready to deploy to
the Diffusion server. For more information, see Deploying publishers on your Diffusion server on page
789
Related concepts
Classic deployment on page 789
Installing publishers into a stopped Diffusion instance.
Hot deployment on page 790
Installing publishers into a running Diffusion instance.
Deployment methods on page 790
There are two ways to deploy a DAR file: file copy or HTTP.
Creating a Publisher class on page 472
A publisher is written by extending the abstract Publisher class (see Publisher API) and overriding
any methods that must be implemented to achieve the functionality required by the publisher.
Loading publisher code on page 465
Diffusion | 500
This describes how to load publisher classes or code it is dependent upon.
Build server application code with Maven
The Diffusion API for server application code is not available in the Push Technology public Maven
repository. To build server components, you must install the product locally and depend on
diffusion.jar using a Maven system scope.
The following pom.xml shows to declare the dependency on diffusion.jar. To use it, you
must set the DIFFUSION_HOME environment variable to the absolute file path of your Diffusion
installation.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://
www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.examplecorp</groupId>
<artifactId>mypublisher</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependency>
<groupId>com.pushtechnology.diffusion</groupId>
<artifactId>diffusion</artifactId>
<version>local-installation</version>
</dependency>
</dependencyManagement>
<profiles>
<profile>
<activation>
<property>
<name>env.DIFFUSION_HOME</name>
</property>
</activation>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.pushtechnology.diffusion</
groupId>
<artifactId>diffusion</artifactId>
<version>local-installation</version>
<scope>system</scope>
<systemPath>${DIFFUSION_HOME}/lib/
diffusion.jar</systemPath>
</dependency>
</dependencies>
</dependencyManagement>
</profile>
</profiles>
</project>
Related concepts
Classic deployment on page 789
Diffusion | 501
Installing publishers into a stopped Diffusion instance.
Hot deployment on page 790
Installing publishers into a running Diffusion instance.
Deployment methods on page 790
There are two ways to deploy a DAR file: file copy or HTTP.
Testing
This section covers some aspects of testing a Diffusion system.
Benchmarking suite
A benchmarking suite for Diffusion is available on GitHub. You can use this suite to test the latency and
throughput of publishers.
The benchmarking suite is available at the following location: https://github.com/pushtechnology/
diffusion-benchmark-suite.
The benchmarking suite works on Linux only and requires the following software be installed on the
system:
•
•
•
Apache Ant™
Java with JDK
Diffusion server
For more information about using the benchmarking suite, see the readme file in the GitHub project.
Diffusion | 502
Part
V
Administrator Guide
This guide describes how to deploy, configure, and manage your Diffusion solution.
In this section:
•
•
•
•
•
•
•
•
•
•
•
•
•
•
Installing the Diffusion server
Configuring your Diffusion server
Starting the Diffusion server
Network security
Going to production
Tuning
Managing and monitoring your running Diffusion server
Web servers
Load balancers
JMS adapter
Push Notification Bridge
Deploying publishers on your Diffusion server
Demos
Tools
Diffusion | 503
Installing the Diffusion server
You can install the Diffusion server from a JAR file, through Docker, or through Red Hat Package
Manager.
Review the system requirements before installing Diffusion.
Download Diffusion from the following location: http://download.pushtechnology.com/releases/6.0
The Diffusion installation includes a developer license that allows up to five concurrent connections to
the Diffusion server. To use Diffusion in production, you can obtain a production license from .
System requirements for the Diffusion server
Review this information before installing the Diffusion server.
The Diffusion server is certified on the system specifications listed here. In addition, the Diffusion
server is supported on a further range of systems.
Certification
Push Technology classes a system as certified if the Diffusion server is fully
functionally tested on that system.
We recommend that you use certified hardware, virtual machines, operating systems,
and other software when setting up your Diffusion servers.
Support
In addition, Push Technology supports other systems that have not been certified.
Other hardware and virtualized systems are supported, but the performance of these
systems can vary.
More recent versions of software and operating systems than those we certify are
supported.
However, Push Technology can agree to support Diffusion on other systems. For more
information, contact Push Technology.
Physical system
The Diffusion server is certified on the following physical system specification:
•
•
•
•
Intel Xeon E-Series Processors
8 Gb RAM
8 CPUs
10 Gigabit NIC
Network, CPU, and RAM (in decreasing order of importance) are the components that have the biggest
impact on performance. High performance file system and disk are required. Intel hardware is used
because of its ubiquity in the marketplace and proven reliability.
Virtualized system
The Diffusion server is certified on the following virtualized system specification:
Host
•
•
Intel Xeon E-Series Processors
32 Gb RAM
Diffusion | 504
•
VMware vSphere 5.5
Virtual machine
•
•
8 VCPUs
8 Gb RAM
When running on a virtualized system, over-committing VCPUs (assigning too many VCPUs compared
to the processors available on the host) can cause increased latency and unpredictable performance.
Consult the VMWare Performance Best Practices documentation for details.
Operating system
Diffusion is certified on the following operating systems:
•
•
Red Hat7.2+
Windows Server 2012 R2 and 2016
We recommend you install your Diffusion server on a Linux-based operating system with enterpriselevel support available, such as Red Hat Enterprise Linux.
Operating system configuration
If you install your Diffusion server on a Linux-based operating system and do SSL offloading of secure
client connections at the Diffusion server, you must disable transparent huge pages.
If you install your Diffusion server on a Linux-based operating system but do not do SSL offloading
of secure client connections at the Diffusion server, disabling transparent huge pages is still
recommended.
Having transparent huge pages enabled on the system your Diffusion server runs on can cause
extremely long pauses for garbage collection. For more information, see https://access.redhat.com/
solutions/46111.
Java
The Diffusion server is certified on Oracle Java Development Kit 8 (minimum update 1.8.0_131-b11).
Only the Oracle JDK is certified.
Ensure that you use the Oracle JDK and not the JRE.
JVM configuration
If you do SSL offloading of secure client connections at the Diffusion server, you must ensure that you
constrain the maximum heap size and the maximum direct memory size so that together these to
values do not use more than 80% of your system's RAM.
Networking
Push Technology recommends the following network configurations:
•
•
•
10 Gigabit network
Load balancers with SSL offloading
In virtualized environments, enable SR-IOV.
For more information about how to enable SR-IOV, see the documentation provided by your virtual
server provider. SR-IOV might be packaged using a vendor-specific name.
Diffusion | 505
Client requirements
For information about the supported client platforms, see Platform support for the Diffusion API
libraries on page 35.
Related concepts
The Diffusion license on page 512
Diffusion includes a development license that enables you to use make up to 5 concurrent connections
to the Diffusion server.
Installed files on page 515
After installing Diffusion the following directory structure exists:
Related tasks
Installing the Diffusion server using the graphical installer on page 506
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
using the graphical installer.
Installing the Diffusion server using the headless installer on page 508
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
from the command line.
Installing the Diffusion server using Red Hat Package Manager on page 509
Diffusion is available as an RPM file from the Push Technology website.
Installing the Diffusion server using Docker on page 510
Diffusion is available as a Docker® image from Docker Hub.
Verifying the Diffusion installation on page 517
Start your Diffusion server, review the logs, and connect to the console to verify that your installation is
correct.
Installing the Diffusion server using the graphical installer
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
using the graphical installer.
Before you begin
You must have Oracle Java Development Kit 8 (minimum update 1.8.0_131-b11) installed on your
system to install and use Diffusion.
About this task
To install Diffusion using the graphical installer, complete the following steps:
Procedure
1. Go to the Diffusion download page:
http://download.pushtechnology.com/releases/6.0
2. Click on the following download links to download the required jar files into a temporary directory:
• Diffusion (Diffusion version_id.jar)
• Installer (install.jar)
3. In the temporary directory, double-click the install.jar file.
The graphical installer launches.
Diffusion | 506
4. Optional: If you have a production license, you can load it into the Diffusion installation at this
point.
You can skip this step if you are using the included development license.
a) Ensure that the license file is available on your system.
b) At the Introduction step, select File > Load license file
c) In the window that opens, navigate to the license file (licence.lic). Click Open.
5. At the Introduction step, click Continue.
6. At the License agreement step, select Accept to accept the End User License Agreement (EULA)
and click Continue.
7. At the Destination directory step, select the install destination.
We recommend you create a Diffusion directory on your system. Click Continue.
8. At the Select products step, select the components you want to install, then click Continue.
For a test installation, we recommend you select All. For a production installation, deselect
components you do not need (for example, Demos, Deploy demos and Examples).
9. At the Confirmation step, review the install information. If the information is correct, click
Continue to confirm.
The installer installs Diffusion into the directory specified.
10.At the Summary step, click Done to exit the graphical installer.
Results
You have successfully downloaded and installed Diffusion.
What to do next
Next:
•
•
•
Edit the configuration of your Diffusion server to suit your requirements. For more information, see
Configuring your Diffusion server on page 519.
Edit the security setup of your Diffusion server.
Start your Diffusion server using the diffusion.bat file, if on Windows, or the diffusion.sh
file, if on Linux or OS X/macOS.
These start up scripts are located in the bin directory of your Diffusion installation.
Related concepts
The Diffusion license on page 512
Diffusion includes a development license that enables you to use make up to 5 concurrent connections
to the Diffusion server.
Installed files on page 515
After installing Diffusion the following directory structure exists:
Related tasks
Installing the Diffusion server using the headless installer on page 508
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
from the command line.
Installing the Diffusion server using Red Hat Package Manager on page 509
Diffusion is available as an RPM file from the Push Technology website.
Installing the Diffusion server using Docker on page 510
Diffusion is available as a Docker® image from Docker Hub.
Verifying the Diffusion installation on page 517
Diffusion | 507
Start your Diffusion server, review the logs, and connect to the console to verify that your installation is
correct.
Related reference
System requirements for the Diffusion server on page 33
Review this information before installing the Diffusion server.
Installing the Diffusion server using the headless installer
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
from the command line.
Before you begin
You must have Oracle Java Development Kit 8 (minimum update 1.8.0_131-b11) installed on your
system to install and use Diffusion.
About this task
You can install in headless mode in circumstances where the graphical installer cannot be used or is
not appropriate.
Procedure
1. Go to the Diffusion download page:
http://download.pushtechnology.com/releases/6.0
2. Click on the following download links to download the required jar files into a temporary directory:
• Diffusion (Diffusion version_id.jar)
• Installer (install.jar)
3. Copy these files to a temporary directory on the system where Diffusion is to be installed.
4. In the terminal window, change to the directory where the Diffusion jar files are located.
5. Type the following command:
java -jar install.jar Diffusionn.n.n.jar
where n.n.n is the Diffusion release number.
6. If you agree to the terms of the license agreement, type Y and Enter.
7. Enter the full path to the directory in which to install Diffusion and type Enter.
8. Type Y to install all packages.
If you choose not to install all packages, the installer asks you about each package individually.
Results
You have successfully downloaded and installed Diffusion.
What to do next
Next:
•
•
•
Edit the configuration of your Diffusion server to suit your requirements. For more information, see
Configuring your Diffusion server on page 519.
Edit the security setup of your Diffusion server.
Start your Diffusion server using the diffusion.bat file, if on Windows, or the diffusion.sh
file, if on Linux or OS X/macOS.
Diffusion | 508
These start up scripts are located in the bin directory of your Diffusion installation.
Related concepts
The Diffusion license on page 512
Diffusion includes a development license that enables you to use make up to 5 concurrent connections
to the Diffusion server.
Installed files on page 515
After installing Diffusion the following directory structure exists:
Related tasks
Installing the Diffusion server using the graphical installer on page 506
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
using the graphical installer.
Installing the Diffusion server using Red Hat Package Manager on page 509
Diffusion is available as an RPM file from the Push Technology website.
Installing the Diffusion server using Docker on page 510
Diffusion is available as a Docker® image from Docker Hub.
Verifying the Diffusion installation on page 517
Start your Diffusion server, review the logs, and connect to the console to verify that your installation is
correct.
Related reference
System requirements for the Diffusion server on page 33
Review this information before installing the Diffusion server.
Installing the Diffusion server using Red Hat Package Manager
Diffusion is available as an RPM file from the Push Technology website.
About this task
On Linux systems that have Red Hat Package Manager installed, you can use it to install Diffusion.
Procedure
1. Go to the Diffusion download page:
http://download.pushtechnology.com/releases/6.0
2. Click on the following download link to download the required RPM file:
•
Diffusion RPM (diffusion-n.n.n_build.noarch.rpm)
3. Copy this file to a temporary directory on the system where Diffusion is to be installed.
4. In the terminal window, change to the directory where the Diffusion RPM file is located.
5. Type the following command:
rpm -ivh diffusion-n.n.n_build.noarch.rpm
where n.n.n is the Diffusion release number and build is an additional string containing numbers to
represent the build level.
Results
Diffusion is installed in the following directory: /opt/Diffusion. A startup script is installed in the /
etc/init.d directory that enables Diffusion to start when you start the system.
Diffusion | 509
What to do next
Your Diffusion installation includes a development license that allows connections from up to five
clients. To use Diffusion in production, you can obtain a production license from .
Copy the license file into the /etc directory of your Diffusion installation.
Related concepts
The Diffusion license on page 512
Diffusion includes a development license that enables you to use make up to 5 concurrent connections
to the Diffusion server.
Installed files on page 515
After installing Diffusion the following directory structure exists:
Related tasks
Installing the Diffusion server using the graphical installer on page 506
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
using the graphical installer.
Installing the Diffusion server using the headless installer on page 508
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
from the command line.
Installing the Diffusion server using Docker on page 510
Diffusion is available as a Docker® image from Docker Hub.
Verifying the Diffusion installation on page 517
Start your Diffusion server, review the logs, and connect to the console to verify that your installation is
correct.
Related reference
System requirements for the Diffusion server on page 33
Review this information before installing the Diffusion server.
Installing the Diffusion server using Docker
Diffusion is available as a Docker® image from Docker Hub.
Before you begin
You must have Docker installed on your system to run Diffusion from a Docker image. For more
information, see https://docs.docker.com/userguide/ .
About this task
You can use Docker to install the Diffusion server, and a minimal complete set of its dependencies, on
a Linux system. This image contains a Diffusion server with a trial license and default configuration and
security.
Using Docker enables you to install the Diffusion server in an isolated and reproducible way.
Procedure
1. Pull the latest version of the Diffusion image.
docker pull pushtechnology/docker-diffusion:latest
Diffusion | 510
2. Run the image.
docker run -p 8080:8080 image_id
Where image_id is the ID of the image to run. Port 8080 is the port that is configured to allow client
connections by default.
Results
Diffusion is now running in a container on your system. Clients can connect through port 8080.
Note: This Diffusion instance contains well known security principals and credentials. Do not
use it in production without changing these values.
Related concepts
The Diffusion license on page 512
Diffusion includes a development license that enables you to use make up to 5 concurrent connections
to the Diffusion server.
Installed files on page 515
After installing Diffusion the following directory structure exists:
Related tasks
Installing the Diffusion server using the graphical installer on page 506
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
using the graphical installer.
Installing the Diffusion server using the headless installer on page 508
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
from the command line.
Installing the Diffusion server using Red Hat Package Manager on page 509
Diffusion is available as an RPM file from the Push Technology website.
Verifying the Diffusion installation on page 517
Start your Diffusion server, review the logs, and connect to the console to verify that your installation is
correct.
Related reference
System requirements for the Diffusion server on page 33
Review this information before installing the Diffusion server.
Next steps with Docker
The Diffusion image on Docker Hub includes the default configuration, default security, and trial
license. Additional steps are required to secure and configure the Diffusion server.
Procedure
1. Create a Dockerfile that contains commands to configure a Diffusion image for your use.
a) Base your Docker image on the Diffusion image.
FROM pushtechnology/docker-diffusion:latest
b) To use Diffusion in production, obtain a production license from .
The default Diffusion image includes a development license that allows connections from up to
five clients.
Diffusion | 511
c) Copy the production license into the /opt/Diffusion/etc directory of your Diffusion
image.
ADD license_file /opt/Diffusion/etc/licence.lic
Where license_file is the path to the production license relative to the location of the Dockerfile.
d) Create versions of the Diffusion configuration files that define your required configuration.
For more information, see Configuring your Diffusion server on page 519.
e) Copy these configuration files into the /opt/Diffusion/etc directory of your Diffusion
image.
ADD configuration_file /opt/Diffusion/etc/file_name
Where configuration_file is the path to the configuration file relative to the location of the
Dockerfile and file_name is the name of the configuration file.
f) Create versions of the Security.store and SystemAuthentication.store that
define roles, principals and authentication actions for your security configuration.
For more information, see Pre-defined roles on page 134, DSL syntax: security store on page
440, and DSL syntax: system authentication store on page 428.
You can instead choose to edit these files using a Diffusion client. However, your Diffusion server
is not secure for production use until you do so.
g) Copy these store files into the /opt/Diffusion/etc directory of your Diffusion image.
ADD store_file /opt/Diffusion/etc/file_name
Where store_file is the path to the store file relative to the location of the Dockerfile and
file_name is the name of the store file.
h) Include any additional configuration actions you want to perform on your image in the
Dockerfile.
2. Build your image.
Run the following command in the directory where your Dockerfile is located:
docker build .
Results
The image you created contains a configured Diffusion server ready for you to use in your solution. You
can run multiple identically configured Diffusion servers from this image.
The Diffusion license
Diffusion includes a development license that enables you to use make up to 5 concurrent connections
to the Diffusion server.
To use Diffusion in production, contact for production licenses.
Related concepts
Installed files on page 515
After installing Diffusion the following directory structure exists:
Related tasks
Installing the Diffusion server using the graphical installer on page 506
Diffusion | 512
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
using the graphical installer.
Installing the Diffusion server using the headless installer on page 508
The Diffusion binary files are available from the Push Technology website. You can install Diffusion
from the command line.
Installing the Diffusion server using Red Hat Package Manager on page 509
Diffusion is available as an RPM file from the Push Technology website