Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: wire v2 handlers #22112

Merged
merged 32 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
233a249
server v2 rest for new modules
Sep 30, 2024
d7202e4
server config with defaults
Sep 30, 2024
f1d7ebe
pre msgrouter
Sep 30, 2024
75e1924
use handlers
Oct 1, 2024
86d450f
it queries
Oct 2, 2024
f0af908
lint
Oct 3, 2024
22b9f92
Merge remote-tracking branch 'origin/main' into feat/wire-v2-handlers
Oct 3, 2024
45b5d74
Merge remote-tracking branch 'origin/main' into feat/wire-v2-handlers
Oct 3, 2024
a67fcc9
include README
Oct 4, 2024
3007e67
create constructor
Oct 4, 2024
b4027dd
add lint
Oct 4, 2024
85bd55a
Merge branch 'main' into feat/wire-v2-handlers
randygrok Oct 7, 2024
f22a135
remove mux usage
Oct 7, 2024
5d05234
Merge branch 'feat/wire-v2-handlers' of github.com-randy:cosmos/cosmo…
Oct 7, 2024
ee8d0a4
go mod tidy
Oct 7, 2024
40b5bae
Merge branch 'main' into feat/wire-v2-handlers
randygrok Oct 8, 2024
52d57cf
Merge branch 'main' into feat/wire-v2-handlers
randygrok Oct 8, 2024
a733c83
changes due to review
Oct 10, 2024
8d78a2c
Merge branch 'feat/wire-v2-handlers' of github.com-randy:cosmos/cosmo…
Oct 10, 2024
8dc09a3
add enable flag
Oct 10, 2024
c186af2
fix config check
Oct 10, 2024
2cf4c62
add some config options
Oct 10, 2024
9e6b8fb
remove Fprintf
Oct 10, 2024
ac25a49
improve part of the code
Oct 10, 2024
9dd5c9f
refactor a little
Oct 10, 2024
2b028fd
Merge branch 'main' into feat/wire-v2-handlers
randygrok Oct 14, 2024
1c7d32c
use cosmos jsonpb
Oct 14, 2024
506267d
unmarshal server config
Oct 14, 2024
a8a173e
Merge branch 'main' into feat/wire-v2-handlers
randygrok Oct 16, 2024
3608feb
make lint and comments from review
Oct 16, 2024
c700ad9
Merge branch 'feat/wire-v2-handlers' of github.com-randy:cosmos/cosmo…
Oct 16, 2024
db3ac74
lint fix
julienrbrt Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion runtime/v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
cosmossdk.io/store/v2 v2.0.0-00010101000000-000000000000
cosmossdk.io/x/tx v0.13.3
github.com/cosmos/gogoproto v1.7.0
github.com/stretchr/testify v1.9.0
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1
)
Expand Down Expand Up @@ -70,7 +71,7 @@ require (
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions runtime/v2/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
Expand Down
50 changes: 50 additions & 0 deletions runtime/v2/services_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package runtime

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/transaction"
"cosmossdk.io/server/v2/stf"
)

// MockModule implements both HasMsgHandlers and HasQueryHandlers
type MockModule struct {
mock.Mock
appmodulev2.AppModule
}

func (m *MockModule) RegisterMsgHandlers(router appmodulev2.MsgRouter) {
m.Called(router)
}

func (m *MockModule) RegisterQueryHandlers(router appmodulev2.QueryRouter) {
m.Called(router)
}

func TestRegisterServices(t *testing.T) {
mockModule := new(MockModule)

app := &App[transaction.Tx]{
msgRouterBuilder: stf.NewMsgRouterBuilder(),
queryRouterBuilder: stf.NewMsgRouterBuilder(),
}

mm := &MM[transaction.Tx]{
modules: map[string]appmodulev2.AppModule{
"mock": mockModule,
},
}

mockModule.On("RegisterMsgHandlers", app.msgRouterBuilder).Once()
mockModule.On("RegisterQueryHandlers", app.queryRouterBuilder).Once()

err := mm.RegisterServices(app)

assert.NoError(t, err)

mockModule.AssertExpectations(t)
}
73 changes: 73 additions & 0 deletions server/v2/api/rest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Cosmos SDK REST API

This document describes how to use a service that exposes endpoints based on Cosmos SDK Protobuf message types. Each endpoint responds with data in JSON format.

## General Description

The service allows querying the blockchain using any type of Protobuf message available in the Cosmos SDK application through HTTP `POST` requests. Each endpoint corresponds to a Cosmos SDK protocol message (`proto`), and responses are returned in JSON format.
Comment on lines +1 to +7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve grammar in the introduction

The introduction is clear and informative. However, there's a minor grammar issue in the last sentence.

Apply this change to improve the grammar:

-The service allows querying the blockchain using any type of Protobuf message available in the Cosmos SDK application through HTTP `POST` requests. Each endpoint corresponds to a Cosmos SDK protocol message (`proto`), and responses are returned in JSON format.
+The service allows querying the blockchain using any type of Protobuf message available in the Cosmos SDK application through HTTP `POST` requests. Each endpoint corresponds to a Cosmos SDK protocol message (`proto`), and responses are returned as JSON format.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Cosmos SDK REST API
This document describes how to use a service that exposes endpoints based on Cosmos SDK Protobuf message types. Each endpoint responds with data in JSON format.
## General Description
The service allows querying the blockchain using any type of Protobuf message available in the Cosmos SDK application through HTTP `POST` requests. Each endpoint corresponds to a Cosmos SDK protocol message (`proto`), and responses are returned in JSON format.
# Cosmos SDK REST API
This document describes how to use a service that exposes endpoints based on Cosmos SDK Protobuf message types. Each endpoint responds with data in JSON format.
## General Description
The service allows querying the blockchain using any type of Protobuf message available in the Cosmos SDK application through HTTP `POST` requests. Each endpoint corresponds to a Cosmos SDK protocol message (`proto`), and responses are returned as JSON format.
🧰 Tools
🪛 LanguageTool

[grammar] ~7-~7: The usual collocation for “returned” is “to”, not “in”.
Context: ...ol message (proto), and responses are returned in JSON format. ## Example ### 1. `Query...

(RETURN_IN_THE)


## Example

### 1. `QueryBalanceRequest`

This endpoint allows querying the balance of an account given an address and a token denomination.

- **URL:** `localhost:8080/cosmos.bank.v2.QueryBalanceRequest`

- **Method:** `POST`

- **Headers:**

- `Content-Type: application/json`

- **Body (JSON):**

```json
{
"address": "<ACCOUNT_ADDRESS>",
"denom": "<TOKEN_DENOMINATION>"
}
```

- `address`: Account address on the Cosmos network.
- `denom`: Token denomination (e.g., `stake`).

- **Request Example:**

```
POST localhost:8080/cosmos.bank.v2.QueryBalanceRequest
Content-Type: application/json

{
"address": "cosmos16tms8tax3ha9exdu7x3maxrvall07yum3rdcu0",
"denom": "stake"
}
```

- **Response Example (JSON):**

```json
{
"balance": {
"denom": "stake",
"amount": "1000000"
}
}
```

The response shows the balance of the specified token for the given account.
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved

## Using Tools

### 1. Using `curl`

To make a request using `curl`, you can run the following command:

```bash
curl -X POST localhost:8080/cosmos.bank.v2.QueryBalanceRequest \
-H "Content-Type: application/json" \
-d '{
"address": "cosmos16tms8tax3ha9exdu7x3maxrvall07yum3rdcu0",
"denom": "stake"
}'
```
32 changes: 32 additions & 0 deletions server/v2/api/rest/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package rest

func DefaultConfig() *Config {
return &Config{
Enable: true,
Address: "localhost:8080",
}
}

type CfgOption func(*Config)
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved

// Config defines configuration for the REST server.
type Config struct {
// Enable defines if the REST server should be enabled.
Enable bool `mapstructure:"enable" toml:"enable" comment:"Enable defines if the REST server should be enabled."`
// Address defines the API server to listen on
Address string `mapstructure:"address" toml:"address" comment:"Address defines the REST server address to bind to."`
}

// OverwriteDefaultConfig overwrites the default config with the new config.
func OverwriteDefaultConfig(newCfg *Config) CfgOption {
return func(cfg *Config) {
*cfg = *newCfg
}
}

// Disable the rest server by default (default enabled).
func Disable() CfgOption {
return func(cfg *Config) {
cfg.Enable = false
}
}
99 changes: 99 additions & 0 deletions server/v2/api/rest/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package rest

import (
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed

Check notice

Code scanning / CodeQL

Sensitive package import Note

Certain system packages contain functions which may be a possible source of non-determinism
"strings"

"github.com/cosmos/gogoproto/jsonpb"
gogoproto "github.com/cosmos/gogoproto/proto"

"cosmossdk.io/core/transaction"
"cosmossdk.io/server/v2/appmanager"
)

const (
ContentTypeJSON = "application/json"
MaxBodySize = 1 << 20 // 1 MB
)

func NewDefaultHandler[T transaction.Tx](appManager *appmanager.AppManager[T]) http.Handler {
return &DefaultHandler[T]{appManager: appManager}
}

type DefaultHandler[T transaction.Tx] struct {
appManager *appmanager.AppManager[T]
}

func (h *DefaultHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h.validateMethodIsPOST(r); err != nil {
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
return
}

if err := h.validateContentTypeIsJSON(r); err != nil {
http.Error(w, err.Error(), http.StatusUnsupportedMediaType)
return
}

msg, err := h.createMessage(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid exposing internal error details to clients

Revealing internal error messages to clients can expose sensitive information. Return a generic error message to the client and log the detailed error internally.

Apply this diff to return a generic error message:

-		http.Error(w, err.Error(), http.StatusBadRequest)
+		http.Error(w, "Bad Request", http.StatusBadRequest)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
http.Error(w, err.Error(), http.StatusBadRequest)
http.Error(w, "Bad Request", http.StatusBadRequest)

return
}

query, err := h.appManager.Query(r.Context(), 0, msg)
if err != nil {
http.Error(w, "Error querying", http.StatusInternalServerError)
return
}
Comment on lines +48 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Log internal errors for better diagnostics

In the ServeHTTP method, when h.appManager.Query returns an error, the error is not logged. Logging internal errors while returning a generic message to the client aids in debugging without exposing sensitive information.

Apply this diff to log the error internally:

 if err != nil {
+	log.Printf("Error querying: %v", err)
 	http.Error(w, "Error querying", http.StatusInternalServerError)
 	return
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
query, err := h.appManager.Query(r.Context(), 0, msg)
if err != nil {
http.Error(w, "Error querying", http.StatusInternalServerError)
return
}
query, err := h.appManager.Query(r.Context(), 0, msg)
if err != nil {
log.Printf("Error querying: %v", err)
http.Error(w, "Error querying", http.StatusInternalServerError)
return
}


w.Header().Set("Content-Type", ContentTypeJSON)
if err := json.NewEncoder(w).Encode(query); err != nil {
http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid exposing internal error details when encoding response

Including internal error details in the HTTP response may leak sensitive information. Return a generic error message to the client and log the detailed error internally.

Apply this diff to return a generic error message:

-		http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError)
+		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)

}
}

// validateMethodIsPOST validates that the request method is POST.
func (h *DefaultHandler[T]) validateMethodIsPOST(r *http.Request) error {
if r.Method != http.MethodPost {
return fmt.Errorf("method not allowed")
}
Comment on lines +62 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve error message for unsupported methods

In the validateMethodIsPOST function, the error message "method not allowed" lacks specificity. To enhance clarity and adhere to Go's error handling conventions, consider returning the HTTP status text corresponding to the error.

Apply this diff to improve the error message:

-		return fmt.Errorf("method not allowed")
+		return fmt.Errorf(http.StatusText(http.StatusMethodNotAllowed))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if r.Method != http.MethodPost {
return fmt.Errorf("method not allowed")
}
if r.Method != http.MethodPost {
return fmt.Errorf(http.StatusText(http.StatusMethodNotAllowed))
}

return nil
}

// validateContentTypeIsJSON validates that the request content type is JSON.
func (h *DefaultHandler[T]) validateContentTypeIsJSON(r *http.Request) error {
contentType := r.Header.Get("Content-Type")
if contentType != ContentTypeJSON {
return fmt.Errorf("unsupported content type, expected %s", ContentTypeJSON)
}

return nil
Comment on lines +70 to +75
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle Content-Type header with parameters

The current validation checks if the Content-Type header exactly matches application/json. However, Content-Type may include parameters like charset=utf-8, causing valid requests to fail. Use mime.ParseMediaType to robustly parse the media type.

Apply this diff to enhance content type validation:

-import "strings"
+import (
+	"mime"
+	"strings"
+)

 func (h *DefaultHandler[T]) validateContentTypeIsJSON(r *http.Request) error {
 	contentType := r.Header.Get("Content-Type")
-	if contentType != ContentTypeJSON {
+	mediaType, _, err := mime.ParseMediaType(contentType)
+	if err != nil || mediaType != ContentTypeJSON {
 		return fmt.Errorf("unsupported content type, expected %s", ContentTypeJSON)
 	}

Committable suggestion was skipped due to low confidence.

}

// createMessage creates the message by unmarshalling the request body.
func (h *DefaultHandler[T]) createMessage(r *http.Request) (gogoproto.Message, error) {
path := strings.TrimPrefix(r.URL.Path, "/")
requestType := gogoproto.MessageType(path)
if requestType == nil {
return nil, fmt.Errorf("unknown request type")
}
Comment on lines +80 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate and sanitize request path

Using r.URL.Path directly can lead to security issues if the path contains unexpected segments or escape characters. Ensure the path is sanitized and validated to prevent potential misuse.

Consider applying this diff to safely extract the message type:

 path := strings.TrimPrefix(r.URL.Path, "/")
+if path == "" {
+	return nil, fmt.Errorf("request path cannot be empty")
+}
+if strings.Contains(path, "/") {
+	return nil, fmt.Errorf("invalid request path")
+}
 requestType := gogoproto.MessageType(path)
 if requestType == nil {
 	return nil, fmt.Errorf("unknown request type")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
path := strings.TrimPrefix(r.URL.Path, "/")
requestType := gogoproto.MessageType(path)
if requestType == nil {
return nil, fmt.Errorf("unknown request type")
}
path := strings.TrimPrefix(r.URL.Path, "/")
if path == "" {
return nil, fmt.Errorf("request path cannot be empty")
}
if strings.Contains(path, "/") {
return nil, fmt.Errorf("invalid request path")
}
requestType := gogoproto.MessageType(path)
if requestType == nil {
return nil, fmt.Errorf("unknown request type")
}


msg, ok := reflect.New(requestType.Elem()).Interface().(gogoproto.Message)
if !ok {
return nil, fmt.Errorf("failed to create message instance")
}

defer r.Body.Close()
limitedReader := io.LimitReader(r.Body, MaxBodySize)
err := jsonpb.Unmarshal(limitedReader, msg)
if err != nil {
return nil, fmt.Errorf("error parsing body: %w", err)
}
Comment on lines +93 to +96
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use io.ReadAll for better error handling

Reading from io.LimitReader and then unmarshalling can lead to partial reads if the body exceeds the MaxBodySize. Instead, read the full body into a byte slice and check for io.EOF to ensure the entire body is read.

Apply this diff to improve request body handling:

 defer r.Body.Close()
-limitedReader := io.LimitReader(r.Body, MaxBodySize)
-err := jsonpb.Unmarshal(limitedReader, msg)
+body, err := io.ReadAll(io.LimitReader(r.Body, MaxBodySize))
+if err != nil {
+	return nil, fmt.Errorf("error reading body: %w", err)
+}
+err = jsonpb.Unmarshal(bytes.NewReader(body), msg)
 if err != nil {
 	return nil, fmt.Errorf("error parsing body: %w", err)
 }

Don't forget to import the bytes package:

 import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"bytes"
 	"net/http"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
err := jsonpb.Unmarshal(limitedReader, msg)
if err != nil {
return nil, fmt.Errorf("error parsing body: %w", err)
}
body, err := io.ReadAll(io.LimitReader(r.Body, MaxBodySize))
if err != nil {
return nil, fmt.Errorf("error reading body: %w", err)
}
err = jsonpb.Unmarshal(bytes.NewReader(body), msg)
if err != nil {
return nil, fmt.Errorf("error parsing body: %w", err)
}


return msg, nil
}
96 changes: 96 additions & 0 deletions server/v2/api/rest/server.go
Copy link
Member

@julienrbrt julienrbrt Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit, for consistency for the other, can we get the logger error lowercase?
additionally, could you fix conflicts?

Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package rest

import (
"context"
"errors"
"fmt"
"net/http"

"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
serverv2 "cosmossdk.io/server/v2"
)

const (
ServerName = "rest-v2"
)

type Server[T transaction.Tx] struct {
logger log.Logger
router *http.ServeMux

httpServer *http.Server
config *Config
cfgOptions []CfgOption
}

func New[T transaction.Tx](cfgOptions ...CfgOption) *Server[T] {
return &Server[T]{
cfgOptions: cfgOptions,
}
}

func (s *Server[T]) Name() string {
return ServerName
}

func (s *Server[T]) Init(appI serverv2.AppI[T], cfg map[string]any, logger log.Logger) error {
s.logger = logger.With(log.ModuleKey, s.Name())

serverCfg := s.Config().(*Config)
if len(cfg) > 0 {
if err := serverv2.UnmarshalSubConfig(cfg, s.Name(), &serverCfg); err != nil {
return fmt.Errorf("failed to unmarshal config: %w", err)
}
}

s.router = http.NewServeMux()
s.router.Handle("/", NewDefaultHandler(appI.GetAppManager()))
s.config = serverCfg

return nil
}

func (s *Server[T]) Start(ctx context.Context) error {
if !s.config.Enable {
s.logger.Info(fmt.Sprintf("%s server is disabled via config", s.Name()))
return nil
}

s.httpServer = &http.Server{
randygrok marked this conversation as resolved.
Show resolved Hide resolved
Addr: s.config.Address,
Handler: s.router,
}

s.logger.Info("starting HTTP server", "address", s.config.Address)
if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
s.logger.Error("failed to start HTTP server", "error", err)
return err
}

return nil
}

func (s *Server[T]) Stop(ctx context.Context) error {
if !s.config.Enable {
return nil
}

s.logger.Info("stopping HTTP server")

return s.httpServer.Shutdown(ctx)
}

func (s *Server[T]) Config() any {
if s.config == nil || s.config.Address == "" {
cfg := DefaultConfig()

for _, opt := range s.cfgOptions {
opt(cfg)
}

return cfg
}

return s.config
}
Loading
Loading