Skip to content

Commit

Permalink
removing gorilla mux for gateway (#68)
Browse files Browse the repository at this point in the history
* removing gorilla mux for gateway

* removing gorilla mux for gateway

* Update README.md

Co-authored-by: Dec <20417324+DeWarner@users.noreply.github.com>

* the gateway is now detached from mux

* remove unused code and .vscode launch.json

---------

Co-authored-by: Dec <20417324+DeWarner@users.noreply.github.com>
  • Loading branch information
Aaron Parfitt and DeWarner authored Mar 23, 2023
1 parent c885605 commit 843ff2b
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 416 deletions.
39 changes: 0 additions & 39 deletions .vscode/launch.json

This file was deleted.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ clean:

# Test target (run all tests)
test:
$(GO) test -v
$(GO) test -v ./...

bench:
go test -v -bench=. -benchmem
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ Supported Load Balancer types:
"options": {
"weights": [1, 2, 3]
}
}
```

- Least Connection
Expand Down
4 changes: 2 additions & 2 deletions cmd/frontman/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ func main() {
fmt.Println("failed to initialize logger")
os.Exit(1)
}

// Create a new Gateway instance
gateway, err := frontman.NewGateway(config, logger)
gateway, err := frontman.NewFrontman(config, logger)
if err != nil {
logger.Fatalf("failed to create gateway: %v", err)
}
Expand Down
189 changes: 189 additions & 0 deletions frontman.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package frontman

import (
"context"
"crypto/tls"
"fmt"
"net/http"
"sync"

"github.com/Frontman-Labs/frontman/config"
"github.com/Frontman-Labs/frontman/gateway"
"github.com/Frontman-Labs/frontman/log"
"github.com/Frontman-Labs/frontman/plugins"
"github.com/Frontman-Labs/frontman/service"
"github.com/Frontman-Labs/frontman/ssl"
"github.com/gorilla/mux"
)

// Frontman contains the backend services and the router
type Frontman struct {
router *gateway.APIGateway
service *mux.Router
backendServices service.ServiceRegistry
conf *config.Config
log log.Logger
}

func NewServicesRouter(backendServices service.ServiceRegistry) *mux.Router {
router := mux.NewRouter()

router.HandleFunc("/api/services", getServicesHandler(backendServices)).Methods("GET")
router.HandleFunc("/api/services", addServiceHandler(backendServices)).Methods("POST")
router.HandleFunc("/api/services/{name}", removeServiceHandler(backendServices)).Methods("DELETE")
router.HandleFunc("/api/services/{name}", updateServiceHandler(backendServices)).Methods("PUT")
router.HandleFunc("/api/health", getHealthHandler(backendServices)).Methods("GET")

return router
}

// NewGateway creates a new Frontman instance with a Redis client connection factory
func NewFrontman(conf *config.Config, log log.Logger) (*Frontman, error) {

// Retrieve the Redis client connection from the factory
ctx := context.Background()

// Create a new BackendServices instance
backendServices, err := service.NewServiceRegistry(ctx, conf.GlobalConfig.ServiceType, conf)
if err != nil {
return nil, err
}

servicesRouter := NewServicesRouter(backendServices)

// Load plugins
var plug []plugins.FrontmanPlugin

// Create new APIGateway instance

if conf.PluginConfig.Enabled {
plug, err = plugins.LoadPlugins(conf.PluginConfig.Order)
if err != nil {
return nil, err
}

}

// Create new APIGateway instance
clients := make(map[string]*http.Client)
lock := sync.Mutex{}
apiGateway := gateway.NewAPIGateway(backendServices, plug, conf, clients, log, &lock)
go gateway.RefreshConnections(backendServices, clients, &lock)

// Create the Frontman instance
return &Frontman{
router: apiGateway,
service: servicesRouter,
backendServices: backendServices,
conf: conf,
log: log,
}, nil
}

func (gw *Frontman) Start() error {
apiAddr := gw.conf.APIConfig.Addr
if apiAddr == "" {
apiAddr = "0.0.0.0:8080"
}
gatewayAddr := gw.conf.GatewayConfig.Addr
if gatewayAddr == "" {
gatewayAddr = "0.0.0.0:8000"
}

var apiHandler http.Handler
var gatewayHandler http.Handler

if gw.conf.APIConfig.SSL.Enabled {
apiHandler = gw.service
cert, err := ssl.LoadCert(gw.conf.APIConfig.SSL.Cert, gw.conf.APIConfig.SSL.Key)
if err != nil {
return err
}
apiServer := createServer(apiAddr, apiHandler, &cert)
gw.log.Infof("Started Frontman API with SSL on %s", apiAddr)
go func() {
if err := startServer(apiServer); err != nil {
gw.log.Fatal(err)
}
}()
} else {
apiHandler = gw.service
api := createServer(apiAddr, apiHandler, nil)
gw.log.Infof("Started Frontman API on %s", apiAddr)
go func() {
if err := startServer(api); err != nil {
gw.log.Fatal(err)
}
}()
}

if gw.conf.GatewayConfig.SSL.Enabled {
gatewayHandler = gw.router
cert, err := ssl.LoadCert(gw.conf.GatewayConfig.SSL.Cert, gw.conf.GatewayConfig.SSL.Key)
if err != nil {
return err
}

// Redirect HTTP traffic to HTTPS
httpAddr := "0.0.0.0:80"
httpRedirect := createRedirectServer(httpAddr, gatewayAddr)
gw.log.Infof("Started HTTP redirect server on %s", httpAddr)
go func() {
if err := startServer(httpRedirect); err != nil {
gw.log.Fatal(err)
}
}()

gatewayServer := createServer(gatewayAddr, gatewayHandler, &cert)
gw.log.Infof("Started Frontman Frontman with SSL on %s", gatewayAddr)
if err := startServer(gatewayServer); err != nil {
return err
}
} else {
gatewayHandler = gw.router
gateway := createServer(gatewayAddr, gatewayHandler, nil)
gw.log.Infof("Started Frontman Frontman on %s", gatewayAddr)
if err := startServer(gateway); err != nil {
return err
}
}

return nil
}

func createRedirectServer(addr string, redirectAddr string) *http.Server {
redirect := func(w http.ResponseWriter, req *http.Request) {
httpsURL := "https://" + req.Host + req.URL.Path
http.Redirect(w, req, httpsURL, http.StatusMovedPermanently)
}
return &http.Server{
Addr: addr,
Handler: http.HandlerFunc(redirect),
}
}

func createServer(addr string, handler http.Handler, cert *tls.Certificate) *http.Server {
server := &http.Server{
Addr: addr,
Handler: handler,
}
if cert != nil {
server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{*cert},
}
}
return server
}

func startServer(server *http.Server) error {
if server.TLSConfig != nil {
if err := server.ListenAndServeTLS("", ""); err != nil {
return fmt.Errorf("Failed to start server with TLS: %w", err)
}
} else {
if err := server.ListenAndServe(); err != nil {
return fmt.Errorf("Failed to start server without TLS: %w", err)
}
}
return nil
}
35 changes: 35 additions & 0 deletions frontman_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package frontman

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestCreateRedirectServer(t *testing.T) {
redirectAddr := "0.0.0.0:8000"
redirectServer := createRedirectServer("0.0.0.0:80", redirectAddr)

// Create a test request to the redirect server
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
t.Fatal(err)
}

// Create a test response recorder
rr := httptest.NewRecorder()

// Call the redirect server's handler function
redirectServer.Handler.ServeHTTP(rr, req)

// Check that the response has a 301 status code
if status := rr.Code; status != http.StatusMovedPermanently {
t.Errorf("Unexpected status code: got %v, expected %v", status, http.StatusMovedPermanently)
}

// Check that the response includes a "Location" header with the expected value
expectedURL := "https://example.com/foo"
if location := rr.Header().Get("Location"); location != expectedURL {
t.Errorf("Unexpected Location header value: got %v, expected %v", location, expectedURL)
}
}
Loading

0 comments on commit 843ff2b

Please sign in to comment.