From 76b9dbf2543e2f44aacaf74742d97c23cf89ce47 Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Sat, 15 Jun 2024 21:20:55 +0800 Subject: [PATCH 1/8] Adds documentation for server package --- server/server.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/server/server.go b/server/server.go index c6423d2..8b41bb2 100644 --- a/server/server.go +++ b/server/server.go @@ -1,3 +1,31 @@ +// Package server provides a HTTP server with custom timeouts and error handling. +// +// This package is designed to be used with the wasabi package for handling HTTP requests. +// It provides a Server struct that wraps the standard http.Server with additional functionality. +// +// The Server struct includes a base context, a net.Listener for accepting connections, and a mutex for thread safety. +// It also includes a ready channel that can be used to signal when the server is ready to accept connections. +// +// The server uses custom read header and read timeouts, defined as constants. +// These timeouts help to prevent slow client attacks by limiting the amount of time the server will wait for a client to send its request. +// +// The server also provides a custom error, ErrServerAlreadyRunning, which is returned when there's an attempt to start a server that's already running. +// +// Usage: +// +// s := &server.Server{ +// baseCtx: context.Background(), +// handler: &http.Server{ +// Addr: ":8080", +// Handler: myHandler, +// }, +// } +// err := s.Start() +// if err != nil { +// log.Fatal(err) +// } +// +// This will start a new server on port 8080 with the provided handler. package server import ( From 1247f432e6c837540c02d163f4f25806264eb6b6 Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Sun, 16 Jun 2024 11:11:16 +0800 Subject: [PATCH 2/8] Adds core concepts description and server concept description --- README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++- server/server.go | 11 ++-------- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index df367fe..9feadf0 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ func main() { // Finally, we start the server with server.Run. // If the server fails to start, we log the error and exit the program. - if err := server.Run(context.Background()); err != nil { + if err := server.Run(); err != nil { slog.Error("Fail to start app server", "error", err) os.Exit(1) } @@ -98,6 +98,56 @@ func main() { } ``` + +## Core concepts + +- **Server**: This is the main component that listens for incoming HTTP requests. It manages channels and dispatches requests to them. +- **Channel**: A channel represents an endpoint for WebSocket connections. It's responsible for handling all WebSocket connections and messages for a specific path. +- **Dispatcher**: This acts as a router for incoming WebSocket messages. It uses middleware to process messages and dispatches them to the appropriate backend. +- **Backend**: This is the handler for WebSocket messages. Once a message has been processed by the dispatcher and any middleware, it's sent to the backend for further processing. +- **Connection Registry**: This is a central registry for managing WebSocket connections. It keeps track of all active connections and their settings. +- **Connection**: This represents a single WebSocket connection. It's managed by the connection registry and used by the server and channel to send and receive messages. +- **Request**: This represents a single WebSocket message. It's created by the server when a new message is received and processed by the dispatcher and backend. +- **Middleware** is used to process messages before they reach the backend. There are two types of middleware: + - **HTTP Middleware**: This processes incoming HTTP requests before they're upgraded to WebSocket connections. + - **Request Middleware**: This processes incoming WebSocket messages before they're dispatched to the backend. + + +### Server + +The Server is the main component of the library. It listens for incoming HTTP requests, manages channels, and dispatches requests to them. The server is responsible for starting the WebSocket service and managing its lifecycle. + +When a new server is created with `server.NewServer`, it's initialized with a port number. This is the port that the server will listen on for incoming HTTP requests. + +```golang +server := server.NewServer(":8080") +``` + +Channels are added to the server with `server.AddChannel`. Each channel represents a different WebSocket endpoint. The server will dispatch incoming WebSocket requests to the appropriate channel based on the request path. + +```golang +channel := channel.NewChannel("/", dispatcher, connRegistry) +server.AddChannel(channel) +``` + +The server is started with `server.Run`. This method takes a context, which is used to control the server's lifecycle. If the server fails to start for any reason, `server.Run` will return an error. + +```golang +if err := server.Run(); err != nil { + slog.Error("Fail to start app server", "error", err) + os.Exit(1) +} +``` + +In this example, if the server fails to start, the error is logged and the program is exited with a non-zero status code. + +The server is a crucial part of the WebSocket service. It's responsible for managing the service's lifecycle and dispatching HTTP requests to the appropriate channels. + + +### Channel + +### Dispatcher + ## Contributing Contributions to Wasabi are welcome! Please submit a pull request or create an issue to contribute. diff --git a/server/server.go b/server/server.go index 8b41bb2..445d07a 100644 --- a/server/server.go +++ b/server/server.go @@ -9,17 +9,10 @@ // The server uses custom read header and read timeouts, defined as constants. // These timeouts help to prevent slow client attacks by limiting the amount of time the server will wait for a client to send its request. // -// The server also provides a custom error, ErrServerAlreadyRunning, which is returned when there's an attempt to start a server that's already running. -// // Usage: // -// s := &server.Server{ -// baseCtx: context.Background(), -// handler: &http.Server{ -// Addr: ":8080", -// Handler: myHandler, -// }, -// } +// s := server.NewServer(":8080") +// s.AddChannel(channel.NewChannel("/path", handler)) // err := s.Start() // if err != nil { // log.Fatal(err) From e50ce566b20133dc4236b2ac502314b53c7abf1e Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Sun, 16 Jun 2024 11:23:15 +0800 Subject: [PATCH 3/8] Adds description for Channel core concept --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9feadf0..4f20662 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,8 @@ The Server is the main component of the library. It listens for incoming HTTP re When a new server is created with `server.NewServer`, it's initialized with a port number. This is the port that the server will listen on for incoming HTTP requests. ```golang +import "github.com/ksysoev/wasabi/server" + server := server.NewServer(":8080") ``` @@ -143,9 +145,28 @@ In this example, if the server fails to start, the error is logged and the progr The server is a crucial part of the WebSocket service. It's responsible for managing the service's lifecycle and dispatching HTTP requests to the appropriate channels. - ### Channel +A Channel represents an endpoint for WebSocket connections. It's responsible for handling all WebSocket connections and messages for a specific path. + +When a new channel is created with `channel.NewChannel`, it's initialized with a path, a dispatcher, and a connection registry. The path is the URL path that the channel will handle. The dispatcher is used to process incoming WebSocket messages, and the connection registry is used to manage active WebSocket connections. + +```golang +channel := channel.NewChannel("/chat", dispatcher, connRegistry) +``` + +In this example, a new channel is created to handle WebSocket connections on the `/chat` path. + +Channels are added to a server with `server.AddChannel`. The server will dispatch incoming WebSocket requests to the appropriate channel based on the request path. + +```golang +server.AddChannel(channel) +``` + +In this example, the channel is added to the server. Any incoming WebSocket requests on the `/chat` path will be handled by this channel. + +The channel is a crucial part of the WebSocket service. It's responsible for managing WebSocket connections and processing WebSocket messages for a specific path. + ### Dispatcher ## Contributing From 7b341e9b5aee0dcff3864418721f9a562a72cf88 Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Sun, 16 Jun 2024 12:08:17 +0800 Subject: [PATCH 4/8] Adds dipatcher concept description --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f20662..52c47a4 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,8 @@ func main() { // We create a new dispatcher with dispatch.NewPipeDispatcher. // This dispatcher sends/routes each incoming WebSocket message to the backend. - dispatcher := dispatch.NewRouterDispatcher(backend, func(conn wasabi.Connection, msgType wasabi.MessageType, data []byte) wasabi.Request { - return dispatch.NewRawRequest(conn.Context(), msgType, data) + dispatcher := dispatch.NewRouterDispatcher(backend, func(conn wasabi.Connection, ctx context.Context, msgType wasabi.MessageType, data []byte) wasabi.Request { + return dispatch.NewRawRequest(ctx, msgType, data) }) dispatcher.Use(ErrHandler) @@ -169,6 +169,19 @@ The channel is a crucial part of the WebSocket service. It's responsible for man ### Dispatcher +A Dispatcher acts as a router for incoming WebSocket messages. It uses middleware to process messages and dispatches them to the appropriate backend. + +When a new dispatcher is created with `dispatcher.NewRouterDispatcher`, it's initialized with a default backend and request parser. The backend is the handler for WebSocket messages. Once a message has been processed by the dispatcher and any middleware, it's sent to the backend for further processing. + +```golang +dispatcher := dispatcher.NewRouterDispatcher( + backend, + func(conn wasabi.Connection, ctx context.Context, msgType wasabi.MessageType, data []byte) wasabi.Request { + return dispatch.NewRawRequest(ctx, msgType, data) + }, +) +``` + ## Contributing Contributions to Wasabi are welcome! Please submit a pull request or create an issue to contribute. From 89bba9e98029c8a505a3ccbc2fab81ab03437ecc Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Sun, 16 Jun 2024 14:25:46 +0800 Subject: [PATCH 5/8] Adds section about backend abstraction --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 52c47a4..1acf990 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,9 @@ A Channel represents an endpoint for WebSocket connections. It's responsible for When a new channel is created with `channel.NewChannel`, it's initialized with a path, a dispatcher, and a connection registry. The path is the URL path that the channel will handle. The dispatcher is used to process incoming WebSocket messages, and the connection registry is used to manage active WebSocket connections. ```golang -channel := channel.NewChannel("/chat", dispatcher, connRegistry) +import "github.com/ksysoev/wasabi/channel" + +chatChan := channel.NewChannel("/chat", dispatcher, connRegistry) ``` In this example, a new channel is created to handle WebSocket connections on the `/chat` path. @@ -160,7 +162,7 @@ In this example, a new channel is created to handle WebSocket connections on the Channels are added to a server with `server.AddChannel`. The server will dispatch incoming WebSocket requests to the appropriate channel based on the request path. ```golang -server.AddChannel(channel) +server.AddChannel(chatChan) ``` In this example, the channel is added to the server. Any incoming WebSocket requests on the `/chat` path will be handled by this channel. @@ -174,14 +176,58 @@ A Dispatcher acts as a router for incoming WebSocket messages. It uses middlewar When a new dispatcher is created with `dispatcher.NewRouterDispatcher`, it's initialized with a default backend and request parser. The backend is the handler for WebSocket messages. Once a message has been processed by the dispatcher and any middleware, it's sent to the backend for further processing. ```golang -dispatcher := dispatcher.NewRouterDispatcher( - backend, +import "github.com/ksysoev/wasabi/dispatcher" + +chatDipatcher := dispatcher.NewRouterDispatcher( + myBackend, func(conn wasabi.Connection, ctx context.Context, msgType wasabi.MessageType, data []byte) wasabi.Request { return dispatch.NewRawRequest(ctx, msgType, data) }, ) ``` +In this example, a new dispatcher is created with a custom backend that is stored in `myBackend` variable. The second argument is the request parser that accepts WebSocket messages and returns Request. + +The router dispatcher allows to routing of incoming WebSocket messages to the desired backend, to add additional backends to the created dispatcher you can use `channel.AddBackend` method: + +```golang +chatDipatcher.AddBackend(myNotificationBackend, []string{"notifications", "subscriptions"}) +``` + +In this example, we're adding a backend to the chatDispatcher. The backend is named myNotificationBackend and it's being associated with two routing keys: "notifications" and "subscriptions". + +The dispatcher is responsible for processing WebSocket messages and dispatching them to the appropriate backend. + +### Backend + +A Backend is the handler for WebSocket messages. After a message has been processed by the dispatcher and any middleware, it's forwarded to the backend for further processing. + +To integrate a backend with the dispatcher, it should implement the `wasabi.RequestHandler` interface. This interface ensures that the backend has the necessary method for handling requests. + +Wasabi provides several predefined backends that can be used directly: + +- **HTTP backend**: This backend is designed for integration with HTTP application servers. It allows WebSocket messages to be processed and responded to using standard HTTP request handling. +- **WebSocket backend**: This backend is designed for integration with WebSocket applications. It provides a direct interface for processing WebSocket messages. +- **Queue backend**: This backend is designed for integration with backend applications via queue systems like RabbitMQ, Redis, Kafka, etc. It allows WebSocket messages to be processed asynchronously and in a distributed manner. + +Here's an example of creating an HTTP backend: + +```golang +backend := backend.NewBackend(func(req wasabi.Request) (*http.Request, error) { + httpReq, err := http.NewRequest("POST", "http://localhost:8081/", bytes.NewBuffer(req.Data())) + if err != nil { + return nil, err + } + + return httpReq, nil +}) +``` + +In this code example, we're creating an HTTP backend to integrate with our application service. The backend takes a WebSocket request, creates a new HTTP request with the same data, and returns the HTTP request for further processing. + + + + ## Contributing Contributions to Wasabi are welcome! Please submit a pull request or create an issue to contribute. From bea8c440cad708c44c286d37fad29121c61e6d0b Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Sun, 16 Jun 2024 15:04:51 +0800 Subject: [PATCH 6/8] Adds documentation for connection and connection registry sections --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1acf990..5bac230 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ func main() { // We create an error handling middleware with request.NewErrorHandlingMiddleware. // This middleware logs any errors that occur when handling a request and sends a response back to the client. ErrHandler := request.NewErrorHandlingMiddleware(func(conn wasabi.Connection, req wasabi.Request, err error) error { - conn.Send([]byte("Failed to process request: " + err.Error())) + conn.Send(wasabi.MsgTypeTex, []byte("Failed to process request: " + err.Error())) return nil }) @@ -147,9 +147,16 @@ The server is a crucial part of the WebSocket service. It's responsible for mana ### Channel -A Channel represents an endpoint for WebSocket connections. It's responsible for handling all WebSocket connections and messages for a specific path. +A Channel in the context of WebSocket connections serves as an endpoint. It is responsible for managing WebSocket connections and messages for a specific path. -When a new channel is created with `channel.NewChannel`, it's initialized with a path, a dispatcher, and a connection registry. The path is the URL path that the channel will handle. The dispatcher is used to process incoming WebSocket messages, and the connection registry is used to manage active WebSocket connections. +Key responsibilities of the Channel abstraction include: + +- Processing client requests to establish WebSocket connections. +- Storing configuration required for establishing WebSocket connections. +- Managing and executing middleware for HTTP requests. + + +When a new channel is created with `channel.NewChannel`, it is initialized with a path, a dispatcher, and a connection registry. The path is the URL path that the channel will handle. The dispatcher is used to process incoming WebSocket messages, and the connection registry is used to manage active WebSocket connections. ```golang import "github.com/ksysoev/wasabi/channel" @@ -167,7 +174,41 @@ server.AddChannel(chatChan) In this example, the channel is added to the server. Any incoming WebSocket requests on the `/chat` path will be handled by this channel. -The channel is a crucial part of the WebSocket service. It's responsible for managing WebSocket connections and processing WebSocket messages for a specific path. +### Connection Registry + +The Connection Registry is responsible for: + +- Managing the lifecycle of connections. +- Defining WebSocket connections configurations. +- Managing hooks for establishing and closing client connections. + +```golang +import "github.com/ksysoev/wasabi/channel" + +connRegistry := channel.NewConnectionRegistry() +``` + +In this example, a new connection registry is created. + +### Connection + +A Connection represents an active WebSocket connection. It provides methods for sending messages and closing the connection. + +To send a message, use the `Send` method. This method takes a message type and a bytes slice as arguments. + +```golang +err := conn.Send(wasabi.MsgTypeText, "Hello World!") +``` + +In this example, a text message "Hello World!" is being sent over the WebSocket connection. + +To close a WebSocket connection, use the `Close` method. This method takes a status code and a string reason as arguments. + +```golang +conn.Close(websocket.StatusGoingAway, "Server is restarting") +``` + +In this example, the WebSocket connection is being closed with a status code indicating that the server is going away and a reason "Server is restarting". ### Dispatcher @@ -226,8 +267,6 @@ backend := backend.NewBackend(func(req wasabi.Request) (*http.Request, error) { In this code example, we're creating an HTTP backend to integrate with our application service. The backend takes a WebSocket request, creates a new HTTP request with the same data, and returns the HTTP request for further processing. - - ## Contributing Contributions to Wasabi are welcome! Please submit a pull request or create an issue to contribute. From e938bd7b516fa5b6d7ce3a7014a096f5ac7b0d50 Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Sun, 16 Jun 2024 15:24:13 +0800 Subject: [PATCH 7/8] Adds documentation for request abstraction --- README.md | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5bac230..bad5580 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ chatDipatcher := dispatcher.NewRouterDispatcher( In this example, a new dispatcher is created with a custom backend that is stored in `myBackend` variable. The second argument is the request parser that accepts WebSocket messages and returns Request. -The router dispatcher allows to routing of incoming WebSocket messages to the desired backend, to add additional backends to the created dispatcher you can use `channel.AddBackend` method: +The router dispatcher allows to routing of incoming WebSocket messages to multiple backends, to add additional backends to the created dispatcher you can use `channel.AddBackend` method: ```golang chatDipatcher.AddBackend(myNotificationBackend, []string{"notifications", "subscriptions"}) @@ -239,6 +239,47 @@ In this example, we're adding a backend to the chatDispatcher. The backend is na The dispatcher is responsible for processing WebSocket messages and dispatching them to the appropriate backend. +# Request + +A Request represents a single WebSocket message. It encapsulates the data and metadata of a WebSocket message that is to be processed by the dispatcher and backend. + +To allow integration with the dispatcher and backend abstractions, the request structure should implement the wasabi.Request interface. This interface ensures that the request has the necessary methods for handling and processing. + +- `Context`: This method returns the context of the request. It can be used to carry request-scoped values, cancellation signals, and deadlines across API boundaries and between processes. +- `Data`: This method returns the data of the WebSocket message. This is the actual content of the message that needs to be processed. +- `RoutingKey`: This method returns the routing key that will be used for routing the message to the correct backend. +- `WithContext(ctx context.Context)`: This method is used to assign an adjusted context to the request. It's useful when you want to propagate a new derived context to the request. + +Here's an example of a custom request structure that implements the wasabi.Request interface: + +```golang +type MyRequest struct { + ctx context.Context + msgType wasabi.MessageType + data []byte + routingKey string +} + +func (r *MyRequest) Context() context.Context { + return r.ctx +} + +func (r *MyRequest) Data() []byte { + return r.data +} + +func (r *MyRequest) RoutingKey() string { + return r.routingKey +} + +func (r *MyRequest) WithContext(ctx context.Context) wasabi.Request { + r.ctx = ctx + return r +} +``` + +In this example, `MyRequest` implements the `wasabi.Request` interface. It can now be used with the dispatcher and backend abstractions to process WebSocket messages. + ### Backend A Backend is the handler for WebSocket messages. After a message has been processed by the dispatcher and any middleware, it's forwarded to the backend for further processing. From 05a8cc36d77c516ff63f9606f3fa424e22357082 Mon Sep 17 00:00:00 2001 From: Kirill Sysoev Date: Sun, 16 Jun 2024 15:40:35 +0800 Subject: [PATCH 8/8] feat: Add documentation for middleware concepts and usage --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index bad5580..f90ab19 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,45 @@ backend := backend.NewBackend(func(req wasabi.Request) (*http.Request, error) { In this code example, we're creating an HTTP backend to integrate with our application service. The backend takes a WebSocket request, creates a new HTTP request with the same data, and returns the HTTP request for further processing. +### Middleware + +Middleware in Wasabi provides a way to perform additional processing on HTTP requests and WebSocket messages. There are two types of middleware: HTTP Middleware and Request Middleware. + +#### HTTP Middleware + +HTTP Middleware is applied to HTTP requests that are used to establish WebSocket connections. This type of middleware is useful when you need to apply connection-wide values or handle HTTP headers. + +For example, you might use HTTP Middleware to process the Authorization header and authenticate the client before establishing a WebSocket connection. + +#### Request Middleware + +Request Middleware is applied at the WebSocket message level. This type of middleware is useful when you need to apply logic that is related to WebSocket requests. + +For example, you might use Request Middleware to validate the data in WebSocket messages or to transform the data before it's processed by the backend. + +Here's an example of how to add HTTP Middleware and Request Middleware to a channel: + +```golang +import ( + "github.com/ksysoev/wasabi/channel" + "github.com/ksysoev/wasabi/dispatcher" + "github.com/ksysoev/wasabi/middleware/http" + "github.com/ksysoev/wasabi/middleware/request" +) + +// Example for HTTP midleware +ClientIPHandler := http.NewClientIPMiddleware(http.Cloudflare) +myChan.Use(ClientIPHandler) + +// Example for Request middleware +ErrHandler := request.NewErrorHandlingMiddleware(func(conn wasabi.Connection, req wasabi.Request, err error) error { + conn.Send(wasabi.MsgTypeTex, []byte("Failed to process request: " + err.Error())) + return nil +}) +myDispatch.Use(ErrHandler) +``` + +In this example, ClientIPHandler is an HTTP middleware that extracts the client's IP address from the HTTP headers. ErrHandler is a Request middleware that handles errors during the processing of WebSocket messages. Both middleware are added to their respective handlers using the Use method. ## Contributing