diff --git a/404.html b/404.html index a220b07f3..ff390379c 100644 --- a/404.html +++ b/404.html @@ -15,8 +15,8 @@ - - + +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

diff --git a/assets/js/02080b57.dd81be33.js b/assets/js/02080b57.ba223a6f.js similarity index 78% rename from assets/js/02080b57.dd81be33.js rename to assets/js/02080b57.ba223a6f.js index c631c3abf..7f6013383 100644 --- a/assets/js/02080b57.dd81be33.js +++ b/assets/js/02080b57.ba223a6f.js @@ -1 +1 @@ -"use strict";(self.webpackChunkcentrifugal_dev=self.webpackChunkcentrifugal_dev||[]).push([[9040],{94569:e=>{e.exports=JSON.parse('{"permalink":"/blog/tags/centrifugo","page":1,"postsPerPage":30,"totalPages":1,"totalCount":12,"blogDescription":"Centrifugal Blog","blogTitle":"Centrifugal Blog"}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkcentrifugal_dev=self.webpackChunkcentrifugal_dev||[]).push([[9040],{94569:e=>{e.exports=JSON.parse('{"permalink":"/blog/tags/centrifugo","page":1,"postsPerPage":30,"totalPages":1,"totalCount":13,"blogDescription":"Centrifugal Blog","blogTitle":"Centrifugal Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/059c3f88.711da6fc.js b/assets/js/059c3f88.711da6fc.js new file mode 100644 index 000000000..cbce43603 --- /dev/null +++ b/assets/js/059c3f88.711da6fc.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkcentrifugal_dev=self.webpackChunkcentrifugal_dev||[]).push([[2750],{90671:e=>{e.exports=JSON.parse('{"permalink":"/blog/tags/grpc","page":1,"postsPerPage":30,"totalPages":1,"totalCount":1,"blogDescription":"Centrifugal Blog","blogTitle":"Centrifugal Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/0d1e4c44.eff4b587.js b/assets/js/0d1e4c44.eff4b587.js new file mode 100644 index 000000000..898e77153 --- /dev/null +++ b/assets/js/0d1e4c44.eff4b587.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkcentrifugal_dev=self.webpackChunkcentrifugal_dev||[]).push([[5104],{73669:e=>{e.exports=JSON.parse('{"permalink":"/blog/tags/loki","page":1,"postsPerPage":30,"totalPages":1,"totalCount":1,"blogDescription":"Centrifugal Blog","blogTitle":"Centrifugal Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/29b4017f.58f99fc7.js b/assets/js/29b4017f.58f99fc7.js new file mode 100644 index 000000000..d7656919d --- /dev/null +++ b/assets/js/29b4017f.58f99fc7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkcentrifugal_dev=self.webpackChunkcentrifugal_dev||[]).push([[5993],{33416:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>l,contentTitle:()=>n,default:()=>c,frontMatter:()=>s,metadata:()=>a,toc:()=>u});var o=i(85893),r=i(11151);const s={title:"Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions",tags:["centrifugo","loki","grpc"],description:"Centrifugo has GRPC subscription streams feature, in this post we show how this feature may simplify a task of delivering data to application UI in real-time. We integrate with Loki, injest log entries and stream logs to the browser based on user-supplied query",author:"Alexander Emelin",authorTitle:"Founder of Centrifugal Labs",authorImageURL:"/img/alexander_emelin.jpeg",image:"/img/centrifugo_loki.jpg",hide_table_of_contents:!1},n=void 0,a={permalink:"/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions",editUrl:"https://github.com/centrifugal/centrifugal.dev/edit/main/blog/2024-03-18-stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions.md",source:"@site/blog/2024-03-18-stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions.md",title:"Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions",description:"Centrifugo has GRPC subscription streams feature, in this post we show how this feature may simplify a task of delivering data to application UI in real-time. We integrate with Loki, injest log entries and stream logs to the browser based on user-supplied query",date:"2024-03-18T00:00:00.000Z",formattedDate:"March 18, 2024",tags:[{label:"centrifugo",permalink:"/blog/tags/centrifugo"},{label:"loki",permalink:"/blog/tags/loki"},{label:"grpc",permalink:"/blog/tags/grpc"}],readingTime:7.68,hasTruncateMarker:!0,authors:[{name:"Alexander Emelin",title:"Founder of Centrifugal Labs",imageURL:"/img/alexander_emelin.jpeg"}],frontMatter:{title:"Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions",tags:["centrifugo","loki","grpc"],description:"Centrifugo has GRPC subscription streams feature, in this post we show how this feature may simplify a task of delivering data to application UI in real-time. We integrate with Loki, injest log entries and stream logs to the browser based on user-supplied query",author:"Alexander Emelin",authorTitle:"Founder of Centrifugal Labs",authorImageURL:"/img/alexander_emelin.jpeg",image:"/img/centrifugo_loki.jpg",hide_table_of_contents:!1},unlisted:!1,nextItem:{title:"Discovering Centrifugo PRO: push notifications API",permalink:"/blog/2023/10/29/discovering-centrifugo-pro-push-notifications"}},l={authorsImageUrls:[void 0]},u=[];function g(e){const t={p:"p",...(0,r.a)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("img",{src:"/img/centrifugo_loki.jpg"}),"\n",(0,o.jsx)(t.p,{children:"As of version 5.1.0, Centrifugo introduces an experimental yet powerful extension that promises to simplify the data delivery process to the browser using GRPC streams. We believe it may help you to solve some practical tasks in minutes. Let's dive into how this feature works and how you can leverage it in your applications integrating with Loki real-time log streaming capabilities."})]})}function c(e={}){const{wrapper:t}={...(0,r.a)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(g,{...e})}):g(e)}},11151:(e,t,i)=>{i.d(t,{Z:()=>a,a:()=>n});var o=i(67294);const r={},s=o.createContext(r);function n(e){const t=o.useContext(s);return o.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:n(e.components),o.createElement(s.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5f78e650.4da80a6b.js b/assets/js/5f78e650.250c7a0e.js similarity index 76% rename from assets/js/5f78e650.4da80a6b.js rename to assets/js/5f78e650.250c7a0e.js index bdf87dbe9..0890496cd 100644 --- a/assets/js/5f78e650.4da80a6b.js +++ b/assets/js/5f78e650.250c7a0e.js @@ -1 +1 @@ -"use strict";(self.webpackChunkcentrifugal_dev=self.webpackChunkcentrifugal_dev||[]).push([[6979],{74300:e=>{e.exports=JSON.parse('{"label":"centrifugo","permalink":"/blog/tags/centrifugo","allTagsPath":"/blog/tags","count":12,"unlisted":false}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkcentrifugal_dev=self.webpackChunkcentrifugal_dev||[]).push([[6979],{74300:e=>{e.exports=JSON.parse('{"label":"centrifugo","permalink":"/blog/tags/centrifugo","allTagsPath":"/blog/tags","count":13,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/6aa24bd3.db8303bc.js b/assets/js/6aa24bd3.db8303bc.js new file mode 100644 index 000000000..b5bc047d7 --- /dev/null +++ b/assets/js/6aa24bd3.db8303bc.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkcentrifugal_dev=self.webpackChunkcentrifugal_dev||[]).push([[6103],{19203:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>o,metadata:()=>a,toc:()=>c});var r=n(85893),i=n(11151);const o={title:"Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions",tags:["centrifugo","loki","grpc"],description:"Centrifugo has GRPC subscription streams feature, in this post we show how this feature may simplify a task of delivering data to application UI in real-time. We integrate with Loki, injest log entries and stream logs to the browser based on user-supplied query",author:"Alexander Emelin",authorTitle:"Founder of Centrifugal Labs",authorImageURL:"/img/alexander_emelin.jpeg",image:"/img/centrifugo_loki.jpg",hide_table_of_contents:!1},s=void 0,a={permalink:"/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions",editUrl:"https://github.com/centrifugal/centrifugal.dev/edit/main/blog/2024-03-18-stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions.md",source:"@site/blog/2024-03-18-stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions.md",title:"Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions",description:"Centrifugo has GRPC subscription streams feature, in this post we show how this feature may simplify a task of delivering data to application UI in real-time. We integrate with Loki, injest log entries and stream logs to the browser based on user-supplied query",date:"2024-03-18T00:00:00.000Z",formattedDate:"March 18, 2024",tags:[{label:"centrifugo",permalink:"/blog/tags/centrifugo"},{label:"loki",permalink:"/blog/tags/loki"},{label:"grpc",permalink:"/blog/tags/grpc"}],readingTime:7.68,hasTruncateMarker:!0,authors:[{name:"Alexander Emelin",title:"Founder of Centrifugal Labs",imageURL:"/img/alexander_emelin.jpeg"}],frontMatter:{title:"Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions",tags:["centrifugo","loki","grpc"],description:"Centrifugo has GRPC subscription streams feature, in this post we show how this feature may simplify a task of delivering data to application UI in real-time. We integrate with Loki, injest log entries and stream logs to the browser based on user-supplied query",author:"Alexander Emelin",authorTitle:"Founder of Centrifugal Labs",authorImageURL:"/img/alexander_emelin.jpeg",image:"/img/centrifugo_loki.jpg",hide_table_of_contents:!1},unlisted:!1,nextItem:{title:"Discovering Centrifugo PRO: push notifications API",permalink:"/blog/2023/10/29/discovering-centrifugo-pro-push-notifications"}},l={authorsImageUrls:[void 0]},c=[{value:"What Are Proxy Subscription Streams?",id:"what-are-proxy-subscription-streams",level:2},{value:"Demo and source code",id:"demo-and-source-code",level:2},{value:"Setting Up Loki",id:"setting-up-loki",level:2},{value:"Configuring Centrifugo",id:"configuring-centrifugo",level:2},{value:"Writing frontend",id:"writing-frontend",level:2},{value:"Handle subscription stream on the Go side",id:"handle-subscription-stream-on-the-go-side",level:2},{value:"Conclusion",id:"conclusion",level:2}];function d(e){const t={a:"a",code:"code",h2:"h2",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,i.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)("img",{src:"/img/centrifugo_loki.jpg"}),"\n",(0,r.jsx)(t.p,{children:"As of version 5.1.0, Centrifugo introduces an experimental yet powerful extension that promises to simplify the data delivery process to the browser using GRPC streams. We believe it may help you to solve some practical tasks in minutes. Let's dive into how this feature works and how you can leverage it in your applications integrating with Loki real-time log streaming capabilities."}),"\n",(0,r.jsx)(t.h2,{id:"what-are-proxy-subscription-streams",children:"What Are Proxy Subscription Streams?"}),"\n",(0,r.jsxs)(t.p,{children:[(0,r.jsx)(t.a,{href:"/docs/server/proxy_streams",children:"Proxy Subscription Streams"})," support pushing data directly to Centrifugo client channel subscriptions from your application backend over GRPC streams. This feature is designed to facilitate individual data streams to clients as soon as they subscribe to a channel, acting as a bridge between WebSocket connections from clients and GRPC streams to the backend. It supports both unidirectional (backend to client) and bidirectional (both ways) streams, thereby enhancing flexibility in data streaming."]}),"\n",(0,r.jsx)(t.p,{children:(0,r.jsx)(t.img,{src:n(75968).Z+"",width:"3242",height:"1065"})}),"\n",(0,r.jsx)(t.p,{children:"Establishing a stream between Centrifugo and your application backend upon a channel subscription provides a straightforward path for data to travel directly to the subscribed clients. This mechanism not only simplifies the architecture for real-time data delivery but also ensures fast and individualized data streaming."}),"\n",(0,r.jsxs)(t.p,{children:["The design is inspired by ",(0,r.jsx)(t.a,{href:"http://websocketd.com/",children:"Websocketd"})," server \u2013 but while Websocketd transforms data from programs running locally, Centrifugo provides a more generic network interface with GRPC. And all other features of Centrifugo like connection authentication, online presence come as a great bonus."]}),"\n",(0,r.jsx)(t.p,{children:"In the documentation for Proxy Subscription Streams we mentioned streaming logs from Loki as one of the possible use cases. Let's expand on the idea and implement the working solution in just 10 minutes."}),"\n",(0,r.jsx)(t.h2,{id:"demo-and-source-code",children:"Demo and source code"}),"\n",(0,r.jsx)(t.p,{children:"Here is a demo of what we well get:"}),"\n",(0,r.jsx)("video",{width:"100%",loop:!0,autoPlay:"autoplay",muted:!0,controls:"",src:"/img/loki.mp4"}),"\n",(0,r.jsxs)(t.p,{children:["Take a look at ",(0,r.jsx)(t.a,{href:"https://github.com/centrifugal/examples/tree/master/v5/subscription_streams_loki",children:"full source code on Github"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"setting-up-loki",children:"Setting Up Loki"}),"\n",(0,r.jsx)(t.p,{children:"Loki is a horizontally-scalable, highly-available, multi-tenant log aggregation system inspired by Prometheus. It is designed to be very cost-effective and easy to operate, making it a perfect candidate for our real-time log streaming example."}),"\n",(0,r.jsxs)(t.p,{children:["We will build the example using Docker Compose, all we have to do for the example is to include Loki image to ",(0,r.jsx)(t.code,{children:"docker-compose.yml"}),":"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-yaml",children:'services:\n loki:\n image: grafana/loki:latest\n ports:\n - "3100:3100"\n'})}),"\n",(0,r.jsx)(t.p,{children:"Loki can ingest logs via various methods, including Promtail, Grafana Agent, Fluentd, and more. For simplicity, we will send logs to Loki ourselves from the Go application."}),"\n",(0,r.jsx)(t.p,{children:"To send logs to Loki, we can use the HTTP API that Loki provides. This is a straightforward way to push logs directly from an application. The example below demonstrates how to create a simple Go application that generates logs and sends them to Loki using HTTP POST requests."}),"\n",(0,r.jsx)(t.p,{children:"First, define a function to send a log entry to Loki:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-go",children:'const (\n\tlokiPushEndpoint = "http://loki:3100/loki/api/v1/push"\n)\n\ntype lokiPushMessage struct {\n\tStreams []lokiStream `json:"streams"`\n}\n\ntype lokiStream struct {\n\tStream map[string]string `json:"stream"`\n\tValues [][]string `json:"values"`\n}\n\nfunc sendLogMessageToLoki(_ context.Context) error {\n\tsources := []string{"backend1", "backend2", "backend3"}\n\tsource := sources[rand.Intn(len(sources))]\n\tlogMessage := fmt.Sprintf("log from %s source", source)\n\n\tpayload := lokiPushMessage{\n\t\tStreams: []lokiStream{\n\t\t\t{\n\t\t\t\tStream: map[string]string{\n\t\t\t\t\t"source": source,\n\t\t\t\t},\n\t\t\t\tValues: [][]string{\n\t\t\t\t\t{fmt.Sprintf("%d", time.Now().UnixNano()), logMessage},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tjsonData, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp, err := http.Post(\n\t\tlokiPushEndpoint, "application/json", bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusNoContent {\n\t\treturn fmt.Errorf("unexpected status code: %d", resp.StatusCode)\n\t}\n\treturn nil\n}\n\nfunc sendLogsToLoki(ctx context.Context) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-time.After(200 * time.Millisecond):\n\t\t\terr := sendLogMessageToLoki(ctx)\n\t\t\tif err != nil {\n\t\t\t\tlog.Println("error sending log to Loki:", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc main() {\n\tctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)\n\tdefer cancel()\n\n\tsendLogsToLoki(ctx)\n}\n'})}),"\n",(0,r.jsxs)(t.p,{children:["This program defines a ",(0,r.jsx)(t.code,{children:"sendLogsToLoki"})," function that constructs a log entry and sends it to Loki using its HTTP API. It continuously generates log messages every 200 milliseconds."]}),"\n",(0,r.jsxs)(t.p,{children:["The ",(0,r.jsx)(t.code,{children:"lokiPushMessage"})," struct is structured to match the JSON payload expected by Loki's ",(0,r.jsx)(t.a,{href:"https://grafana.com/docs/loki/latest/reference/api/#push-log-entries-to-loki",children:(0,r.jsx)(t.code,{children:"/loki/api/v1/push"})})," endpoint. Each log entry consists of a set of labels (in the Stream map) and log line values, where each value is a two-element array containing the timestamp and the log line. The timestamp is in nanoseconds to match Loki's expected format."]}),"\n",(0,r.jsxs)(t.p,{children:["Note, in the example we randomly set log entry ",(0,r.jsx)(t.code,{children:"source"})," label choosing between ",(0,r.jsx)(t.code,{children:"backend1"}),", ",(0,r.jsx)(t.code,{children:"backend2"})," and ",(0,r.jsx)(t.code,{children:"backend3"})," values."]}),"\n",(0,r.jsx)(t.p,{children:"At this point our program pushes some logs to Loki, now let's add Centrifugo to consume them from browser in real-time."}),"\n",(0,r.jsx)(t.h2,{id:"configuring-centrifugo",children:"Configuring Centrifugo"}),"\n",(0,r.jsx)(t.p,{children:"Adding Centrifugo is also rather straightforward:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{children:"services:\n centrifugo:\n image: centrifugo/centrifugo:v5.3.0\n restart: unless-stopped\n volumes:\n - ./centrifugo/config.json:/centrifugo/config.json\n command: centrifugo -c config.json\n expose:\n - 8000\n"})}),"\n",(0,r.jsxs)(t.p,{children:["Where ",(0,r.jsx)(t.code,{children:"config.json"})," is:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-json",children:'{\n "client_insecure": true,\n "allowed_origins": ["http://localhost:9000"],\n "proxy_subscribe_stream_endpoint": "grpc://backend:12000",\n "proxy_subscribe_stream_timeout": "3s",\n "namespaces": [\n {\n "name": "logs",\n "proxy_subscribe_stream": true\n }\n ]\n}\n'})}),"\n",(0,r.jsxs)(t.p,{children:["Note, we enabled ",(0,r.jsx)(t.code,{children:"client_insecure"})," option here \u2013 this is to keep example short, but in real live you may benefit from Centrifugo authentication: ",(0,r.jsx)(t.a,{href:"/docs/server/authentication",children:"JWT-based"})," or ",(0,r.jsx)(t.a,{href:"/docs/server/proxy#connect-proxy",children:"proxy-based"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"writing-frontend",children:"Writing frontend"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-html",children:'\n\n\n \n Streaming logs with Centrifugo and Loki\n\n\n
\n
\n \n \n
\n
\n \n
\n
\n - + + -
Skip to main content
October 29, 2023 by Alexander Emelin
We start talking more about recently launched Centrifugo PRO. In this post, we share details about Centrifugo PRO push notification API implementation - how it works and what makes it special and practical.
August 29, 2023 by Centrifugal + RabbitX
In this post, the engineering team of RabbitX platform shares details about the usage of Centrifugo in their product.
August 19, 2023 by Alexander Emelin
In this post, we'll demonstrate how to asynchronously stream messages into Centrifugo channels from external data providers using Benthos tool. We also highlight some pitfalls which become more important in asynchronous publishing scenario.
June 29, 2023 by Centrifugal team
We are excited to announce a new version of Centrifugo. It's an evolutionary step which makes Centrifugo cleaner and more intuitive to use.
July 19, 2022 by Centrifugal team
Centrifugo v4 provides an optimized client protocol, modern WebSocket emulation, improved channel security, redesigned client SDK behavior, experimental HTTP/3 and WebTransport support.
October 18, 2021 by Alexander Emelin
In this tutorial we are integrating Centrifugo with NodeJS. We are using Centrifugo connect proxy feature to authenticate connections over standard Express.js session middleware.
August 31, 2021 by Centrifugal team
Centrifugo v3 released with lots of exciting improvements
November 12, 2020 by Alexander Emelin
The post describes techniques to write scalable WebSocket servers within Go ecosystem and beyond it
February 10, 2020 by Centrifugal team
Describing a test stand in Kubernetes where we connect one million websocket connections to a server, using Redis to scale nodes, and providing insights about hardware resources required to achieve 500k messages per second
+
Skip to main content
October 29, 2023 by Alexander Emelin
We start talking more about recently launched Centrifugo PRO. In this post, we share details about Centrifugo PRO push notification API implementation - how it works and what makes it special and practical.
August 29, 2023 by Centrifugal + RabbitX
In this post, the engineering team of RabbitX platform shares details about the usage of Centrifugo in their product.
August 19, 2023 by Alexander Emelin
In this post, we'll demonstrate how to asynchronously stream messages into Centrifugo channels from external data providers using Benthos tool. We also highlight some pitfalls which become more important in asynchronous publishing scenario.
June 29, 2023 by Centrifugal team
We are excited to announce a new version of Centrifugo. It's an evolutionary step which makes Centrifugo cleaner and more intuitive to use.
July 19, 2022 by Centrifugal team
Centrifugo v4 provides an optimized client protocol, modern WebSocket emulation, improved channel security, redesigned client SDK behavior, experimental HTTP/3 and WebTransport support.
October 18, 2021 by Alexander Emelin
In this tutorial we are integrating Centrifugo with NodeJS. We are using Centrifugo connect proxy feature to authenticate connections over standard Express.js session middleware.
August 31, 2021 by Centrifugal team
Centrifugo v3 released with lots of exciting improvements
November 12, 2020 by Alexander Emelin
The post describes techniques to write scalable WebSocket servers within Go ecosystem and beyond it
February 10, 2020 by Centrifugal team
Describing a test stand in Kubernetes where we connect one million websocket connections to a server, using Redis to scale nodes, and providing insights about hardware resources required to achieve 500k messages per second
\ No newline at end of file diff --git a/blog/2020/02/10/million-connections-with-centrifugo.html b/blog/2020/02/10/million-connections-with-centrifugo.html index 14b8385aa..445f65a0a 100644 --- a/blog/2020/02/10/million-connections-with-centrifugo.html +++ b/blog/2020/02/10/million-connections-with-centrifugo.html @@ -15,8 +15,8 @@ - - + +
Skip to main content

Million connections with Centrifugo

· 4 min read
Centrifugal team
diff --git a/blog/2020/10/16/experimenting-with-quic-transport.html b/blog/2020/10/16/experimenting-with-quic-transport.html index 53b7f9db8..055b5ae7c 100644 --- a/blog/2020/10/16/experimenting-with-quic-transport.html +++ b/blog/2020/10/16/experimenting-with-quic-transport.html @@ -15,8 +15,8 @@ - - + +

Experimenting with QUIC and WebTransport

· 15 min read
Alexander Emelin

post-cover

diff --git a/blog/2020/11/12/scaling-websocket.html b/blog/2020/11/12/scaling-websocket.html index 543c196d9..0ee92c97c 100644 --- a/blog/2020/11/12/scaling-websocket.html +++ b/blog/2020/11/12/scaling-websocket.html @@ -15,8 +15,8 @@ - - + +

Scaling WebSocket in Go and beyond

· 19 min read
Alexander Emelin

gopher-broker

diff --git a/blog/2021/01/15/centrifuge-intro.html b/blog/2021/01/15/centrifuge-intro.html index e29571693..14d654a46 100644 --- a/blog/2021/01/15/centrifuge-intro.html +++ b/blog/2021/01/15/centrifuge-intro.html @@ -15,8 +15,8 @@ - - + +

Centrifuge – real-time messaging with Go

· 23 min read
Alexander Emelin

Centrifuge

diff --git a/blog/2021/08/31/hello-centrifugo-v3.html b/blog/2021/08/31/hello-centrifugo-v3.html index 11dfeb38e..8cbe1a904 100644 --- a/blog/2021/08/31/hello-centrifugo-v3.html +++ b/blog/2021/08/31/hello-centrifugo-v3.html @@ -15,8 +15,8 @@ - - + +

Centrifugo v3 released

· 15 min read
Centrifugal team

Centrifuge

diff --git a/blog/2021/10/18/integrating-with-nodejs.html b/blog/2021/10/18/integrating-with-nodejs.html index 4bdd8773f..317ddac82 100644 --- a/blog/2021/10/18/integrating-with-nodejs.html +++ b/blog/2021/10/18/integrating-with-nodejs.html @@ -15,8 +15,8 @@ - - + +

Centrifugo integration with NodeJS tutorial

· 7 min read
Alexander Emelin

Centrifuge

diff --git a/blog/2021/11/04/integrating-with-django-building-chat-application.html b/blog/2021/11/04/integrating-with-django-building-chat-application.html index f7ca5f035..642eb0317 100644 --- a/blog/2021/11/04/integrating-with-django-building-chat-application.html +++ b/blog/2021/11/04/integrating-with-django-building-chat-application.html @@ -15,8 +15,8 @@ - - + +

Centrifugo integration with Django – building a basic chat application

· 16 min read
Alexander Emelin

Centrifuge

diff --git a/blog/2021/12/14/laravel-multi-room-chat-tutorial.html b/blog/2021/12/14/laravel-multi-room-chat-tutorial.html index 61ced8820..a52e9ce39 100644 --- a/blog/2021/12/14/laravel-multi-room-chat-tutorial.html +++ b/blog/2021/12/14/laravel-multi-room-chat-tutorial.html @@ -15,8 +15,8 @@ - - + +

Building a multi-room chat application with Laravel and Centrifugo

· 11 min read
Anton Silischev

Image

diff --git a/blog/2022/07/19/centrifugo-v4-released.html b/blog/2022/07/19/centrifugo-v4-released.html index 965af176b..ea0ab75cb 100644 --- a/blog/2022/07/19/centrifugo-v4-released.html +++ b/blog/2022/07/19/centrifugo-v4-released.html @@ -15,8 +15,8 @@ - - + +

Centrifugo v4 released – a little revolution

· 21 min read
Centrifugal team

Centrifuge

diff --git a/blog/2022/07/29/101-way-to-subscribe.html b/blog/2022/07/29/101-way-to-subscribe.html index 50c4d4d01..8ffaa88b9 100644 --- a/blog/2022/07/29/101-way-to-subscribe.html +++ b/blog/2022/07/29/101-way-to-subscribe.html @@ -15,8 +15,8 @@ - - + +

101 ways to subscribe user on a personal channel in Centrifugo

· 11 min read
Alexander Emelin

Centrifuge

diff --git a/blog/2022/12/20/improving-redis-engine-performance.html b/blog/2022/12/20/improving-redis-engine-performance.html index 5ba7a3495..34486e4be 100644 --- a/blog/2022/12/20/improving-redis-engine-performance.html +++ b/blog/2022/12/20/improving-redis-engine-performance.html @@ -15,8 +15,8 @@ - - + +

Improving Centrifugo Redis Engine throughput and allocation efficiency with Rueidis Go library

· 29 min read
Alexander Emelin

Centrifugo_Redis_Engine_Improvements

diff --git a/blog/2023/03/31/keycloak-sso-centrifugo.html b/blog/2023/03/31/keycloak-sso-centrifugo.html index 9d27735e0..3341acbe1 100644 --- a/blog/2023/03/31/keycloak-sso-centrifugo.html +++ b/blog/2023/03/31/keycloak-sso-centrifugo.html @@ -15,8 +15,8 @@ - - + +

Setting up Keycloak SSO authentication flow and connecting to Centrifugo WebSocket

· 5 min read
Alexander Emelin

diff --git a/blog/2023/06/29/centrifugo-v5-released.html b/blog/2023/06/29/centrifugo-v5-released.html index dcad47e85..deafa8fa3 100644 --- a/blog/2023/06/29/centrifugo-v5-released.html +++ b/blog/2023/06/29/centrifugo-v5-released.html @@ -15,8 +15,8 @@ - - + +

Centrifugo v5 released

· 13 min read
Centrifugal team

In Centrifugo v5 we're phasing out old client protocol support, introducing a more intuitive HTTP API, adjusting token management behaviour in SDKs, improving configuration process, and refactoring the history meta ttl option. As the result you get a cleaner, more user-friendly, and optimized Centrifugo experience. And we have important news about the project - check it out in the end of this post.

diff --git a/blog/2023/08/19/asynchronous-message-streaming-to-centrifugo-with-benthos.html b/blog/2023/08/19/asynchronous-message-streaming-to-centrifugo-with-benthos.html index 76a1b36c2..7697d5af5 100644 --- a/blog/2023/08/19/asynchronous-message-streaming-to-centrifugo-with-benthos.html +++ b/blog/2023/08/19/asynchronous-message-streaming-to-centrifugo-with-benthos.html @@ -15,8 +15,8 @@ - - + +

Asynchronous message streaming to Centrifugo with Benthos

· 8 min read
Alexander Emelin
diff --git a/blog/2023/08/29/using-centrifugo-in-rabbitx.html b/blog/2023/08/29/using-centrifugo-in-rabbitx.html index 27be414a3..4b36c80a8 100644 --- a/blog/2023/08/29/using-centrifugo-in-rabbitx.html +++ b/blog/2023/08/29/using-centrifugo-in-rabbitx.html @@ -15,8 +15,8 @@ - - + +

Using Centrifugo in RabbitX

· 4 min read
Centrifugal + RabbitX
diff --git a/blog/2023/10/29/discovering-centrifugo-pro-push-notifications.html b/blog/2023/10/29/discovering-centrifugo-pro-push-notifications.html index 671b35ca2..67e95c92f 100644 --- a/blog/2023/10/29/discovering-centrifugo-pro-push-notifications.html +++ b/blog/2023/10/29/discovering-centrifugo-pro-push-notifications.html @@ -15,8 +15,8 @@ - - + +

Discovering Centrifugo PRO: push notifications API

· 14 min read
Alexander Emelin
@@ -110,6 +110,6 @@

Conclusion

We really believe in our push notifications and will be working hard to make them even better. The API we already have serves well to cover common push notification delivery use cases, but we won't stop here. Some areas for improvements are: functionality of built-in push notifications web UI, extending push analytics by providing user friendly UI for the insights about push delivery and engagement. The good thing is that we already have a ground for making this.

Take a look at the documentation of Centrifugo PRO push notification API for more formal details and some things not mentioned here. Probably at the time you are reading this we already added something great to the API.

-

Even though Centrifugo PRO is pretty new, it already has a lot of helpful features, and we have plans to add even more. You can see what’s coming up next on our Centrifugo PRO planned features board. We're excited to share more blog posts like this one in the future.

+

Even though Centrifugo PRO is pretty new, it already has a lot of helpful features, and we have plans to add even more. You can see what’s coming up next on our Centrifugo PRO planned features board. We're excited to share more blog posts like this one in the future.

\ No newline at end of file diff --git a/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions.html b/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions.html new file mode 100644 index 000000000..e05e7f82e --- /dev/null +++ b/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions.html @@ -0,0 +1,77 @@ + + + + + +Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions | Centrifugo + + + + + + + + + + + + + + + +

Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions

· 8 min read
Alexander Emelin
+

As of version 5.1.0, Centrifugo introduces an experimental yet powerful extension that promises to simplify the data delivery process to the browser using GRPC streams. We believe it may help you to solve some practical tasks in minutes. Let's dive into how this feature works and how you can leverage it in your applications integrating with Loki real-time log streaming capabilities.

+

What Are Proxy Subscription Streams?

+

Proxy Subscription Streams support pushing data directly to Centrifugo client channel subscriptions from your application backend over GRPC streams. This feature is designed to facilitate individual data streams to clients as soon as they subscribe to a channel, acting as a bridge between WebSocket connections from clients and GRPC streams to the backend. It supports both unidirectional (backend to client) and bidirectional (both ways) streams, thereby enhancing flexibility in data streaming.

+

+

Establishing a stream between Centrifugo and your application backend upon a channel subscription provides a straightforward path for data to travel directly to the subscribed clients. This mechanism not only simplifies the architecture for real-time data delivery but also ensures fast and individualized data streaming.

+

The design is inspired by Websocketd server – but while Websocketd transforms data from programs running locally, Centrifugo provides a more generic network interface with GRPC. And all other features of Centrifugo like connection authentication, online presence come as a great bonus.

+

In the documentation for Proxy Subscription Streams we mentioned streaming logs from Loki as one of the possible use cases. Let's expand on the idea and implement the working solution in just 10 minutes.

+

Demo and source code

+

Here is a demo of what we well get:

+ +

Take a look at full source code on Github.

+

Setting Up Loki

+

Loki is a horizontally-scalable, highly-available, multi-tenant log aggregation system inspired by Prometheus. It is designed to be very cost-effective and easy to operate, making it a perfect candidate for our real-time log streaming example.

+

We will build the example using Docker Compose, all we have to do for the example is to include Loki image to docker-compose.yml:

+
services:
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
+

Loki can ingest logs via various methods, including Promtail, Grafana Agent, Fluentd, and more. For simplicity, we will send logs to Loki ourselves from the Go application.

+

To send logs to Loki, we can use the HTTP API that Loki provides. This is a straightforward way to push logs directly from an application. The example below demonstrates how to create a simple Go application that generates logs and sends them to Loki using HTTP POST requests.

+

First, define a function to send a log entry to Loki:

+
const (
lokiPushEndpoint = "http://loki:3100/loki/api/v1/push"
)

type lokiPushMessage struct {
Streams []lokiStream `json:"streams"`
}

type lokiStream struct {
Stream map[string]string `json:"stream"`
Values [][]string `json:"values"`
}

func sendLogMessageToLoki(_ context.Context) error {
sources := []string{"backend1", "backend2", "backend3"}
source := sources[rand.Intn(len(sources))]
logMessage := fmt.Sprintf("log from %s source", source)

payload := lokiPushMessage{
Streams: []lokiStream{
{
Stream: map[string]string{
"source": source,
},
Values: [][]string{
{fmt.Sprintf("%d", time.Now().UnixNano()), logMessage},
},
},
},
}

jsonData, err := json.Marshal(payload)
if err != nil {
return err
}
resp, err := http.Post(
lokiPushEndpoint, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
return nil
}

func sendLogsToLoki(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case <-time.After(200 * time.Millisecond):
err := sendLogMessageToLoki(ctx)
if err != nil {
log.Println("error sending log to Loki:", err)
continue
}
}
}
}

func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer cancel()

sendLogsToLoki(ctx)
}
+

This program defines a sendLogsToLoki function that constructs a log entry and sends it to Loki using its HTTP API. It continuously generates log messages every 200 milliseconds.

+

The lokiPushMessage struct is structured to match the JSON payload expected by Loki's /loki/api/v1/push endpoint. Each log entry consists of a set of labels (in the Stream map) and log line values, where each value is a two-element array containing the timestamp and the log line. The timestamp is in nanoseconds to match Loki's expected format.

+

Note, in the example we randomly set log entry source label choosing between backend1, backend2 and backend3 values.

+

At this point our program pushes some logs to Loki, now let's add Centrifugo to consume them from browser in real-time.

+

Configuring Centrifugo

+

Adding Centrifugo is also rather straightforward:

+
services:
centrifugo:
image: centrifugo/centrifugo:v5.3.0
restart: unless-stopped
volumes:
- ./centrifugo/config.json:/centrifugo/config.json
command: centrifugo -c config.json
expose:
- 8000
+

Where config.json is:

+
{
"client_insecure": true,
"allowed_origins": ["http://localhost:9000"],
"proxy_subscribe_stream_endpoint": "grpc://backend:12000",
"proxy_subscribe_stream_timeout": "3s",
"namespaces": [
{
"name": "logs",
"proxy_subscribe_stream": true
}
]
}
+

Note, we enabled client_insecure option here – this is to keep example short, but in real live you may benefit from Centrifugo authentication: JWT-based or proxy-based.

+

Writing frontend

+
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Streaming logs with Centrifugo and Loki</title>
</head>
<body>
<div id="app">
<form id="input" onsubmit="subscribeToLogs(event)">
<input type="text" id="query" autocomplete="off" placeholder="Enter log query" />
<button id="submit" type="submit">SUBSCRIBE</button>
</form>
<div id="logs" style="margin-top: 20px;">
<ul id="lines"></ul>
</div>
</div>
<script src="https://unpkg.com/centrifuge@^5/dist/centrifuge.js"></script>
<script src="app.js"></script>
</body>
</html>
+

In the final version we've also included some CSS to this HTML to make it look nicer.

+

And our Javascript code in app.js:

+
const logs = document.getElementById('logs');
const lines = document.getElementById('lines');
const queryInput = document.getElementById('query');
const button = document.getElementById('submit');

function subscribeToLogs(e) {
e.preventDefault();

const query = queryInput.value;
if (!query) {
alert('Please enter a query.');
return;
}
queryInput.disabled = true;
button.disabled = true;

const centrifuge = new Centrifuge('ws://localhost:9000/connection/websocket');

const subscription = centrifuge.newSubscription('logs:stream', {
data: { query: query }
});

subscription.on('publication', function(ctx) {
const logLine = ctx.data.line;
const logItem = document.createElement('li');
logItem.textContent = logLine;
lines.appendChild(logItem);
logs.scrollTop = logs.scrollHeight;
});

subscription.subscribe();
centrifuge.connect();
}
+

In the final example we've also added Nginx container to serve static files and proxy WebSocket connections to Centrifugo. Check it out in the source code.

+

When user enters Loki query to input, subscription goes to Centrifugo and Centrifugo then realizes it's a proxy stream subscription. So it calls the backend GRPC endpoint (backend:12000) and expect it to implement unidirectional GRPC stream conract. Let's implement it.

+

Handle subscription stream on the Go side

+

On your backend, you'll implement a GRPC service that interacts with Loki to tail logs and then re-send them to Centrifugo subscription stream. Let's implement such service. We first need to take Centrifugo proxy.proto definitions. And we will implement SubscribeUnidirectional method from it.

+

You need to install protoc, also install plugins for Go and GRPC:

+
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
+

And then:

+
protoc -I ./ proxy.proto --go_out=./ --go-grpc_out=./
+

This will generate Protobuf messages and GRPC code. Let's implement our server now:

+
const (
lokiGRPCAddress = "loki:9095"
)

type streamerServer struct {
pb.UnimplementedCentrifugoProxyServer
lokiQuerierClient logproto.QuerierClient
}

type clientData struct {
Query string `json:"query"`
}

func (s *streamerServer) SubscribeUnidirectional(
req *pb.SubscribeRequest,
stream pb.CentrifugoProxy_SubscribeUnidirectionalServer,
) error {
var cd clientData
err := json.Unmarshal(req.Data, &cd)
if err != nil {
return fmt.Errorf("error unmarshaling data: %w", err)
}
query := &logproto.TailRequest{
Query: cd.Query,
}
ctx, cancel := context.WithCancel(stream.Context())
defer cancel()

logStream, err := s.lokiQuerierClient.Tail(ctx, query)
if err != nil {
return fmt.Errorf("error querying Loki: %w", err)
}

started := time.Now()
log.Println("unidirectional subscribe called with request", req)
defer func() {
log.Println("unidirectional subscribe finished, elapsed", time.Since(started))
}()
err = stream.Send(&pb.StreamSubscribeResponse{
SubscribeResponse: &pb.SubscribeResponse{},
})
if err != nil {
return err
}

for {
select {
case <-stream.Context().Done():
return stream.Context().Err()
default:
resp, err := logStream.Recv()
if err != nil {
return fmt.Errorf("error receiving from Loki stream: %v", err)
}
for _, entry := range resp.Stream.Entries {
line := fmt.Sprintf("%s: %s", entry.Timestamp.Format("2006-01-02T15:04:05.000Z07:00"), entry.Line)
err = stream.Send(&pb.StreamSubscribeResponse{
Publication: &pb.Publication{Data: []byte(`{"line":"` + line + `"}`)},
})
if err != nil {
return err
}
}
}
}
}

func main() {
querierConn, err := grpc.Dial(lokiGRPCAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed to dial Loki: %v", err)
}
querierClient := logproto.NewQuerierClient(querierConn)

addr := ":12000"
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

s := grpc.NewServer(grpc.MaxConcurrentStreams(math.MaxUint32))
pb.RegisterCentrifugoProxyServer(s, &streamerServer{
lokiQuerierClient: querierClient,
})

log.Println("Server listening on", addr)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
+

Things to note:

+
    +
  • Loki also supports GRPC interface to tail logs, so we use it here. We could also use Loki WebSocket endpoint /loki/api/v1/tail but this would mean establishing new connection for every tail operation - with GRPC we can use many concurrent tail requests all multiplexed over a single network connection.
  • +
  • When subscription stream initialized from Centrifugo side we start tailing logs from Loki and resend them to Centrifugo
  • +
  • Centrifugo then packs data to WebSocket connection and delivers to browser.
  • +
+

Conclusion

+

Subscription streams may be a very powerful generic feature in your arsenal. Here we've shown how simple it could be to make a proof of concept of the real-time application which consumes individual data from third-party streaming provider.

+

Centrifugo provides WebSocket SDKs for popular languages used to build UI layer, provides authentication and proper management of real-time connections. And with subscription streams feature Centrifugo gives you an answer on how to quickly translate real-time data based on individual query to user.

+ + \ No newline at end of file diff --git a/blog/archive.html b/blog/archive.html index bc707f595..4db67d548 100644 --- a/blog/archive.html +++ b/blog/archive.html @@ -15,10 +15,10 @@ - - + + - + \ No newline at end of file diff --git a/blog/atom.xml b/blog/atom.xml index 3dadea30a..1411baeab 100644 --- a/blog/atom.xml +++ b/blog/atom.xml @@ -2,11 +2,78 @@ https://centrifugal.dev/blog - <updated>2023-10-29T00:00:00.000Z</updated> + <updated>2024-03-18T00:00:00.000Z</updated> <generator>https://github.com/jpmonette/feed</generator> <link rel="alternate" href="https://centrifugal.dev/blog"/> <icon>https://centrifugal.dev/img/favicon.png</icon> <rights>Centrifugal Labs LTD</rights> + <entry> + <title type="html"><![CDATA[Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions]]> + https://centrifugal.dev/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions + + 2024-03-18T00:00:00.000Z + + +

As of version 5.1.0, Centrifugo introduces an experimental yet powerful extension that promises to simplify the data delivery process to the browser using GRPC streams. We believe it may help you to solve some practical tasks in minutes. Let's dive into how this feature works and how you can leverage it in your applications integrating with Loki real-time log streaming capabilities.

+

What Are Proxy Subscription Streams?

+

Proxy Subscription Streams support pushing data directly to Centrifugo client channel subscriptions from your application backend over GRPC streams. This feature is designed to facilitate individual data streams to clients as soon as they subscribe to a channel, acting as a bridge between WebSocket connections from clients and GRPC streams to the backend. It supports both unidirectional (backend to client) and bidirectional (both ways) streams, thereby enhancing flexibility in data streaming.

+

+

Establishing a stream between Centrifugo and your application backend upon a channel subscription provides a straightforward path for data to travel directly to the subscribed clients. This mechanism not only simplifies the architecture for real-time data delivery but also ensures fast and individualized data streaming.

+

The design is inspired by Websocketd server – but while Websocketd transforms data from programs running locally, Centrifugo provides a more generic network interface with GRPC. And all other features of Centrifugo like connection authentication, online presence come as a great bonus.

+

In the documentation for Proxy Subscription Streams we mentioned streaming logs from Loki as one of the possible use cases. Let's expand on the idea and implement the working solution in just 10 minutes.

+

Demo and source code

+

Here is a demo of what we well get:

+ +

Take a look at full source code on Github.

+

Setting Up Loki

+

Loki is a horizontally-scalable, highly-available, multi-tenant log aggregation system inspired by Prometheus. It is designed to be very cost-effective and easy to operate, making it a perfect candidate for our real-time log streaming example.

+

We will build the example using Docker Compose, all we have to do for the example is to include Loki image to docker-compose.yml:

+
services:
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
+

Loki can ingest logs via various methods, including Promtail, Grafana Agent, Fluentd, and more. For simplicity, we will send logs to Loki ourselves from the Go application.

+

To send logs to Loki, we can use the HTTP API that Loki provides. This is a straightforward way to push logs directly from an application. The example below demonstrates how to create a simple Go application that generates logs and sends them to Loki using HTTP POST requests.

+

First, define a function to send a log entry to Loki:

+
const (
lokiPushEndpoint = "http://loki:3100/loki/api/v1/push"
)

type lokiPushMessage struct {
Streams []lokiStream `json:"streams"`
}

type lokiStream struct {
Stream map[string]string `json:"stream"`
Values [][]string `json:"values"`
}

func sendLogMessageToLoki(_ context.Context) error {
sources := []string{"backend1", "backend2", "backend3"}
source := sources[rand.Intn(len(sources))]
logMessage := fmt.Sprintf("log from %s source", source)

payload := lokiPushMessage{
Streams: []lokiStream{
{
Stream: map[string]string{
"source": source,
},
Values: [][]string{
{fmt.Sprintf("%d", time.Now().UnixNano()), logMessage},
},
},
},
}

jsonData, err := json.Marshal(payload)
if err != nil {
return err
}
resp, err := http.Post(
lokiPushEndpoint, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
return nil
}

func sendLogsToLoki(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case <-time.After(200 * time.Millisecond):
err := sendLogMessageToLoki(ctx)
if err != nil {
log.Println("error sending log to Loki:", err)
continue
}
}
}
}

func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer cancel()

sendLogsToLoki(ctx)
}
+

This program defines a sendLogsToLoki function that constructs a log entry and sends it to Loki using its HTTP API. It continuously generates log messages every 200 milliseconds.

+

The lokiPushMessage struct is structured to match the JSON payload expected by Loki's /loki/api/v1/push endpoint. Each log entry consists of a set of labels (in the Stream map) and log line values, where each value is a two-element array containing the timestamp and the log line. The timestamp is in nanoseconds to match Loki's expected format.

+

Note, in the example we randomly set log entry source label choosing between backend1, backend2 and backend3 values.

+

At this point our program pushes some logs to Loki, now let's add Centrifugo to consume them from browser in real-time.

+

Configuring Centrifugo

+

Adding Centrifugo is also rather straightforward:

+
services:
centrifugo:
image: centrifugo/centrifugo:v5.3.0
restart: unless-stopped
volumes:
- ./centrifugo/config.json:/centrifugo/config.json
command: centrifugo -c config.json
expose:
- 8000
+

Where config.json is:

+
{
"client_insecure": true,
"allowed_origins": ["http://localhost:9000"],
"proxy_subscribe_stream_endpoint": "grpc://backend:12000",
"proxy_subscribe_stream_timeout": "3s",
"namespaces": [
{
"name": "logs",
"proxy_subscribe_stream": true
}
]
}
+

Note, we enabled client_insecure option here – this is to keep example short, but in real live you may benefit from Centrifugo authentication: JWT-based or proxy-based.

+

Writing frontend

+
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Streaming logs with Centrifugo and Loki</title>
</head>
<body>
<div id="app">
<form id="input" onsubmit="subscribeToLogs(event)">
<input type="text" id="query" autocomplete="off" placeholder="Enter log query" />
<button id="submit" type="submit">SUBSCRIBE</button>
</form>
<div id="logs" style="margin-top: 20px;">
<ul id="lines"></ul>
</div>
</div>
<script src="https://unpkg.com/centrifuge@^5/dist/centrifuge.js"></script>
<script src="app.js"></script>
</body>
</html>
+

In the final version we've also included some CSS to this HTML to make it look nicer.

+

And our Javascript code in app.js:

+
const logs = document.getElementById('logs');
const lines = document.getElementById('lines');
const queryInput = document.getElementById('query');
const button = document.getElementById('submit');

function subscribeToLogs(e) {
e.preventDefault();

const query = queryInput.value;
if (!query) {
alert('Please enter a query.');
return;
}
queryInput.disabled = true;
button.disabled = true;

const centrifuge = new Centrifuge('ws://localhost:9000/connection/websocket');

const subscription = centrifuge.newSubscription('logs:stream', {
data: { query: query }
});

subscription.on('publication', function(ctx) {
const logLine = ctx.data.line;
const logItem = document.createElement('li');
logItem.textContent = logLine;
lines.appendChild(logItem);
logs.scrollTop = logs.scrollHeight;
});

subscription.subscribe();
centrifuge.connect();
}
+

In the final example we've also added Nginx container to serve static files and proxy WebSocket connections to Centrifugo. Check it out in the source code.

+

When user enters Loki query to input, subscription goes to Centrifugo and Centrifugo then realizes it's a proxy stream subscription. So it calls the backend GRPC endpoint (backend:12000) and expect it to implement unidirectional GRPC stream conract. Let's implement it.

+

Handle subscription stream on the Go side

+

On your backend, you'll implement a GRPC service that interacts with Loki to tail logs and then re-send them to Centrifugo subscription stream. Let's implement such service. We first need to take Centrifugo proxy.proto definitions. And we will implement SubscribeUnidirectional method from it.

+

You need to install protoc, also install plugins for Go and GRPC:

+
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
+

And then:

+
protoc -I ./ proxy.proto --go_out=./ --go-grpc_out=./
+

This will generate Protobuf messages and GRPC code. Let's implement our server now:

+
const (
lokiGRPCAddress = "loki:9095"
)

type streamerServer struct {
pb.UnimplementedCentrifugoProxyServer
lokiQuerierClient logproto.QuerierClient
}

type clientData struct {
Query string `json:"query"`
}

func (s *streamerServer) SubscribeUnidirectional(
req *pb.SubscribeRequest,
stream pb.CentrifugoProxy_SubscribeUnidirectionalServer,
) error {
var cd clientData
err := json.Unmarshal(req.Data, &cd)
if err != nil {
return fmt.Errorf("error unmarshaling data: %w", err)
}
query := &logproto.TailRequest{
Query: cd.Query,
}
ctx, cancel := context.WithCancel(stream.Context())
defer cancel()

logStream, err := s.lokiQuerierClient.Tail(ctx, query)
if err != nil {
return fmt.Errorf("error querying Loki: %w", err)
}

started := time.Now()
log.Println("unidirectional subscribe called with request", req)
defer func() {
log.Println("unidirectional subscribe finished, elapsed", time.Since(started))
}()
err = stream.Send(&pb.StreamSubscribeResponse{
SubscribeResponse: &pb.SubscribeResponse{},
})
if err != nil {
return err
}

for {
select {
case <-stream.Context().Done():
return stream.Context().Err()
default:
resp, err := logStream.Recv()
if err != nil {
return fmt.Errorf("error receiving from Loki stream: %v", err)
}
for _, entry := range resp.Stream.Entries {
line := fmt.Sprintf("%s: %s", entry.Timestamp.Format("2006-01-02T15:04:05.000Z07:00"), entry.Line)
err = stream.Send(&pb.StreamSubscribeResponse{
Publication: &pb.Publication{Data: []byte(`{"line":"` + line + `"}`)},
})
if err != nil {
return err
}
}
}
}
}

func main() {
querierConn, err := grpc.Dial(lokiGRPCAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed to dial Loki: %v", err)
}
querierClient := logproto.NewQuerierClient(querierConn)

addr := ":12000"
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

s := grpc.NewServer(grpc.MaxConcurrentStreams(math.MaxUint32))
pb.RegisterCentrifugoProxyServer(s, &streamerServer{
lokiQuerierClient: querierClient,
})

log.Println("Server listening on", addr)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
+

Things to note:

+
    +
  • Loki also supports GRPC interface to tail logs, so we use it here. We could also use Loki WebSocket endpoint /loki/api/v1/tail but this would mean establishing new connection for every tail operation - with GRPC we can use many concurrent tail requests all multiplexed over a single network connection.
  • +
  • When subscription stream initialized from Centrifugo side we start tailing logs from Loki and resend them to Centrifugo
  • +
  • Centrifugo then packs data to WebSocket connection and delivers to browser.
  • +
+

Conclusion

+

Subscription streams may be a very powerful generic feature in your arsenal. Here we've shown how simple it could be to make a proof of concept of the real-time application which consumes individual data from third-party streaming provider.

+

Centrifugo provides WebSocket SDKs for popular languages used to build UI layer, provides authentication and proper management of real-time connections. And with subscription streams feature Centrifugo gives you an answer on how to quickly translate real-time data based on individual query to user.

]]>
+ + Alexander Emelin + + + + + <![CDATA[Discovering Centrifugo PRO: push notifications API]]> https://centrifugal.dev/blog/2023/10/29/discovering-centrifugo-pro-push-notifications diff --git a/blog/feed.json b/blog/feed.json index 3c2026831..af206dce1 100644 --- a/blog/feed.json +++ b/blog/feed.json @@ -3,6 +3,22 @@ "title": "", "home_page_url": "https://centrifugal.dev/blog", "items": [ + { + "id": "https://centrifugal.dev/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions", + "content_html": "\n

As of version 5.1.0, Centrifugo introduces an experimental yet powerful extension that promises to simplify the data delivery process to the browser using GRPC streams. We believe it may help you to solve some practical tasks in minutes. Let's dive into how this feature works and how you can leverage it in your applications integrating with Loki real-time log streaming capabilities.

\n

What Are Proxy Subscription Streams?

\n

Proxy Subscription Streams support pushing data directly to Centrifugo client channel subscriptions from your application backend over GRPC streams. This feature is designed to facilitate individual data streams to clients as soon as they subscribe to a channel, acting as a bridge between WebSocket connections from clients and GRPC streams to the backend. It supports both unidirectional (backend to client) and bidirectional (both ways) streams, thereby enhancing flexibility in data streaming.

\n

\n

Establishing a stream between Centrifugo and your application backend upon a channel subscription provides a straightforward path for data to travel directly to the subscribed clients. This mechanism not only simplifies the architecture for real-time data delivery but also ensures fast and individualized data streaming.

\n

The design is inspired by Websocketd server – but while Websocketd transforms data from programs running locally, Centrifugo provides a more generic network interface with GRPC. And all other features of Centrifugo like connection authentication, online presence come as a great bonus.

\n

In the documentation for Proxy Subscription Streams we mentioned streaming logs from Loki as one of the possible use cases. Let's expand on the idea and implement the working solution in just 10 minutes.

\n

Demo and source code

\n

Here is a demo of what we well get:

\n\n

Take a look at full source code on Github.

\n

Setting Up Loki

\n

Loki is a horizontally-scalable, highly-available, multi-tenant log aggregation system inspired by Prometheus. It is designed to be very cost-effective and easy to operate, making it a perfect candidate for our real-time log streaming example.

\n

We will build the example using Docker Compose, all we have to do for the example is to include Loki image to docker-compose.yml:

\n
services:
loki:
image: grafana/loki:latest
ports:
- \"3100:3100\"
\n

Loki can ingest logs via various methods, including Promtail, Grafana Agent, Fluentd, and more. For simplicity, we will send logs to Loki ourselves from the Go application.

\n

To send logs to Loki, we can use the HTTP API that Loki provides. This is a straightforward way to push logs directly from an application. The example below demonstrates how to create a simple Go application that generates logs and sends them to Loki using HTTP POST requests.

\n

First, define a function to send a log entry to Loki:

\n
const (
\tlokiPushEndpoint = \"http://loki:3100/loki/api/v1/push\"
)

type lokiPushMessage struct {
\tStreams []lokiStream `json:\"streams\"`
}

type lokiStream struct {
\tStream map[string]string `json:\"stream\"`
\tValues [][]string `json:\"values\"`
}

func sendLogMessageToLoki(_ context.Context) error {
\tsources := []string{\"backend1\", \"backend2\", \"backend3\"}
\tsource := sources[rand.Intn(len(sources))]
\tlogMessage := fmt.Sprintf(\"log from %s source\", source)

\tpayload := lokiPushMessage{
\t\tStreams: []lokiStream{
\t\t\t{
\t\t\t\tStream: map[string]string{
\t\t\t\t\t\"source\": source,
\t\t\t\t},
\t\t\t\tValues: [][]string{
\t\t\t\t\t{fmt.Sprintf(\"%d\", time.Now().UnixNano()), logMessage},
\t\t\t\t},
\t\t\t},
\t\t},
\t}

\tjsonData, err := json.Marshal(payload)
\tif err != nil {
\t\treturn err
\t}
\tresp, err := http.Post(
\t\tlokiPushEndpoint, \"application/json\", bytes.NewBuffer(jsonData))
\tif err != nil {
\t\treturn err
\t}
\tdefer resp.Body.Close()

\tif resp.StatusCode != http.StatusNoContent {
\t\treturn fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)
\t}
\treturn nil
}

func sendLogsToLoki(ctx context.Context) {
\tfor {
\t\tselect {
\t\tcase <-ctx.Done():
\t\t\treturn
\t\tcase <-time.After(200 * time.Millisecond):
\t\t\terr := sendLogMessageToLoki(ctx)
\t\t\tif err != nil {
\t\t\t\tlog.Println(\"error sending log to Loki:\", err)
\t\t\t\tcontinue
\t\t\t}
\t\t}
\t}
}

func main() {
\tctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
\tdefer cancel()

\tsendLogsToLoki(ctx)
}
\n

This program defines a sendLogsToLoki function that constructs a log entry and sends it to Loki using its HTTP API. It continuously generates log messages every 200 milliseconds.

\n

The lokiPushMessage struct is structured to match the JSON payload expected by Loki's /loki/api/v1/push endpoint. Each log entry consists of a set of labels (in the Stream map) and log line values, where each value is a two-element array containing the timestamp and the log line. The timestamp is in nanoseconds to match Loki's expected format.

\n

Note, in the example we randomly set log entry source label choosing between backend1, backend2 and backend3 values.

\n

At this point our program pushes some logs to Loki, now let's add Centrifugo to consume them from browser in real-time.

\n

Configuring Centrifugo

\n

Adding Centrifugo is also rather straightforward:

\n
services:
centrifugo:
image: centrifugo/centrifugo:v5.3.0
restart: unless-stopped
volumes:
- ./centrifugo/config.json:/centrifugo/config.json
command: centrifugo -c config.json
expose:
- 8000
\n

Where config.json is:

\n
{
\"client_insecure\": true,
\"allowed_origins\": [\"http://localhost:9000\"],
\"proxy_subscribe_stream_endpoint\": \"grpc://backend:12000\",
\"proxy_subscribe_stream_timeout\": \"3s\",
\"namespaces\": [
{
\"name\": \"logs\",
\"proxy_subscribe_stream\": true
}
]
}
\n

Note, we enabled client_insecure option here – this is to keep example short, but in real live you may benefit from Centrifugo authentication: JWT-based or proxy-based.

\n

Writing frontend

\n
<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"UTF-8\">
<title>Streaming logs with Centrifugo and Loki</title>
</head>
<body>
<div id=\"app\">
<form id=\"input\" onsubmit=\"subscribeToLogs(event)\">
<input type=\"text\" id=\"query\" autocomplete=\"off\" placeholder=\"Enter log query\" />
<button id=\"submit\" type=\"submit\">SUBSCRIBE</button>
</form>
<div id=\"logs\" style=\"margin-top: 20px;\">
<ul id=\"lines\"></ul>
</div>
</div>
<script src=\"https://unpkg.com/centrifuge@^5/dist/centrifuge.js\"></script>
<script src=\"app.js\"></script>
</body>
</html>
\n

In the final version we've also included some CSS to this HTML to make it look nicer.

\n

And our Javascript code in app.js:

\n
const logs = document.getElementById('logs');
const lines = document.getElementById('lines');
const queryInput = document.getElementById('query');
const button = document.getElementById('submit');

function subscribeToLogs(e) {
e.preventDefault();

const query = queryInput.value;
if (!query) {
alert('Please enter a query.');
return;
}
queryInput.disabled = true;
button.disabled = true;

const centrifuge = new Centrifuge('ws://localhost:9000/connection/websocket');

const subscription = centrifuge.newSubscription('logs:stream', {
data: { query: query }
});

subscription.on('publication', function(ctx) {
const logLine = ctx.data.line;
const logItem = document.createElement('li');
logItem.textContent = logLine;
lines.appendChild(logItem);
logs.scrollTop = logs.scrollHeight;
});

subscription.subscribe();
centrifuge.connect();
}
\n

In the final example we've also added Nginx container to serve static files and proxy WebSocket connections to Centrifugo. Check it out in the source code.

\n

When user enters Loki query to input, subscription goes to Centrifugo and Centrifugo then realizes it's a proxy stream subscription. So it calls the backend GRPC endpoint (backend:12000) and expect it to implement unidirectional GRPC stream conract. Let's implement it.

\n

Handle subscription stream on the Go side

\n

On your backend, you'll implement a GRPC service that interacts with Loki to tail logs and then re-send them to Centrifugo subscription stream. Let's implement such service. We first need to take Centrifugo proxy.proto definitions. And we will implement SubscribeUnidirectional method from it.

\n

You need to install protoc, also install plugins for Go and GRPC:

\n
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
\n

And then:

\n
protoc -I ./ proxy.proto --go_out=./ --go-grpc_out=./
\n

This will generate Protobuf messages and GRPC code. Let's implement our server now:

\n
const (
\tlokiGRPCAddress = \"loki:9095\"
)

type streamerServer struct {
\tpb.UnimplementedCentrifugoProxyServer
\tlokiQuerierClient logproto.QuerierClient
}

type clientData struct {
\tQuery string `json:\"query\"`
}

func (s *streamerServer) SubscribeUnidirectional(
\treq *pb.SubscribeRequest,
\tstream pb.CentrifugoProxy_SubscribeUnidirectionalServer,
) error {
\tvar cd clientData
\terr := json.Unmarshal(req.Data, &cd)
\tif err != nil {
\t\treturn fmt.Errorf(\"error unmarshaling data: %w\", err)
\t}
\tquery := &logproto.TailRequest{
\t\tQuery: cd.Query,
\t}
\tctx, cancel := context.WithCancel(stream.Context())
\tdefer cancel()

\tlogStream, err := s.lokiQuerierClient.Tail(ctx, query)
\tif err != nil {
\t\treturn fmt.Errorf(\"error querying Loki: %w\", err)
\t}

\tstarted := time.Now()
\tlog.Println(\"unidirectional subscribe called with request\", req)
\tdefer func() {
\t\tlog.Println(\"unidirectional subscribe finished, elapsed\", time.Since(started))
\t}()
\terr = stream.Send(&pb.StreamSubscribeResponse{
\t\tSubscribeResponse: &pb.SubscribeResponse{},
\t})
\tif err != nil {
\t\treturn err
\t}

\tfor {
\t\tselect {
\t\tcase <-stream.Context().Done():
\t\t\treturn stream.Context().Err()
\t\tdefault:
\t\t\tresp, err := logStream.Recv()
\t\t\tif err != nil {
\t\t\t\treturn fmt.Errorf(\"error receiving from Loki stream: %v\", err)
\t\t\t}
\t\t\tfor _, entry := range resp.Stream.Entries {
\t\t\t\tline := fmt.Sprintf(\"%s: %s\", entry.Timestamp.Format(\"2006-01-02T15:04:05.000Z07:00\"), entry.Line)
\t\t\t\terr = stream.Send(&pb.StreamSubscribeResponse{
\t\t\t\t\tPublication: &pb.Publication{Data: []byte(`{\"line\":\"` + line + `\"}`)},
\t\t\t\t})
\t\t\t\tif err != nil {
\t\t\t\t\treturn err
\t\t\t\t}
\t\t\t}
\t\t}
\t}
}

func main() {
\tquerierConn, err := grpc.Dial(lokiGRPCAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
\tif err != nil {
\t\tlog.Fatalf(\"failed to dial Loki: %v\", err)
\t}
\tquerierClient := logproto.NewQuerierClient(querierConn)

\taddr := \":12000\"
\tlis, err := net.Listen(\"tcp\", addr)
\tif err != nil {
\t\tlog.Fatalf(\"failed to listen: %v\", err)
\t}

\ts := grpc.NewServer(grpc.MaxConcurrentStreams(math.MaxUint32))
\tpb.RegisterCentrifugoProxyServer(s, &streamerServer{
\t\tlokiQuerierClient: querierClient,
\t})

\tlog.Println(\"Server listening on\", addr)
\tif err := s.Serve(lis); err != nil {
\t\tlog.Fatalf(\"failed to serve: %v\", err)
\t}
}
\n

Things to note:

\n
    \n
  • Loki also supports GRPC interface to tail logs, so we use it here. We could also use Loki WebSocket endpoint /loki/api/v1/tail but this would mean establishing new connection for every tail operation - with GRPC we can use many concurrent tail requests all multiplexed over a single network connection.
  • \n
  • When subscription stream initialized from Centrifugo side we start tailing logs from Loki and resend them to Centrifugo
  • \n
  • Centrifugo then packs data to WebSocket connection and delivers to browser.
  • \n
\n

Conclusion

\n

Subscription streams may be a very powerful generic feature in your arsenal. Here we've shown how simple it could be to make a proof of concept of the real-time application which consumes individual data from third-party streaming provider.

\n

Centrifugo provides WebSocket SDKs for popular languages used to build UI layer, provides authentication and proper management of real-time connections. And with subscription streams feature Centrifugo gives you an answer on how to quickly translate real-time data based on individual query to user.

", + "url": "https://centrifugal.dev/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions", + "title": "Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions", + "summary": "Centrifugo has GRPC subscription streams feature, in this post we show how this feature may simplify a task of delivering data to application UI in real-time. We integrate with Loki, injest log entries and stream logs to the browser based on user-supplied query", + "date_modified": "2024-03-18T00:00:00.000Z", + "author": { + "name": "Alexander Emelin" + }, + "tags": [ + "centrifugo", + "loki", + "grpc" + ] + }, { "id": "https://centrifugal.dev/blog/2023/10/29/discovering-centrifugo-pro-push-notifications", "content_html": "\n

In our v5 release post, we announced the upcoming launch of Centrifugo PRO. We are happy to say that it was released soon after that, and at this point, we already have several customers of the PRO version.

\n

I think it's time to look at the current state of the PRO version and finally start talking more about its benefits. In this post, we will talk more about one of the coolest PRO features we have at this point: the push notifications API.

\n

Centrifugo PRO goals

\n

When Centrifugo was originally created, its main goal was to help introduce real-time messaging features to existing systems, written in traditional frameworks which work on top of the worker/thread model. Serving many concurrent connections is a non-trivial task in general, and without native efficient concurrency support, it becomes mostly impossible without a shift in the technology stack. Integrating with Centrifugo makes it simple to introduce an efficient real-time layer, while keeping the existing application architecture.

\n

As time went on, Centrifugo got some unique features which now justify its usage even in conjunction with languages/frameworks with good concurrency support. Simply using Centrifugo for at-most-once PUB/SUB may already save a lot of development time. The task, which seems trivial at first glance, has a lot of challenges in practice: client SDKs with reconnect and channel multiplexing, scalability to many nodes, WebSocket fallbacks, etc.

\n

The combination of useful possibilities has made Centrifugo an attractive component for building enterprise-level applications. Let's be honest here - for pet projects, developers often prefer writing WebSocket communications themselves, and Centrifugo may be too heavy and an extra dependency. But in a corporate environment, the decision on which technology to use should take into account a lot of factors, like those we just mentioned above. Using a mature technology is often preferred to building from scratch and making all the mistakes along the way.

\n

With the PRO version, our goal is to provide even more value for established businesses when switching to Centrifugo. We want to solve tricky cases and simplify them for our customers; we want to step into related areas where we see we can provide sufficient value.

\n

One rule we try to follow for PRO features that extend Centrifugo’s scope is this: we are not trying to replicate something that already exists in other systems, but rather, we strive to improve upon it. We focus on solving practical issues that we observe, providing a unique value proposition for our customers. This post describes one such example — we will demonstrate our approach to push notifications, which is one the features of Centrifugo PRO.

\n

Why providing push notifications API

\n\n

Why provide a push notifications API at all? Well, actually, real-time messages and push notifications are so close that many developers hardly see the difference before starting to work with both more closely.

\n

I’ve heard several stories where chat functionality on mobile devices was implemented using only native push notifications — without using a separate real-time transport like WebSocket while the app is in the foreground. While this is not a recommended approach due to the delivery properties of push notifications, it proves that real-time messages and push notifications are closely related concepts and sometimes may interchange with each other.

\n

When developers introduce WebSocket communication in an application, they often ask the question—what should I do next to deliver some important messages to a user who is currently not actively using the application? WebSockets are great when the app is in the foreground, but when the app goes to the background, the recommended approach is to close the WebSocket connection. This is important to save battery, and operating systems force the closing of connections after some time anyway.

\n

The delivery of important app data is then possible over push notifications. See a good overview of them on web.dev.

\n

Previously, Centrifugo positioned itself solely as a transport layer for real-time messages. In our FAQ, we emphasized this fact and suggested using separate software products to send push notifications.

\n

Now, with Centrifugo PRO, we provide this functionality to our customers. We have extended our server API with methods to manage and send push notifications. I promised to tell you why we believe our implementation is super cool. Let’s dive into the details.

\n

Push notifications API like no one provides

\n

Push notifications are super handy, but there’s a bit to do to get them working right. Let's break it down!

\n

On the user's side (frontend)

\n
    \n
  • Request permission from the user to receive push notifications.
  • \n
  • Integrate with the platform-specific notification service (e.g., Apple Push Notification Service for iOS, Firebase Cloud Messaging for Android) to obtain the device token.
  • \n
  • Send the device token to the server for storage and future use.
  • \n
  • Integrate with the platform-specific notification handler to listen for incoming push notifications
  • \n
  • Handle incoming push notifications: display the notification content to the user, either as a banner, alert, or in-app message, depending on the user's preferences and the type of notification. Handle user actions on the notification, such as opening the app, dismissing the notification, or taking a specific action related to the notification content.
  • \n
\n

On the server (backend)

\n
    \n
  • Store device tokens in a database when received from the client side
  • \n
  • Regularly clean up the database to remove stale or invalid device tokens. and handle scenarios where a device token becomes invalid or is revoked by the user, ensuring that no further notifications are sent to that device.
  • \n
  • Integrate with platform-specific notification services (e.g., APNS, FCM) to send notifications to devices. Handle errors or failures in sending notifications and implement retry mechanisms if necessary.
  • \n
  • Track the delivery status of each push notification sent out. Monitor the open rates, click-through rates, and other relevant metrics for the notifications.
  • \n
  • Use analytics to understand user behavior in response to notifications and refine the notification strategy based on insights gained.
  • \n
\n

We believe that we were able to achieve a unique combination of design decisions which allows us to provide push notification support like no one else provides. Let’s dive into what makes our approach special!

\n

Frontend decisions

\n

When providing the push notification feature, other solutions like Pusher or Ably also offer their own SDKs for managing notifications on the client side.

\n

What we've learned, though, during the Centrifugo life cycle, is that creating and maintaining client SDKs for various environments (iOS, Android, Web, Flutter) is one of the hardest parts of the Centrifugo project.

\n

So the decision here was simple and natural: Centrifugo PRO does not introduce any client SDKs for push notifications on the client side.

\n

When integrating with Centrifugo, you can simply use the native SDKs provided by each platform. We bypass the complexities of SDK development and concentrate on server-side improvements. With this decision, we are not introducing any limitations to the client side.

\n

You get:

\n
    \n
  • Wealthy documentation and community support. Platforms like APNs provide comprehensive documentation, tutorials, and best practices, making the integration process smoother.
  • \n
  • Stability and reliability: native SDKs are rigorously tested and frequently updated by the platform providers. This ensures that they are stable, reliable, and free from critical bugs.
  • \n
  • Access to the latest features. As platform providers roll out new features or enhancements, native SDKs are usually the first to get updated. This ensures that your application can leverage the latest functionalities without waiting for SDKs to catch up.
  • \n
\n

This approach was not possible with our real-time SDKs, as WebSocket communication is very low-level, and Centrifugo’s main goal was to provide some high-level features on top of it. However, with push notifications, proceeding without a custom SDK seems like a choice beneficial for everyone.

\n

Server implementation

\n

The main work we did was on the server side. Let's go through the entire workflow of push notification delivery and describe what Centrifugo PRO provides for each step.

\n

How we keep tokens

\n

Let's suppose you got the permission from the user and received the device push token. At this point you must save it to database for sending notifications later using this token. Centrifugo PRO provides API called device_register to do exactly this.

\n

At this point, we use PostgreSQL for storing tokens – which is a very popular SQL database. Probably we will add more storage backend options in the future.

\n

When calling Centrifugo device_register API you can provide user ID, list of topics to subscribe, platform from which the user came from (ios, android, web), also push notifications provider. To deliver push notifications to devices Centrifugo PRO integrates with the following push notification providers:

\n\n

\"Push\"

\n

So we basically cover all the most popular platforms out of the box.

\n

After registering the device token, Centrifugo PRO returns a device_id to you. This device ID must be stored on the client device. As long as the frontend has this device_id, it can update the device's push token information from time to time to keep it current (by just calling device_register again, but with device_id attached).

\n

After saving the token, your backend can start sending push notifications to devices.

\n

How we send notifications

\n

To send push notifications we provide another API called send_push_notification. You need to provide some filter in the API request to tell Centrifugo who you want to send notification. You also need to provide push notification payload. For example, using Centrifugo HTTP API:

\n
curl -X POST http://localhost:8000/api/send_push_notification \\
-H \"Authorization: apikey <KEY>\" \\
-d @- <<'EOF'

{
\"recipient\": {
\"filter\": {
\"topics\": [\"test\"]
}
},
\"notification\": {
\"fcm\": {
\"message\": {
\"notification\": {\"title\": \"Hello\", \"body\": \"How are you?\"}
}
}
}
}
EOF
\n

Here is another important decision we made: Centrifugo PRO allows you to specify raw JSON objects for each provider we support. In other words, we do not wrap the push notifications API for FCM, APNS, HMS - we give you a way to construct the entire push notification message.

\n

This means the Centrifugo push API supports all the fields of push notification payloads out-of-the-box, for all push providers. You can simply use the documentation of FCM, APNs, and send the constructed requests to Centrifugo. There is no need for us to update Centrifugo PRO in any way to support new fields added by providers to push APIs.

\n

When you send a push notification with a filter and push payload for each provider you want, it's queued by Centrifugo. We use Redis Streams for queuing and optionally a queue based on PostgreSQL (less efficient, but still robust enough).

\n

The fact that the notification is being queued means a very fast response time – so you can integrate with Centrifugo from within the hot paths of your application backend. You may additionally provide a push expiration time and a unique push identifier. If you have not provided a unique identifier, Centrifugo generates one for you and returns it in the response. The unique identifier may later be used to track push status in Centrifugo PRO's push notification analytics.

\n

We then have efficient workers which process the queue with minimal latency and send push notifications using batch requests for each provider - i.e., we do this in the most effective way possible. We conducted a benchmark of our worker system with FCM – and we can easily send several million pushes per minute.

\n

Another decision we made - Centrifugo PRO supports sending push notifications to a raw list of tokens. This makes it possible for our customers to use their own token storage. For example, such storage could already exist before you started using Centrifugo, or you might need a different storage/schema. In such cases, you can use Centrifugo just as an effective push sender server.

\n

Finally, Centrifugo PRO supports sending delayed push notification - to queue push for a later delivery, so for example you can send notification based on user time zone and let Centrifugo PRO send it when needed. Or you may send slightly delayed push notification together with real-time message and if client provided an ack to real-time message - cancel push notification.

\n

Secure unified topics

\n

FCM and HMS have a built-in way of sending notification to large groups of devices over topics mechanism (the same for HMS). One problem with native FCM or HMS topics though is that device can subscribe to any topic from the frontend side without any permission check. In today's world this is usually not desired. So Centrifugo PRO re-implements FCM and HMS topics by introducing an additional API to manage device subscriptions to topics.

\n

Centrifugo PRO device topic subscriptions also add a way to introduce the missing topic semantics for APNs.

\n

Centrifugo PRO additionally provides an API to create persistent bindings of user to notification topics. See user_topic_list and user_topic_update. As soon as user registers a device – it will be automatically subscribed to its own topics pre-created over the API. As soon as user logs out from the app and you update user ID of the device - user topics binded to the device automatically removed/switched.

\n

This design solves one of the issues with push notifications (with FCM in particular) – if two different users use the same device it's becoming problematic to unsubscribe the device from large number of topics upon logout. Also, as soon as user to topic binding added (using user_topic_update API) – it will be synchronized across all user active devices. You can still manage such persistent subscriptions on the application backend side if you prefer and provide the full list inside device_register call - Centrifugo PRO API gives you freedom here.

\n

Push analytics

\n

Centrifugo PRO offers the ability to inspect sent push notifications using ClickHouse analytics. Push providers may also offer their own analytics, such as FCM, which provides insight into push notification delivery. Centrifugo PRO also offers a way to analyze push notification delivery and interaction using the update_push_status API. This API allows updating ClickHouse table and add status for each push sent:

\n
    \n
  • delivered
  • \n
  • or interacted
  • \n
\n

It's then possible to make queries to ClickHouse and build various analytical reports. Or use ClickHouse for real-time graphs - for example, from Grafana.

\n

Push notifications UI

\n

Finally, Centrifugo PRO provides a simple web UI for inspecting registered devices. It can simplify development, provide a way to look at live data, and send simple push notification alerts to users or topics.

\n

\n

Conclusion

\n

We really believe in our push notifications and will be working hard to make them even better. The API we already have serves well to cover common push notification delivery use cases, but we won't stop here. Some areas for improvements are: functionality of built-in push notifications web UI, extending push analytics by providing user friendly UI for the insights about push delivery and engagement. The good thing is that we already have a ground for making this.

\n

Take a look at the documentation of Centrifugo PRO push notification API for more formal details and some things not mentioned here. Probably at the time you are reading this we already added something great to the API.

\n

Even though Centrifugo PRO is pretty new, it already has a lot of helpful features, and we have plans to add even more. You can see what’s coming up next on our Centrifugo PRO planned features board. We're excited to share more blog posts like this one in the future.

", diff --git a/blog/rss.xml b/blog/rss.xml index 2dc01f853..0df0d425c 100644 --- a/blog/rss.xml +++ b/blog/rss.xml @@ -4,11 +4,75 @@ https://centrifugal.dev/blog - Sun, 29 Oct 2023 00:00:00 GMT + Mon, 18 Mar 2024 00:00:00 GMT https://validator.w3.org/feed/docs/rss2.html https://github.com/jpmonette/feed en Centrifugal Labs LTD + + <![CDATA[Stream logs from Loki to browser with Centrifugo Websocket-to-GRPC subscriptions]]> + https://centrifugal.dev/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions + https://centrifugal.dev/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptions + Mon, 18 Mar 2024 00:00:00 GMT + + +

As of version 5.1.0, Centrifugo introduces an experimental yet powerful extension that promises to simplify the data delivery process to the browser using GRPC streams. We believe it may help you to solve some practical tasks in minutes. Let's dive into how this feature works and how you can leverage it in your applications integrating with Loki real-time log streaming capabilities.

+

What Are Proxy Subscription Streams?

+

Proxy Subscription Streams support pushing data directly to Centrifugo client channel subscriptions from your application backend over GRPC streams. This feature is designed to facilitate individual data streams to clients as soon as they subscribe to a channel, acting as a bridge between WebSocket connections from clients and GRPC streams to the backend. It supports both unidirectional (backend to client) and bidirectional (both ways) streams, thereby enhancing flexibility in data streaming.

+

+

Establishing a stream between Centrifugo and your application backend upon a channel subscription provides a straightforward path for data to travel directly to the subscribed clients. This mechanism not only simplifies the architecture for real-time data delivery but also ensures fast and individualized data streaming.

+

The design is inspired by Websocketd server – but while Websocketd transforms data from programs running locally, Centrifugo provides a more generic network interface with GRPC. And all other features of Centrifugo like connection authentication, online presence come as a great bonus.

+

In the documentation for Proxy Subscription Streams we mentioned streaming logs from Loki as one of the possible use cases. Let's expand on the idea and implement the working solution in just 10 minutes.

+

Demo and source code

+

Here is a demo of what we well get:

+ +

Take a look at full source code on Github.

+

Setting Up Loki

+

Loki is a horizontally-scalable, highly-available, multi-tenant log aggregation system inspired by Prometheus. It is designed to be very cost-effective and easy to operate, making it a perfect candidate for our real-time log streaming example.

+

We will build the example using Docker Compose, all we have to do for the example is to include Loki image to docker-compose.yml:

+
services:
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
+

Loki can ingest logs via various methods, including Promtail, Grafana Agent, Fluentd, and more. For simplicity, we will send logs to Loki ourselves from the Go application.

+

To send logs to Loki, we can use the HTTP API that Loki provides. This is a straightforward way to push logs directly from an application. The example below demonstrates how to create a simple Go application that generates logs and sends them to Loki using HTTP POST requests.

+

First, define a function to send a log entry to Loki:

+
const (
lokiPushEndpoint = "http://loki:3100/loki/api/v1/push"
)

type lokiPushMessage struct {
Streams []lokiStream `json:"streams"`
}

type lokiStream struct {
Stream map[string]string `json:"stream"`
Values [][]string `json:"values"`
}

func sendLogMessageToLoki(_ context.Context) error {
sources := []string{"backend1", "backend2", "backend3"}
source := sources[rand.Intn(len(sources))]
logMessage := fmt.Sprintf("log from %s source", source)

payload := lokiPushMessage{
Streams: []lokiStream{
{
Stream: map[string]string{
"source": source,
},
Values: [][]string{
{fmt.Sprintf("%d", time.Now().UnixNano()), logMessage},
},
},
},
}

jsonData, err := json.Marshal(payload)
if err != nil {
return err
}
resp, err := http.Post(
lokiPushEndpoint, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
return nil
}

func sendLogsToLoki(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case <-time.After(200 * time.Millisecond):
err := sendLogMessageToLoki(ctx)
if err != nil {
log.Println("error sending log to Loki:", err)
continue
}
}
}
}

func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer cancel()

sendLogsToLoki(ctx)
}
+

This program defines a sendLogsToLoki function that constructs a log entry and sends it to Loki using its HTTP API. It continuously generates log messages every 200 milliseconds.

+

The lokiPushMessage struct is structured to match the JSON payload expected by Loki's /loki/api/v1/push endpoint. Each log entry consists of a set of labels (in the Stream map) and log line values, where each value is a two-element array containing the timestamp and the log line. The timestamp is in nanoseconds to match Loki's expected format.

+

Note, in the example we randomly set log entry source label choosing between backend1, backend2 and backend3 values.

+

At this point our program pushes some logs to Loki, now let's add Centrifugo to consume them from browser in real-time.

+

Configuring Centrifugo

+

Adding Centrifugo is also rather straightforward:

+
services:
centrifugo:
image: centrifugo/centrifugo:v5.3.0
restart: unless-stopped
volumes:
- ./centrifugo/config.json:/centrifugo/config.json
command: centrifugo -c config.json
expose:
- 8000
+

Where config.json is:

+
{
"client_insecure": true,
"allowed_origins": ["http://localhost:9000"],
"proxy_subscribe_stream_endpoint": "grpc://backend:12000",
"proxy_subscribe_stream_timeout": "3s",
"namespaces": [
{
"name": "logs",
"proxy_subscribe_stream": true
}
]
}
+

Note, we enabled client_insecure option here – this is to keep example short, but in real live you may benefit from Centrifugo authentication: JWT-based or proxy-based.

+

Writing frontend

+
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Streaming logs with Centrifugo and Loki</title>
</head>
<body>
<div id="app">
<form id="input" onsubmit="subscribeToLogs(event)">
<input type="text" id="query" autocomplete="off" placeholder="Enter log query" />
<button id="submit" type="submit">SUBSCRIBE</button>
</form>
<div id="logs" style="margin-top: 20px;">
<ul id="lines"></ul>
</div>
</div>
<script src="https://unpkg.com/centrifuge@^5/dist/centrifuge.js"></script>
<script src="app.js"></script>
</body>
</html>
+

In the final version we've also included some CSS to this HTML to make it look nicer.

+

And our Javascript code in app.js:

+
const logs = document.getElementById('logs');
const lines = document.getElementById('lines');
const queryInput = document.getElementById('query');
const button = document.getElementById('submit');

function subscribeToLogs(e) {
e.preventDefault();

const query = queryInput.value;
if (!query) {
alert('Please enter a query.');
return;
}
queryInput.disabled = true;
button.disabled = true;

const centrifuge = new Centrifuge('ws://localhost:9000/connection/websocket');

const subscription = centrifuge.newSubscription('logs:stream', {
data: { query: query }
});

subscription.on('publication', function(ctx) {
const logLine = ctx.data.line;
const logItem = document.createElement('li');
logItem.textContent = logLine;
lines.appendChild(logItem);
logs.scrollTop = logs.scrollHeight;
});

subscription.subscribe();
centrifuge.connect();
}
+

In the final example we've also added Nginx container to serve static files and proxy WebSocket connections to Centrifugo. Check it out in the source code.

+

When user enters Loki query to input, subscription goes to Centrifugo and Centrifugo then realizes it's a proxy stream subscription. So it calls the backend GRPC endpoint (backend:12000) and expect it to implement unidirectional GRPC stream conract. Let's implement it.

+

Handle subscription stream on the Go side

+

On your backend, you'll implement a GRPC service that interacts with Loki to tail logs and then re-send them to Centrifugo subscription stream. Let's implement such service. We first need to take Centrifugo proxy.proto definitions. And we will implement SubscribeUnidirectional method from it.

+

You need to install protoc, also install plugins for Go and GRPC:

+
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
+

And then:

+
protoc -I ./ proxy.proto --go_out=./ --go-grpc_out=./
+

This will generate Protobuf messages and GRPC code. Let's implement our server now:

+
const (
lokiGRPCAddress = "loki:9095"
)

type streamerServer struct {
pb.UnimplementedCentrifugoProxyServer
lokiQuerierClient logproto.QuerierClient
}

type clientData struct {
Query string `json:"query"`
}

func (s *streamerServer) SubscribeUnidirectional(
req *pb.SubscribeRequest,
stream pb.CentrifugoProxy_SubscribeUnidirectionalServer,
) error {
var cd clientData
err := json.Unmarshal(req.Data, &cd)
if err != nil {
return fmt.Errorf("error unmarshaling data: %w", err)
}
query := &logproto.TailRequest{
Query: cd.Query,
}
ctx, cancel := context.WithCancel(stream.Context())
defer cancel()

logStream, err := s.lokiQuerierClient.Tail(ctx, query)
if err != nil {
return fmt.Errorf("error querying Loki: %w", err)
}

started := time.Now()
log.Println("unidirectional subscribe called with request", req)
defer func() {
log.Println("unidirectional subscribe finished, elapsed", time.Since(started))
}()
err = stream.Send(&pb.StreamSubscribeResponse{
SubscribeResponse: &pb.SubscribeResponse{},
})
if err != nil {
return err
}

for {
select {
case <-stream.Context().Done():
return stream.Context().Err()
default:
resp, err := logStream.Recv()
if err != nil {
return fmt.Errorf("error receiving from Loki stream: %v", err)
}
for _, entry := range resp.Stream.Entries {
line := fmt.Sprintf("%s: %s", entry.Timestamp.Format("2006-01-02T15:04:05.000Z07:00"), entry.Line)
err = stream.Send(&pb.StreamSubscribeResponse{
Publication: &pb.Publication{Data: []byte(`{"line":"` + line + `"}`)},
})
if err != nil {
return err
}
}
}
}
}

func main() {
querierConn, err := grpc.Dial(lokiGRPCAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed to dial Loki: %v", err)
}
querierClient := logproto.NewQuerierClient(querierConn)

addr := ":12000"
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

s := grpc.NewServer(grpc.MaxConcurrentStreams(math.MaxUint32))
pb.RegisterCentrifugoProxyServer(s, &streamerServer{
lokiQuerierClient: querierClient,
})

log.Println("Server listening on", addr)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
+

Things to note:

+
    +
  • Loki also supports GRPC interface to tail logs, so we use it here. We could also use Loki WebSocket endpoint /loki/api/v1/tail but this would mean establishing new connection for every tail operation - with GRPC we can use many concurrent tail requests all multiplexed over a single network connection.
  • +
  • When subscription stream initialized from Centrifugo side we start tailing logs from Loki and resend them to Centrifugo
  • +
  • Centrifugo then packs data to WebSocket connection and delivers to browser.
  • +
+

Conclusion

+

Subscription streams may be a very powerful generic feature in your arsenal. Here we've shown how simple it could be to make a proof of concept of the real-time application which consumes individual data from third-party streaming provider.

+

Centrifugo provides WebSocket SDKs for popular languages used to build UI layer, provides authentication and proper management of real-time connections. And with subscription streams feature Centrifugo gives you an answer on how to quickly translate real-time data based on individual query to user.

]]>
+ centrifugo + loki + grpc +
<![CDATA[Discovering Centrifugo PRO: push notifications API]]> https://centrifugal.dev/blog/2023/10/29/discovering-centrifugo-pro-push-notifications diff --git a/blog/tags.html b/blog/tags.html index fbf76f29d..9eb180cf3 100644 --- a/blog/tags.html +++ b/blog/tags.html @@ -15,10 +15,10 @@ - - + + - + \ No newline at end of file diff --git a/blog/tags/authentication.html b/blog/tags/authentication.html index a3cd546d1..d422e87ac 100644 --- a/blog/tags/authentication.html +++ b/blog/tags/authentication.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "authentication"

View All Tags
diff --git a/blog/tags/benthos.html b/blog/tags/benthos.html index b6a730b8a..793cb62ca 100644 --- a/blog/tags/benthos.html +++ b/blog/tags/benthos.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "benthos"

View All Tags
August 19, 2023 by Alexander Emelin
In this post, we'll demonstrate how to asynchronously stream messages into Centrifugo channels from external data providers using Benthos tool. We also highlight some pitfalls which become more important in asynchronous publishing scenario.
diff --git a/blog/tags/centrifuge.html b/blog/tags/centrifuge.html index a1a19f664..6c6853c65 100644 --- a/blog/tags/centrifuge.html +++ b/blog/tags/centrifuge.html @@ -15,8 +15,8 @@ - - + +

2 posts tagged with "centrifuge"

View All Tags
February 10, 2020 by Centrifugal team
Describing a test stand in Kubernetes where we connect one million websocket connections to a server, using Redis to scale nodes, and providing insights about hardware resources required to achieve 500k messages per second
diff --git a/blog/tags/centrifugo.html b/blog/tags/centrifugo.html index f4da93b5c..adcf8f1de 100644 --- a/blog/tags/centrifugo.html +++ b/blog/tags/centrifugo.html @@ -3,7 +3,7 @@ -12 posts tagged with "centrifugo" | Centrifugo +13 posts tagged with "centrifugo" | Centrifugo @@ -15,10 +15,10 @@ - - + + -

12 posts tagged with "centrifugo"

View All Tags
October 29, 2023 by Alexander Emelin
We start talking more about recently launched Centrifugo PRO. In this post, we share details about Centrifugo PRO push notification API implementation - how it works and what makes it special and practical.
August 29, 2023 by Centrifugal + RabbitX
In this post, the engineering team of RabbitX platform shares details about the usage of Centrifugo in their product.
August 19, 2023 by Alexander Emelin
In this post, we'll demonstrate how to asynchronously stream messages into Centrifugo channels from external data providers using Benthos tool. We also highlight some pitfalls which become more important in asynchronous publishing scenario.
June 29, 2023 by Centrifugal team
We are excited to announce a new version of Centrifugo. It's an evolutionary step which makes Centrifugo cleaner and more intuitive to use.
July 19, 2022 by Centrifugal team
Centrifugo v4 provides an optimized client protocol, modern WebSocket emulation, improved channel security, redesigned client SDK behavior, experimental HTTP/3 and WebTransport support.
October 18, 2021 by Alexander Emelin
In this tutorial we are integrating Centrifugo with NodeJS. We are using Centrifugo connect proxy feature to authenticate connections over standard Express.js session middleware.
August 31, 2021 by Centrifugal team
Centrifugo v3 released with lots of exciting improvements
+

13 posts tagged with "centrifugo"

View All Tags
October 29, 2023 by Alexander Emelin
We start talking more about recently launched Centrifugo PRO. In this post, we share details about Centrifugo PRO push notification API implementation - how it works and what makes it special and practical.
August 29, 2023 by Centrifugal + RabbitX
In this post, the engineering team of RabbitX platform shares details about the usage of Centrifugo in their product.
August 19, 2023 by Alexander Emelin
In this post, we'll demonstrate how to asynchronously stream messages into Centrifugo channels from external data providers using Benthos tool. We also highlight some pitfalls which become more important in asynchronous publishing scenario.
June 29, 2023 by Centrifugal team
We are excited to announce a new version of Centrifugo. It's an evolutionary step which makes Centrifugo cleaner and more intuitive to use.
July 19, 2022 by Centrifugal team
Centrifugo v4 provides an optimized client protocol, modern WebSocket emulation, improved channel security, redesigned client SDK behavior, experimental HTTP/3 and WebTransport support.
October 18, 2021 by Alexander Emelin
In this tutorial we are integrating Centrifugo with NodeJS. We are using Centrifugo connect proxy feature to authenticate connections over standard Express.js session middleware.
August 31, 2021 by Centrifugal team
Centrifugo v3 released with lots of exciting improvements
\ No newline at end of file diff --git a/blog/tags/django.html b/blog/tags/django.html index 8811c52a3..839c2a6aa 100644 --- a/blog/tags/django.html +++ b/blog/tags/django.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "django"

View All Tags
diff --git a/blog/tags/go.html b/blog/tags/go.html index 3ea08ca8d..649973258 100644 --- a/blog/tags/go.html +++ b/blog/tags/go.html @@ -15,8 +15,8 @@ - - + +

5 posts tagged with "go"

View All Tags
November 12, 2020 by Alexander Emelin
The post describes techniques to write scalable WebSocket servers within Go ecosystem and beyond it
February 10, 2020 by Centrifugal team
Describing a test stand in Kubernetes where we connect one million websocket connections to a server, using Redis to scale nodes, and providing insights about hardware resources required to achieve 500k messages per second
diff --git a/blog/tags/grpc.html b/blog/tags/grpc.html new file mode 100644 index 000000000..ec8ee0239 --- /dev/null +++ b/blog/tags/grpc.html @@ -0,0 +1,24 @@ + + + + + +One post tagged with "grpc" | Centrifugo + + + + + + + + + + + + + + + +

One post tagged with "grpc"

View All Tags
+ + \ No newline at end of file diff --git a/blog/tags/interview.html b/blog/tags/interview.html index 0b410ce0d..e2ef09a9b 100644 --- a/blog/tags/interview.html +++ b/blog/tags/interview.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "interview"

View All Tags
August 29, 2023 by Centrifugal + RabbitX
In this post, the engineering team of RabbitX platform shares details about the usage of Centrifugo in their product.
diff --git a/blog/tags/keycloak.html b/blog/tags/keycloak.html index 963e70e3a..1d3a21790 100644 --- a/blog/tags/keycloak.html +++ b/blog/tags/keycloak.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "keycloak"

View All Tags
diff --git a/blog/tags/laravel.html b/blog/tags/laravel.html index 8b67a95dd..5f20d6708 100644 --- a/blog/tags/laravel.html +++ b/blog/tags/laravel.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "laravel"

View All Tags
diff --git a/blog/tags/loki.html b/blog/tags/loki.html new file mode 100644 index 000000000..8a05ec848 --- /dev/null +++ b/blog/tags/loki.html @@ -0,0 +1,24 @@ + + + + + +One post tagged with "loki" | Centrifugo + + + + + + + + + + + + + + + +

One post tagged with "loki"

View All Tags
+ + \ No newline at end of file diff --git a/blog/tags/php.html b/blog/tags/php.html index 7afe4341c..cdfc4cd63 100644 --- a/blog/tags/php.html +++ b/blog/tags/php.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "php"

View All Tags
diff --git a/blog/tags/pro.html b/blog/tags/pro.html index db035fef7..9f7c56571 100644 --- a/blog/tags/pro.html +++ b/blog/tags/pro.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "pro"

View All Tags
October 29, 2023 by Alexander Emelin
We start talking more about recently launched Centrifugo PRO. In this post, we share details about Centrifugo PRO push notification API implementation - how it works and what makes it special and practical.
diff --git a/blog/tags/proxy.html b/blog/tags/proxy.html index d00e86f11..edf5bb355 100644 --- a/blog/tags/proxy.html +++ b/blog/tags/proxy.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "proxy"

View All Tags
October 18, 2021 by Alexander Emelin
In this tutorial we are integrating Centrifugo with NodeJS. We are using Centrifugo connect proxy feature to authenticate connections over standard Express.js session middleware.
diff --git a/blog/tags/push-notifications.html b/blog/tags/push-notifications.html index 4e95f1759..346b233ff 100644 --- a/blog/tags/push-notifications.html +++ b/blog/tags/push-notifications.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "push notifications"

View All Tags
October 29, 2023 by Alexander Emelin
We start talking more about recently launched Centrifugo PRO. In this post, we share details about Centrifugo PRO push notification API implementation - how it works and what makes it special and practical.
diff --git a/blog/tags/quic.html b/blog/tags/quic.html index cfdd6d0b7..f9f6b768e 100644 --- a/blog/tags/quic.html +++ b/blog/tags/quic.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "quic"

View All Tags
diff --git a/blog/tags/redis.html b/blog/tags/redis.html index 1c63120fa..c7e357353 100644 --- a/blog/tags/redis.html +++ b/blog/tags/redis.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "redis"

View All Tags
diff --git a/blog/tags/release.html b/blog/tags/release.html index d0e07437c..0755d9450 100644 --- a/blog/tags/release.html +++ b/blog/tags/release.html @@ -15,8 +15,8 @@ - - + +

3 posts tagged with "release"

View All Tags
June 29, 2023 by Centrifugal team
We are excited to announce a new version of Centrifugo. It's an evolutionary step which makes Centrifugo cleaner and more intuitive to use.
July 19, 2022 by Centrifugal team
Centrifugo v4 provides an optimized client protocol, modern WebSocket emulation, improved channel security, redesigned client SDK behavior, experimental HTTP/3 and WebTransport support.
August 31, 2021 by Centrifugal team
Centrifugo v3 released with lots of exciting improvements
diff --git a/blog/tags/sso.html b/blog/tags/sso.html index e7ba34190..45ebae3b7 100644 --- a/blog/tags/sso.html +++ b/blog/tags/sso.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "sso"

View All Tags
diff --git a/blog/tags/tutorial.html b/blog/tags/tutorial.html index b097aaeef..64655db22 100644 --- a/blog/tags/tutorial.html +++ b/blog/tags/tutorial.html @@ -15,8 +15,8 @@ - - + +

5 posts tagged with "tutorial"

View All Tags
August 19, 2023 by Alexander Emelin
In this post, we'll demonstrate how to asynchronously stream messages into Centrifugo channels from external data providers using Benthos tool. We also highlight some pitfalls which become more important in asynchronous publishing scenario.
October 18, 2021 by Alexander Emelin
In this tutorial we are integrating Centrifugo with NodeJS. We are using Centrifugo connect proxy feature to authenticate connections over standard Express.js session middleware.
diff --git a/blog/tags/usecase.html b/blog/tags/usecase.html index b64c251f1..92e3d5b51 100644 --- a/blog/tags/usecase.html +++ b/blog/tags/usecase.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "usecase"

View All Tags
August 29, 2023 by Centrifugal + RabbitX
In this post, the engineering team of RabbitX platform shares details about the usage of Centrifugo in their product.
diff --git a/blog/tags/websocket.html b/blog/tags/websocket.html index 300b1ad73..60230b921 100644 --- a/blog/tags/websocket.html +++ b/blog/tags/websocket.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "websocket"

View All Tags
November 12, 2020 by Alexander Emelin
The post describes techniques to write scalable WebSocket servers within Go ecosystem and beyond it
diff --git a/blog/tags/webtransport.html b/blog/tags/webtransport.html index cbd147010..db4d40f2c 100644 --- a/blog/tags/webtransport.html +++ b/blog/tags/webtransport.html @@ -15,8 +15,8 @@ - - + +

One post tagged with "webtransport"

View All Tags
diff --git a/components/Highlight.html b/components/Highlight.html index f13337d46..fe7398b61 100644 --- a/components/Highlight.html +++ b/components/Highlight.html @@ -15,8 +15,8 @@ - - + +

diff --git a/components/logo.html b/components/logo.html index 5851d8888..600b84f40 100644 --- a/components/logo.html +++ b/components/logo.html @@ -15,8 +15,8 @@ - - + +
diff --git a/components/logos/Badoo.html b/components/logos/Badoo.html index 8723bf657..d56bd62b4 100644 --- a/components/logos/Badoo.html +++ b/components/logos/Badoo.html @@ -15,8 +15,8 @@ - - + +
diff --git a/components/logos/Grafana.html b/components/logos/Grafana.html index c70d63566..572c46293 100644 --- a/components/logos/Grafana.html +++ b/components/logos/Grafana.html @@ -15,8 +15,8 @@ - - + +
diff --git a/components/logos/ManyChat.html b/components/logos/ManyChat.html index 73c3c8464..8fe9a0fe9 100644 --- a/components/logos/ManyChat.html +++ b/components/logos/ManyChat.html @@ -15,8 +15,8 @@ - - + +
diff --git a/components/logos/OpenWeb.html b/components/logos/OpenWeb.html index 52c3d4a6c..e4de54cdb 100644 --- a/components/logos/OpenWeb.html +++ b/components/logos/OpenWeb.html @@ -15,8 +15,8 @@ - - + +
diff --git a/docs/3/attributions.html b/docs/3/attributions.html index 56aa80947..a1ad25b54 100644 --- a/docs/3/attributions.html +++ b/docs/3/attributions.html @@ -15,8 +15,8 @@ - - + +

Attributions

Landing Page Images

diff --git a/docs/3/ecosystem/centrifuge.html b/docs/3/ecosystem/centrifuge.html index c2e686d2b..69950661e 100644 --- a/docs/3/ecosystem/centrifuge.html +++ b/docs/3/ecosystem/centrifuge.html @@ -15,8 +15,8 @@ - - + +

Centrifuge library

Centrifugo is a server built on top of Centrifuge library for Go language.

diff --git a/docs/3/ecosystem/integrations.html b/docs/3/ecosystem/integrations.html index ec37b8b41..1777423c7 100644 --- a/docs/3/ecosystem/integrations.html +++ b/docs/3/ecosystem/integrations.html @@ -15,8 +15,8 @@ - - + +

Framework integrations

tip

In general, integrating Centrifugo can be done in several steps even without third-party libraries – see our integration guide. But there are some community-driven projects that provide integration for more native experience or even some additional functionality on top of Centrifugo.

diff --git a/docs/3/faq.html b/docs/3/faq.html index 222b01546..7245ea303 100644 --- a/docs/3/faq.html +++ b/docs/3/faq.html @@ -15,8 +15,8 @@ - - + +

Frequently Asked Questions

Answers to popular questions here.

diff --git a/docs/3/flow_diagrams.html b/docs/3/flow_diagrams.html index 750a58912..eeb50b53f 100644 --- a/docs/3/flow_diagrams.html +++ b/docs/3/flow_diagrams.html @@ -15,8 +15,8 @@ - - + +

flow_diagrams

For swimlines.io:

diff --git a/docs/3/getting-started/client_api.html b/docs/3/getting-started/client_api.html index 0feee5bf9..1afa50964 100644 --- a/docs/3/getting-started/client_api.html +++ b/docs/3/getting-started/client_api.html @@ -15,8 +15,8 @@ - - + +

Client API showcase

This chapter showcases Centrifugo bidirectional client API capabilities – i.e. real-time messaging primitives available on a front-end (can be a browser or a mobile device).

diff --git a/docs/3/getting-started/design.html b/docs/3/getting-started/design.html index c4d759f43..a21a9b903 100644 --- a/docs/3/getting-started/design.html +++ b/docs/3/getting-started/design.html @@ -15,8 +15,8 @@ - - + +

Design overview

Let's discuss some architectural and design topics about Centrifugo.

diff --git a/docs/3/getting-started/highlights.html b/docs/3/getting-started/highlights.html index b5a287a07..724b09d85 100644 --- a/docs/3/getting-started/highlights.html +++ b/docs/3/getting-started/highlights.html @@ -15,8 +15,8 @@ - - + +

Main highlights

At this point you know how to build the simplest real-time app with Centrifugo. Beyond the core PUB/SUB functionality Centrifugo provides more features and primitives to build scalable real-time applications. Let's summarize main Centrifugo ✨highlights✨ here. Every point is then extended throughout the documentation.

diff --git a/docs/3/getting-started/installation.html b/docs/3/getting-started/installation.html index f31ebefce..4a75a797a 100644 --- a/docs/3/getting-started/installation.html +++ b/docs/3/getting-started/installation.html @@ -15,8 +15,8 @@ - - + +

Install Centrifugo

Centrifugo server is written in Go language. It's an open-source software, the source code is available on Github.

diff --git a/docs/3/getting-started/integration.html b/docs/3/getting-started/integration.html index efc5bc18c..c424ec99a 100644 --- a/docs/3/getting-started/integration.html +++ b/docs/3/getting-started/integration.html @@ -15,8 +15,8 @@ - - + +

Integration guide

This chapter aims to help you get started with Centrifugo. We will look at a step-by-step workflow of integrating your application with Centrifugo providing links to relevant parts of this documentation.

diff --git a/docs/3/getting-started/introduction.html b/docs/3/getting-started/introduction.html index 0f80c8755..39062ddf4 100644 --- a/docs/3/getting-started/introduction.html +++ b/docs/3/getting-started/introduction.html @@ -15,8 +15,8 @@ - - + +

Centrifugo introduction

Centrifugo is an open-source scalable real-time messaging server in a language-agnostic way.

diff --git a/docs/3/getting-started/migration_v3.html b/docs/3/getting-started/migration_v3.html index 5aaf9b3ff..6c7abb0c6 100644 --- a/docs/3/getting-started/migration_v3.html +++ b/docs/3/getting-started/migration_v3.html @@ -15,8 +15,8 @@ - - + +

Migrating to v3

This chapter aims to help developers migrate from Centrifugo v2 to Centrifugo v3. Migration should mostly affect the backend part only, so you won't need to change the code of your frontend applications at all. In most cases, all you should do is adapt Centrifugo configuration to match v3 changes and redeploy Centrifugo using v3 build instead of v2.

diff --git a/docs/3/getting-started/quickstart.html b/docs/3/getting-started/quickstart.html index 6df78885e..9be83cb57 100644 --- a/docs/3/getting-started/quickstart.html +++ b/docs/3/getting-started/quickstart.html @@ -15,8 +15,8 @@ - - + +

Quickstart tutorial ⏱️

Here we will build a very simple browser application with Centrifugo. It works in a way that users connect to Centrifugo over WebSocket, subscribe to a channel, and start receiving all messages published to that channel. In our case, we will send a counter value to all channel subscribers to update it in all open browser tabs in real-time.

diff --git a/docs/3/pro/analytics.html b/docs/3/pro/analytics.html index a68aaa84e..ce56fadbe 100644 --- a/docs/3/pro/analytics.html +++ b/docs/3/pro/analytics.html @@ -15,8 +15,8 @@ - - + +

Analytics with ClickHouse

This feature allows exporting information about connections, subscriptions and client operations to ClickHouse thus providing an integration with a real-time (with seconds delay) analytics storage. ClickHouse is super fast and simple to operate with, and it allows effective data keeping for a window of time.

diff --git a/docs/3/pro/db_namespaces.html b/docs/3/pro/db_namespaces.html index d1887d035..7ca5ed2c4 100644 --- a/docs/3/pro/db_namespaces.html +++ b/docs/3/pro/db_namespaces.html @@ -15,8 +15,8 @@ - - + +

Database-driven namespace configuration

Centrifugo PRO supports database-driven namespace configuration. This means that instead of configuring namespaces in a configuration file you will be able to configure them in admin web UI. It's also possible to select a namespace for automatic personal channel subscription.

diff --git a/docs/3/pro/install_and_run.html b/docs/3/pro/install_and_run.html index cf347c6cb..efe8633ef 100644 --- a/docs/3/pro/install_and_run.html +++ b/docs/3/pro/install_and_run.html @@ -15,8 +15,8 @@ - - + +

Install and run PRO version

caution

Centrifugo PRO is distributed under commercial license which is different from OSS version. By downloading Centrifugo PRO you automatically accept license terms.

diff --git a/docs/3/pro/overview.html b/docs/3/pro/overview.html index 4f0603598..904257a88 100644 --- a/docs/3/pro/overview.html +++ b/docs/3/pro/overview.html @@ -15,8 +15,8 @@ - - + +

Centrifugo PRO overview

Centrifugo PRO is an extended version of Centrifugo with a set of additional features. These features can provide your business with unique benefits – drastically save development time, reduce resource usage on a server, protect your backend from misusing, and put the system observability to the next level.

diff --git a/docs/3/pro/performance.html b/docs/3/pro/performance.html index d2b4023b6..98351332d 100644 --- a/docs/3/pro/performance.html +++ b/docs/3/pro/performance.html @@ -15,8 +15,8 @@ - - + +

Faster performance

Centrifugo PRO has performance improvements for several server parts. These improvements can help to reduce tail end-to-end latencies in application, increase server throughput and/or reduce CPU usage on server machines.

diff --git a/docs/3/pro/process_stats.html b/docs/3/pro/process_stats.html index 0b396fd0a..ad636b757 100644 --- a/docs/3/pro/process_stats.html +++ b/docs/3/pro/process_stats.html @@ -15,8 +15,8 @@ - - + +

CPU and RSS stats

A useful addition of Centrifugo PRO is an ability to show CPU and RSS memory usage of each node in admin web UI.

diff --git a/docs/3/pro/singleflight.html b/docs/3/pro/singleflight.html index c44a42eb6..c2c109f83 100644 --- a/docs/3/pro/singleflight.html +++ b/docs/3/pro/singleflight.html @@ -15,8 +15,8 @@ - - + +

Singleflight

Centrifugo PRO provides an additional boolean option use_singleflight (default false). When this option enabled Centrifugo will automatically try to merge identical requests to history, online presence or presence stats issued at the same time into one real network request.

diff --git a/docs/3/pro/throttling.html b/docs/3/pro/throttling.html index 6f7d93f24..b2c7201fc 100644 --- a/docs/3/pro/throttling.html +++ b/docs/3/pro/throttling.html @@ -15,8 +15,8 @@ - - + +

Operation throttling

The throttling feature allows limiting the number of operations each user can issue during a configured time interval. This is useful to protect the system from misusing or protect it from a bug in the application frontend code.

diff --git a/docs/3/pro/token_revocation.html b/docs/3/pro/token_revocation.html index 48f908b97..8a4a72788 100644 --- a/docs/3/pro/token_revocation.html +++ b/docs/3/pro/token_revocation.html @@ -15,8 +15,8 @@ - - + +

Token revocation API

One more protective instrument in Centrifugo PRO is API to manage token revocations.

diff --git a/docs/3/pro/tracing.html b/docs/3/pro/tracing.html index f52a4d648..a7b3f6cfb 100644 --- a/docs/3/pro/tracing.html +++ b/docs/3/pro/tracing.html @@ -15,8 +15,8 @@ - - + +

User and channel tracing

That's a unique thing. The tracing feature of Centrifugo PRO allows attaching to any channel to see all messages flying towards subscribers or attach to a specific user ID to see all user-related events in real-time.

diff --git a/docs/3/pro/user_block.html b/docs/3/pro/user_block.html index 7f4324fa1..37e2c6f04 100644 --- a/docs/3/pro/user_block.html +++ b/docs/3/pro/user_block.html @@ -15,8 +15,8 @@ - - + +

User blocking API

In addition to detailed observability properties Centrifugo PRO provides instruments for performing protective actions. One of such instruments is user blocking API which allows blocking a specific user in Centrifugo.

diff --git a/docs/3/pro/user_connections.html b/docs/3/pro/user_connections.html index 97074027b..181a995b2 100644 --- a/docs/3/pro/user_connections.html +++ b/docs/3/pro/user_connections.html @@ -15,8 +15,8 @@ - - + +

User connections API

Centrifugo PRO provides an additional API call user_connections. It allows getting all active sessions of the user (by user ID) without turning on presence feature for channels at all. It's also possible to attach any JSON payload to a connection which will be then visible in the result of user_connections call. The important thing is that this additional meta information won't be exposed to a client-side (unlike connection info for example).

diff --git a/docs/3/pro/user_status.html b/docs/3/pro/user_status.html index 66468f61a..14ee1092a 100644 --- a/docs/3/pro/user_status.html +++ b/docs/3/pro/user_status.html @@ -15,8 +15,8 @@ - - + +

User status

Centrifugo OSS provides a presence feature for channels. It works well (for channels with reasonably small number of active subscribers though), but sometimes you may need a bit different functionality.

diff --git a/docs/3/server/admin_web.html b/docs/3/server/admin_web.html index ab321a489..0292c6554 100644 --- a/docs/3/server/admin_web.html +++ b/docs/3/server/admin_web.html @@ -15,8 +15,8 @@ - - + +

Admin web UI

Centrifugo comes with builtin admin web interface. It can:

diff --git a/docs/3/server/authentication.html b/docs/3/server/authentication.html index cedc86794..e9567b72c 100644 --- a/docs/3/server/authentication.html +++ b/docs/3/server/authentication.html @@ -15,8 +15,8 @@ - - + +

Client authentication

To authenticate incoming connection (client) Centrifugo can use JSON Web Token (JWT) passed from the client-side. This way Centrifugo may know the ID of user in your application, also application can pass additional data to Centrifugo inside JWT claims. This chapter describes this authentication mechanism.

diff --git a/docs/3/server/channels.html b/docs/3/server/channels.html index 3121e3fdf..93f129cf5 100644 --- a/docs/3/server/channels.html +++ b/docs/3/server/channels.html @@ -15,8 +15,8 @@ - - + +

Channels

Channel is a route for publications. Clients can be subscribed to a channel to receive real-time messages published to a channel – new publications and join/leave events (if enabled for a channel namespace). A channel subscriber can also ask for a channel presence or channel history information (if enabled for a channel namespace).

diff --git a/docs/3/server/codes.html b/docs/3/server/codes.html index fde59abe5..37c6d2d2e 100644 --- a/docs/3/server/codes.html +++ b/docs/3/server/codes.html @@ -15,8 +15,8 @@ - - + +

Error and disconnect codes

This chapter describes error and disconnect codes Centrifugo uses in a client protocol, also error codes which a server API can return in response.

diff --git a/docs/3/server/configuration.html b/docs/3/server/configuration.html index bb387fa2f..20225cbef 100644 --- a/docs/3/server/configuration.html +++ b/docs/3/server/configuration.html @@ -15,8 +15,8 @@ - - + +

Configure Centrifugo

Let's look at how Centrifugo can be configured.

diff --git a/docs/3/server/console_commands.html b/docs/3/server/console_commands.html index 4102aba02..687e6b725 100644 --- a/docs/3/server/console_commands.html +++ b/docs/3/server/console_commands.html @@ -15,8 +15,8 @@ - - + +

Console commands

Here is a list of console commands that come with Centrifugo executable.

diff --git a/docs/3/server/engines.html b/docs/3/server/engines.html index 772bc8424..fe3fe2193 100644 --- a/docs/3/server/engines.html +++ b/docs/3/server/engines.html @@ -15,8 +15,8 @@ - - + +

Engines, scalability

The Engine in Centrifugo is responsible for publishing messages between nodes, handle PUB/SUB broker subscriptions, save/retrieve online presence and history data.

diff --git a/docs/3/server/history_and_recovery.html b/docs/3/server/history_and_recovery.html index 406658330..afa669e1c 100644 --- a/docs/3/server/history_and_recovery.html +++ b/docs/3/server/history_and_recovery.html @@ -15,8 +15,8 @@ - - + +

History and recovery

Centrifugo engines can maintain publication history for channels with configured history size and TTL.

diff --git a/docs/3/server/infra_tuning.html b/docs/3/server/infra_tuning.html index 7a6c9b88e..01e826219 100644 --- a/docs/3/server/infra_tuning.html +++ b/docs/3/server/infra_tuning.html @@ -15,8 +15,8 @@ - - + +

Infrastructure tuning

As Centrifugo deals with lots of persistent connections your operating system and server infrastructure must be ready for it.

diff --git a/docs/3/server/load_balancing.html b/docs/3/server/load_balancing.html index 9a6d25864..b7ff24b6a 100644 --- a/docs/3/server/load_balancing.html +++ b/docs/3/server/load_balancing.html @@ -15,8 +15,8 @@ - - + +

Load balancing

This chapter shows how to deal with persistent connection load balancing.

diff --git a/docs/3/server/monitoring.html b/docs/3/server/monitoring.html index 76f1b7583..e37bd9704 100644 --- a/docs/3/server/monitoring.html +++ b/docs/3/server/monitoring.html @@ -15,8 +15,8 @@ - - + +

Monitoring

Centrifugo supports reporting metrics in Prometheus format and can automatically export metrics to Graphite.

diff --git a/docs/3/server/private_channels.html b/docs/3/server/private_channels.html index a36b1caa3..2a03488f3 100644 --- a/docs/3/server/private_channels.html +++ b/docs/3/server/private_channels.html @@ -15,8 +15,8 @@ - - + +

Private channels

In the channels chapter we mentioned private channels. This chapter has more information about the private channel mechanism in Centrifugo.

diff --git a/docs/3/server/proxy.html b/docs/3/server/proxy.html index d55b9ec5b..feb344eb0 100644 --- a/docs/3/server/proxy.html +++ b/docs/3/server/proxy.html @@ -15,8 +15,8 @@ - - + +

Proxy to backend

It's possible to proxy some client connection events from Centrifugo to the application backend and react to them in a custom way. For example, it's possible to authenticate connection via request from Centrifugo to application backend, refresh client sessions and answer to RPC calls sent by a client over bidirectional connection.

diff --git a/docs/3/server/server_api.html b/docs/3/server/server_api.html index c3601e8fe..de3ce6e0f 100644 --- a/docs/3/server/server_api.html +++ b/docs/3/server/server_api.html @@ -15,8 +15,8 @@ - - + +

Server API

Server API is a way to send various commands to Centrifugo. For example, server API allows publishing messages to channels, get server statistics, etc. There are two kinds of API available at the moment:

diff --git a/docs/3/server/server_subs.html b/docs/3/server/server_subs.html index 19f5c43a0..2a665bd7e 100644 --- a/docs/3/server/server_subs.html +++ b/docs/3/server/server_subs.html @@ -15,8 +15,8 @@ - - + +

Server-side subscriptions

Centrifugo clients can initiate a subscription to a channel by calling the Subscribe method of client protocol. In most cases, this is the most flexible approach since a client-side usually knows which channels it needs to consume at a concrete moment. But in some situations, all you need is to subscribe your connections to several channels on a server-side at the moment of connection establishment. So client effectively starts receiving publications from those channels without calling the Subscribe method at all.

diff --git a/docs/3/server/tls.html b/docs/3/server/tls.html index 7027d37b4..f75f723b8 100644 --- a/docs/3/server/tls.html +++ b/docs/3/server/tls.html @@ -15,8 +15,8 @@ - - + +

Configure TLS

TLS/SSL layer is very important not only for securing your connections but also to increase a diff --git a/docs/3/transports/client_protocol.html b/docs/3/transports/client_protocol.html index 61fac37bf..438d3e855 100644 --- a/docs/3/transports/client_protocol.html +++ b/docs/3/transports/client_protocol.html @@ -15,8 +15,8 @@ - - + +

Client protocol

This chapter describes internal bidirectional client-server protocol in details to help developers build new client libraries or understand how existing client connectors work.

diff --git a/docs/3/transports/client_sdk.html b/docs/3/transports/client_sdk.html index 32a4adc34..fb6fbc455 100644 --- a/docs/3/transports/client_sdk.html +++ b/docs/3/transports/client_sdk.html @@ -15,8 +15,8 @@ - - + +

Client real-time SDKs

The following SDKs allow connecting to Centrifugo from the application frontend:

diff --git a/docs/3/transports/overview.html b/docs/3/transports/overview.html index 5b958d23a..f9c927e08 100644 --- a/docs/3/transports/overview.html +++ b/docs/3/transports/overview.html @@ -15,8 +15,8 @@ - - + +

Real-time transports

Centrifugo supports a variety of transports to deliver real-time messages to clients.

diff --git a/docs/3/transports/sockjs.html b/docs/3/transports/sockjs.html index 215150031..9e990971b 100644 --- a/docs/3/transports/sockjs.html +++ b/docs/3/transports/sockjs.html @@ -15,8 +15,8 @@ - - + +

SockJS

SockJS is a polyfill browser library which provides HTTP-based fallback transports in case when it's not possible to establish Websocket connection. This can happen in old client browsers or because of some proxy behind client and server that cuts of Websocket traffic. You can find more information on SockJS project Github page.

diff --git a/docs/3/transports/uni_grpc.html b/docs/3/transports/uni_grpc.html index 6cdbfb8f6..f82b0274d 100644 --- a/docs/3/transports/uni_grpc.html +++ b/docs/3/transports/uni_grpc.html @@ -15,8 +15,8 @@ - - + +

Unidirectional GRPC

It's possible to connect to GRPC unidirectional stream to consume real-time messages from Centrifugo. In this case you need to generate GRPC code for your language on client-side.

diff --git a/docs/3/transports/uni_http_stream.html b/docs/3/transports/uni_http_stream.html index d8d7e4c0d..85635f40d 100644 --- a/docs/3/transports/uni_http_stream.html +++ b/docs/3/transports/uni_http_stream.html @@ -15,8 +15,8 @@ - - + +

Unidirectional HTTP streaming

Default unidirectional HTTP streaming connection endpoint in Centrifugo is:

diff --git a/docs/3/transports/uni_sse.html b/docs/3/transports/uni_sse.html index 41a2e8adb..0cab47b0f 100644 --- a/docs/3/transports/uni_sse.html +++ b/docs/3/transports/uni_sse.html @@ -15,8 +15,8 @@ - - + +

Unidirectional SSE (EventSource)

Default unidirectional SSE (EventSource) connection endpoint in Centrifugo is:

diff --git a/docs/3/transports/uni_websocket.html b/docs/3/transports/uni_websocket.html index f893e2c9c..3c679ecc7 100644 --- a/docs/3/transports/uni_websocket.html +++ b/docs/3/transports/uni_websocket.html @@ -15,8 +15,8 @@ - - + +

Unidirectional WebSocket

Default unidirectional WebSocket connection endpoint in Centrifugo is:

diff --git a/docs/3/transports/websocket.html b/docs/3/transports/websocket.html index 658491bf2..e2751ffb6 100644 --- a/docs/3/transports/websocket.html +++ b/docs/3/transports/websocket.html @@ -15,8 +15,8 @@ - - + +

WebSocket

Websocket is the main transport in Centrifugo. It's a very efficient low-overhead protocol on top of TCP.

diff --git a/docs/4/attributions.html b/docs/4/attributions.html index f68589175..e72d28eb3 100644 --- a/docs/4/attributions.html +++ b/docs/4/attributions.html @@ -15,8 +15,8 @@ - - + +

Attributions

Landing Page Images

diff --git a/docs/4/faq.html b/docs/4/faq.html index bfe88ffc9..6274b0e43 100644 --- a/docs/4/faq.html +++ b/docs/4/faq.html @@ -15,8 +15,8 @@ - - + +

Frequently Asked Questions

Answers to popular questions here.

diff --git a/docs/4/flow_diagrams.html b/docs/4/flow_diagrams.html index dfee2aef8..507554da6 100644 --- a/docs/4/flow_diagrams.html +++ b/docs/4/flow_diagrams.html @@ -15,8 +15,8 @@ - - + +

flow_diagrams

For swimlanes.io:

diff --git a/docs/4/getting-started/client_api.html b/docs/4/getting-started/client_api.html index 2c08156e4..2a0129c7a 100644 --- a/docs/4/getting-started/client_api.html +++ b/docs/4/getting-started/client_api.html @@ -15,8 +15,8 @@ - - + +

Client API showcase

This chapter showcases Centrifugo bidirectional client API capabilities – i.e. real-time messaging primitives available on a front-end (can be a browser or a mobile device).

diff --git a/docs/4/getting-started/community.html b/docs/4/getting-started/community.html index 36cbb4c2a..17bee1962 100644 --- a/docs/4/getting-started/community.html +++ b/docs/4/getting-started/community.html @@ -15,8 +15,8 @@ - - + +

Join community

If you find Centrifugo interesting – please welcome to our community rooms in Telegram (the most active) and Discord:

diff --git a/docs/4/getting-started/design.html b/docs/4/getting-started/design.html index 8061ff8a8..624c7fc77 100644 --- a/docs/4/getting-started/design.html +++ b/docs/4/getting-started/design.html @@ -15,8 +15,8 @@ - - + +

Design overview

Let's discuss some architectural and design topics about Centrifugo.

diff --git a/docs/4/getting-started/ecosystem.html b/docs/4/getting-started/ecosystem.html index 40f4335b4..e33703ad5 100644 --- a/docs/4/getting-started/ecosystem.html +++ b/docs/4/getting-started/ecosystem.html @@ -15,8 +15,8 @@ - - + +

Ecosystem notes

Some additional notes about our ecosystem which may help you develop with our tech.

diff --git a/docs/4/getting-started/highlights.html b/docs/4/getting-started/highlights.html index e8fad95d9..77e56205e 100644 --- a/docs/4/getting-started/highlights.html +++ b/docs/4/getting-started/highlights.html @@ -15,8 +15,8 @@ - - + +

Main highlights

At this point you know how to build the simplest real-time app with Centrifugo. Beyond the core PUB/SUB functionality Centrifugo provides more features and primitives to build scalable real-time applications. Let's summarize main Centrifugo ✨highlights✨ here. Every point is then extended throughout the documentation.

diff --git a/docs/4/getting-started/installation.html b/docs/4/getting-started/installation.html index 931c488bc..6b8c0dda6 100644 --- a/docs/4/getting-started/installation.html +++ b/docs/4/getting-started/installation.html @@ -15,8 +15,8 @@ - - + +

Install Centrifugo

Centrifugo server is written in Go language. It's an open-source software, the source code is available on Github.

diff --git a/docs/4/getting-started/integration.html b/docs/4/getting-started/integration.html index f79ed9d6f..b23480535 100644 --- a/docs/4/getting-started/integration.html +++ b/docs/4/getting-started/integration.html @@ -15,8 +15,8 @@ - - + +

Integration guide

This chapter aims to help you get started with Centrifugo. We will look at a step-by-step workflow of integrating your application with Centrifugo providing links to relevant parts of this documentation.

diff --git a/docs/4/getting-started/introduction.html b/docs/4/getting-started/introduction.html index b6f136609..e0963fba9 100644 --- a/docs/4/getting-started/introduction.html +++ b/docs/4/getting-started/introduction.html @@ -15,8 +15,8 @@ - - + +

Centrifugo introduction

diff --git a/docs/4/getting-started/migration_v4.html b/docs/4/getting-started/migration_v4.html index ca19a785a..b3719c052 100644 --- a/docs/4/getting-started/migration_v4.html +++ b/docs/4/getting-started/migration_v4.html @@ -15,8 +15,8 @@ - - + +

Migrating to v4

Centrifugo v4 development was concentrated around two main things:

diff --git a/docs/4/getting-started/quickstart.html b/docs/4/getting-started/quickstart.html index ef587300d..2f4d57ca3 100644 --- a/docs/4/getting-started/quickstart.html +++ b/docs/4/getting-started/quickstart.html @@ -15,8 +15,8 @@ - - + +

Quickstart tutorial ⏱️

In this tutorial we will build a very simple browser application with Centrifugo. Users will connect to Centrifugo over WebSocket, subscribe to a channel, and start receiving all channel publications (messages published to that channel). In our case, we will send a counter value to all channel subscribers to update counter widget in all open browser tabs in real-time.

diff --git a/docs/4/pro/analytics.html b/docs/4/pro/analytics.html index a42c289dd..709cffbd2 100644 --- a/docs/4/pro/analytics.html +++ b/docs/4/pro/analytics.html @@ -15,8 +15,8 @@ - - + +

Analytics with ClickHouse

This feature allows exporting information about channel publications, client connections, channel subscriptions, client operations and push notifications to ClickHouse thus providing an integration with a real-time (with seconds delay) analytics storage. ClickHouse is super fast for analytical queries, simple to operate with and it allows effective data keeping for a window of time. Also, it's relatively simple to create a high performance ClickHouse cluster.

diff --git a/docs/4/pro/capabilities.html b/docs/4/pro/capabilities.html index 0b14c3593..76955a395 100644 --- a/docs/4/pro/capabilities.html +++ b/docs/4/pro/capabilities.html @@ -15,8 +15,8 @@ - - + +

Channel capabilities

At this point you know that Centrifugo allows configuring channel permissions on a per-namespace level. When creating a new real-time feature it's recommended to create a new namespace for it and configure permissions. To achieve a better channel permission control inside a namespace Centrifugo PRO provides possibility to set capabilities on individual connection basis, or individual channel subscription basis.

diff --git a/docs/4/pro/cel_expressions.html b/docs/4/pro/cel_expressions.html index 60fdad8a1..f5915de8b 100644 --- a/docs/4/pro/cel_expressions.html +++ b/docs/4/pro/cel_expressions.html @@ -15,8 +15,8 @@ - - + +

CEL expressions

This PRO feature is under active development, some changes expected here 🚧

diff --git a/docs/4/pro/channel_patterns.html b/docs/4/pro/channel_patterns.html index 822b6caab..37819ec95 100644 --- a/docs/4/pro/channel_patterns.html +++ b/docs/4/pro/channel_patterns.html @@ -15,8 +15,8 @@ - - + +

Channel patterns

This PRO feature is under active development, some changes expected here 🚧

diff --git a/docs/4/pro/client_message_batching.html b/docs/4/pro/client_message_batching.html index e0177614e..be69bce6c 100644 --- a/docs/4/pro/client_message_batching.html +++ b/docs/4/pro/client_message_batching.html @@ -15,8 +15,8 @@ - - + +

Message batching control

This PRO feature is under active development, some changes expected here 🚧

diff --git a/docs/4/pro/connections.html b/docs/4/pro/connections.html index e236c5e06..f16042464 100644 --- a/docs/4/pro/connections.html +++ b/docs/4/pro/connections.html @@ -15,8 +15,8 @@ - - + +

Connections API

Centrifugo PRO offers an extra API call, connections, which enables retrieval of all active sessions (based on user ID or expression) without the need to activate the presence feature for channels. Furthermore, developers can attach any desired JSON payload to a connection that will then be visible in the result of the connections call. It's worth noting that this additional meta-information remains hidden from the client-side, unlike the info associated with the connection.

diff --git a/docs/4/pro/install_and_run.html b/docs/4/pro/install_and_run.html index aa1320831..04ef8d58d 100644 --- a/docs/4/pro/install_and_run.html +++ b/docs/4/pro/install_and_run.html @@ -15,8 +15,8 @@ - - + +

Install and run PRO version

Centrifugo PRO license agreement

Centrifugo PRO is distributed by Centrifugal Labs LTD under commercial license which is different from OSS version. By downloading Centrifugo PRO you automatically accept commercial license terms.

diff --git a/docs/4/pro/overview.html b/docs/4/pro/overview.html index a656a0940..8456e3760 100644 --- a/docs/4/pro/overview.html +++ b/docs/4/pro/overview.html @@ -15,8 +15,8 @@ - - + +

Centrifugo PRO overview

diff --git a/docs/4/pro/performance.html b/docs/4/pro/performance.html index c3007a461..887aa3bb6 100644 --- a/docs/4/pro/performance.html +++ b/docs/4/pro/performance.html @@ -15,8 +15,8 @@ - - + +

Faster performance

diff --git a/docs/4/pro/process_stats.html b/docs/4/pro/process_stats.html index 61c33d2ef..d0860db62 100644 --- a/docs/4/pro/process_stats.html +++ b/docs/4/pro/process_stats.html @@ -15,8 +15,8 @@ - - + +

CPU and RSS stats

A useful addition of Centrifugo PRO is an ability to show CPU and RSS memory usage of each node in admin web UI.

diff --git a/docs/4/pro/push_notifications.html b/docs/4/pro/push_notifications.html index 9212a2da4..9c54d97ee 100644 --- a/docs/4/pro/push_notifications.html +++ b/docs/4/pro/push_notifications.html @@ -15,8 +15,8 @@ - - + +

Push notification API

This PRO feature is under active development, some changes expected here 🚧

diff --git a/docs/4/pro/singleflight.html b/docs/4/pro/singleflight.html index 7f66f3532..ba71c9c69 100644 --- a/docs/4/pro/singleflight.html +++ b/docs/4/pro/singleflight.html @@ -15,8 +15,8 @@ - - + +

Singleflight

Centrifugo PRO provides an additional boolean option use_singleflight (default false). When this option enabled Centrifugo will automatically try to merge identical requests to history, online presence or presence stats issued at the same time into one real network request. It will do this by using in-memory component called singleflight.

diff --git a/docs/4/pro/throttling.html b/docs/4/pro/throttling.html index 28965af2b..051fdad90 100644 --- a/docs/4/pro/throttling.html +++ b/docs/4/pro/throttling.html @@ -15,8 +15,8 @@ - - + +

Operation throttling

The throttling feature allows limiting the number of operations each connection or user can issue during a configured time interval. This is useful to protect the system from misusing, detecting and disconnecting abusive or broken (due to the bug in the frontend application) clients which add unwanted load on a server.

diff --git a/docs/4/pro/token_revocation.html b/docs/4/pro/token_revocation.html index 492190042..4955221a9 100644 --- a/docs/4/pro/token_revocation.html +++ b/docs/4/pro/token_revocation.html @@ -15,8 +15,8 @@ - - + +

Token revocation API

One more protective instrument in Centrifugo PRO is API to manage token revocations.

diff --git a/docs/4/pro/tracing.html b/docs/4/pro/tracing.html index ea2177b3b..b253c5799 100644 --- a/docs/4/pro/tracing.html +++ b/docs/4/pro/tracing.html @@ -15,8 +15,8 @@ - - + +

User and channel tracing

That's a unique thing. The tracing feature of Centrifugo PRO allows attaching to any channel to see all messages flying towards subscribers or attach to a specific user ID to see all user-related events in real-time.

diff --git a/docs/4/pro/user_block.html b/docs/4/pro/user_block.html index d02f5df0a..f4856cfd7 100644 --- a/docs/4/pro/user_block.html +++ b/docs/4/pro/user_block.html @@ -15,8 +15,8 @@ - - + +

User blocking API

One additional instrument for making protective actions in Centrifugo PRO is user blocking API which allows blocking a specific user on Centrifugo level.

diff --git a/docs/4/pro/user_status.html b/docs/4/pro/user_status.html index 13f00de81..1ec68a147 100644 --- a/docs/4/pro/user_status.html +++ b/docs/4/pro/user_status.html @@ -15,8 +15,8 @@ - - + +

User status API

Centrifugo OSS provides a presence feature for channels. It works well (for channels with reasonably small number of active subscribers though), but sometimes you may need a bit different functionality.

diff --git a/docs/4/server/admin_web.html b/docs/4/server/admin_web.html index e230b232a..3ef610827 100644 --- a/docs/4/server/admin_web.html +++ b/docs/4/server/admin_web.html @@ -15,8 +15,8 @@ - - + +

Admin web UI

Centrifugo comes with a built-in admin web interface. It can:

diff --git a/docs/4/server/authentication.html b/docs/4/server/authentication.html index 5074b1a55..6c902a122 100644 --- a/docs/4/server/authentication.html +++ b/docs/4/server/authentication.html @@ -15,8 +15,8 @@ - - + +

Client JWT authentication

To authenticate incoming connection (client) Centrifugo can use JSON Web Token (JWT) passed from the client-side. This way Centrifugo may know the ID of user in your application, also application can pass additional data to Centrifugo inside JWT claims. This chapter describes this authentication mechanism.

diff --git a/docs/4/server/channel_permissions.html b/docs/4/server/channel_permissions.html index 365219e00..a9a6a3d50 100644 --- a/docs/4/server/channel_permissions.html +++ b/docs/4/server/channel_permissions.html @@ -15,8 +15,8 @@ - - + +

Channel permission model

When using Centrifugo server API you don't need to think about channel permissions at all – everything is allowed. In server API case, request to Centrifugo must be issued by your application backend – so you have all the power to check any required permissions before issuing API request to Centrifugo.

diff --git a/docs/4/server/channel_token_auth.html b/docs/4/server/channel_token_auth.html index c28f1112b..d966cd71e 100644 --- a/docs/4/server/channel_token_auth.html +++ b/docs/4/server/channel_token_auth.html @@ -15,8 +15,8 @@ - - + +

Channel JWT authorization

In the chapter about channel permissions we mentioned that to subscribe on a channel client can provide subscription token. This chapter has more information about the subscription token mechanism in Centrifugo.

diff --git a/docs/4/server/channels.html b/docs/4/server/channels.html index c3ccb090c..e23c22cab 100644 --- a/docs/4/server/channels.html +++ b/docs/4/server/channels.html @@ -15,8 +15,8 @@ - - + +

Channels and namespaces

Upon connecting to a server clients can subscribe to channels. Channel is one of the core concepts of Centrifugo. Most of the time when integrating Centrifugo you will work with channels and decide what is the best channel configuration for your application.

diff --git a/docs/4/server/codes.html b/docs/4/server/codes.html index 095a8f6f3..9179705cb 100644 --- a/docs/4/server/codes.html +++ b/docs/4/server/codes.html @@ -15,8 +15,8 @@ - - + +

Error and disconnect codes

This chapter describes error and disconnect codes Centrifugo uses in a client protocol, also error codes which a server API can return in response.

diff --git a/docs/4/server/configuration.html b/docs/4/server/configuration.html index 2d96cde6e..bf6b6f286 100644 --- a/docs/4/server/configuration.html +++ b/docs/4/server/configuration.html @@ -15,8 +15,8 @@ - - + +

Configure Centrifugo

Let's look at how Centrifugo can be configured.

diff --git a/docs/4/server/console_commands.html b/docs/4/server/console_commands.html index 615f408fc..53a2bce13 100644 --- a/docs/4/server/console_commands.html +++ b/docs/4/server/console_commands.html @@ -15,8 +15,8 @@ - - + +

Helper CLI commands

Here is a list of helpful command-line commands that come with Centrifugo executable.

diff --git a/docs/4/server/engines.html b/docs/4/server/engines.html index 85e1998d5..4cef8087d 100644 --- a/docs/4/server/engines.html +++ b/docs/4/server/engines.html @@ -15,8 +15,8 @@ - - + +

Engines and scalability

The Engine in Centrifugo is responsible for publishing messages between nodes, handle PUB/SUB broker subscriptions, save/retrieve online presence and history data.

diff --git a/docs/4/server/history_and_recovery.html b/docs/4/server/history_and_recovery.html index 446c94642..2b40a4dcd 100644 --- a/docs/4/server/history_and_recovery.html +++ b/docs/4/server/history_and_recovery.html @@ -15,8 +15,8 @@ - - + +

History and recovery

Centrifugo engines can maintain publication history for channels with configured history size and TTL.

diff --git a/docs/4/server/infra_tuning.html b/docs/4/server/infra_tuning.html index f9bfe070e..09215280b 100644 --- a/docs/4/server/infra_tuning.html +++ b/docs/4/server/infra_tuning.html @@ -15,8 +15,8 @@ - - + +

Infrastructure tuning

As Centrifugo deals with lots of persistent connections your operating system and server infrastructure must be ready for it.

diff --git a/docs/4/server/load_balancing.html b/docs/4/server/load_balancing.html index 6907f8e8d..0ff39a30c 100644 --- a/docs/4/server/load_balancing.html +++ b/docs/4/server/load_balancing.html @@ -15,8 +15,8 @@ - - + +

Load balancing

This chapter shows how to deal with persistent connection load balancing.

diff --git a/docs/4/server/monitoring.html b/docs/4/server/monitoring.html index a878e6527..8ce8bb3a2 100644 --- a/docs/4/server/monitoring.html +++ b/docs/4/server/monitoring.html @@ -15,8 +15,8 @@ - - + +

Metrics monitoring

Centrifugo supports reporting metrics in Prometheus format and can automatically export metrics to Graphite.

diff --git a/docs/4/server/presence.html b/docs/4/server/presence.html index 0863cd95d..8ccc46dc4 100644 --- a/docs/4/server/presence.html +++ b/docs/4/server/presence.html @@ -15,8 +15,8 @@ - - + +

Online presence

The online presence feature of Centrifugo is a powerful tool that allows you to monitor and manage active users in real-time on a specific channel. It provides live data about which users are currently connected to your application.

diff --git a/docs/4/server/proxy.html b/docs/4/server/proxy.html index fd4f6624c..e9cc43c0e 100644 --- a/docs/4/server/proxy.html +++ b/docs/4/server/proxy.html @@ -15,8 +15,8 @@ - - + +

Proxy events to the backend

It's possible to proxy some client connection events from Centrifugo to the application backend and react to them in a custom way. For example, it's possible to authenticate connection via request from Centrifugo to application backend, refresh client sessions and answer to RPC calls sent by a client over bidirectional connection. Also, you may control subscription and publication permissions using these hooks.

diff --git a/docs/4/server/server_api.html b/docs/4/server/server_api.html index 58f10e031..d307156f1 100644 --- a/docs/4/server/server_api.html +++ b/docs/4/server/server_api.html @@ -15,8 +15,8 @@ - - + +

Server API walkthrough

Server API provides a way to send various commands to Centrifugo. For example, server API allows publishing messages to channels, get server statistics, etc. There are two kinds of API available at the moment:

diff --git a/docs/4/server/server_subs.html b/docs/4/server/server_subs.html index 6b2095c02..6e8023f4f 100644 --- a/docs/4/server/server_subs.html +++ b/docs/4/server/server_subs.html @@ -15,8 +15,8 @@ - - + +

Server-side subscriptions

Centrifugo clients can initiate a subscription to a channel by calling the subscribe method of client API. In most cases, client-side subscriptions is a more flexible and recommended approach since a frontend usually knows which channels it needs to consume at a concrete moment.

diff --git a/docs/4/server/tls.html b/docs/4/server/tls.html index 9970e988d..5bc7c7a54 100644 --- a/docs/4/server/tls.html +++ b/docs/4/server/tls.html @@ -15,8 +15,8 @@ - - + +

Configure TLS

TLS/SSL layer is very important not only for securing your connections but also to increase a diff --git a/docs/4/transports/client_api.html b/docs/4/transports/client_api.html index 87942a545..3211d8658 100644 --- a/docs/4/transports/client_api.html +++ b/docs/4/transports/client_api.html @@ -15,8 +15,8 @@ - - + +

Client SDK API

Centrifugo has several client SDKs to establish a real-time connection with a server. Centrifugo SDKs use WebSocket as the main data transport and send/receive messages encoded according to our bidirectional protocol. That protocol is built on top of the Protobuf schema (both JSON and binary Protobuf formats are supported). It provides asynchronous communication, sending RPC, multiplexing subscriptions to channels, etc. Client SDK wraps the protocol and exposes a set of APIs to developers.

diff --git a/docs/4/transports/client_protocol.html b/docs/4/transports/client_protocol.html index cc05571c3..e42e1d480 100644 --- a/docs/4/transports/client_protocol.html +++ b/docs/4/transports/client_protocol.html @@ -15,8 +15,8 @@ - - + +

Client protocol

This chapter describes the core concepts of Centrifugo bidirectional client protocol – concentrating on framing level. If you want to find out details about exposed client API then look at client API document.

diff --git a/docs/4/transports/client_sdk.html b/docs/4/transports/client_sdk.html index a6cb8fdaf..b6be41430 100644 --- a/docs/4/transports/client_sdk.html +++ b/docs/4/transports/client_sdk.html @@ -15,8 +15,8 @@ - - + +

Client real-time SDKs

In the previous chapter we investigated common principles of Centrifugo client SDK API. Here we will provide a list of available bidirectional connectors you can use to communicate with Centrifugo.

diff --git a/docs/4/transports/http_stream.html b/docs/4/transports/http_stream.html index 6d3f207bb..f43b88969 100644 --- a/docs/4/transports/http_stream.html +++ b/docs/4/transports/http_stream.html @@ -15,8 +15,8 @@ - - + +

HTTP streaming, with bidirectional emulation

HTTP streaming connection endpoint in Centrifugo is:

diff --git a/docs/4/transports/overview.html b/docs/4/transports/overview.html index 5bf8b01a2..42c4a180a 100644 --- a/docs/4/transports/overview.html +++ b/docs/4/transports/overview.html @@ -15,8 +15,8 @@ - - + +

Real-time transports

Centrifugo supports a variety of transports to deliver real-time messages to clients.

diff --git a/docs/4/transports/sockjs.html b/docs/4/transports/sockjs.html index f46a3c487..172730d53 100644 --- a/docs/4/transports/sockjs.html +++ b/docs/4/transports/sockjs.html @@ -15,8 +15,8 @@ - - + +

SockJS

SockJS is a polyfill browser library which provides HTTP-based fallback transports in case when it's not possible to establish Websocket connection. This can happen in old client browsers or because of some proxy behind client and server that cuts of Websocket traffic. You can find more information on SockJS project Github page.

diff --git a/docs/4/transports/sse.html b/docs/4/transports/sse.html index ddd5ef8e2..123b9cb25 100644 --- a/docs/4/transports/sse.html +++ b/docs/4/transports/sse.html @@ -15,8 +15,8 @@ - - + +

SSE (EventSource), with bidirectional emulation

SSE (EventSource) connection endpoint in Centrifugo is:

diff --git a/docs/4/transports/uni_grpc.html b/docs/4/transports/uni_grpc.html index 147785cee..4425e52b1 100644 --- a/docs/4/transports/uni_grpc.html +++ b/docs/4/transports/uni_grpc.html @@ -15,8 +15,8 @@ - - + +

Unidirectional GRPC

It's possible to connect to GRPC unidirectional stream to consume real-time messages from Centrifugo. In this case you need to generate GRPC code for your language on client-side.

diff --git a/docs/4/transports/uni_http_stream.html b/docs/4/transports/uni_http_stream.html index 8b5033c29..273f81096 100644 --- a/docs/4/transports/uni_http_stream.html +++ b/docs/4/transports/uni_http_stream.html @@ -15,8 +15,8 @@ - - + +

Unidirectional HTTP streaming

Default unidirectional HTTP streaming connection endpoint in Centrifugo is:

diff --git a/docs/4/transports/uni_sse.html b/docs/4/transports/uni_sse.html index ec0250544..1ad6d497b 100644 --- a/docs/4/transports/uni_sse.html +++ b/docs/4/transports/uni_sse.html @@ -15,8 +15,8 @@ - - + +

Unidirectional SSE (EventSource)

Default unidirectional SSE (EventSource) connection endpoint in Centrifugo is:

diff --git a/docs/4/transports/uni_websocket.html b/docs/4/transports/uni_websocket.html index ec23e8892..fbeb95e1c 100644 --- a/docs/4/transports/uni_websocket.html +++ b/docs/4/transports/uni_websocket.html @@ -15,8 +15,8 @@ - - + +

Unidirectional WebSocket

Default unidirectional WebSocket connection endpoint in Centrifugo is:

diff --git a/docs/4/transports/websocket.html b/docs/4/transports/websocket.html index df9c68a09..db315b40b 100644 --- a/docs/4/transports/websocket.html +++ b/docs/4/transports/websocket.html @@ -15,8 +15,8 @@ - - + +

WebSocket

Websocket is the main transport in Centrifugo. It's a very efficient low-overhead protocol on top of TCP.

diff --git a/docs/4/transports/webtransport.html b/docs/4/transports/webtransport.html index a213515d9..17833ede5 100644 --- a/docs/4/transports/webtransport.html +++ b/docs/4/transports/webtransport.html @@ -15,8 +15,8 @@ - - + +

WebTransport

WebTransport is an API offering low-latency, bidirectional, client-server messaging on top of HTTP/3. See Using WebTransport article that gives a good overview of it.

diff --git a/docs/attributions.html b/docs/attributions.html index 4207fc340..c36ba3622 100644 --- a/docs/attributions.html +++ b/docs/attributions.html @@ -15,8 +15,8 @@ - - + +

Attributions

Landing Page Images

diff --git a/docs/faq.html b/docs/faq.html index 99cbaf939..c34ec7ce3 100644 --- a/docs/faq.html +++ b/docs/faq.html @@ -15,8 +15,8 @@ - - + +

Frequently Asked Questions

Answers to popular questions here.

diff --git a/docs/flow_diagrams.html b/docs/flow_diagrams.html index 7d9c52cf6..470275272 100644 --- a/docs/flow_diagrams.html +++ b/docs/flow_diagrams.html @@ -15,8 +15,8 @@ - - + +

flow_diagrams

For swimlanes.io:

diff --git a/docs/getting-started/community.html b/docs/getting-started/community.html index c8c819d7f..7964c9561 100644 --- a/docs/getting-started/community.html +++ b/docs/getting-started/community.html @@ -15,8 +15,8 @@ - - + +

Join community

If you find Centrifugo interesting, you are welcome to join our community rooms on Telegram (the most active) and Discord:

diff --git a/docs/getting-started/comparisons.html b/docs/getting-started/comparisons.html index c3a32f016..8c23635d3 100644 --- a/docs/getting-started/comparisons.html +++ b/docs/getting-started/comparisons.html @@ -15,8 +15,8 @@ - - + +

Comparing with others

Let's compare Centrifugo with various systems. These comparisons arose from popular questions raised in our communities. Here we are emphasizing things that make Centrifugo special.

diff --git a/docs/getting-started/design.html b/docs/getting-started/design.html index fe6ce104a..fa8023a26 100644 --- a/docs/getting-started/design.html +++ b/docs/getting-started/design.html @@ -15,8 +15,8 @@ - - + +

Design overview

Let's discuss some architectural and design topics about Centrifugo.

diff --git a/docs/getting-started/ecosystem.html b/docs/getting-started/ecosystem.html index d6149a2b3..4a63eefa1 100644 --- a/docs/getting-started/ecosystem.html +++ b/docs/getting-started/ecosystem.html @@ -15,8 +15,8 @@ - - + +

Ecosystem notes

Some additional notes about our ecosystem which may help you develop with our tech.

diff --git a/docs/getting-started/highlights.html b/docs/getting-started/highlights.html index dbd7444fb..b3dd9f4ab 100644 --- a/docs/getting-started/highlights.html +++ b/docs/getting-started/highlights.html @@ -15,8 +15,8 @@ - - + +

Main highlights

At this point, you know how to build the simplest real-time app with Centrifugo. Beyond the core PUB/SUB functionality, Centrifugo provides more features and primitives to build scalable real-time applications. Let's summarize the main Centrifugo ✨highlights✨ here. Every point is then extended throughout the documentation.

diff --git a/docs/getting-started/installation.html b/docs/getting-started/installation.html index cdbda7fe6..cb46bea5f 100644 --- a/docs/getting-started/installation.html +++ b/docs/getting-started/installation.html @@ -15,8 +15,8 @@ - - + +

Install Centrifugo

Centrifugo server is written in the Go language. It's open-source software, and the source code is available on Github.

diff --git a/docs/getting-started/integration.html b/docs/getting-started/integration.html index d22478f62..ee806a6ab 100644 --- a/docs/getting-started/integration.html +++ b/docs/getting-started/integration.html @@ -15,8 +15,8 @@ - - + +

Integration guide

This chapter aims to help you get started with Centrifugo. We will look at a step-by-step workflow of integrating your application with Centrifugo, providing links to relevant parts of this documentation.

diff --git a/docs/getting-started/introduction.html b/docs/getting-started/introduction.html index 09fdbdb09..e1bf526aa 100644 --- a/docs/getting-started/introduction.html +++ b/docs/getting-started/introduction.html @@ -15,8 +15,8 @@ - - + +

Centrifugo introduction

diff --git a/docs/getting-started/migration_v4.html b/docs/getting-started/migration_v4.html index ebd533684..43af127e1 100644 --- a/docs/getting-started/migration_v4.html +++ b/docs/getting-started/migration_v4.html @@ -15,8 +15,8 @@ - - + +

Migrating to v4

Centrifugo v4 development was concentrated around two main things:

diff --git a/docs/getting-started/migration_v5.html b/docs/getting-started/migration_v5.html index eddd0b625..5bfda4bc5 100644 --- a/docs/getting-started/migration_v5.html +++ b/docs/getting-started/migration_v5.html @@ -15,8 +15,8 @@ - - + +

Migrating to v5

Centrifugo v5 migration from v4 should be smooth for most of the use cases.

diff --git a/docs/getting-started/quickstart.html b/docs/getting-started/quickstart.html index ddeecb330..0e67a1a7b 100644 --- a/docs/getting-started/quickstart.html +++ b/docs/getting-started/quickstart.html @@ -15,8 +15,8 @@ - - + +

Quickstart tutorial ⏱️

In this tutorial, we will build a very simple browser application with Centrifugo. Users will connect to Centrifugo over WebSocket, subscribe to a channel, and start receiving all channel publications (messages published to that channel). In our case, we will send a counter value to all channel subscribers to update the counter widget in all open browser tabs in real-time.

diff --git a/docs/pro/admin_idp_auth.html b/docs/pro/admin_idp_auth.html index bc7e2077c..f18713eaa 100644 --- a/docs/pro/admin_idp_auth.html +++ b/docs/pro/admin_idp_auth.html @@ -15,8 +15,8 @@ - - + +

SSO for admin UI using OpenID connect (OIDC)

Admin UI of Centrifugo OSS supports only one admin user identified by the preconfigured password. For the corporate and enterprise environments Centrifugo PRO provides a way to integrate with popular User Identity Providers (IDP), such as Okta, KeyCloak, Google Workspace, Azure and others. Most of the modern providers which support OpenID connect (OIDC) protocol with Proof Key for Code Exchange diff --git a/docs/pro/analytics.html b/docs/pro/analytics.html index e56926f17..83b5944d6 100644 --- a/docs/pro/analytics.html +++ b/docs/pro/analytics.html @@ -15,8 +15,8 @@ - - + +

Analytics with ClickHouse

This feature allows exporting information about channel publications, client connections, channel subscriptions, client operations and push notifications to ClickHouse thus providing an integration with a real-time (with seconds delay) analytics storage. ClickHouse is super fast for analytical queries, simple to operate with and it allows effective data keeping for a window of time. Also, it's relatively simple to create a high performance ClickHouse cluster.

diff --git a/docs/pro/capabilities.html b/docs/pro/capabilities.html index bbf41d2a1..7a632b450 100644 --- a/docs/pro/capabilities.html +++ b/docs/pro/capabilities.html @@ -15,8 +15,8 @@ - - + +

Channel capabilities

At this point you know that Centrifugo allows configuring channel permissions on a per-namespace level. When creating a new real-time feature it's recommended to create a new namespace for it and configure permissions. To achieve a better channel permission control inside a namespace Centrifugo PRO provides possibility to set capabilities on individual connection basis, or individual channel subscription basis.

diff --git a/docs/pro/cel_expressions.html b/docs/pro/cel_expressions.html index 465018ff2..da852738d 100644 --- a/docs/pro/cel_expressions.html +++ b/docs/pro/cel_expressions.html @@ -15,8 +15,8 @@ - - + +

Channel CEL expressions

Centrifugo PRO supports CEL expressions (Common Expression Language) for checking channel operation permissions.

diff --git a/docs/pro/channel_patterns.html b/docs/pro/channel_patterns.html index b150f3730..83c99d09c 100644 --- a/docs/pro/channel_patterns.html +++ b/docs/pro/channel_patterns.html @@ -15,8 +15,8 @@ - - + +

Channel patterns

Centrifugo PRO enhances a way to configure channels with Channel Patterns feature. This opens a road for building channel model similar to what developers got used to when writing HTTP servers and configuring routes for HTTP request processing.

diff --git a/docs/pro/channel_state_events.html b/docs/pro/channel_state_events.html index bb314af0b..c946361ba 100644 --- a/docs/pro/channel_state_events.html +++ b/docs/pro/channel_state_events.html @@ -15,8 +15,8 @@ - - + +

Channel state events

Centrifugo PRO has a feature to enable channel state event webhooks to be sent to your configured backend endpoint:

diff --git a/docs/pro/client_message_batching.html b/docs/pro/client_message_batching.html index 07347d359..5bfbb3b70 100644 --- a/docs/pro/client_message_batching.html +++ b/docs/pro/client_message_batching.html @@ -15,8 +15,8 @@ - - + +

Message batching control

Centrifugo PRO provides advanced options to tweak connection message write behaviour.

diff --git a/docs/pro/connections.html b/docs/pro/connections.html index d3479a3ab..d0269f9f4 100644 --- a/docs/pro/connections.html +++ b/docs/pro/connections.html @@ -15,8 +15,8 @@ - - + +

Connections API

Centrifugo PRO offers an extra API call, connections, which enables retrieval of all active sessions (based on user ID or expression) without the need to activate the presence feature for channels. Furthermore, developers can attach any desired JSON payload to a connection that will then be visible in the result of the connections call. It's worth noting that this additional meta-information remains hidden from the client-side, unlike the info associated with the connection.

diff --git a/docs/pro/distributed_rate_limit.html b/docs/pro/distributed_rate_limit.html index 21b7ac681..76282d269 100644 --- a/docs/pro/distributed_rate_limit.html +++ b/docs/pro/distributed_rate_limit.html @@ -15,8 +15,8 @@ - - + +

Distributed rate limit API

In addition to connection operation rate limiting features Centrifugo PRO provides a generic high precision rate limiting API. It may be used for custom quota managing tasks not even related to real-time connections. Its distributed nature allows managing quotas across different instances of your application backend.

diff --git a/docs/pro/install_and_run.html b/docs/pro/install_and_run.html index c94fb4e43..fec3db91d 100644 --- a/docs/pro/install_and_run.html +++ b/docs/pro/install_and_run.html @@ -15,8 +15,8 @@ - - + +

Install and run PRO version

Centrifugo PRO license agreement

Centrifugo PRO is distributed by Centrifugal Labs LTD under commercial license which is different from OSS version. By downloading Centrifugo PRO you automatically accept commercial license terms.

diff --git a/docs/pro/observability_enhancements.html b/docs/pro/observability_enhancements.html index 7b4c6e6a8..f2deb43f0 100644 --- a/docs/pro/observability_enhancements.html +++ b/docs/pro/observability_enhancements.html @@ -15,8 +15,8 @@ - - + +

Observability enhancements

Centrifugo PRO has some enhancements to exposed metrics. At this moment it provides channel namespace resolution to the following metrics:

diff --git a/docs/pro/overview.html b/docs/pro/overview.html index f5cc9078a..a82eeb341 100644 --- a/docs/pro/overview.html +++ b/docs/pro/overview.html @@ -15,8 +15,8 @@ - - + +

Centrifugo PRO

diff --git a/docs/pro/performance.html b/docs/pro/performance.html index 85e7df341..1e7580903 100644 --- a/docs/pro/performance.html +++ b/docs/pro/performance.html @@ -15,8 +15,8 @@ - - + +

Faster performance

diff --git a/docs/pro/process_stats.html b/docs/pro/process_stats.html index 5b203e7c9..a716afaa9 100644 --- a/docs/pro/process_stats.html +++ b/docs/pro/process_stats.html @@ -15,8 +15,8 @@ - - + +

CPU and RSS stats

A useful addition of Centrifugo PRO is an ability to show CPU and RSS memory usage of each node in admin web UI.

diff --git a/docs/pro/push_notifications.html b/docs/pro/push_notifications.html index adda32d86..2ac4012c9 100644 --- a/docs/pro/push_notifications.html +++ b/docs/pro/push_notifications.html @@ -15,8 +15,8 @@ - - + +

Push notification API

Centrifugo excels in delivering real-time in-app messages to online users. Sometimes though you need a way to engage offline users to come back to your app. Or trigger some update in the app while it's running in the background. That's where push notifications may be used. Push notifications delivered over battery-efficient platform-dependent transport.

diff --git a/docs/pro/rate_limiting.html b/docs/pro/rate_limiting.html index dca38d330..549f28c71 100644 --- a/docs/pro/rate_limiting.html +++ b/docs/pro/rate_limiting.html @@ -15,8 +15,8 @@ - - + +

Operation rate limits

The rate limit feature allows limiting the number of operations each connection or user can issue during a configured time interval. This is useful to protect the system from misusing, detecting and disconnecting abusive or broken (due to the bug in the frontend application) clients which add unwanted load on a server.

diff --git a/docs/pro/singleflight.html b/docs/pro/singleflight.html index bf752b1b8..a1066d540 100644 --- a/docs/pro/singleflight.html +++ b/docs/pro/singleflight.html @@ -15,8 +15,8 @@ - - + +

Singleflight

Centrifugo PRO provides an additional boolean option use_singleflight (default false). When this option enabled Centrifugo will automatically try to merge identical requests to history, online presence or presence stats issued at the same time into one real network request. It will do this by using in-memory component called singleflight.

diff --git a/docs/pro/token_revocation.html b/docs/pro/token_revocation.html index c84ff8050..47bd76d38 100644 --- a/docs/pro/token_revocation.html +++ b/docs/pro/token_revocation.html @@ -15,8 +15,8 @@ - - + +

Token revocation API

One more protective instrument in Centrifugo PRO is API to manage token revocations.

diff --git a/docs/pro/tracing.html b/docs/pro/tracing.html index 326df4e4b..ee5c56d96 100644 --- a/docs/pro/tracing.html +++ b/docs/pro/tracing.html @@ -15,8 +15,8 @@ - - + +

User and channel tracing

That's a unique thing. The tracing feature of Centrifugo PRO allows attaching to any channel to see all messages flying towards subscribers or attach to a specific user ID to see all user-related events in real-time.

diff --git a/docs/pro/user_block.html b/docs/pro/user_block.html index 3c3bcfbfe..b08c16467 100644 --- a/docs/pro/user_block.html +++ b/docs/pro/user_block.html @@ -15,8 +15,8 @@ - - + +

User blocking API

One additional instrument for making protective actions in Centrifugo PRO is user blocking API which allows blocking a specific user on Centrifugo level.

diff --git a/docs/pro/user_status.html b/docs/pro/user_status.html index ec0a8184c..4eb19388c 100644 --- a/docs/pro/user_status.html +++ b/docs/pro/user_status.html @@ -15,8 +15,8 @@ - - + +

User status API

Centrifugo OSS provides a presence feature for channels. It works well (for channels with reasonably small number of active subscribers though), but sometimes you may need a bit different functionality.

diff --git a/docs/server/admin_web.html b/docs/server/admin_web.html index 727514741..f4b8f38d8 100644 --- a/docs/server/admin_web.html +++ b/docs/server/admin_web.html @@ -15,8 +15,8 @@ - - + +

Admin web UI

Centrifugo comes with a built-in administrative web interface. It enables users to:

diff --git a/docs/server/authentication.html b/docs/server/authentication.html index 5ca99ea50..f5b4b11a3 100644 --- a/docs/server/authentication.html +++ b/docs/server/authentication.html @@ -15,8 +15,8 @@ - - + +

Client JWT authentication

To authenticate an incoming connection (client), Centrifugo can use a JSON Web Token (JWT) provided by your application backend to the client-side. This allows Centrifugo to identify the user ID within your application in a secure way. Also, the application can pass additional data to Centrifugo inside JWT claims. This chapter explains this authentication mechanism.

diff --git a/docs/server/channel_permissions.html b/docs/server/channel_permissions.html index c1a23fa44..a99fe1d7e 100644 --- a/docs/server/channel_permissions.html +++ b/docs/server/channel_permissions.html @@ -15,8 +15,8 @@ - - + +

Channel permission model

When using Centrifugo server API you don't need to think about channel permissions at all – everything is allowed. In server API case, request to Centrifugo must be issued by your application backend – so you have all the power to check any required permissions before issuing API request to Centrifugo.

diff --git a/docs/server/channel_token_auth.html b/docs/server/channel_token_auth.html index 5d523acca..e0b542ac7 100644 --- a/docs/server/channel_token_auth.html +++ b/docs/server/channel_token_auth.html @@ -15,8 +15,8 @@ - - + +

Channel JWT authorization

In the chapter about channel permissions we mentioned that to subscribe on a channel client can provide subscription token. This chapter has more information about the subscription token mechanism in Centrifugo.

diff --git a/docs/server/channels.html b/docs/server/channels.html index 7f91cf79e..ff3bb917d 100644 --- a/docs/server/channels.html +++ b/docs/server/channels.html @@ -15,8 +15,8 @@ - - + +

Channels and namespaces

Upon connecting to a server, clients can subscribe to channels. A channel is one of the core concepts of Centrifugo. Most of the time when integrating Centrifugo, you will work with channels and determine the optimal channel configuration for your application.

diff --git a/docs/server/codes.html b/docs/server/codes.html index eb96aecfa..d2df9b966 100644 --- a/docs/server/codes.html +++ b/docs/server/codes.html @@ -15,8 +15,8 @@ - - + +

Error and disconnect codes

This chapter describes error and disconnect codes Centrifugo uses in a client protocol, also error codes which a server API can return in response.

diff --git a/docs/server/configuration.html b/docs/server/configuration.html index bd01ac80c..5ef8c42ec 100644 --- a/docs/server/configuration.html +++ b/docs/server/configuration.html @@ -15,8 +15,8 @@ - - + +

Configure Centrifugo

Let's look at how Centrifugo can be configured.

diff --git a/docs/server/console_commands.html b/docs/server/console_commands.html index 295ea5482..e12cf6361 100644 --- a/docs/server/console_commands.html +++ b/docs/server/console_commands.html @@ -15,8 +15,8 @@ - - + +

Helper CLI commands

Here is a list of helpful command-line commands that come with Centrifugo executable.

diff --git a/docs/server/consumers.html b/docs/server/consumers.html index 0e908a9cc..bd0f6c436 100644 --- a/docs/server/consumers.html +++ b/docs/server/consumers.html @@ -15,8 +15,8 @@ - - + +

Built-in API command async consumers

In server API chapter we've shown how to execute various Centrifugo server API commands (publish, broadcast, etc.) over HTTP or GRPC. In many cases you will call those APIs from your application business logic synchronously. But to deal with temporary network and availability issues, and achieve reliable execution of API commands upon changes in your primary application database you may want to use queuing techniques and call Centrifugo API asynchronously.

diff --git a/docs/server/engines.html b/docs/server/engines.html index 90520df30..5d89e6d98 100644 --- a/docs/server/engines.html +++ b/docs/server/engines.html @@ -15,8 +15,8 @@ - - + +

Engines and scalability

The Engine in Centrifugo is responsible for publishing messages between nodes, handle PUB/SUB broker subscriptions, save/retrieve online presence and history data.

diff --git a/docs/server/history_and_recovery.html b/docs/server/history_and_recovery.html index 6b539eb1c..d2ddeb33a 100644 --- a/docs/server/history_and_recovery.html +++ b/docs/server/history_and_recovery.html @@ -15,8 +15,8 @@ - - + +

History and recovery

Centrifugo engines can maintain publication history for channels with configured history size and TTL.

diff --git a/docs/server/infra_tuning.html b/docs/server/infra_tuning.html index ca256491e..2c82754d0 100644 --- a/docs/server/infra_tuning.html +++ b/docs/server/infra_tuning.html @@ -15,8 +15,8 @@ - - + +

Infrastructure tuning

As Centrifugo deals with lots of persistent connections your operating system and server infrastructure must be ready for it.

diff --git a/docs/server/load_balancing.html b/docs/server/load_balancing.html index 44951b4b8..e9c080816 100644 --- a/docs/server/load_balancing.html +++ b/docs/server/load_balancing.html @@ -15,8 +15,8 @@ - - + +

Load balancing

This chapter shows how to deal with persistent connection load balancing.

diff --git a/docs/server/monitoring.html b/docs/server/monitoring.html index aa9fe30f6..4b9356c5e 100644 --- a/docs/server/monitoring.html +++ b/docs/server/monitoring.html @@ -15,8 +15,8 @@ - - + +

Metrics monitoring

Centrifugo supports reporting metrics in Prometheus format and can automatically export metrics to Graphite.

diff --git a/docs/server/observability.html b/docs/server/observability.html index d83a98a2c..fefa764f7 100644 --- a/docs/server/observability.html +++ b/docs/server/observability.html @@ -15,8 +15,8 @@ - - + +

Server observability

To provide a better server observability Centrifugo supports reporting metrics in Prometheus format and can automatically export metrics to Graphite.

diff --git a/docs/server/presence.html b/docs/server/presence.html index 981c2c0e5..d725e25f0 100644 --- a/docs/server/presence.html +++ b/docs/server/presence.html @@ -15,8 +15,8 @@ - - + +

Online presence

The online presence feature of Centrifugo is a powerful tool that allows you to monitor and manage active users inside a specific channel. It provides an instantaneous snapshot of users currently subscribed to a specific channel. Additionally, Centrifugo may emit join and leave events when clients subscribe to channel and unsubscribe from it.

diff --git a/docs/server/proxy.html b/docs/server/proxy.html index af15946cd..e4da6e9cd 100644 --- a/docs/server/proxy.html +++ b/docs/server/proxy.html @@ -15,8 +15,8 @@ - - + +

Proxy events to the backend

It's possible to proxy some client connection events from Centrifugo to the application backend and react to them in a custom way. For example, it's possible to authenticate connection via request from Centrifugo to application backend, refresh client sessions and answer to RPC calls sent by a client over bidirectional connection. Also, you may control subscription and publication permissions using these hooks.

diff --git a/docs/server/proxy_streams.html b/docs/server/proxy_streams.html index 4f2f1c93b..a9abd836e 100644 --- a/docs/server/proxy_streams.html +++ b/docs/server/proxy_streams.html @@ -15,8 +15,8 @@ - - + +

Proxy subscription streams

Experimental

This is an experimental extension of Centrifugo proxy. We appreciate your feedback to make sure it's useful and solves real-world problems before marking it as stable and commit to the API.

diff --git a/docs/server/server_api.html b/docs/server/server_api.html index a63513813..460921f2e 100644 --- a/docs/server/server_api.html +++ b/docs/server/server_api.html @@ -15,8 +15,8 @@ - - + +

Server API walkthrough

Server API provides different methods to interact with Centrifugo. Specifically, in most cases this is an entry point for publications into channels coming from your application backend. There are two kinds of server API available at the moment:

diff --git a/docs/server/server_subs.html b/docs/server/server_subs.html index a376573b6..51d256616 100644 --- a/docs/server/server_subs.html +++ b/docs/server/server_subs.html @@ -15,8 +15,8 @@ - - + +

Server-side subscriptions

Centrifugo clients can initiate a subscription to a channel by calling the subscribe method of client API. In most cases, client-side subscriptions is a more flexible and recommended approach since a frontend usually knows which channels it needs to consume at a concrete moment.

diff --git a/docs/server/tls.html b/docs/server/tls.html index b01e151f4..503cb50eb 100644 --- a/docs/server/tls.html +++ b/docs/server/tls.html @@ -15,8 +15,8 @@ - - + +

Configure TLS

TLS/SSL layer is very important not only for securing your connections but also to increase a diff --git a/docs/transports/client_api.html b/docs/transports/client_api.html index e6230228b..e3d27e9d8 100644 --- a/docs/transports/client_api.html +++ b/docs/transports/client_api.html @@ -15,8 +15,8 @@ - - + +

Client SDK API

Centrifugo has several client SDKs to establish a real-time connection with a server. Centrifugo SDKs use WebSocket as the main data transport and send/receive messages encoded according to our bidirectional protocol. That protocol is built on top of the Protobuf schema (both JSON and binary Protobuf formats are supported). It provides asynchronous communication, sending RPC, multiplexing subscriptions to channels, etc. Client SDK wraps the protocol and exposes a set of APIs to developers.

diff --git a/docs/transports/client_protocol.html b/docs/transports/client_protocol.html index f63f72fee..8d03199fd 100644 --- a/docs/transports/client_protocol.html +++ b/docs/transports/client_protocol.html @@ -15,8 +15,8 @@ - - + +

Client protocol

This chapter describes the core concepts of Centrifugo bidirectional client protocol – concentrating on framing level. If you want to find out details about exposed client API then look at client API document.

diff --git a/docs/transports/client_sdk.html b/docs/transports/client_sdk.html index e751e239e..dabaa918a 100644 --- a/docs/transports/client_sdk.html +++ b/docs/transports/client_sdk.html @@ -15,8 +15,8 @@ - - + +

Client real-time SDKs

In the previous chapter we investigated common principles of Centrifugo client SDK API. Here we will provide a list of available bidirectional connectors you can use to communicate with Centrifugo.

diff --git a/docs/transports/http_stream.html b/docs/transports/http_stream.html index 2c9a4381a..005a4d01d 100644 --- a/docs/transports/http_stream.html +++ b/docs/transports/http_stream.html @@ -15,8 +15,8 @@ - - + +

HTTP streaming, with bidirectional emulation

HTTP streaming is a technique based on using a long-lived HTTP connection between a client and a server with a chunked transfer encoding. Usually it only allows unidirectional flow of messages from server to client but with Centrifugo bidirectional emulation layer it may be used as a full-featured fallback or alternative to WebSocket.

diff --git a/docs/transports/overview.html b/docs/transports/overview.html index 17bedc66d..0149aa14e 100644 --- a/docs/transports/overview.html +++ b/docs/transports/overview.html @@ -15,8 +15,8 @@ - - + +

Real-time transports

Centrifugo supports a variety of transports to deliver real-time messages to clients.

diff --git a/docs/transports/sockjs.html b/docs/transports/sockjs.html index 867476d4c..56609504b 100644 --- a/docs/transports/sockjs.html +++ b/docs/transports/sockjs.html @@ -15,8 +15,8 @@ - - + +

SockJS

SockJS is a polyfill browser library which provides HTTP-based fallback transports in case when it's not possible to establish Websocket connection. This can happen in old client browsers or because of some proxy behind client and server that cuts of Websocket traffic. You can find more information on SockJS project Github page.

diff --git a/docs/transports/sse.html b/docs/transports/sse.html index f98197cf0..c3510fabf 100644 --- a/docs/transports/sse.html +++ b/docs/transports/sse.html @@ -15,8 +15,8 @@ - - + +

SSE (EventSource), with bidirectional emulation

Server-Sent Events or EventSource is a well-known HTTP-based transport available in all modern browsers and loved by many developers. It's unidirectional in its nature but with Centrifugo bidirectional emulation layer it may be used as a fallback or alternative to WebSocket.

diff --git a/docs/transports/uni_client_protocol.html b/docs/transports/uni_client_protocol.html index 3a30be49f..34b69b4dd 100644 --- a/docs/transports/uni_client_protocol.html +++ b/docs/transports/uni_client_protocol.html @@ -15,8 +15,8 @@ - - + +

Unidirectional client protocol

As we mentioned in overview you can avoid using Centrifugo SDKs if you stick with unidirectional approach. In this case though you will need to implement some basic parsing on client side to consume message types sent by Centrifugo into unidirectional connections.

diff --git a/docs/transports/uni_grpc.html b/docs/transports/uni_grpc.html index ac34dbd4b..7ce4ce46b 100644 --- a/docs/transports/uni_grpc.html +++ b/docs/transports/uni_grpc.html @@ -15,8 +15,8 @@ - - + +

Unidirectional GRPC

It's possible to connect to GRPC unidirectional stream to consume real-time messages from Centrifugo. In this case you need to generate GRPC code for your language on client-side.

diff --git a/docs/transports/uni_http_stream.html b/docs/transports/uni_http_stream.html index 5ddb43034..e7e642316 100644 --- a/docs/transports/uni_http_stream.html +++ b/docs/transports/uni_http_stream.html @@ -15,8 +15,8 @@ - - + +

Unidirectional HTTP streaming

HTTP streaming is a technique based on using a long-lived HTTP connection between a client and a server with a chunked transfer encoding. These days it's possible to use it from the web browser using modern Fetch and Readable Streams API.

diff --git a/docs/transports/uni_sse.html b/docs/transports/uni_sse.html index 637718dd5..ac5aea847 100644 --- a/docs/transports/uni_sse.html +++ b/docs/transports/uni_sse.html @@ -15,8 +15,8 @@ - - + +

Unidirectional SSE (EventSource)

Server-Sent Events or EventSource is a well-known HTTP-based transport available in all modern browsers and loved by many developers.

diff --git a/docs/transports/uni_websocket.html b/docs/transports/uni_websocket.html index abc77b038..e51b15068 100644 --- a/docs/transports/uni_websocket.html +++ b/docs/transports/uni_websocket.html @@ -15,8 +15,8 @@ - - + +

Unidirectional WebSocket

Default unidirectional WebSocket connection endpoint in Centrifugo is:

diff --git a/docs/transports/websocket.html b/docs/transports/websocket.html index d7312b22c..05c5213d0 100644 --- a/docs/transports/websocket.html +++ b/docs/transports/websocket.html @@ -15,8 +15,8 @@ - - + +

WebSocket

Websocket is the main transport in Centrifugo. It's a very efficient low-overhead protocol on top of TCP.

diff --git a/docs/transports/webtransport.html b/docs/transports/webtransport.html index 9af577e04..1c1cd1170 100644 --- a/docs/transports/webtransport.html +++ b/docs/transports/webtransport.html @@ -15,8 +15,8 @@ - - + +

WebTransport

WebTransport is an API offering low-latency, bidirectional, client-server messaging on top of HTTP/3 (with QUIC under the hood). See Using WebTransport article that gives a good overview of it.

diff --git a/docs/tutorial/backend.html b/docs/tutorial/backend.html index ad529ea4e..fae02c5f8 100644 --- a/docs/tutorial/backend.html +++ b/docs/tutorial/backend.html @@ -15,8 +15,8 @@ - - + +

Setting up backend and database

Let's start building the app. As the first step, create a directory for the new app:

diff --git a/docs/tutorial/centrifugo.html b/docs/tutorial/centrifugo.html index e7d1b8038..23e2bd630 100644 --- a/docs/tutorial/centrifugo.html +++ b/docs/tutorial/centrifugo.html @@ -15,8 +15,8 @@ - - + +

Integrating Centrifugo for real-time event delivery

It's finally time for the real-time! In some cases you already have an application and when integrating Centrifugo you start from here.

diff --git a/docs/tutorial/frontend.html b/docs/tutorial/frontend.html index 7de0e9347..07350bb09 100644 --- a/docs/tutorial/frontend.html +++ b/docs/tutorial/frontend.html @@ -15,8 +15,8 @@ - - + +

Creating SPA frontend with React

On the frontend we will use Vite with React and Typescript. In this tutorial we are not paying a lot of attention to making all the types strict and using any a lot. Which is actually a point for improvement, but at least helps to make the tutorial slightly shorter. The prerequisites is NodeJS >= 18.

diff --git a/docs/tutorial/improvements.html b/docs/tutorial/improvements.html index 84fed46c2..f184602ff 100644 --- a/docs/tutorial/improvements.html +++ b/docs/tutorial/improvements.html @@ -15,8 +15,8 @@ - - + +

Appendix #1: Possible Improvements

There are still many areas for improvement in GrandChat, but we had to halt at a certain point to prevent the tutorial from becoming a book. If you enjoyed the tutorial and wish to enhance GrandChat further, here are some bright ideas:

diff --git a/docs/tutorial/intro.html b/docs/tutorial/intro.html index 9f9591bae..f9d78083b 100644 --- a/docs/tutorial/intro.html +++ b/docs/tutorial/intro.html @@ -15,8 +15,8 @@ - - + +

Building WebSocket chat (messenger) app from scratch

In this tutorial, we show how to build a rather complex real-time application with Centrifugo. It features a modern and responsive frontend, user authentication, channel permission checks, and the main database as a source of truth.

diff --git a/docs/tutorial/layout.html b/docs/tutorial/layout.html index 63c5a1224..bb054fd20 100644 --- a/docs/tutorial/layout.html +++ b/docs/tutorial/layout.html @@ -15,8 +15,8 @@ - - + +

App layout and behavior

Before we start, we would like the reader to be more familiar with the layout and behavior of the application we are creating here. Let's look at it screen by screen, describe the behavior, and explain which parts will be endowed with real-time superpowers.

diff --git a/docs/tutorial/outbox_cdc.html b/docs/tutorial/outbox_cdc.html index c144fbafa..910c232c1 100644 --- a/docs/tutorial/outbox_cdc.html +++ b/docs/tutorial/outbox_cdc.html @@ -15,8 +15,8 @@ - - + +

Broadcast using transactional outbox and CDC

Some of you may notice one potential issue which could prevent event delivery to users when publishing messages to Centrifugo API. Since we do this after a transaction and via a network call (in our case, using HTTP), it means the broadcast API call may return an error.

diff --git a/docs/tutorial/outro.html b/docs/tutorial/outro.html index 8213509e0..39132033c 100644 --- a/docs/tutorial/outro.html +++ b/docs/tutorial/outro.html @@ -15,8 +15,8 @@ - - + +

Wrapping up – things learnt

At this point, we have a working real-time app, so the tutorial comes to an end. We've covered some concepts of Centrifugo, such as:

diff --git a/docs/tutorial/recovery.html b/docs/tutorial/recovery.html index 474250803..e11d76d7d 100644 --- a/docs/tutorial/recovery.html +++ b/docs/tutorial/recovery.html @@ -15,8 +15,8 @@ - - + +

Missed messages recovery

At this point, we already have a real-time application with the instant delivery of events to interested messenger users. Now, let's focus on ensuring reliable message delivery. The first step would be enabling Centrifugo's automatic message recovery for personal channels.

diff --git a/docs/tutorial/reverse_proxy.html b/docs/tutorial/reverse_proxy.html index 9ad064fd0..22bdfea0d 100644 --- a/docs/tutorial/reverse_proxy.html +++ b/docs/tutorial/reverse_proxy.html @@ -15,8 +15,8 @@ - - + +

Adding Nginx as a reverse proxy

As mentioned, we are building a single-page frontend application here, and the frontend will be completely decoupled from the backend. This separation is advantageous because Centrifugo users can theoretically swap only the backend or frontend components while following this tutorial. For example, one could keep the frontend part but attempt to implement the backend in Laravel, Rails, or another framework.

diff --git a/docs/tutorial/scale.html b/docs/tutorial/scale.html index ae4b15407..f541896dc 100644 --- a/docs/tutorial/scale.html +++ b/docs/tutorial/scale.html @@ -15,8 +15,8 @@ - - + +

Scale to 100k cats in room

Congratulations – we've built an awesome app and we are done with the development within this tutorial! 🎉

diff --git a/docs/tutorial/tips_and_tricks.html b/docs/tutorial/tips_and_tricks.html index 65efc3bf2..e80665802 100644 --- a/docs/tutorial/tips_and_tricks.html +++ b/docs/tutorial/tips_and_tricks.html @@ -15,8 +15,8 @@ - - + +

Appendix #2: Tips and tricks

Making this tutorial took quite a lot of time for us. We want to collect some useful tips and tricks here for those who decide to play with the final example. Feel free to contribute if you find something which could help others.

diff --git a/img/centrifugo_loki.jpg b/img/centrifugo_loki.jpg new file mode 100644 index 000000000..a0f36c290 Binary files /dev/null and b/img/centrifugo_loki.jpg differ diff --git a/img/loki.mp4 b/img/loki.mp4 new file mode 100644 index 000000000..bff487715 Binary files /dev/null and b/img/loki.mp4 differ diff --git a/index.html b/index.html index f51c0f2e8..cee4c7b1f 100644 --- a/index.html +++ b/index.html @@ -15,8 +15,8 @@ - - + +
CENTRIFUGO
Scalable real-time messaging server. Set up once and forever.
Integrates with everything

Integrates with everything

Centrifugo is a self-hosted service which handles connections over various transports and provides a simple publishing API. Centrifugo nicely integrates with any application — no changes in the existing app architecture required to introduce real-time updates.

Great performance

Great performance

Centrifugo is written in Go language with some smart optimizations inside. See the description of the test stand with one million WebSocket connections and 30 million delivered messages per minute with hardware comparable to a single modern server machine.

Feature-rich

Feature-rich

Centrifugo provides flexible authentication, various types of subscriptions, hot channel history, online presence, the ability to proxy connection events to the backend, and much more. It comes with official SDK libraries for both web and mobile development.

Out-of-the-box scalability

Out-of-the-box scalability

Built-in Redis, KeyDB, Tarantool engines, or Nats broker make it possible to scale connections across different Centrifugo nodes. So Centrifugo helps you to scale to millions of active connections with reasonable hardware requirements.

Used in production

Used in production

Started a decade ago, Centrifugo (and Centrifuge library for Go it's built on top of) is mature, battle-tested software that has been successfully used in production by many companies around the world: VK, Badoo, ManyChat, OpenWeb, Grafana, and others.

Centrifugo PRO

Centrifugo PRO

Centrifugo PRO offers great benefits for corporate and enterprise environments by providing unique features on top of the OSS version: analytics with ClickHouse, real-time tracing, performance optimizations, push notification API, SSO integrations for web UI, etc.

What is real-time messaging?

Real-time messaging is used to create interactive applications where events are delivered to online users with minimal delay.

Chats apps, live comments, multiplayer games, real-time data visualizations, collaborative tools, etc. can all be built on top of a real-time messaging system.

Centrifugo is a user facing PUB/SUB server that handles persistent connections over various real-time transports — WebSocket, HTTP-streaming, SSE (Server-Sent Events), SockJS, WebTransport, GRPC.

Looking for a cool demo?

Here is the real-time telemetry streamed from the Assetto Corsa racing simulator to the Grafana dashboard with a help of our WebSocket technologies.

This demonstrates that you can stream 60Hz data towards client connections and thus provide instant visual feedback on the state of the system.

Slack-scale messenger?

Straightforward with Centrifugo! Even though your backend does not support concurrency. See the tutorial where we build a beautiful messenger app and go beyond usually shown basics.

Centrifugo is a versatile real-time component – it can be used to build various types of real-time applications, not just messengers.

Rotating Image

Are you Enterprise?

Centrifugal Labs offers a PRO version of Centrifugo that includes a set of unique features, additional APIs, and enhanced performance. Ever dreamed about a self-hosted real-time messaging system combined with a push notification system? Want to benefit from analytics of real-time connections and subscriptions? Centrifugo PRO makes this possible. And much more actually.

diff --git a/license.html b/license.html index b36b09321..2edbd14c2 100644 --- a/license.html +++ b/license.html @@ -15,8 +15,8 @@ - - + +

Centrifugal Labs LTD license agreement

diff --git a/license_exchange_lemon.html b/license_exchange_lemon.html index e10bb43a9..6c6cfe900 100644 --- a/license_exchange_lemon.html +++ b/license_exchange_lemon.html @@ -15,8 +15,8 @@ - - + +

Thanks for purchasing Centrifugo PRO 🎉

diff --git a/search.html b/search.html index 20adf923d..7fac57efb 100644 --- a/search.html +++ b/search.html @@ -15,8 +15,8 @@ - - + + diff --git a/sitemap.xml b/sitemap.xml index 742545884..862fd2895 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1 +1 @@ -https://centrifugal.dev/blogweekly0.5https://centrifugal.dev/blog/2020/02/10/million-connections-with-centrifugoweekly0.5https://centrifugal.dev/blog/2020/10/16/experimenting-with-quic-transportweekly0.5https://centrifugal.dev/blog/2020/11/12/scaling-websocketweekly0.5https://centrifugal.dev/blog/2021/01/15/centrifuge-introweekly0.5https://centrifugal.dev/blog/2021/08/31/hello-centrifugo-v3weekly0.5https://centrifugal.dev/blog/2021/10/18/integrating-with-nodejsweekly0.5https://centrifugal.dev/blog/2021/11/04/integrating-with-django-building-chat-applicationweekly0.5https://centrifugal.dev/blog/2021/12/14/laravel-multi-room-chat-tutorialweekly0.5https://centrifugal.dev/blog/2022/07/19/centrifugo-v4-releasedweekly0.5https://centrifugal.dev/blog/2022/07/29/101-way-to-subscribeweekly0.5https://centrifugal.dev/blog/2022/12/20/improving-redis-engine-performanceweekly0.5https://centrifugal.dev/blog/2023/03/31/keycloak-sso-centrifugoweekly0.5https://centrifugal.dev/blog/2023/06/29/centrifugo-v5-releasedweekly0.5https://centrifugal.dev/blog/2023/08/19/asynchronous-message-streaming-to-centrifugo-with-benthosweekly0.5https://centrifugal.dev/blog/2023/08/29/using-centrifugo-in-rabbitxweekly0.5https://centrifugal.dev/blog/2023/10/29/discovering-centrifugo-pro-push-notificationsweekly0.5https://centrifugal.dev/blog/archiveweekly0.5https://centrifugal.dev/blog/tagsweekly0.5https://centrifugal.dev/blog/tags/authenticationweekly0.5https://centrifugal.dev/blog/tags/benthosweekly0.5https://centrifugal.dev/blog/tags/centrifugeweekly0.5https://centrifugal.dev/blog/tags/centrifugoweekly0.5https://centrifugal.dev/blog/tags/djangoweekly0.5https://centrifugal.dev/blog/tags/goweekly0.5https://centrifugal.dev/blog/tags/interviewweekly0.5https://centrifugal.dev/blog/tags/keycloakweekly0.5https://centrifugal.dev/blog/tags/laravelweekly0.5https://centrifugal.dev/blog/tags/phpweekly0.5https://centrifugal.dev/blog/tags/proweekly0.5https://centrifugal.dev/blog/tags/proxyweekly0.5https://centrifugal.dev/blog/tags/push-notificationsweekly0.5https://centrifugal.dev/blog/tags/quicweekly0.5https://centrifugal.dev/blog/tags/redisweekly0.5https://centrifugal.dev/blog/tags/releaseweekly0.5https://centrifugal.dev/blog/tags/ssoweekly0.5https://centrifugal.dev/blog/tags/tutorialweekly0.5https://centrifugal.dev/blog/tags/usecaseweekly0.5https://centrifugal.dev/blog/tags/websocketweekly0.5https://centrifugal.dev/blog/tags/webtransportweekly0.5https://centrifugal.dev/components/Highlightweekly0.5https://centrifugal.dev/components/logoweekly0.5https://centrifugal.dev/components/logos/Badooweekly0.5https://centrifugal.dev/components/logos/Grafanaweekly0.5https://centrifugal.dev/components/logos/ManyChatweekly0.5https://centrifugal.dev/components/logos/OpenWebweekly0.5https://centrifugal.dev/licenseweekly0.5https://centrifugal.dev/searchweekly0.5https://centrifugal.dev/docs/3/attributionsweekly0.5https://centrifugal.dev/docs/3/ecosystem/centrifugeweekly0.5https://centrifugal.dev/docs/3/ecosystem/integrationsweekly0.5https://centrifugal.dev/docs/3/faqweekly0.5https://centrifugal.dev/docs/3/flow_diagramsweekly0.5https://centrifugal.dev/docs/3/getting-started/client_apiweekly0.5https://centrifugal.dev/docs/3/getting-started/designweekly0.5https://centrifugal.dev/docs/3/getting-started/highlightsweekly0.5https://centrifugal.dev/docs/3/getting-started/installationweekly0.5https://centrifugal.dev/docs/3/getting-started/integrationweekly0.5https://centrifugal.dev/docs/3/getting-started/introductionweekly0.5https://centrifugal.dev/docs/3/getting-started/migration_v3weekly0.5https://centrifugal.dev/docs/3/getting-started/quickstartweekly0.5https://centrifugal.dev/docs/3/pro/analyticsweekly0.5https://centrifugal.dev/docs/3/pro/db_namespacesweekly0.5https://centrifugal.dev/docs/3/pro/install_and_runweekly0.5https://centrifugal.dev/docs/3/pro/overviewweekly0.5https://centrifugal.dev/docs/3/pro/performanceweekly0.5https://centrifugal.dev/docs/3/pro/process_statsweekly0.5https://centrifugal.dev/docs/3/pro/singleflightweekly0.5https://centrifugal.dev/docs/3/pro/throttlingweekly0.5https://centrifugal.dev/docs/3/pro/token_revocationweekly0.5https://centrifugal.dev/docs/3/pro/tracingweekly0.5https://centrifugal.dev/docs/3/pro/user_blockweekly0.5https://centrifugal.dev/docs/3/pro/user_connectionsweekly0.5https://centrifugal.dev/docs/3/pro/user_statusweekly0.5https://centrifugal.dev/docs/3/server/admin_webweekly0.5https://centrifugal.dev/docs/3/server/authenticationweekly0.5https://centrifugal.dev/docs/3/server/channelsweekly0.5https://centrifugal.dev/docs/3/server/codesweekly0.5https://centrifugal.dev/docs/3/server/configurationweekly0.5https://centrifugal.dev/docs/3/server/console_commandsweekly0.5https://centrifugal.dev/docs/3/server/enginesweekly0.5https://centrifugal.dev/docs/3/server/history_and_recoveryweekly0.5https://centrifugal.dev/docs/3/server/infra_tuningweekly0.5https://centrifugal.dev/docs/3/server/load_balancingweekly0.5https://centrifugal.dev/docs/3/server/monitoringweekly0.5https://centrifugal.dev/docs/3/server/private_channelsweekly0.5https://centrifugal.dev/docs/3/server/proxyweekly0.5https://centrifugal.dev/docs/3/server/server_apiweekly0.5https://centrifugal.dev/docs/3/server/server_subsweekly0.5https://centrifugal.dev/docs/3/server/tlsweekly0.5https://centrifugal.dev/docs/3/transports/client_protocolweekly0.5https://centrifugal.dev/docs/3/transports/client_sdkweekly0.5https://centrifugal.dev/docs/3/transports/overviewweekly0.5https://centrifugal.dev/docs/3/transports/sockjsweekly0.5https://centrifugal.dev/docs/3/transports/uni_grpcweekly0.5https://centrifugal.dev/docs/3/transports/uni_http_streamweekly0.5https://centrifugal.dev/docs/3/transports/uni_sseweekly0.5https://centrifugal.dev/docs/3/transports/uni_websocketweekly0.5https://centrifugal.dev/docs/3/transports/websocketweekly0.5https://centrifugal.dev/docs/4/attributionsweekly0.5https://centrifugal.dev/docs/4/faqweekly0.5https://centrifugal.dev/docs/4/flow_diagramsweekly0.5https://centrifugal.dev/docs/4/getting-started/client_apiweekly0.5https://centrifugal.dev/docs/4/getting-started/communityweekly0.5https://centrifugal.dev/docs/4/getting-started/designweekly0.5https://centrifugal.dev/docs/4/getting-started/ecosystemweekly0.5https://centrifugal.dev/docs/4/getting-started/highlightsweekly0.5https://centrifugal.dev/docs/4/getting-started/installationweekly0.5https://centrifugal.dev/docs/4/getting-started/integrationweekly0.5https://centrifugal.dev/docs/4/getting-started/introductionweekly0.5https://centrifugal.dev/docs/4/getting-started/migration_v4weekly0.5https://centrifugal.dev/docs/4/getting-started/quickstartweekly0.5https://centrifugal.dev/docs/4/pro/analyticsweekly0.5https://centrifugal.dev/docs/4/pro/capabilitiesweekly0.5https://centrifugal.dev/docs/4/pro/cel_expressionsweekly0.5https://centrifugal.dev/docs/4/pro/channel_patternsweekly0.5https://centrifugal.dev/docs/4/pro/client_message_batchingweekly0.5https://centrifugal.dev/docs/4/pro/connectionsweekly0.5https://centrifugal.dev/docs/4/pro/install_and_runweekly0.5https://centrifugal.dev/docs/4/pro/overviewweekly0.5https://centrifugal.dev/docs/4/pro/performanceweekly0.5https://centrifugal.dev/docs/4/pro/process_statsweekly0.5https://centrifugal.dev/docs/4/pro/push_notificationsweekly0.5https://centrifugal.dev/docs/4/pro/singleflightweekly0.5https://centrifugal.dev/docs/4/pro/throttlingweekly0.5https://centrifugal.dev/docs/4/pro/token_revocationweekly0.5https://centrifugal.dev/docs/4/pro/tracingweekly0.5https://centrifugal.dev/docs/4/pro/user_blockweekly0.5https://centrifugal.dev/docs/4/pro/user_statusweekly0.5https://centrifugal.dev/docs/4/server/admin_webweekly0.5https://centrifugal.dev/docs/4/server/authenticationweekly0.5https://centrifugal.dev/docs/4/server/channel_permissionsweekly0.5https://centrifugal.dev/docs/4/server/channel_token_authweekly0.5https://centrifugal.dev/docs/4/server/channelsweekly0.5https://centrifugal.dev/docs/4/server/codesweekly0.5https://centrifugal.dev/docs/4/server/configurationweekly0.5https://centrifugal.dev/docs/4/server/console_commandsweekly0.5https://centrifugal.dev/docs/4/server/enginesweekly0.5https://centrifugal.dev/docs/4/server/history_and_recoveryweekly0.5https://centrifugal.dev/docs/4/server/infra_tuningweekly0.5https://centrifugal.dev/docs/4/server/load_balancingweekly0.5https://centrifugal.dev/docs/4/server/monitoringweekly0.5https://centrifugal.dev/docs/4/server/presenceweekly0.5https://centrifugal.dev/docs/4/server/proxyweekly0.5https://centrifugal.dev/docs/4/server/server_apiweekly0.5https://centrifugal.dev/docs/4/server/server_subsweekly0.5https://centrifugal.dev/docs/4/server/tlsweekly0.5https://centrifugal.dev/docs/4/transports/client_apiweekly0.5https://centrifugal.dev/docs/4/transports/client_protocolweekly0.5https://centrifugal.dev/docs/4/transports/client_sdkweekly0.5https://centrifugal.dev/docs/4/transports/http_streamweekly0.5https://centrifugal.dev/docs/4/transports/overviewweekly0.5https://centrifugal.dev/docs/4/transports/sockjsweekly0.5https://centrifugal.dev/docs/4/transports/sseweekly0.5https://centrifugal.dev/docs/4/transports/uni_grpcweekly0.5https://centrifugal.dev/docs/4/transports/uni_http_streamweekly0.5https://centrifugal.dev/docs/4/transports/uni_sseweekly0.5https://centrifugal.dev/docs/4/transports/uni_websocketweekly0.5https://centrifugal.dev/docs/4/transports/websocketweekly0.5https://centrifugal.dev/docs/4/transports/webtransportweekly0.5https://centrifugal.dev/docs/attributionsweekly0.5https://centrifugal.dev/docs/faqweekly0.5https://centrifugal.dev/docs/flow_diagramsweekly0.5https://centrifugal.dev/docs/getting-started/communityweekly0.5https://centrifugal.dev/docs/getting-started/comparisonsweekly0.5https://centrifugal.dev/docs/getting-started/designweekly0.5https://centrifugal.dev/docs/getting-started/ecosystemweekly0.5https://centrifugal.dev/docs/getting-started/highlightsweekly0.5https://centrifugal.dev/docs/getting-started/installationweekly0.5https://centrifugal.dev/docs/getting-started/integrationweekly0.5https://centrifugal.dev/docs/getting-started/introductionweekly0.5https://centrifugal.dev/docs/getting-started/migration_v4weekly0.5https://centrifugal.dev/docs/getting-started/migration_v5weekly0.5https://centrifugal.dev/docs/getting-started/quickstartweekly0.5https://centrifugal.dev/docs/pro/admin_idp_authweekly0.5https://centrifugal.dev/docs/pro/analyticsweekly0.5https://centrifugal.dev/docs/pro/capabilitiesweekly0.5https://centrifugal.dev/docs/pro/cel_expressionsweekly0.5https://centrifugal.dev/docs/pro/channel_patternsweekly0.5https://centrifugal.dev/docs/pro/channel_state_eventsweekly0.5https://centrifugal.dev/docs/pro/client_message_batchingweekly0.5https://centrifugal.dev/docs/pro/connectionsweekly0.5https://centrifugal.dev/docs/pro/distributed_rate_limitweekly0.5https://centrifugal.dev/docs/pro/install_and_runweekly0.5https://centrifugal.dev/docs/pro/observability_enhancementsweekly0.5https://centrifugal.dev/docs/pro/overviewweekly0.5https://centrifugal.dev/docs/pro/performanceweekly0.5https://centrifugal.dev/docs/pro/process_statsweekly0.5https://centrifugal.dev/docs/pro/push_notificationsweekly0.5https://centrifugal.dev/docs/pro/rate_limitingweekly0.5https://centrifugal.dev/docs/pro/singleflightweekly0.5https://centrifugal.dev/docs/pro/token_revocationweekly0.5https://centrifugal.dev/docs/pro/tracingweekly0.5https://centrifugal.dev/docs/pro/user_blockweekly0.5https://centrifugal.dev/docs/pro/user_statusweekly0.5https://centrifugal.dev/docs/server/admin_webweekly0.5https://centrifugal.dev/docs/server/authenticationweekly0.5https://centrifugal.dev/docs/server/channel_permissionsweekly0.5https://centrifugal.dev/docs/server/channel_token_authweekly0.5https://centrifugal.dev/docs/server/channelsweekly0.5https://centrifugal.dev/docs/server/codesweekly0.5https://centrifugal.dev/docs/server/configurationweekly0.5https://centrifugal.dev/docs/server/console_commandsweekly0.5https://centrifugal.dev/docs/server/consumersweekly0.5https://centrifugal.dev/docs/server/enginesweekly0.5https://centrifugal.dev/docs/server/history_and_recoveryweekly0.5https://centrifugal.dev/docs/server/infra_tuningweekly0.5https://centrifugal.dev/docs/server/load_balancingweekly0.5https://centrifugal.dev/docs/server/monitoringweekly0.5https://centrifugal.dev/docs/server/observabilityweekly0.5https://centrifugal.dev/docs/server/presenceweekly0.5https://centrifugal.dev/docs/server/proxyweekly0.5https://centrifugal.dev/docs/server/proxy_streamsweekly0.5https://centrifugal.dev/docs/server/server_apiweekly0.5https://centrifugal.dev/docs/server/server_subsweekly0.5https://centrifugal.dev/docs/server/tlsweekly0.5https://centrifugal.dev/docs/transports/client_apiweekly0.5https://centrifugal.dev/docs/transports/client_protocolweekly0.5https://centrifugal.dev/docs/transports/client_sdkweekly0.5https://centrifugal.dev/docs/transports/http_streamweekly0.5https://centrifugal.dev/docs/transports/overviewweekly0.5https://centrifugal.dev/docs/transports/sockjsweekly0.5https://centrifugal.dev/docs/transports/sseweekly0.5https://centrifugal.dev/docs/transports/uni_client_protocolweekly0.5https://centrifugal.dev/docs/transports/uni_grpcweekly0.5https://centrifugal.dev/docs/transports/uni_http_streamweekly0.5https://centrifugal.dev/docs/transports/uni_sseweekly0.5https://centrifugal.dev/docs/transports/uni_websocketweekly0.5https://centrifugal.dev/docs/transports/websocketweekly0.5https://centrifugal.dev/docs/transports/webtransportweekly0.5https://centrifugal.dev/docs/tutorial/backendweekly0.5https://centrifugal.dev/docs/tutorial/centrifugoweekly0.5https://centrifugal.dev/docs/tutorial/frontendweekly0.5https://centrifugal.dev/docs/tutorial/improvementsweekly0.5https://centrifugal.dev/docs/tutorial/introweekly0.5https://centrifugal.dev/docs/tutorial/layoutweekly0.5https://centrifugal.dev/docs/tutorial/outbox_cdcweekly0.5https://centrifugal.dev/docs/tutorial/outroweekly0.5https://centrifugal.dev/docs/tutorial/recoveryweekly0.5https://centrifugal.dev/docs/tutorial/reverse_proxyweekly0.5https://centrifugal.dev/docs/tutorial/scaleweekly0.5https://centrifugal.dev/docs/tutorial/tips_and_tricksweekly0.5https://centrifugal.dev/weekly0.5 \ No newline at end of file +https://centrifugal.dev/blogweekly0.5https://centrifugal.dev/blog/2020/02/10/million-connections-with-centrifugoweekly0.5https://centrifugal.dev/blog/2020/10/16/experimenting-with-quic-transportweekly0.5https://centrifugal.dev/blog/2020/11/12/scaling-websocketweekly0.5https://centrifugal.dev/blog/2021/01/15/centrifuge-introweekly0.5https://centrifugal.dev/blog/2021/08/31/hello-centrifugo-v3weekly0.5https://centrifugal.dev/blog/2021/10/18/integrating-with-nodejsweekly0.5https://centrifugal.dev/blog/2021/11/04/integrating-with-django-building-chat-applicationweekly0.5https://centrifugal.dev/blog/2021/12/14/laravel-multi-room-chat-tutorialweekly0.5https://centrifugal.dev/blog/2022/07/19/centrifugo-v4-releasedweekly0.5https://centrifugal.dev/blog/2022/07/29/101-way-to-subscribeweekly0.5https://centrifugal.dev/blog/2022/12/20/improving-redis-engine-performanceweekly0.5https://centrifugal.dev/blog/2023/03/31/keycloak-sso-centrifugoweekly0.5https://centrifugal.dev/blog/2023/06/29/centrifugo-v5-releasedweekly0.5https://centrifugal.dev/blog/2023/08/19/asynchronous-message-streaming-to-centrifugo-with-benthosweekly0.5https://centrifugal.dev/blog/2023/08/29/using-centrifugo-in-rabbitxweekly0.5https://centrifugal.dev/blog/2023/10/29/discovering-centrifugo-pro-push-notificationsweekly0.5https://centrifugal.dev/blog/2024/03/18/stream-loki-logs-to-browser-with-websocket-to-grpc-subscriptionsweekly0.5https://centrifugal.dev/blog/archiveweekly0.5https://centrifugal.dev/blog/tagsweekly0.5https://centrifugal.dev/blog/tags/authenticationweekly0.5https://centrifugal.dev/blog/tags/benthosweekly0.5https://centrifugal.dev/blog/tags/centrifugeweekly0.5https://centrifugal.dev/blog/tags/centrifugoweekly0.5https://centrifugal.dev/blog/tags/djangoweekly0.5https://centrifugal.dev/blog/tags/goweekly0.5https://centrifugal.dev/blog/tags/grpcweekly0.5https://centrifugal.dev/blog/tags/interviewweekly0.5https://centrifugal.dev/blog/tags/keycloakweekly0.5https://centrifugal.dev/blog/tags/laravelweekly0.5https://centrifugal.dev/blog/tags/lokiweekly0.5https://centrifugal.dev/blog/tags/phpweekly0.5https://centrifugal.dev/blog/tags/proweekly0.5https://centrifugal.dev/blog/tags/proxyweekly0.5https://centrifugal.dev/blog/tags/push-notificationsweekly0.5https://centrifugal.dev/blog/tags/quicweekly0.5https://centrifugal.dev/blog/tags/redisweekly0.5https://centrifugal.dev/blog/tags/releaseweekly0.5https://centrifugal.dev/blog/tags/ssoweekly0.5https://centrifugal.dev/blog/tags/tutorialweekly0.5https://centrifugal.dev/blog/tags/usecaseweekly0.5https://centrifugal.dev/blog/tags/websocketweekly0.5https://centrifugal.dev/blog/tags/webtransportweekly0.5https://centrifugal.dev/components/Highlightweekly0.5https://centrifugal.dev/components/logoweekly0.5https://centrifugal.dev/components/logos/Badooweekly0.5https://centrifugal.dev/components/logos/Grafanaweekly0.5https://centrifugal.dev/components/logos/ManyChatweekly0.5https://centrifugal.dev/components/logos/OpenWebweekly0.5https://centrifugal.dev/licenseweekly0.5https://centrifugal.dev/searchweekly0.5https://centrifugal.dev/docs/3/attributionsweekly0.5https://centrifugal.dev/docs/3/ecosystem/centrifugeweekly0.5https://centrifugal.dev/docs/3/ecosystem/integrationsweekly0.5https://centrifugal.dev/docs/3/faqweekly0.5https://centrifugal.dev/docs/3/flow_diagramsweekly0.5https://centrifugal.dev/docs/3/getting-started/client_apiweekly0.5https://centrifugal.dev/docs/3/getting-started/designweekly0.5https://centrifugal.dev/docs/3/getting-started/highlightsweekly0.5https://centrifugal.dev/docs/3/getting-started/installationweekly0.5https://centrifugal.dev/docs/3/getting-started/integrationweekly0.5https://centrifugal.dev/docs/3/getting-started/introductionweekly0.5https://centrifugal.dev/docs/3/getting-started/migration_v3weekly0.5https://centrifugal.dev/docs/3/getting-started/quickstartweekly0.5https://centrifugal.dev/docs/3/pro/analyticsweekly0.5https://centrifugal.dev/docs/3/pro/db_namespacesweekly0.5https://centrifugal.dev/docs/3/pro/install_and_runweekly0.5https://centrifugal.dev/docs/3/pro/overviewweekly0.5https://centrifugal.dev/docs/3/pro/performanceweekly0.5https://centrifugal.dev/docs/3/pro/process_statsweekly0.5https://centrifugal.dev/docs/3/pro/singleflightweekly0.5https://centrifugal.dev/docs/3/pro/throttlingweekly0.5https://centrifugal.dev/docs/3/pro/token_revocationweekly0.5https://centrifugal.dev/docs/3/pro/tracingweekly0.5https://centrifugal.dev/docs/3/pro/user_blockweekly0.5https://centrifugal.dev/docs/3/pro/user_connectionsweekly0.5https://centrifugal.dev/docs/3/pro/user_statusweekly0.5https://centrifugal.dev/docs/3/server/admin_webweekly0.5https://centrifugal.dev/docs/3/server/authenticationweekly0.5https://centrifugal.dev/docs/3/server/channelsweekly0.5https://centrifugal.dev/docs/3/server/codesweekly0.5https://centrifugal.dev/docs/3/server/configurationweekly0.5https://centrifugal.dev/docs/3/server/console_commandsweekly0.5https://centrifugal.dev/docs/3/server/enginesweekly0.5https://centrifugal.dev/docs/3/server/history_and_recoveryweekly0.5https://centrifugal.dev/docs/3/server/infra_tuningweekly0.5https://centrifugal.dev/docs/3/server/load_balancingweekly0.5https://centrifugal.dev/docs/3/server/monitoringweekly0.5https://centrifugal.dev/docs/3/server/private_channelsweekly0.5https://centrifugal.dev/docs/3/server/proxyweekly0.5https://centrifugal.dev/docs/3/server/server_apiweekly0.5https://centrifugal.dev/docs/3/server/server_subsweekly0.5https://centrifugal.dev/docs/3/server/tlsweekly0.5https://centrifugal.dev/docs/3/transports/client_protocolweekly0.5https://centrifugal.dev/docs/3/transports/client_sdkweekly0.5https://centrifugal.dev/docs/3/transports/overviewweekly0.5https://centrifugal.dev/docs/3/transports/sockjsweekly0.5https://centrifugal.dev/docs/3/transports/uni_grpcweekly0.5https://centrifugal.dev/docs/3/transports/uni_http_streamweekly0.5https://centrifugal.dev/docs/3/transports/uni_sseweekly0.5https://centrifugal.dev/docs/3/transports/uni_websocketweekly0.5https://centrifugal.dev/docs/3/transports/websocketweekly0.5https://centrifugal.dev/docs/4/attributionsweekly0.5https://centrifugal.dev/docs/4/faqweekly0.5https://centrifugal.dev/docs/4/flow_diagramsweekly0.5https://centrifugal.dev/docs/4/getting-started/client_apiweekly0.5https://centrifugal.dev/docs/4/getting-started/communityweekly0.5https://centrifugal.dev/docs/4/getting-started/designweekly0.5https://centrifugal.dev/docs/4/getting-started/ecosystemweekly0.5https://centrifugal.dev/docs/4/getting-started/highlightsweekly0.5https://centrifugal.dev/docs/4/getting-started/installationweekly0.5https://centrifugal.dev/docs/4/getting-started/integrationweekly0.5https://centrifugal.dev/docs/4/getting-started/introductionweekly0.5https://centrifugal.dev/docs/4/getting-started/migration_v4weekly0.5https://centrifugal.dev/docs/4/getting-started/quickstartweekly0.5https://centrifugal.dev/docs/4/pro/analyticsweekly0.5https://centrifugal.dev/docs/4/pro/capabilitiesweekly0.5https://centrifugal.dev/docs/4/pro/cel_expressionsweekly0.5https://centrifugal.dev/docs/4/pro/channel_patternsweekly0.5https://centrifugal.dev/docs/4/pro/client_message_batchingweekly0.5https://centrifugal.dev/docs/4/pro/connectionsweekly0.5https://centrifugal.dev/docs/4/pro/install_and_runweekly0.5https://centrifugal.dev/docs/4/pro/overviewweekly0.5https://centrifugal.dev/docs/4/pro/performanceweekly0.5https://centrifugal.dev/docs/4/pro/process_statsweekly0.5https://centrifugal.dev/docs/4/pro/push_notificationsweekly0.5https://centrifugal.dev/docs/4/pro/singleflightweekly0.5https://centrifugal.dev/docs/4/pro/throttlingweekly0.5https://centrifugal.dev/docs/4/pro/token_revocationweekly0.5https://centrifugal.dev/docs/4/pro/tracingweekly0.5https://centrifugal.dev/docs/4/pro/user_blockweekly0.5https://centrifugal.dev/docs/4/pro/user_statusweekly0.5https://centrifugal.dev/docs/4/server/admin_webweekly0.5https://centrifugal.dev/docs/4/server/authenticationweekly0.5https://centrifugal.dev/docs/4/server/channel_permissionsweekly0.5https://centrifugal.dev/docs/4/server/channel_token_authweekly0.5https://centrifugal.dev/docs/4/server/channelsweekly0.5https://centrifugal.dev/docs/4/server/codesweekly0.5https://centrifugal.dev/docs/4/server/configurationweekly0.5https://centrifugal.dev/docs/4/server/console_commandsweekly0.5https://centrifugal.dev/docs/4/server/enginesweekly0.5https://centrifugal.dev/docs/4/server/history_and_recoveryweekly0.5https://centrifugal.dev/docs/4/server/infra_tuningweekly0.5https://centrifugal.dev/docs/4/server/load_balancingweekly0.5https://centrifugal.dev/docs/4/server/monitoringweekly0.5https://centrifugal.dev/docs/4/server/presenceweekly0.5https://centrifugal.dev/docs/4/server/proxyweekly0.5https://centrifugal.dev/docs/4/server/server_apiweekly0.5https://centrifugal.dev/docs/4/server/server_subsweekly0.5https://centrifugal.dev/docs/4/server/tlsweekly0.5https://centrifugal.dev/docs/4/transports/client_apiweekly0.5https://centrifugal.dev/docs/4/transports/client_protocolweekly0.5https://centrifugal.dev/docs/4/transports/client_sdkweekly0.5https://centrifugal.dev/docs/4/transports/http_streamweekly0.5https://centrifugal.dev/docs/4/transports/overviewweekly0.5https://centrifugal.dev/docs/4/transports/sockjsweekly0.5https://centrifugal.dev/docs/4/transports/sseweekly0.5https://centrifugal.dev/docs/4/transports/uni_grpcweekly0.5https://centrifugal.dev/docs/4/transports/uni_http_streamweekly0.5https://centrifugal.dev/docs/4/transports/uni_sseweekly0.5https://centrifugal.dev/docs/4/transports/uni_websocketweekly0.5https://centrifugal.dev/docs/4/transports/websocketweekly0.5https://centrifugal.dev/docs/4/transports/webtransportweekly0.5https://centrifugal.dev/docs/attributionsweekly0.5https://centrifugal.dev/docs/faqweekly0.5https://centrifugal.dev/docs/flow_diagramsweekly0.5https://centrifugal.dev/docs/getting-started/communityweekly0.5https://centrifugal.dev/docs/getting-started/comparisonsweekly0.5https://centrifugal.dev/docs/getting-started/designweekly0.5https://centrifugal.dev/docs/getting-started/ecosystemweekly0.5https://centrifugal.dev/docs/getting-started/highlightsweekly0.5https://centrifugal.dev/docs/getting-started/installationweekly0.5https://centrifugal.dev/docs/getting-started/integrationweekly0.5https://centrifugal.dev/docs/getting-started/introductionweekly0.5https://centrifugal.dev/docs/getting-started/migration_v4weekly0.5https://centrifugal.dev/docs/getting-started/migration_v5weekly0.5https://centrifugal.dev/docs/getting-started/quickstartweekly0.5https://centrifugal.dev/docs/pro/admin_idp_authweekly0.5https://centrifugal.dev/docs/pro/analyticsweekly0.5https://centrifugal.dev/docs/pro/capabilitiesweekly0.5https://centrifugal.dev/docs/pro/cel_expressionsweekly0.5https://centrifugal.dev/docs/pro/channel_patternsweekly0.5https://centrifugal.dev/docs/pro/channel_state_eventsweekly0.5https://centrifugal.dev/docs/pro/client_message_batchingweekly0.5https://centrifugal.dev/docs/pro/connectionsweekly0.5https://centrifugal.dev/docs/pro/distributed_rate_limitweekly0.5https://centrifugal.dev/docs/pro/install_and_runweekly0.5https://centrifugal.dev/docs/pro/observability_enhancementsweekly0.5https://centrifugal.dev/docs/pro/overviewweekly0.5https://centrifugal.dev/docs/pro/performanceweekly0.5https://centrifugal.dev/docs/pro/process_statsweekly0.5https://centrifugal.dev/docs/pro/push_notificationsweekly0.5https://centrifugal.dev/docs/pro/rate_limitingweekly0.5https://centrifugal.dev/docs/pro/singleflightweekly0.5https://centrifugal.dev/docs/pro/token_revocationweekly0.5https://centrifugal.dev/docs/pro/tracingweekly0.5https://centrifugal.dev/docs/pro/user_blockweekly0.5https://centrifugal.dev/docs/pro/user_statusweekly0.5https://centrifugal.dev/docs/server/admin_webweekly0.5https://centrifugal.dev/docs/server/authenticationweekly0.5https://centrifugal.dev/docs/server/channel_permissionsweekly0.5https://centrifugal.dev/docs/server/channel_token_authweekly0.5https://centrifugal.dev/docs/server/channelsweekly0.5https://centrifugal.dev/docs/server/codesweekly0.5https://centrifugal.dev/docs/server/configurationweekly0.5https://centrifugal.dev/docs/server/console_commandsweekly0.5https://centrifugal.dev/docs/server/consumersweekly0.5https://centrifugal.dev/docs/server/enginesweekly0.5https://centrifugal.dev/docs/server/history_and_recoveryweekly0.5https://centrifugal.dev/docs/server/infra_tuningweekly0.5https://centrifugal.dev/docs/server/load_balancingweekly0.5https://centrifugal.dev/docs/server/monitoringweekly0.5https://centrifugal.dev/docs/server/observabilityweekly0.5https://centrifugal.dev/docs/server/presenceweekly0.5https://centrifugal.dev/docs/server/proxyweekly0.5https://centrifugal.dev/docs/server/proxy_streamsweekly0.5https://centrifugal.dev/docs/server/server_apiweekly0.5https://centrifugal.dev/docs/server/server_subsweekly0.5https://centrifugal.dev/docs/server/tlsweekly0.5https://centrifugal.dev/docs/transports/client_apiweekly0.5https://centrifugal.dev/docs/transports/client_protocolweekly0.5https://centrifugal.dev/docs/transports/client_sdkweekly0.5https://centrifugal.dev/docs/transports/http_streamweekly0.5https://centrifugal.dev/docs/transports/overviewweekly0.5https://centrifugal.dev/docs/transports/sockjsweekly0.5https://centrifugal.dev/docs/transports/sseweekly0.5https://centrifugal.dev/docs/transports/uni_client_protocolweekly0.5https://centrifugal.dev/docs/transports/uni_grpcweekly0.5https://centrifugal.dev/docs/transports/uni_http_streamweekly0.5https://centrifugal.dev/docs/transports/uni_sseweekly0.5https://centrifugal.dev/docs/transports/uni_websocketweekly0.5https://centrifugal.dev/docs/transports/websocketweekly0.5https://centrifugal.dev/docs/transports/webtransportweekly0.5https://centrifugal.dev/docs/tutorial/backendweekly0.5https://centrifugal.dev/docs/tutorial/centrifugoweekly0.5https://centrifugal.dev/docs/tutorial/frontendweekly0.5https://centrifugal.dev/docs/tutorial/improvementsweekly0.5https://centrifugal.dev/docs/tutorial/introweekly0.5https://centrifugal.dev/docs/tutorial/layoutweekly0.5https://centrifugal.dev/docs/tutorial/outbox_cdcweekly0.5https://centrifugal.dev/docs/tutorial/outroweekly0.5https://centrifugal.dev/docs/tutorial/recoveryweekly0.5https://centrifugal.dev/docs/tutorial/reverse_proxyweekly0.5https://centrifugal.dev/docs/tutorial/scaleweekly0.5https://centrifugal.dev/docs/tutorial/tips_and_tricksweekly0.5https://centrifugal.dev/weekly0.5 \ No newline at end of file