Real-time messaging library for Go with scalability in mind.

Join the chat at https://t.me/joinchat/ABFVWBE0AhkyyhREoaboXQ Build Status codecov.io GoDoc

This library has no v1 release, API still evolves. Use with strict versioning. Before v1 release patch version updates only have backwards compatible changes and fixes, minor version updates can have backwards-incompatible API changes. For all breaking changes we provide a detailed changelog. Master branch can have unreleased code.

Centrifuge library is a real-time core of Centrifugo server. It's also supposed to be a general purpose real-time messaging library for Go programming language. The library built on top of strict client-server protocol schema and exposes various real-time oriented primitives for a developer. Centrifuge solves several problems a developer may come across when building complex real-time applications – like scalability (millions of connections), proper persistent connection management and invalidation, fast reconnect with message recovery, WebSocket fallback option.

Library highlights:

  • Fast and optimized for low-latency communication with millions of client connections. See test stand with 1 million connections in Kubernetes
  • Builtin bidirectional transports: WebSocket (JSON or binary Protobuf) and SockJS (JSON only)
  • Possibility to use unidirectional transports without using custom Centrifuge client library: see examples for GRPC, EventSource(SSE), Fetch Streams, Unidirectional WebSocket
  • Built-in horizontal scalability with Redis PUB/SUB, consistent Redis sharding, Sentinel and Redis Cluster for HA
  • Native authentication over HTTP middleware or custom token-based
  • Channel concept to broadcast message to all active subscribers
  • Client-side and server-side channel subscriptions
  • Bidirectional asynchronous message communication and RPC calls
  • Presence information for channels (show all active clients in a channel)
  • History information for channels (ephemeral streams with size and TTL retention)
  • Join/leave events for channels (aka client goes online/offline)
  • Possibility to register a custom PUB/SUB Broker and Presence Manager implementations
  • Message recovery mechanism for channels to survive PUB/SUB delivery problems, short network disconnects or node restart
  • Prometheus instrumentation
  • Client libraries for main application environments (see below)

For bidirectional communication between a client and a Centrifuge-based server we have a bunch of client libraries:

If you opt for a unidirectional communication then you may leverage Centrifuge possibilities without any specific library on client-side - simply by using native browser API or GRPC-generated code. See examples of unidirectional communication over GRPC, EventSource(SSE), Fetch Streams, WebSocket.

Explore Centrifuge

Installation

To install use:

go get github.com/centrifugal/centrifuge

go mod is a recommended way of adding this library to your project dependencies.

Quick example

Let's take a look on how to build the simplest real-time chat with Centrifuge library. Clients will be able to connect to a server over Websocket, send a message into a channel and this message will be instantly delivered to all active channel subscribers. On a server side we will accept all connections and will work as a simple PUB/SUB proxy without worrying too much about permissions. In this example we will use Centrifuge Javascript client on a frontend.

Create file main.go with the following code:

package main

import (
	"log"
	"net/http"

	// Import this library.
	"github.com/centrifugal/centrifuge"
)

// Authentication middleware example. Centrifuge expects Credentials
// with current user ID set. Without provided Credentials client
// connection won't be accepted. Another way to authenticate connection
// is reacting to node.OnConnecting event where you may authenticate
// connection based on a custom token sent by a client in first protocol
// frame. See _examples folder in repo to find real-life auth samples
// (OAuth2, Gin sessions, JWT etc).
func auth(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		// Put authentication Credentials into request Context.
		// Since we don't have any session backend here we simply
		// set user ID as empty string. Users with empty ID called
		// anonymous users, in real app you should decide whether
		// anonymous users allowed to connect to your server or not.
		cred := &centrifuge.Credentials{
			UserID: "",
		}
		newCtx := centrifuge.SetCredentials(ctx, cred)
		r = r.WithContext(newCtx)
		h.ServeHTTP(w, r)
	})
}

func main() {
	// We use default config here as a starting point. Default config
	// contains reasonable values for available options.
	cfg := centrifuge.DefaultConfig

	// Node is the core object in Centrifuge library responsible for
	// many useful things. For example Node allows to publish messages
	// to channels with its Publish method.
	node, err := centrifuge.New(cfg)
	if err != nil {
		log.Fatal(err)
	}

	// Set ConnectHandler called when client successfully connected to Node.
	// Your code inside a handler must be synchronized since it will be called
	// concurrently from different goroutines (belonging to different client
	// connections). See information about connection life cycle in library readme.
	// This handler should not block – so do minimal work here, set required
	// connection event handlers and return.
	node.OnConnect(func(client *centrifuge.Client) {
		// In our example transport will always be Websocket but it can also be SockJS.
		transportName := client.Transport().Name()
		// In our example clients connect with JSON protocol but it can also be Protobuf.
		transportProto := client.Transport().Protocol()
		log.Printf("client connected via %s (%s)", transportName, transportProto)

		// Set SubscribeHandler to react on every channel subscription attempt
		// initiated by a client. Here you can theoretically return an error or
		// disconnect a client from a server if needed. But here we just accept
		// all subscriptions to all channels. In real life you may use a more
		// complex permission check here. The reason why we use callback style
		// inside client event handlers is that it gives a possibility to control
		// operation concurrency to developer and still control order of events.
		client.OnSubscribe(func(e centrifuge.SubscribeEvent, cb centrifuge.SubscribeCallback) {
			log.Printf("client subscribes on channel %s", e.Channel)
			cb(centrifuge.SubscribeReply{}, nil)
		})

		// By default, clients can not publish messages into channels. By setting
		// PublishHandler we tell Centrifuge that publish from a client-side is
		// possible. Now each time client calls publish method this handler will be
		// called and you have a possibility to validate publication request. After
		// returning from this handler Publication will be published to a channel and
		// reach active subscribers with at most once delivery guarantee. In our simple
		// chat app we allow everyone to publish into any channel but in real case
		// you may have more validation.
		client.OnPublish(func(e centrifuge.PublishEvent, cb centrifuge.PublishCallback) {
			log.Printf("client publishes into channel %s: %s", e.Channel, string(e.Data))
			cb(centrifuge.PublishReply{}, nil)
		})

		// Set Disconnect handler to react on client disconnect events.
		client.OnDisconnect(func(e centrifuge.DisconnectEvent) {
			log.Printf("client disconnected")
		})
	})

	// Run node. This method does not block. See also node.Shutdown method
	// to finish application gracefully.
	if err := node.Run(); err != nil {
		log.Fatal(err)
	}

	// Now configure HTTP routes.

	// Serve Websocket connections using WebsocketHandler.
	wsHandler := centrifuge.NewWebsocketHandler(node, centrifuge.WebsocketConfig{})
	http.Handle("/connection/websocket", auth(wsHandler))

	// The second route is for serving index.html file.
	http.Handle("/", http.FileServer(http.Dir("./")))

	log.Printf("Starting server, visit http://localhost:8000")
	if err := http.ListenAndServe(":8000", nil); err != nil {
		log.Fatal(err)
	}
}

Also create file index.html near main.go with content:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <script type="text/javascript" src="https://rawgit.com/centrifugal/centrifuge-js/master/dist/centrifuge.min.js"></script>
        <title>Centrifuge library chat example</title>
    </head>
    <body>
        <input type="text" id="input" />
        <script type="text/javascript">
            // Create Centrifuge object with Websocket endpoint address set in main.go
            const centrifuge = new Centrifuge('ws://localhost:8000/connection/websocket');
            function drawText(text) {
                const div = document.createElement('div');
                div.innerHTML = text + '<br>';
                document.body.appendChild(div);
            }
            centrifuge.on('connect', function(ctx){
                drawText('Connected over ' + ctx.transport);
            });
            centrifuge.on('disconnect', function(ctx){
                drawText('Disconnected: ' + ctx.reason);
            });
            const sub = centrifuge.subscribe("chat", function(ctx) {
                drawText(JSON.stringify(ctx.data));
            })
            const input = document.getElementById("input");
            input.addEventListener('keyup', function(e) {
                if (e.keyCode === 13) {
                    sub.publish(this.value);
                    input.value = '';
                }
            });
            // After setting event handlers – initiate actual connection with server.
            centrifuge.connect();
        </script>
    </body>
</html>

Then run as usual:

go run main.go

Open several browser tabs with http://localhost:8000 and see chat in action.

While this example is only the top of an iceberg, it should give you a good insight on library API. Check out examples folder for more.

Keep in mind that Centrifuge library is not a framework to build chat applications. It's a general purpose real-time transport for your messages with some helpful primitives. You can build many kinds of real-time apps on top of this library including chats but depending on application you may need to write business logic yourself.

Tips and tricks

Some useful advices about library here.

Connection life cycle

Let's describe some aspects related to connection life cycle and event handling in Centrifuge:

  • If you set middleware for transport handlers (WebsocketHandler, SockjsHandler) – then it will be called first before a client sent any command to a server and handler had a chance to start working. Just like a regular HTTP middleware. You can put Credentials to Context to authenticate connection.
  • node.OnConnecting called as soon as client sent Connect command to server. At this point no Client instance exists. You have incoming Context and Transport information. You still can authenticate Client at this point (based on string token sent from client side or any other way). Also, you can add extra data to context and return modified context to Centrifuge. Context cancelled as soon as client connection closes. This handler is synchronous and connection read loop can't proceed until you return ConnectReply.
  • node.OnConnect then called (after a reply to Connect command already written to connection). Inside OnConnect closure you have a possibility to define per-connection event handlers. If particular handler not set then client will get ErrorNotAvailable errors requesting it. Remember that none of event handlers available in Centrifuge should block forever – do minimal work, start separate goroutines if you need blocking code.
  • Client initiated request handlers called one by one from connection reading goroutine. This includes OnSubscribe, OnPublish, OnPresence, OnPresenceStats, OnHistory, client-side OnRefresh, client-side OnSubRefresh.
  • Other handlers like OnAlive, OnDisconnect, server-side OnSubRefresh, server-side OnRefresh called from separate internal goroutines.
  • OnAlive handler must not be called after OnDisconnect.
  • Client initiated request handlers can be processed asynchronously in goroutines to manage operation concurrency. This is achieved using callback functions. See concurrency example for more details.

Channel history stream

Centrifuge Broker interface supports saving Publication to history stream on publish. Depending on Broker implementation this feature can be missing though. Builtin Memory and Redis brokers support keeping Publication stream.

When using default MemoryBroker Publication stream kept in process memory and lost as soon as process restarts. RedisBroker keeps Publication stream in Redis LIST or STREAM data structures – reliability inherited from Redis configuration in this case.

Centrifuge library publication stream not meant to be used as the only source of missed Publications for a client. It mostly exists to help many clients reconnect at once (load balancer reload, application deploy) without creating a massive spike in load on your main application database. So application database still required in idiomatic use case.

Centrifuge message recovery protocol feature designed to be used together with reasonably small Publication stream size as all missed publications sent towards client in one protocol frame on resubscribe to channel.

Logging

Centrifuge library exposes logs with different log level. In your app you can set special function to handle these log entries in a way you want.

// Function to handle Centrifuge internal logs.
func handleLog(e centrifuge.LogEntry) {
	log.Printf("%s: %v", e.Message, e.Fields)
}

cfg := centrifuge.DefaultConfig
cfg.LogLevel = centrifuge.LogLevelDebug
cfg.LogHandler = handleLog
Owner
Comments
  • Add Node.Send() method

    Add Node.Send() method

    The method is required to send personalized messages to channel subscribers when certain channel events occur. For example:

    
    	node, _ := centrifuge.New(cfg)
    	node.On().ClientConnected(func(ctx context.Context, client *centrifuge.Client) {
    		client.On().Message(func(e centrifuge.MessageEvent) centrifuge.MessageReply {
    
    			ci, _ := node.Presence("channel")
    			for uid := range ci {
    				node.Send(uid, []byte(uid))
    			}
    
    			return centrifuge.MessageReply{}
    		})
    	})
    
    
  • Migrate to github.com/rueian/rueidis

    Migrate to github.com/rueian/rueidis

    An alternative approach to #235 + Sharded PUB/SUB support (relates #228). Relates #210.

    @j178 hello, I was already working on replacing redigo to rueidis for some time before you opened #235, but rueidis did not support RESP2 till the v0.0.80 release (published today). So it seems a viable alternative to go-redis now.

    This PR contains an approach to scale PUB/SUB in Redis Cluster using Rediis 7 sharded PUB/SUB feature. Probably it should be a separate pr - but just had it already... In sharded PUB/SUB case we split the key space of broker to configured NumClusterShards parts. Then we start separate PUB/SUB routines to work with each PUB/SUB shard. This means that instead of 16384 slots (usually used by Redis Cluster) we have NumClusterShards slots for all broker ops. This still limits scalability a bit (though I think sth like NumClusterShards == 16/32/64 is OK for many use cases) but otherwise we would require too many connections to deal with PUB/SUB for millions of channels. It's also worth noting that turning on NumClusterShards option to a value greater than zero will result into different keys to keep values in Redis than keys we have when not using sharded PUB/SUB.

    I am personally biased towards rueidis at the moment, though still have not put enough effort into testing and comparing both implementations. I'll try to update this pr with comments as I proceed with some checks.

    I also removed IdleTimeout and UseTLS options from Redis shard config as they do not match rueidis options (matched to redigo options currently). And added possibility to set ClientName

    @tony-pang, hello - are you still interested in sharded PUB/SUB implementation?

  • History

    History

    Hi,A client when subscribing to a channel needs to get last n messages published in that channel. How can I handle this use case?Any reference for that? centrifuge-js is the client library I am using.

  • Access client's subscribed channels in DisconnectEvent

    Access client's subscribed channels in DisconnectEvent

    We are looking to implement behaviour on client Disconnect based on the channels that the client was subscribed to. However, the client.close() function unsubscribes the client from all channels before calling the disconnect handler.

    Is there any way the client's channels can be passed into the DisconnectEvent?

  • Swappable Persistence Backends?

    Swappable Persistence Backends?

    I'm curious if making the persistence backends swappable is on the roadmap for this library.

    In my specific use case, it would be nice to use the lightweight Bolt for persistence while still utilizing centrifuge's pub sub system, unlike the way you have it set up with Redis now which seems to require using Redis's pub/sub system if you want persistence.

    If there was an API for writing pluggable backends, I'd happily get started on one for bolt.

  • Close Redis Broker and PresenceManager on Node shutdown

    Close Redis Broker and PresenceManager on Node shutdown

    This closes all the running goroutines of Redis Broker on Node Shutdown. This should also fix our Survey tests when those are launched with count > 1.

  • Does it safe to put handleCommand in goroutine? (Manage client operations concurrency)

    Does it safe to put handleCommand in goroutine? (Manage client operations concurrency)

    In https://github.com/centrifugal/centrifuge/blob/master/client.go#L651 image

    Can I put it in to goroutine? Because slow rpc command can effect to all of application. I think it need to put to goroutine.

    P/s: sorry for my English. Thanks.

  • Multi tenant support

    Multi tenant support

    Hi!

    We have been using centrifuge in production for more than a year now, without any issues, so thanks for the awesome project. A couple weeks ago we started launching multiple websites using our api, and we need to separate them completely. Our api was desigend for this, so it was no issue, but we need to separate centrifuge channels as well.

    My first idea was:

    • user subscribes to some:channel
    • in centrifuge, I add support for multiple site, and modify the channel name to some:<somesite>.channel (eg: based on a header, or some values encoded in the jwt token)
    • when messages arrive on the some:<somesite>.channel, i send them out as some:channel

    and although it would be best for us, I found no mechanism in centrifuge where a channel name is rewritten, so this approach would be very difficult for me to implement. If you think this is a good idea I would still love for it to happen, but until then I found a different approach.

    My second idea:

    • user subscibes to some:<somesite>.channel
    • I validate that the user belongs to the site (eg: based on some values encoded in the jwt token)

    I could almost implement this using centrifuge as a library (instead of using centrifugo server). I made a handler for the client.Subscribed event. The only thing I miss is to be able to access the JWT token info in the client.On().Subscribe handler. I made a fork exposing the client.info, with that it works perfectly.

  • reporting node health

    reporting node health

    How is node health reported? Looked around in the code and couldn't see anything obvious.

    centrifugo's api has no healthcheck endpoint, while investigating how to create one I noticed that health would need to be reported by each node. Is that correct?

    To start simple we could just ping the configured engine, then report "ok" or "not ok".

    I'm willing to send a PR, but would probably need a little guidance to get me started.

    Great project btw. Thanks!

  • Set Client.user w/o Disconnect/Connect events

    Set Client.user w/o Disconnect/Connect events

    Hello! I implement the user authentication process completely with WebSockets. As I understand it, changing Client.user now is possible only at node.On().ClientConnecting method (by return Credentials at ConnectReply struct). But Singin reguest is possible only after the Connecting event. And for add UserID to Client.user i have emit Disconnect/Connect events. I would like to avoid this. Is it possible to add Client.SetUserID method like this:

    // SetUserID set user ID.
    func (c *Client) SetUserID(userID string) {
    	c.mu.Lock()
    	c.user = userID
    	c.mu.Unlock()
    }
    

    P.S.: I have studied the source code and did not see difficulty when applying sharding with Client.SetUserID method.

  • How to publish and subscribe to multiple channels?

    How to publish and subscribe to multiple channels?

    Hello, I'm currently working on a use-case wherein:

    1. a client should be able to publish to one or more channels (at the same time) and a subscriber should be able to subscribe to one or many channels (at the same time).
    2. publish to all subscribers without a client publish. Something like a PUT call with the channel name and the message to publish.

    Is this possible? Any directions for this use-case?

    Also, I see two handers NewWebsocketHandler and NewSockjsHandler. is WebsocketHandler a pure websocket handler with no means to downgrade to other methods where WS is not supported? So, if thats the case do you suggest using SockjsHandler in these scenarios?

  • Bump github.com/centrifugal/protocol from 0.8.11 to 0.9.0

    Bump github.com/centrifugal/protocol from 0.8.11 to 0.9.0

    Bumps github.com/centrifugal/protocol from 0.8.11 to 0.9.0.

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • Configure write delay and max messages in frame

    Configure write delay and max messages in frame

    Experiment with possibility to reduce syscalls in the case when message rate towards individual client is large enough - let's say 30-120 messages per second. In this case we can collect more messages by pausing the client message writer loop. So latency against resource usage due to reduced number of syscalls trade-off again. Like we had in Redis case: https://centrifugal.dev/blog/2022/12/20/improving-redis-engine-performance#switching-to-rueidis-reducing-cpu-usage.

    https://user-images.githubusercontent.com/1196565/209398701-c9668d91-8c6a-47d9-9f77-63b89126567d.mp4

    The video shows my local experiments, I am publishing with rate limit 60 messages per second. First run without delay (you can see CPU usage of client and server in htop). Second run with 100ms delay - same throughput but reduced CPU (150% -> 25% for the server).

    When PushDelay set to the value > 0 we are sending replies to commands without using queue at all – directly sending them to the transport. Only pushes go to the client's queue. This has been done to avoid extra delay for request-response style communication. Though probably it should be optional to achieve even more CPU savings in some scenarios 🤔

    This can be configured on per-client basis and disabled by default – so Centrifugo delivers messages as fast as possible by default.

  • Per-channel history meta TTL

    Per-channel history meta TTL

    This is an early draft of making History Meta TTL configured on per-channel level. It's currently only possible to set it globally. It's now an option of all Engines which is a bit awkward.

    I want it to be configurable in one place (Node's config) and be re-defined on per-channel level. Some motivation why this level of meta ttl may be useful is described in this example: https://github.com/centrifugal/examples/tree/master/v4/go_async_processing

    This pr contains some changes to achieve it. Still thinking whether there is a better way 🤔

  • One shot encode/decode for control proto

    One shot encode/decode for control proto

    Similar to what we did with client protocol - avoid double encoded control messages. Reduces allocs a bit. Also results into more compact code.

    The change will break compatibility between different versions of Centrifuge nodes running in the same cluster. I've never heard about use cases though where different versions of nodes are mixed in one setup.

  • Clients being disconnected due to

    Clients being disconnected due to "Slow" reason - throttling options

    We have a non-standard setup using Centrifuge, where a client subscribes to a channel and we start a go routine that streams data on that channel to the client from our database.

    In some instances, the client cannot keep up with the data stream, and Centrifuge disconnects them after the write queue has become too large.

    Is there some way for us to detect the size of the write queue and throttle the publishing of new data on the server side?

Minimal and idiomatic WebSocket library for Go

websocket websocket is a minimal and idiomatic WebSocket library for Go. Install go get nhooyr.io/websocket Highlights Minimal and idiomatic API First

Dec 31, 2022
go-socket.io is library an implementation of Socket.IO in Golang

go-socket.io is library an implementation of Socket.IO in Golang, which is a realtime application framework.

Jan 5, 2023
A go library for consuming Binance Websocket Market Streams

binancestream A go library for consuming Binance Websocket Market Streams This library handles network failures by automatically reconnecting to the w

Aug 1, 2022
Real-time Map displays real-time positions of public transport vehicles in Helsinki
Real-time Map displays real-time positions of public transport vehicles in Helsinki

Real-time Map Real-time Map displays real-time positions of public transport vehicles in Helsinki. It's a showcase for Proto.Actor - an ultra-fast dis

Nov 30, 2022
LinDB is an open-source Time Series Database which provides high performance, high availability and horizontal scalability.
LinDB is an open-source Time Series Database which provides high performance, high availability and horizontal scalability.

LinDB is an open-source Time Series Database which provides high performance, high availability and horizontal scalability. LinDB stores all monitoring data of ELEME Inc, there is 88TB incremental writes per day and 2.7PB total raw data.

Jan 1, 2023
💨 A real time messaging system to build a scalable in-app notifications, multiplayer games, chat apps in web and mobile apps.
💨 A real time messaging system to build a scalable in-app notifications, multiplayer games, chat apps in web and mobile apps.

Beaver A Real Time Messaging Server. Beaver is a real-time messaging server. With beaver you can easily build scalable in-app notifications, realtime

Jan 1, 2023
Scalable real-time messaging server in language-agnostic way
Scalable real-time messaging server in language-agnostic way

Centrifugo is a scalable real-time messaging server in language-agnostic way. Centrifugo works in conjunction with application backend written in any

Jan 2, 2023
Scalable real-time messaging server in language-agnostic way
Scalable real-time messaging server in language-agnostic way

Centrifugo is a scalable real-time messaging server in language-agnostic way. Centrifugo works in conjunction with application backend written in any

Jan 1, 2023
A LoRaWAN nodes' and network simulator that works with a real LoRaWAN environment (such as Chirpstack) and equipped with a web interface for real-time interaction.
A LoRaWAN nodes' and network simulator that works with a real LoRaWAN environment (such as Chirpstack) and equipped with a web interface for real-time interaction.

LWN Simulator A LoRaWAN nodes' simulator to simulate a LoRaWAN Network. Table of Contents General Info Requirements Installation General Info LWN Simu

Nov 20, 2022
Instant messaging server for the Extensible Messaging and Presence Protocol (XMPP).
Instant messaging server for the Extensible Messaging and Presence Protocol (XMPP).

Instant messaging server for the Extensible Messaging and Presence Protocol (XMPP).

Dec 31, 2022
Simple-messaging - Brokerless messaging. Pub/Sub. Producer/Consumer. Pure Go. No C.

Simple Messaging Simple messaging for pub/sub and producer/consumer. Pure Go! Usage Request-Response Producer: consumerAddr, err := net.ResolveTCPAddr

Jan 20, 2022
Kitex byte-dance internal Golang microservice RPC framework with high performance and strong scalability, customized extensions for byte internal.
Kitex byte-dance internal Golang microservice RPC framework with high performance and strong scalability, customized extensions for byte internal.

Kitex 字节跳动内部的 Golang 微服务 RPC 框架,具有高性能、强可扩展的特点,针对字节内部做了定制扩展。

Jan 9, 2023
A web application framework with complete functions and good scalability
A web application framework with complete functions and good scalability

English | 中文 Abuout Goravel Goravel is a web application framework with complete

Jan 6, 2023
Fast specialized time-series database for IoT, real-time internet connected devices and AI analytics.
Fast specialized time-series database for IoT, real-time internet connected devices and AI analytics.

unitdb Unitdb is blazing fast specialized time-series database for microservices, IoT, and realtime internet connected devices. As Unitdb satisfy the

Jan 1, 2023
Time-Based One-Time Password (TOTP) and HMAC-Based One-Time Password (HOTP) library for Go.

otpgo HMAC-Based and Time-Based One-Time Password (HOTP and TOTP) library for Go. Implements RFC 4226 and RFC 6238. Contents Supported Operations Read

Dec 19, 2022
GO2P is a P2P framework, designed with flexibility and simplicity in mind
GO2P is a P2P framework, designed with flexibility and simplicity in mind

go2p golang p2p framework By v-braun - viktor-braun.de. Description GO2P is a P2P framework, designed with flexibility and simplicity in mind. You can

Jan 5, 2023
Turn asterisk-indented text lines into mind maps
Turn asterisk-indented text lines into mind maps

Crumbs Turn asterisk-indented text lines into mind maps. Organize your notes in a hierarchical tree structure, using a simple text editor. an asterisk

Jan 8, 2023
🏮Blazing fast URL shortener made with simplicity in mind

klein Blazing fast URL shortener made with simplicity in mind Structures The project is what people would call a "monolith".

Feb 16, 2022
🏮 ― Blazing fast URL shortener made with simplicity in mind

klein Blazing fast URL shortener made with simplicity in mind Run As easy as filling out config/config.yaml and running make. Of course, you need to h

Feb 16, 2022
Oogway is a simple web server with dynamic content generation and extendability in mind supporting a Git based workflow.

Oogway Oogway is a simple web server with dynamic content generation and extendability in mind supporting a Git based workflow. It's somewhere in betw

Nov 9, 2022