Skip to content

Commit

Permalink
Merge pull request #77 from multiversx/refactor-web-server-config
Browse files Browse the repository at this point in the history
Refactor web server
  • Loading branch information
ssd04 authored Aug 17, 2023
2 parents 0a17f10 + 88113ef commit 1e09bba
Show file tree
Hide file tree
Showing 23 changed files with 392 additions and 189 deletions.
33 changes: 14 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,30 +73,25 @@ Before launching the proxy (notifier) service, it has to be configured so that i
correct environment variables.

The supported config variables are:
- `Port`: the port on which the http server listens on. Should be the same
as the port in the `ProxyUrl` described above.
- `Username`: the username used to authorize an observer. Can be left empty for `UseAuthorization = false`.
- `Password`: the password used to authorize an observer. Can be left empty for `UseAuthorization = false`.
- `Host`: the host and port on which the http server listens on. Should be the same port
as the one specified in the `ProxyUrl` described above.
- `Username`: the username used to authorize an observer. Can be left empty for `UseAuthorization = false` on observer connector.
- `Password`: the password used to authorize an observer. Can be left empty for `UseAuthorization = false` on observer connector.
- `CheckDuplicates`: if true, it will check (based on a locker service using redis) if the event have been already pushed to clients

The [config](https://github.com/multiversx/mx-chain-notifier-go/blob/main/cmd/notifier/config/config.toml) file:

If observer connector is set to use BasicAuth with `UseAuthorization = true`, `Username` and `Password` has to be
set here on events notifier, and `Auth` flag has to be enabled in
[`api.toml`](https://github.com/multiversx/mx-chain-notifier-go/blob/main/cmd/notifier/config/api.toml) config file for events path.
For example:
```toml
[ConnectorApi]
# The port on which the Hub listens for subscriptions
Port = "5000"

# Username is the username needed to authorize an observer to push data
Username = ""

# Password is the password needed to authorize an observer to push event data
Password = ""

# CheckDuplicates signals if the events received from observers have been already pushed to clients
# Requires a redis instance/cluster and should be used when multiple observers push from the same shard
CheckDuplicates = true
[APIPackages.events]
Routes = [
{ Name = "/push", Open = true, Auth = true },
{ Name = "/revert", Open = true, Auth = false },
```

The main config file can be found [here](https://github.com/multiversx/mx-chain-notifier-go/blob/main/cmd/notifier/config/config.toml).

After the configuration file is set up, the notifier instance can be
launched.

Expand Down
47 changes: 30 additions & 17 deletions api/gin/webServer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,25 @@ import (
"github.com/multiversx/mx-chain-notifier-go/config"
)

const (
defaultRestInterface = "localhost:5000"
)

var log = logger.GetOrCreate("api/gin")

// ArgsWebServerHandler holds the arguments needed to create a web server handler
type ArgsWebServerHandler struct {
Facade shared.FacadeHandler
Config config.ConnectorApiConfig
Type string
Facade shared.FacadeHandler
Configs config.Configs
}

// webServer is a wrapper for gin.Engine, holding additional components
type webServer struct {
sync.RWMutex
facade shared.FacadeHandler
httpServer shared.HTTPServerCloser
config config.ConnectorApiConfig
configs config.Configs
groups map[string]shared.GroupHandler
apiType string
wasTriggered bool
cancelFunc func()
}
Expand All @@ -47,8 +49,7 @@ func NewWebServerHandler(args ArgsWebServerHandler) (*webServer, error) {

return &webServer{
facade: args.Facade,
config: args.Config,
apiType: args.Type,
configs: args.Configs,
groups: make(map[string]shared.GroupHandler),
wasTriggered: false,
}, nil
Expand All @@ -58,13 +59,26 @@ func checkArgs(args ArgsWebServerHandler) error {
if check.IfNil(args.Facade) {
return apiErrors.ErrNilFacadeHandler
}
if args.Type == "" {
if args.Configs.Flags.APIType == "" {
return common.ErrInvalidAPIType
}

return nil
}

func (w *webServer) getWSAddr() string {
addr := w.configs.GeneralConfig.ConnectorApi.Host
if addr == "" {
return defaultRestInterface
}

if !strings.Contains(addr, ":") {
return fmt.Sprintf(":%s", addr)
}

return addr
}

// Run starts the server and the Hub as goroutines
// It returns an instance of http.Server
func (w *webServer) Run() error {
Expand All @@ -78,11 +92,6 @@ func (w *webServer) Run() error {
return nil
}

port := w.config.Port
if !strings.Contains(port, ":") {
port = fmt.Sprintf(":%s", port)
}

engine := gin.Default()
engine.Use(cors.Default())

Expand All @@ -93,8 +102,10 @@ func (w *webServer) Run() error {

w.registerRoutes(engine)

addr := w.getWSAddr()

server := &http.Server{
Addr: port,
Addr: addr,
Handler: engine,
}

Expand Down Expand Up @@ -125,7 +136,7 @@ func (w *webServer) createGroups() error {
}
groupsMap["status"] = statusGroup

if w.apiType == common.WSAPIType {
if w.configs.Flags.APIType == common.WSAPIType {
hubHandler, err := groups.NewHubGroup(w.facade)
if err != nil {
return err
Expand All @@ -141,8 +152,10 @@ func (w *webServer) createGroups() error {
func (w *webServer) registerRoutes(ginEngine *gin.Engine) {
for groupName, groupHandler := range w.groups {
log.Info("registering API group", "group name", groupName)
ginGroup := ginEngine.Group(fmt.Sprintf("/%s", groupName)).Use(groupHandler.GetAdditionalMiddlewares()...)
groupHandler.RegisterRoutes(ginGroup)

ginGroup := ginEngine.Group(fmt.Sprintf("/%s", groupName))

groupHandler.RegisterRoutes(ginGroup, w.configs.ApiRoutesConfig)
}
}

Expand Down
14 changes: 10 additions & 4 deletions api/gin/webServer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ import (
func createMockArgsWebServerHandler() gin.ArgsWebServerHandler {
return gin.ArgsWebServerHandler{
Facade: &mocks.FacadeStub{},
Config: config.ConnectorApiConfig{
Port: "8080",
Configs: config.Configs{
GeneralConfig: config.GeneralConfig{
ConnectorApi: config.ConnectorApiConfig{
Host: "8080",
},
},
Flags: config.FlagsConfig{
APIType: "notifier",
},
},
Type: "notifier",
}
}

Expand All @@ -40,7 +46,7 @@ func TestNewWebServerHandler(t *testing.T) {
t.Parallel()

args := createMockArgsWebServerHandler()
args.Type = ""
args.Configs.Flags.APIType = ""

ws, err := gin.NewWebServerHandler(args)
require.True(t, check.IfNil(ws))
Expand Down
70 changes: 67 additions & 3 deletions api/groups/baseGroup.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,86 @@
package groups

import (
"strings"

"github.com/gin-gonic/gin"
logger "github.com/multiversx/mx-chain-logger-go"
"github.com/multiversx/mx-chain-notifier-go/api/shared"
"github.com/multiversx/mx-chain-notifier-go/config"
)

var log = logger.GetOrCreate("api/groups")

type baseGroup struct {
endpoints []*shared.EndpointHandlerData
endpoints []*shared.EndpointHandlerData
additionalMiddlewares []gin.HandlerFunc
authMiddleware gin.HandlerFunc
}

func newBaseGroup() *baseGroup {
return &baseGroup{
additionalMiddlewares: make([]gin.HandlerFunc, 0),
authMiddleware: func(ctx *gin.Context) {},
}
}

// RegisterRoutes will register all the endpoints to the given web server
func (bg *baseGroup) RegisterRoutes(
ws gin.IRoutes,
ws *gin.RouterGroup,
apiConfig config.APIRoutesConfig,
) {
for _, handlerData := range bg.endpoints {
ws.Handle(handlerData.Method, handlerData.Path, handlerData.Handler)
isOpen, isAuthEnabled := getEndpointStatus(ws, handlerData.Path, apiConfig)
if !isOpen {
log.Debug("endpoint is closed", "path", handlerData.Path)
continue
}

handlers := make([]gin.HandlerFunc, 0)

if isAuthEnabled {
handlers = append(handlers, bg.GetAuthMiddleware())
}

handlers = append(handlers, bg.GetAdditionalMiddlewares()...)
handlers = append(handlers, handlerData.Handler)

ws.Handle(handlerData.Method, handlerData.Path, handlers...)
}
}

// GetAdditionalMiddlewares returns additional middlewares
func (bg *baseGroup) GetAdditionalMiddlewares() []gin.HandlerFunc {
return bg.additionalMiddlewares
}

// GetAuthMiddleware returns auth middleware
func (bg *baseGroup) GetAuthMiddleware() gin.HandlerFunc {
return bg.authMiddleware
}

func getEndpointStatus(
ws *gin.RouterGroup,
path string,
apiConfig config.APIRoutesConfig,
) (bool, bool) {
basePath := ws.BasePath()

// ws.BasePath will return paths like /group
// so we need the last token after splitting by /
splitPath := strings.Split(basePath, "/")
basePath = splitPath[len(splitPath)-1]

group, ok := apiConfig.APIPackages[basePath]
if !ok {
return false, false
}

for _, route := range group.Routes {
if route.Name == path {
return route.Open, route.Auth
}
}

return false, false
}
7 changes: 4 additions & 3 deletions api/groups/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import (
"fmt"
"io"

"github.com/multiversx/mx-chain-notifier-go/api/shared"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/multiversx/mx-chain-notifier-go/api/shared"
"github.com/multiversx/mx-chain-notifier-go/config"
)

func startWebServer(group shared.GroupHandler, path string) *gin.Engine {
func startWebServer(group shared.GroupHandler, path string, apiConfig config.APIRoutesConfig) *gin.Engine {
ws := gin.New()
ws.Use(cors.Default())
routes := ws.Group(path)
group.RegisterRoutes(routes)
group.RegisterRoutes(routes, apiConfig)
return ws
}

Expand Down
19 changes: 4 additions & 15 deletions api/groups/eventsGroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ const (

type eventsGroup struct {
*baseGroup
facade EventsFacadeHandler
additionalMiddlewares []gin.HandlerFunc
facade EventsFacadeHandler
}

// NewEventsGroup registers handlers for the /events group
Expand All @@ -30,9 +29,8 @@ func NewEventsGroup(facade EventsFacadeHandler) (*eventsGroup, error) {
}

h := &eventsGroup{
baseGroup: &baseGroup{},
facade: facade,
additionalMiddlewares: make([]gin.HandlerFunc, 0),
facade: facade,
baseGroup: newBaseGroup(),
}

h.createMiddlewares()
Expand Down Expand Up @@ -60,11 +58,6 @@ func NewEventsGroup(facade EventsFacadeHandler) (*eventsGroup, error) {
return h, nil
}

// GetAdditionalMiddlewares return additional middlewares for this group
func (h *eventsGroup) GetAdditionalMiddlewares() []gin.HandlerFunc {
return h.additionalMiddlewares
}

func (h *eventsGroup) pushEvents(c *gin.Context) {
pushEventsRawData, err := c.GetRawData()
if err != nil {
Expand Down Expand Up @@ -133,18 +126,14 @@ func (h *eventsGroup) finalizedEvents(c *gin.Context) {
}

func (h *eventsGroup) createMiddlewares() {
var middleware []gin.HandlerFunc

user, pass := h.facade.GetConnectorUserAndPass()

if user != "" && pass != "" {
basicAuth := gin.BasicAuth(gin.Accounts{
user: pass,
})
middleware = append(middleware, basicAuth)
h.authMiddleware = basicAuth
}

h.additionalMiddlewares = middleware
}

// IsInterfaceNil returns true if there is no value under the interface
Expand Down
Loading

0 comments on commit 1e09bba

Please sign in to comment.