aboutsummaryrefslogtreecommitdiff
path: root/public/simple-server-sent-events-based-pubsub-server.html
diff options
context:
space:
mode:
authorMitja Felicijan <m@mitjafelicijan.com>2023-07-08 23:25:41 +0200
committerMitja Felicijan <m@mitjafelicijan.com>2023-07-08 23:25:41 +0200
commitcd6644ea4ddc78597934ab0ef5ba50e3c3daa927 (patch)
tree03de331a8db6386dfd6fa75155bfbcea6b4feaf3 /public/simple-server-sent-events-based-pubsub-server.html
parent84ed124529ffeee1590295b8de3a8faf51848680 (diff)
downloadmitjafelicijan.com-cd6644ea4ddc78597934ab0ef5ba50e3c3daa927.tar.gz
Moved to a simpler SSG
Diffstat (limited to 'public/simple-server-sent-events-based-pubsub-server.html')
-rwxr-xr-xpublic/simple-server-sent-events-based-pubsub-server.html310
1 files changed, 310 insertions, 0 deletions
diff --git a/public/simple-server-sent-events-based-pubsub-server.html b/public/simple-server-sent-events-based-pubsub-server.html
new file mode 100755
index 0000000..5fb9455
--- /dev/null
+++ b/public/simple-server-sent-events-based-pubsub-server.html
@@ -0,0 +1,310 @@
1<!doctype html><html lang=en-us><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL69vf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv76+/8LBwQkAAAAAAAAAAAAAAAC+vb3/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL+9vf/Bv78JAAAAAAAAAAAAAAAAu7q6/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7ubr/vr29CAAAAAAAAAAAy8nJAZ6foP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnqGj/6GipAoAAAAAHLjU/xcXHf/BwsL/I8XY/yPK3v8XGiD/IbjL/yPF2f8XGiD/Fxkf/yLF2f8gnK3/Fxog/62ztv8fwNf/FRcd/x271v8mz93/GRsi/xkXHf8p097/GiIp/xobIv8p0t3/KdPe/xocIv8fYmr/KNPe/xoZH/8aHCL/J87c/xy81/8VFxz/IsPZ/8zS0/8XGiD/Ir/R/yPH2/8XGiD/Fxkf/yPH2/8dd4T/GBog/yPJ3f8jyNr/uru9/xcUGv8cudb/EhITDKi5vRKlvMP/RUpOERwcHRAdOj4QHTk8EBwdHRAdNTgQHTo/EBwcHRAcHB0QSGduEKW4vf+koqQfHzg+EBqz0ewSFRv7EyMr/xq51vsTERb7ExUb+xq41fsau9j7ExUb+xiPp/sZudb7ExUb+xMVG/sZuNX/GKvI/BIUGfMdvdn/IrfL/xcaIP8n1eb/J9Dh/xkcIf8ZGR7/J8/f/xxCSv8ZGyH/J9Dg/ybQ4P8ZHCL/FSQs/yPK3/8UExj/GE1b/ybS5P8ZGB7/Ghwj/ynW5P8p2Ob/Ghwi/yWrtv8p1eH/Ghwi/xocIv8p1uT/J8XT/xkcIv8m1un/Hb7d/xUYH/8hzOr/HtHu/xcaIf8XGB//I8vi/xgxOv8XGSD/I8rg/yPK4P8XGiD/GUFL/yPP6f8SERj/Fhkh/x3A4f8AAAAAJ2f9/ydr//8mZPH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlYu38J2v//ydo/f8AAAAAAAAAAAd8/fkFqf//Iob8sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMY39awWr//8FfP3/AAAAAAAAAAAFm/7/SfD//wR+/f8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOB/f9B7v//BaX+/wAAAAAAAAAAQ878SAyZ/v9n1v4KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADu9v8DDJb+/z3N/XgAAAAA3/sAAN/7AADf+wAA3/sAAAAAAAAAAAAAAAAAAN/7AAAAAAAAAAAAAAAAAAAAAAAAj/EAAI/5AACP8QAA3/sAAA==" rel=icon type=image/x-icon><title>Simple Server-Sent Events based PubSub Server</title><meta name=description content="Before we continue ."><link rel=alternate type=application/rss+xml title="Mitja Felicijan's posts" href=https://mitjafelicijan.com/index.xml><link rel=alternate type=application/rss+xml title="Mitja Felicijan's notes" href=https://mitjafelicijan.com/notes.xml><style>body{padding:1rem;max-width:760px;background:#fff;font-family:times new roman,Times,serif;line-height:1.35rem}hr{margin-block-start:1.5rem}h1,h2,h3{line-height:initial}footer{margin-block-start:3rem}table{max-width:100%;border-collapse:separate;border-spacing:2px;border:1px solid #000;border-left:1px solid #999;border-top:1px solid #999}blockquote{font-style:italic}table thead{background:#eee}td,th{border:1px solid #000;padding:4px;border-right:1px solid #999;border-bottom:1px solid #999;text-align:left}pre{text-wrap:nowrap;overflow-x:auto;margin-block-start:1.5rem;margin-block-end:1.5rem;padding:.5rem 0;border-top:1px solid #000;border-bottom:1px solid #000}pre code{line-height:1.3em}pre,code,pre *,code *{font-family:monospace;font-size:initial!important}img,video,audio{max-width:100%}header{display:flex;flex-direction:row;gap:3rem}nav{display:flex;gap:.75rem}.pstatus-orange{background:gold}.pstatus-green{background:#9acd32}.pstatus-red{background:#cd5c5c}@media only screen and (max-width:600px){header{flex-direction:column;gap:1rem}a{word-wrap:break-word}}</style><header><nav class=main><a href=/>Home</a>
2<a href=https://git.mitjafelicijan.com/ target=_blank>Git</a>
3<a href=https://files.mitjafelicijan.com/ target=_blank>Files</a>
4<a href=/mitjafelicijan.pgp.pub.txt target=_blank>PGP</a>
5<a href=/curriculum-vitae.html>CV</a>
6<a href=/index.xml target=_blank>RSS</a></nav></header><main><div><h1>Simple Server-Sent Events based PubSub Server</h1><p>Mar 22, 2020<div><h2 id=before-we-continue->Before we continue ...</h2><p>Publisher Subscriber model is nothing new and there are many amazing solutions
7out there, so writing a new one would be a waste of time if other solutions
8wouldn't have quite complex install procedures and weren't so hard to maintain.
9But to be fair, comparing this simple server with something like
10<a href=https://kafka.apache.org/>Kafka</a> or <a href=https://www.rabbitmq.com/>RabbitMQ</a> is
11laughable at the least. Those solutions are enterprise grade and have many
12mechanisms there to ensure messages aren't lost and much more. Regardless of
13these drawbacks, this method has been tested on a large website and worked until
14now without any problems. So now, that we got that cleared up, let's continue.<p><em><strong>Wiki definition:</strong> Publish/subscribe messaging, or pub/sub messaging, is a
15form of asynchronous service-to-service communication used in serverless and
16microservices architectures. In a pub/sub model, any message published to a
17topic is immediately received by all the subscribers to the topic.</em><h2 id=general-goals>General goals</h2><ul><li>provide a simple server that relays messages to all the connected clients,<li>messages can be posted on specific topics,<li>messages get sent via <a href=https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events>Server-Sent
18Events</a>
19to all the subscribers.</ul><h2 id=how-exactly-does-the-pubsub-model-work>How exactly does the pub/sub model work?</h2><p>The easiest way to explain this is with diagram bellow. Basic function is
20simple. We have subscribers that receive messages, and we have publishers that
21create and post messages. Similar model is also well know pattern that works on
22a premise of consumers and producers, and they take similar roles.<p><img src=/assets/simple-pubsub-server/pubsub-overview.png alt="How PubSub works"><p><strong>These are some naive characteristics we want to achieve:</strong><ul><li>producer is publishing messages to subscribe topic,<li>consumer is receiving messages from subscribed topic,<li>servers is also known as Broker,<li>broker does not store messages or tracks success,<li>broker uses
23<a href=https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)>FIFO</a> method
24for delivering messages,<li>if consumer wants to receive messages from a topic, producer and consumer
25topics must match,<li>consumer can subscribe to multiple topics,<li>producer can publish to multiple topics,<li>each message has a messageId.</ul><p><strong>Known drawbacks:</strong><ul><li>messages will not be stored in a persistent queue or unreceived messages like
26<a href=https://en.wikipedia.org/wiki/Dead_letter_queue>DeadLetterQueue</a> so old
27messages could be lost on server restart,<li><a href=https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events>Server-Sent
28Events</a>
29opens a long-running connection between the client and the server so make sure
30if your setup is load balanced that the load balancer in this case can have
31long opened connection,<li>no system moderation due to the dynamic nature of creating queues.</ul><h2 id=server-sent-events>Server-Sent Events</h2><p>Read more about it on <a href=https://html.spec.whatwg.org/multipage/server-sent-events.html>official specification
32page</a>.<h3 id=current-browser-support>Current browser support</h3><p><img src=/assets/simple-pubsub-server/caniuse.png alt="Browser support"><p>Check
33<a href="https://caniuse.com/#feat=eventsource">https://caniuse.com/#feat=eventsource</a>
34for latest information about browser support.<h3 id=known-issues>Known issues</h3><ul><li>Firefox 52 and below do not support EventSource in web/shared workers<li>In Firefox prior to version 36 server-sent events do not reconnect
35automatically in case of a connection interrupt (bug)<li>Reportedly, CORS in EventSource is currently supported in Firefox 10+, Opera
3612+, Chrome 26+, Safari 7.0+.<li>Antivirus software may block the event streaming data chunks.</ul><p>Source: <a href="https://caniuse.com/#feat=eventsource">https://caniuse.com/#feat=eventsource</a><h3 id=message-format>Message format</h3><p>The simplest message that can be sent is only with data attribute:<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span>data: this is a simple message
37</span></span><span style=display:flex><span>&lt;blank line&gt;
38</span></span></code></pre><p>You can send message IDs to be used if the connection is dropped:<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span>id: 33
39</span></span><span style=display:flex><span>data: this is line one
40</span></span><span style=display:flex><span>data: this is line two
41</span></span><span style=display:flex><span>&lt;blank line&gt;
42</span></span></code></pre><p>And you can specify your own event types (the above messages will all trigger
43the message event):<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span>id: 36
44</span></span><span style=display:flex><span>event: price
45</span></span><span style=display:flex><span>data: 103.34
46</span></span><span style=display:flex><span>&lt;blank line&gt;
47</span></span></code></pre><h3 id=server-requirements>Server requirements</h3><p>The important thing is how you send headers and which headers are sent by the
48server that triggers browser to threat response as a EventStream.<p>Headers responsible for this are:<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span>Content-Type: text/event-stream
49</span></span><span style=display:flex><span>Cache-Control: no-cache
50</span></span><span style=display:flex><span>Connection: keep-alive
51</span></span></code></pre><h3 id=debugging-with-google-chrome>Debugging with Google Chrome</h3><p>Google Chrome provides build-in debugging and exploration tool for <a href=https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events>Server-Sent
52Events</a>
53which is quite nice and available from Developer Tools under Network tab.<blockquote><p>You can debug only client side events that get received and not the server
54ones. For debugging server events add <code>console.log</code> to <code>server.js</code> code and
55print out events.</blockquote><p><img src=/assets/simple-pubsub-server/chrome-debugging.png alt="Google Chrome Developer Tools EventStream"><h2 id=server-implementation>Server implementation</h2><p>For the sake of this example we will use <a href=https://nodejs.org/en/>Node.js</a> with
56<a href=https://expressjs.com>Express</a> as our router since this is the easiest way to
57get started and we will use already written SSE library for node
58<a href=https://www.npmjs.com/package/sse-pubsub>sse-pubsub</a> so we don't reinvent the
59wheel.<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span>npm init --yes
60</span></span><span style=display:flex><span>
61</span></span><span style=display:flex><span>npm install express
62</span></span><span style=display:flex><span>npm install body-parser
63</span></span><span style=display:flex><span>npm install sse-pubsub
64</span></span></code></pre><p>Basic implementation of a server (<code>server.js</code>):<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span><span style=color:#00f>const</span> express = require(<span style=color:#a31515>&#39;express&#39;</span>);
65</span></span><span style=display:flex><span><span style=color:#00f>const</span> bodyParser = require(<span style=color:#a31515>&#39;body-parser&#39;</span>);
66</span></span><span style=display:flex><span><span style=color:#00f>const</span> SSETopic = require(<span style=color:#a31515>&#39;sse-pubsub&#39;</span>);
67</span></span><span style=display:flex><span>
68</span></span><span style=display:flex><span><span style=color:#00f>const</span> app = express();
69</span></span><span style=display:flex><span><span style=color:#00f>const</span> port = process.env.PORT || 4000;
70</span></span><span style=display:flex><span>
71</span></span><span style=display:flex><span><span style=color:green>// topics container
72</span></span></span><span style=display:flex><span><span style=color:green></span><span style=color:#00f>const</span> sseTopics = {};
73</span></span><span style=display:flex><span>
74</span></span><span style=display:flex><span>app.use(bodyParser.json());
75</span></span><span style=display:flex><span>
76</span></span><span style=display:flex><span><span style=color:green>// open for all cors
77</span></span></span><span style=display:flex><span><span style=color:green></span>app.all(<span style=color:#a31515>&#39;*&#39;</span>, (req, res, next) =&gt; {
78</span></span><span style=display:flex><span> res.header(<span style=color:#a31515>&#39;Access-Control-Allow-Origin&#39;</span>, <span style=color:#a31515>&#39;*&#39;</span>);
79</span></span><span style=display:flex><span> res.header(<span style=color:#a31515>&#39;Access-Control-Allow-Headers&#39;</span>, <span style=color:#a31515>&#39;X-Requested-With, Content-Type&#39;</span>);
80</span></span><span style=display:flex><span> next();
81</span></span><span style=display:flex><span>});
82</span></span><span style=display:flex><span>
83</span></span><span style=display:flex><span><span style=color:green>// preflight request error fix
84</span></span></span><span style=display:flex><span><span style=color:green></span>app.options(<span style=color:#a31515>&#39;*&#39;</span>, <span style=color:#00f>async</span> (req, res) =&gt; {
85</span></span><span style=display:flex><span> res.header(<span style=color:#a31515>&#39;Access-Control-Allow-Origin&#39;</span>, <span style=color:#a31515>&#39;*&#39;</span>);
86</span></span><span style=display:flex><span> res.header(<span style=color:#a31515>&#39;Access-Control-Allow-Headers&#39;</span>, <span style=color:#a31515>&#39;X-Requested-With, Content-Type&#39;</span>);
87</span></span><span style=display:flex><span> res.send(<span style=color:#a31515>&#39;OK&#39;</span>);
88</span></span><span style=display:flex><span>});
89</span></span><span style=display:flex><span>
90</span></span><span style=display:flex><span><span style=color:green>// serve the event streams
91</span></span></span><span style=display:flex><span><span style=color:green></span>app.get(<span style=color:#a31515>&#39;/stream/:topic&#39;</span>, <span style=color:#00f>async</span> (req, res, next) =&gt; {
92</span></span><span style=display:flex><span> <span style=color:#00f>const</span> topic = req.params.topic;
93</span></span><span style=display:flex><span>
94</span></span><span style=display:flex><span> <span style=color:#00f>if</span> (!(topic <span style=color:#00f>in</span> sseTopics)) {
95</span></span><span style=display:flex><span> sseTopics[topic] = <span style=color:#00f>new</span> SSETopic({
96</span></span><span style=display:flex><span> pingInterval: 0,
97</span></span><span style=display:flex><span> maxStreamDuration: 15000,
98</span></span><span style=display:flex><span> });
99</span></span><span style=display:flex><span> }
100</span></span><span style=display:flex><span>
101</span></span><span style=display:flex><span> <span style=color:green>// subscribing client to topic
102</span></span></span><span style=display:flex><span><span style=color:green></span> sseTopics[topic].subscribe(req, res);
103</span></span><span style=display:flex><span>});
104</span></span><span style=display:flex><span>
105</span></span><span style=display:flex><span><span style=color:green>// accepts new messages into topic
106</span></span></span><span style=display:flex><span><span style=color:green></span>app.post(<span style=color:#a31515>&#39;/publish&#39;</span>, <span style=color:#00f>async</span> (req, res) =&gt; {
107</span></span><span style=display:flex><span> <span style=color:#00f>let</span> body = req.body;
108</span></span><span style=display:flex><span> <span style=color:#00f>let</span> status = 200;
109</span></span><span style=display:flex><span>
110</span></span><span style=display:flex><span> console.log(<span style=color:#a31515>&#39;Incoming message:&#39;</span>, req.body);
111</span></span><span style=display:flex><span>
112</span></span><span style=display:flex><span> <span style=color:#00f>if</span> (
113</span></span><span style=display:flex><span> body.hasOwnProperty(<span style=color:#a31515>&#39;topic&#39;</span>) &amp;&amp;
114</span></span><span style=display:flex><span> body.hasOwnProperty(<span style=color:#a31515>&#39;event&#39;</span>) &amp;&amp;
115</span></span><span style=display:flex><span> body.hasOwnProperty(<span style=color:#a31515>&#39;message&#39;</span>)
116</span></span><span style=display:flex><span> ) {
117</span></span><span style=display:flex><span> <span style=color:#00f>const</span> topic = req.body.topic;
118</span></span><span style=display:flex><span> <span style=color:#00f>const</span> event = req.body.event;
119</span></span><span style=display:flex><span> <span style=color:#00f>const</span> message = req.body.message;
120</span></span><span style=display:flex><span>
121</span></span><span style=display:flex><span> <span style=color:#00f>if</span> (topic <span style=color:#00f>in</span> sseTopics) {
122</span></span><span style=display:flex><span> <span style=color:green>// sends message to all the subscribers
123</span></span></span><span style=display:flex><span><span style=color:green></span> sseTopics[topic].publish(message, event);
124</span></span><span style=display:flex><span> }
125</span></span><span style=display:flex><span> } <span style=color:#00f>else</span> {
126</span></span><span style=display:flex><span> status = 400;
127</span></span><span style=display:flex><span> }
128</span></span><span style=display:flex><span>
129</span></span><span style=display:flex><span> res.status(status).send({
130</span></span><span style=display:flex><span> status,
131</span></span><span style=display:flex><span> });
132</span></span><span style=display:flex><span>});
133</span></span><span style=display:flex><span>
134</span></span><span style=display:flex><span><span style=color:green>// returns JSON object of all opened topics
135</span></span></span><span style=display:flex><span><span style=color:green></span>app.get(<span style=color:#a31515>&#39;/status&#39;</span>, <span style=color:#00f>async</span> (req, res) =&gt; {
136</span></span><span style=display:flex><span> res.send(sseTopics);
137</span></span><span style=display:flex><span>});
138</span></span><span style=display:flex><span>
139</span></span><span style=display:flex><span><span style=color:green>// health-check endpoint
140</span></span></span><span style=display:flex><span><span style=color:green></span>app.get(<span style=color:#a31515>&#39;/&#39;</span>, <span style=color:#00f>async</span> (req, res) =&gt; {
141</span></span><span style=display:flex><span> res.send(<span style=color:#a31515>&#39;OK&#39;</span>);
142</span></span><span style=display:flex><span>});
143</span></span><span style=display:flex><span>
144</span></span><span style=display:flex><span><span style=color:green>// return a 404 if no routes match
145</span></span></span><span style=display:flex><span><span style=color:green></span>app.use((req, res, next) =&gt; {
146</span></span><span style=display:flex><span> res.set(<span style=color:#a31515>&#39;Cache-Control&#39;</span>, <span style=color:#a31515>&#39;private, no-store&#39;</span>);
147</span></span><span style=display:flex><span> res.status(404).end(<span style=color:#a31515>&#39;Not found&#39;</span>);
148</span></span><span style=display:flex><span>});
149</span></span><span style=display:flex><span>
150</span></span><span style=display:flex><span><span style=color:green>// starts the server
151</span></span></span><span style=display:flex><span><span style=color:green></span>app.listen(port, () =&gt; {
152</span></span><span style=display:flex><span> console.log(<span style=color:#a31515>`PubSub server running on http://localhost:</span><span style=color:#a31515>${</span>port<span style=color:#a31515>}</span><span style=color:#a31515>`</span>);
153</span></span><span style=display:flex><span>});
154</span></span></code></pre><h3 id=our-custom-message-format>Our custom message format</h3><p>Each message posted on a server must be in a specific format that out server
155accepts. Having structure like this allows us to have multiple separated type of
156events on each topic.<p>With this we can separate streams and only receive events that belong to the
157topic.<p>One example would be, that we have index page and we want to receive messages
158about new upvotes or new subscribers but we don't want to follow events for
159other pages. This reduces clutter and overall network. And structure is much
160nicer and maintanable.<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span>{
161</span></span><span style=display:flex><span> &#34;topic&#34;: <span style=color:#a31515>&#34;sample-topic&#34;</span>,
162</span></span><span style=display:flex><span> &#34;event&#34;: <span style=color:#a31515>&#34;sample-event&#34;</span>,
163</span></span><span style=display:flex><span> &#34;message&#34;: { &#34;name&#34;: <span style=color:#a31515>&#34;John&#34;</span> }
164</span></span><span style=display:flex><span>}
165</span></span></code></pre><h2 id=publisher-and-subscriber-clients>Publisher and subscriber clients</h2><h3 id=publisher-and-subscriber-in-action>Publisher and subscriber in action</h3><p><video src=/assets/simple-pubsub-server/clients.m4v controls></video><p>You can download <a href=../simple-pubsub-server/sse-pubsub-server.zip>the code</a> and
166follow along.<h3 id=publisher>Publisher</h3><p>As talked about above publisher is the one that send messages to the
167broker/server. Message inside the payload can be whatever you want (string,
168object, array). I would however personally avoid send large chunks of data like
169blobs and such.<pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span><span style=color:#00f>&lt;!DOCTYPE html&gt;</span>
170</span></span><span style=display:flex><span>&lt;html lang=<span style=color:#a31515>&#34;en&#34;</span>&gt;
171</span></span><span style=display:flex><span>
172</span></span><span style=display:flex><span> &lt;head&gt;
173</span></span><span style=display:flex><span> &lt;meta charset=<span style=color:#a31515>&#34;UTF-8&#34;</span>&gt;
174</span></span><span style=display:flex><span> &lt;meta name=<span style=color:#a31515>&#34;viewport&#34;</span> content=<span style=color:#a31515>&#34;width=device-width, initial-scale=1.0&#34;</span>&gt;
175</span></span><span style=display:flex><span> &lt;title&gt;Publisher&lt;/title&gt;
176</span></span><span style=display:flex><span> &lt;/head&gt;
177</span></span><span style=display:flex><span>
178</span></span><span style=display:flex><span> &lt;body&gt;
179</span></span><span style=display:flex><span>
180</span></span><span style=display:flex><span> &lt;h1&gt;Publisher&lt;/h1&gt;
181</span></span><span style=display:flex><span>
182</span></span><span style=display:flex><span> &lt;fieldset&gt;
183</span></span><span style=display:flex><span> &lt;p&gt;
184</span></span><span style=display:flex><span> &lt;label&gt;Server:&lt;/label&gt;
185</span></span><span style=display:flex><span> &lt;input type=<span style=color:#a31515>&#34;text&#34;</span> id=<span style=color:#a31515>&#34;server&#34;</span> value=<span style=color:#a31515>&#34;http://localhost:4000&#34;</span>&gt;
186</span></span><span style=display:flex><span> &lt;/p&gt;
187</span></span><span style=display:flex><span> &lt;p&gt;
188</span></span><span style=display:flex><span> &lt;label&gt;Topic:&lt;/label&gt;
189</span></span><span style=display:flex><span> &lt;input type=<span style=color:#a31515>&#34;text&#34;</span> id=<span style=color:#a31515>&#34;topic&#34;</span> value=<span style=color:#a31515>&#34;sample-topic&#34;</span>&gt;
190</span></span><span style=display:flex><span> &lt;/p&gt;
191</span></span><span style=display:flex><span> &lt;p&gt;
192</span></span><span style=display:flex><span> &lt;label&gt;Event:&lt;/label&gt;
193</span></span><span style=display:flex><span> &lt;input type=<span style=color:#a31515>&#34;text&#34;</span> id=<span style=color:#a31515>&#34;event&#34;</span> value=<span style=color:#a31515>&#34;sample-event&#34;</span>&gt;
194</span></span><span style=display:flex><span> &lt;/p&gt;
195</span></span><span style=display:flex><span> &lt;p&gt;
196</span></span><span style=display:flex><span> &lt;label&gt;Message:&lt;/label&gt;
197</span></span><span style=display:flex><span> &lt;input type=<span style=color:#a31515>&#34;text&#34;</span> id=<span style=color:#a31515>&#34;message&#34;</span> value=<span style=color:#a31515>&#39;{&#34;name&#34;: &#34;John&#34;}&#39;</span>&gt;
198</span></span><span style=display:flex><span> &lt;/p&gt;
199</span></span><span style=display:flex><span> &lt;p&gt;
200</span></span><span style=display:flex><span> &lt;button type=<span style=color:#a31515>&#34;button&#34;</span> id=<span style=color:#a31515>&#34;button&#34;</span>&gt;Publish message to topic&lt;/button&gt;
201</span></span><span style=display:flex><span> &lt;/p&gt;
202</span></span><span style=display:flex><span> &lt;/fieldset&gt;
203</span></span><span style=display:flex><span>
204</span></span><span style=display:flex><span> &lt;script&gt;
205</span></span><span style=display:flex><span>
206</span></span><span style=display:flex><span> <span style=color:#00f>const</span> button = document.querySelector(<span style=color:#a31515>&#39;#button&#39;</span>);
207</span></span><span style=display:flex><span> <span style=color:#00f>const</span> server = document.querySelector(<span style=color:#a31515>&#39;#server&#39;</span>);
208</span></span><span style=display:flex><span> <span style=color:#00f>const</span> topic = document.querySelector(<span style=color:#a31515>&#39;#topic&#39;</span>);
209</span></span><span style=display:flex><span> <span style=color:#00f>const</span> event = document.querySelector(<span style=color:#a31515>&#39;#event&#39;</span>);
210</span></span><span style=display:flex><span> <span style=color:#00f>const</span> message = document.querySelector(<span style=color:#a31515>&#39;#message&#39;</span>);
211</span></span><span style=display:flex><span>
212</span></span><span style=display:flex><span> button.addEventListener(<span style=color:#a31515>&#39;click&#39;</span>, <span style=color:#00f>async</span> (evt) =&gt; {
213</span></span><span style=display:flex><span> <span style=color:#00f>const</span> req = <span style=color:#00f>await</span> fetch(<span style=color:#a31515>`</span><span style=color:#a31515>${</span>server.value<span style=color:#a31515>}</span><span style=color:#a31515>/publish`</span>, {
214</span></span><span style=display:flex><span> method: <span style=color:#a31515>&#39;post&#39;</span>,
215</span></span><span style=display:flex><span> headers: {
216</span></span><span style=display:flex><span> <span style=color:#a31515>&#39;Accept&#39;</span>: <span style=color:#a31515>&#39;application/json&#39;</span>,
217</span></span><span style=display:flex><span> <span style=color:#a31515>&#39;Content-Type&#39;</span>: <span style=color:#a31515>&#39;application/json&#39;</span>,
218</span></span><span style=display:flex><span> },
219</span></span><span style=display:flex><span> body: JSON.stringify({
220</span></span><span style=display:flex><span> topic: topic.value,
221</span></span><span style=display:flex><span> event: event.value,
222</span></span><span style=display:flex><span> message: JSON.parse(message.value),
223</span></span><span style=display:flex><span> }),
224</span></span><span style=display:flex><span> });
225</span></span><span style=display:flex><span>
226</span></span><span style=display:flex><span> <span style=color:#00f>const</span> res = <span style=color:#00f>await</span> req.json();
227</span></span><span style=display:flex><span> console.log(res);
228</span></span><span style=display:flex><span> });
229</span></span><span style=display:flex><span>
230</span></span><span style=display:flex><span> &lt;/script&gt;
231</span></span><span style=display:flex><span>
232</span></span><span style=display:flex><span> &lt;/body&gt;
233</span></span><span style=display:flex><span>
234</span></span><span style=display:flex><span>&lt;/html&gt;
235</span></span></code></pre><h3 id=subscriber>Subscriber</h3><p>Subscriber is responsible for receiving new messages that come from server via
236publisher. The code bellow is very rudimentary but works and follows the
237implementation guidelines for EventSource.<p>You can use either Developer Tools Console to see incoming messages or you can
238defer to Debugging with Google Chrome section above to see all EventStream
239messages.<blockquote><p>Don't be alarmed if the subscriber gets disconnected from the server every so
240often. The code we have here resets connection every 15s but it automatically
241get reconnected and fetches all messages up to last received message id. This
242setting can be adjusted in <code>server.js</code> file; search for the
243<code>maxStreamDuration</code> variable.</blockquote><pre tabindex=0 style=background-color:#fff><code><span style=display:flex><span><span style=color:#00f>&lt;!DOCTYPE html&gt;</span>
244</span></span><span style=display:flex><span>&lt;html lang=<span style=color:#a31515>&#34;en&#34;</span>&gt;
245</span></span><span style=display:flex><span>
246</span></span><span style=display:flex><span> &lt;head&gt;
247</span></span><span style=display:flex><span> &lt;meta charset=<span style=color:#a31515>&#34;UTF-8&#34;</span>&gt;
248</span></span><span style=display:flex><span> &lt;meta name=<span style=color:#a31515>&#34;viewport&#34;</span> content=<span style=color:#a31515>&#34;width=device-width, initial-scale=1.0&#34;</span>&gt;
249</span></span><span style=display:flex><span> &lt;title&gt;Subscriber&lt;/title&gt;
250</span></span><span style=display:flex><span> &lt;link rel=<span style=color:#a31515>&#34;stylesheet&#34;</span> href=<span style=color:#a31515>&#34;style.css&#34;</span>&gt;
251</span></span><span style=display:flex><span> &lt;/head&gt;
252</span></span><span style=display:flex><span>
253</span></span><span style=display:flex><span> &lt;body&gt;
254</span></span><span style=display:flex><span>
255</span></span><span style=display:flex><span> &lt;h1&gt;Subscriber&lt;/h1&gt;
256</span></span><span style=display:flex><span>
257</span></span><span style=display:flex><span> &lt;fieldset&gt;
258</span></span><span style=display:flex><span> &lt;p&gt;
259</span></span><span style=display:flex><span> &lt;label&gt;Server:&lt;/label&gt;
260</span></span><span style=display:flex><span> &lt;input type=<span style=color:#a31515>&#34;text&#34;</span> id=<span style=color:#a31515>&#34;server&#34;</span> value=<span style=color:#a31515>&#34;http://localhost:4000&#34;</span>&gt;
261</span></span><span style=display:flex><span> &lt;/p&gt;
262</span></span><span style=display:flex><span> &lt;p&gt;
263</span></span><span style=display:flex><span> &lt;label&gt;Topic:&lt;/label&gt;
264</span></span><span style=display:flex><span> &lt;input type=<span style=color:#a31515>&#34;text&#34;</span> id=<span style=color:#a31515>&#34;topic&#34;</span> value=<span style=color:#a31515>&#34;sample-topic&#34;</span>&gt;
265</span></span><span style=display:flex><span> &lt;/p&gt;
266</span></span><span style=display:flex><span> &lt;p&gt;
267</span></span><span style=display:flex><span> &lt;label&gt;Event:&lt;/label&gt;
268</span></span><span style=display:flex><span> &lt;input type=<span style=color:#a31515>&#34;text&#34;</span> id=<span style=color:#a31515>&#34;event&#34;</span> value=<span style=color:#a31515>&#34;sample-event&#34;</span>&gt;
269</span></span><span style=display:flex><span> &lt;/p&gt;
270</span></span><span style=display:flex><span> &lt;p&gt;
271</span></span><span style=display:flex><span> &lt;button type=<span style=color:#a31515>&#34;button&#34;</span> id=<span style=color:#a31515>&#34;button&#34;</span>&gt;Subscribe to topic&lt;/button&gt;
272</span></span><span style=display:flex><span> &lt;/p&gt;
273</span></span><span style=display:flex><span> &lt;/fieldset&gt;
274</span></span><span style=display:flex><span>
275</span></span><span style=display:flex><span> &lt;script&gt;
276</span></span><span style=display:flex><span>
277</span></span><span style=display:flex><span> <span style=color:#00f>const</span> button = document.querySelector(<span style=color:#a31515>&#39;#button&#39;</span>);
278</span></span><span style=display:flex><span> <span style=color:#00f>const</span> server = document.querySelector(<span style=color:#a31515>&#39;#server&#39;</span>);
279</span></span><span style=display:flex><span> <span style=color:#00f>const</span> topic = document.querySelector(<span style=color:#a31515>&#39;#topic&#39;</span>);
280</span></span><span style=display:flex><span> <span style=color:#00f>const</span> event = document.querySelector(<span style=color:#a31515>&#39;#event&#39;</span>);
281</span></span><span style=display:flex><span>
282</span></span><span style=display:flex><span> button.addEventListener(<span style=color:#a31515>&#39;click&#39;</span>, <span style=color:#00f>async</span> (evt) =&gt; {
283</span></span><span style=display:flex><span>
284</span></span><span style=display:flex><span> <span style=color:#00f>let</span> es = <span style=color:#00f>new</span> EventSource(<span style=color:#a31515>`</span><span style=color:#a31515>${</span>server.value<span style=color:#a31515>}</span><span style=color:#a31515>/stream/</span><span style=color:#a31515>${</span>topic.value<span style=color:#a31515>}</span><span style=color:#a31515>`</span>);
285</span></span><span style=display:flex><span>
286</span></span><span style=display:flex><span> es.addEventListener(event.value, <span style=color:#00f>function</span> (evt) {
287</span></span><span style=display:flex><span> console.log(<span style=color:#a31515>`incoming message`</span>, JSON.parse(evt.data));
288</span></span><span style=display:flex><span> });
289</span></span><span style=display:flex><span>
290</span></span><span style=display:flex><span> es.addEventListener(<span style=color:#a31515>&#39;open&#39;</span>, <span style=color:#00f>function</span> (evt) {
291</span></span><span style=display:flex><span> console.log(<span style=color:#a31515>&#39;connected&#39;</span>, evt);
292</span></span><span style=display:flex><span> });
293</span></span><span style=display:flex><span>
294</span></span><span style=display:flex><span> es.addEventListener(<span style=color:#a31515>&#39;error&#39;</span>, <span style=color:#00f>function</span> (evt) {
295</span></span><span style=display:flex><span> console.log(<span style=color:#a31515>&#39;error&#39;</span>, evt);
296</span></span><span style=display:flex><span> });
297</span></span><span style=display:flex><span>
298</span></span><span style=display:flex><span> });
299</span></span><span style=display:flex><span>
300</span></span><span style=display:flex><span> &lt;/script&gt;
301</span></span><span style=display:flex><span>
302</span></span><span style=display:flex><span> &lt;/body&gt;
303</span></span><span style=display:flex><span>
304</span></span><span style=display:flex><span>&lt;/html&gt;
305</span></span></code></pre><h2 id=reading-further>Reading further</h2><ul><li><a href=https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events>Using server-sent events</a><li><a href=https://www.smashingmagazine.com/2018/02/sse-websockets-data-flow-http2/>Using SSE Instead Of WebSockets For Unidirectional Data Flow Over HTTP/2</a><li><a href=https://apifriends.com/api-streaming/server-sent-events/>What is Server-Sent Events?</a><li><a href=https://tools.ietf.org/id/draft-xie-bidirectional-messaging-01.html>An HTTP/2 extension for bidirectional messaging communication</a><li><a href=https://developers.google.com/web/fundamentals/performance/http2>Introduction to HTTP/2</a><li><a href=https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API>The WebSocket API (WebSockets)</a></ul></div></div></main><footer><hr><div><h3>Want to comment or have something to add?</h3>You can write me an email at
306<a href=mailto:m@mitjafelicijan.com>m@mitjafelicijan.com</a> or catch up
307with me
308<a href=https://telegram.me/mitjafelicijan target=_blank>on Telegram</a>.</div><hr><p>This website does not track you. Content is made available under
309the <a href=https://creativecommons.org/licenses/by/4.0/ target=_blank rel=noreferrer>CC BY 4.0 license</a> unless specified
310otherwise. Blog feed is available as <a href=/index.xml target=_blank>RSS feed</a>.</footer><script src=https://cdn.usefathom.com/script.js data-site=XHQARKXP defer></script> \ No newline at end of file