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

Fix : Server shouldn't start if HTTP_PORT is blocked #1319

Open
wants to merge 20 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions examples/http-server-using-redis/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"context"
"fmt"
"net/http"
"testing"
"time"
Expand All @@ -22,6 +23,8 @@ import (

func TestHTTPServerUsingRedis(t *testing.T) {
const host = "http://localhost:8000"
t.Setenv("METRICS_PORT", fmt.Sprint(2034))

go main()
time.Sleep(100 * time.Millisecond) // Giving some time to start the server

Expand Down Expand Up @@ -55,6 +58,9 @@ func TestHTTPServerUsingRedis(t *testing.T) {
}

func TestRedisSetHandler(t *testing.T) {
t.Setenv("HTTP_PORT", "8085")
t.Setenv("METRICS_PORT", "2036")

a := gofr.New()
logger := logging.NewLogger(logging.DEBUG)
redisClient, mock := redismock.NewClientMock()
Expand All @@ -78,6 +84,7 @@ func TestRedisSetHandler(t *testing.T) {
}

func TestRedisPipelineHandler(t *testing.T) {
t.Setenv("HTTP_PORT", "8086")
a := gofr.New()
logger := logging.NewLogger(logging.DEBUG)
redisClient, mock := redismock.NewClientMock()
Expand Down
3 changes: 3 additions & 0 deletions examples/http-server/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ func TestIntegration_SimpleAPIServer_Health(t *testing.T) {
}

func TestRedisHandler(t *testing.T) {
t.Setenv("METRICS_PORT", "2036")
t.Setenv("HTTP_PORT", "8082")

a := gofr.New()
logger := logging.NewLogger(logging.DEBUG)
redisClient, mock := redismock.NewClientMock()
Expand Down
3 changes: 3 additions & 0 deletions examples/using-add-rest-handlers/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"fmt"
"net/http"
"testing"
"time"
Expand All @@ -12,6 +13,8 @@ import (

func TestIntegration_AddRESTHandlers(t *testing.T) {
const host = "http://localhost:9090"
t.Setenv("METRICS_PORT", fmt.Sprint(2023))

go main()
time.Sleep(100 * time.Millisecond) // Giving some time to start the server

Expand Down
3 changes: 3 additions & 0 deletions examples/using-cron-jobs/main_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package main

import (
"fmt"
"testing"
"time"

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

func Test_UserPurgeCron(t *testing.T) {
t.Setenv("METRICS_PORT", fmt.Sprint(2022))

go main()
time.Sleep(1100 * time.Millisecond)

Expand Down
3 changes: 3 additions & 0 deletions examples/using-custom-metrics/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (

func TestIntegration(t *testing.T) {
const host = "http://localhost:9011"
t.Setenv("HTTP_PORT", "9011")
t.Setenv("METRICS_PORT", "2120")

go main()
time.Sleep(100 * time.Millisecond) // Giving some time to start the server

Expand Down
3 changes: 3 additions & 0 deletions examples/using-publisher/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"fmt"
"net/http"
"testing"
"time"
Expand All @@ -12,6 +13,8 @@ import (

func TestExamplePublisher(t *testing.T) {
const host = "http://localhost:8100"
t.Setenv("METRICS_PORT", fmt.Sprint(2032))

go main()
time.Sleep(200 * time.Millisecond)

Expand Down
3 changes: 3 additions & 0 deletions examples/using-subscriber/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ func initializeTest(t *testing.T) {

func TestExampleSubscriber(t *testing.T) {
log := testutil.StdoutOutputForFunc(func() {
t.Setenv("HTTP_PORT", "8080")
t.Setenv("METRICS_PORT", "2031")

go main()
time.Sleep(time.Second * 1) // Giving some time to start the server

Expand Down
1 change: 1 addition & 0 deletions examples/using-web-socket/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

func Test_WebSocket_Success(t *testing.T) {
wsURL := fmt.Sprintf("ws://%s/ws", "localhost:8001")
t.Setenv("METRICS_PORT", fmt.Sprint(2030))

go main()
time.Sleep(100 * time.Millisecond)
Expand Down
49 changes: 39 additions & 10 deletions pkg/gofr/gofr.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net"
"net/http"
"os"
"os/signal"
Expand Down Expand Up @@ -40,6 +41,7 @@ const (
shutDownTimeout = 30 * time.Second
gofrTraceExporter = "gofr"
gofrTracerURL = "https://tracer.gofr.dev"
checkPortTimeout = 2 * time.Second
)

// App is the main application in the GoFr framework.
Expand Down Expand Up @@ -77,6 +79,10 @@ func New() *App {
port = defaultMetricPort
}

if !isPortAvailable(port) {
app.container.Logger.Fatalf("metrics port %d is blocked or unreachable", port)
}

app.metricServer = newMetricServer(port)

// HTTP Server
Expand All @@ -94,16 +100,7 @@ func New() *App {
app.add(http.MethodGet, "/.well-known/alive", liveHandler)
app.add(http.MethodGet, "/favicon.ico", faviconHandler)

// If the openapi.json file exists in the static directory, set up routes for OpenAPI and Swagger documentation.
if _, err = os.Stat("./static/" + gofrHTTP.DefaultSwaggerFileName); err == nil {
// Route to serve the OpenAPI JSON specification file.
app.add(http.MethodGet, "/.well-known/"+gofrHTTP.DefaultSwaggerFileName, OpenAPIHandler)
// Route to serve the Swagger UI, providing a user interface for the API documentation.
app.add(http.MethodGet, "/.well-known/swagger", SwaggerUIHandler)
// Catchall route: any request to /.well-known/{name} (e.g., /.well-known/other)
// will be handled by the SwaggerUIHandler, serving the Swagger UI.
app.add(http.MethodGet, "/.well-known/{name}", SwaggerUIHandler)
}
app.checkAndAddOpenAPIDocumentation()

if app.Config.Get("APP_ENV") == "DEBUG" {
app.httpServer.RegisterProfilingRoutes()
Expand All @@ -130,6 +127,19 @@ func New() *App {
return app
}

func (a *App) checkAndAddOpenAPIDocumentation() {
// If the openapi.json file exists in the static directory, set up routes for OpenAPI and Swagger documentation.
if _, err := os.Stat("./static/" + gofrHTTP.DefaultSwaggerFileName); err == nil {
// Route to serve the OpenAPI JSON specification file.
a.add(http.MethodGet, "/.well-known/"+gofrHTTP.DefaultSwaggerFileName, OpenAPIHandler)
// Route to serve the Swagger UI, providing a user interface for the API documentation.
a.add(http.MethodGet, "/.well-known/swagger", SwaggerUIHandler)
// Catchall route: any request to /.well-known/{name} (e.g., /.well-known/other)
// will be handled by the SwaggerUIHandler, serving the Swagger UI.
a.add(http.MethodGet, "/.well-known/{name}", SwaggerUIHandler)
}
}

// NewCMD creates a command-line application.
func NewCMD() *App {
app := &App{}
Expand Down Expand Up @@ -244,6 +254,17 @@ func (a *App) Shutdown(ctx context.Context) error {
return err
}

func isPortAvailable(port int) bool {
conn, err := net.DialTimeout("tcp", fmt.Sprintf(":%d", port), checkPortTimeout)
if err != nil {
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
return true
}

conn.Close()

return false
}
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved

func (a *App) httpServerSetup() {
// TODO: find a way to read REQUEST_TIMEOUT config only once and log it there. currently doing it twice one for populating
// the value and other for logging
Expand Down Expand Up @@ -349,6 +370,10 @@ func (a *App) PATCH(pattern string, handler Handler) {
}

func (a *App) add(method, pattern string, h Handler) {
if !a.httpRegistered && !isPortAvailable(a.httpServer.port) {
a.container.Logger.Fatalf("http port %d is blocked or unreachable", a.httpServer.port)
}

a.httpRegistered = true

reqTimeout, err := strconv.Atoi(a.Config.Get("REQUEST_TIMEOUT"))
Expand Down Expand Up @@ -694,6 +719,10 @@ func contains(elems []string, v string) bool {
// If `filePath` starts with "./", it will be interpreted as a relative path
// to the current working directory.
func (a *App) AddStaticFiles(endpoint, filePath string) {
if !a.httpRegistered && !isPortAvailable(a.httpServer.port) {
a.container.Logger.Fatalf("http port %d is blocked or unreachable", a.httpServer.port)
}

a.httpRegistered = true

if !strings.HasPrefix(filePath, "./") && !filepath.IsAbs(filePath) {
Expand Down
28 changes: 28 additions & 0 deletions pkg/gofr/gofr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,34 @@ func TestGofr_readConfig(t *testing.T) {
}
}

func TestGoFr_isPortAvailable(t *testing.T) {
port := testutil.GetFreePort(t)

tests := []struct {
name string
isAvailable bool
}{
{"Port is available", true},
{"Port is not available", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if !tt.isAvailable {
t.Setenv("HTTP_PORT", fmt.Sprint(port))

g := New()

go g.Run()
time.Sleep(100 * time.Millisecond)
}

isAvailable := isPortAvailable(port)
require.Equal(t, tt.isAvailable, isAvailable)
})
}
}

func TestGofr_ServerRoutes(t *testing.T) {
port := testutil.GetFreePort(t)

Expand Down
4 changes: 4 additions & 0 deletions pkg/gofr/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ var (

// RegisterService adds a gRPC service to the GoFr application.
func (a *App) RegisterService(desc *grpc.ServiceDesc, impl any) {
if !a.grpcRegistered && !isPortAvailable(a.grpcServer.port) {
a.container.Logger.Fatalf("gRPC port %d is blocked or unreachable", a.grpcServer.port)
}

a.container.Logger.Infof("registering gRPC Server: %s", desc.ServiceName)
a.grpcServer.server.RegisterService(desc, impl)

Expand Down
Loading