diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3dcd4a7..960d078 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,6 @@ name: CI on: push: tags: - - 0.** - v0.** jobs: docker: @@ -26,7 +25,12 @@ jobs: - name: Get the tag name id: get_tag_name - run: echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + run: | + # Extract tag name and remove "v" if present + TAG_NAME="${GITHUB_REF#refs/tags/}" + VERSION="${TAG_NAME#v}" + echo "VERSION=${VERSION}" >> $GITHUB_ENV + echo "BUILD_TIME=${(date -u +"%Y-%m-%dT%H:%M:%SZ")}" - name: Build and push uses: docker/build-push-action@v3 @@ -35,8 +39,8 @@ jobs: file: "./Dockerfile" platforms: linux/amd64,linux/arm64,linux/arm/v7 build-args: | - appVersion=${{ env.TAG_NAME }} + appVersion=${{ env.VERSION }} tags: | - "${{vars.BUILDKIT_IMAGE}}:${{ env.TAG_NAME }}" + "${{vars.BUILDKIT_IMAGE}}:${{ env.VERSION }}" "${{vars.BUILDKIT_IMAGE}}:latest" diff --git a/README.md b/README.md index 755cf78..bce469c 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,8 @@ docker run --rm --name goma-gateway \ ``` ### 4. Healthcheck -- Goma Gateway readiness: `/readyz` -- Routes health check: `/healthz` +- Goma Gateway health check: `/health/live` +- Routes health check: `health/routes` ### 5. Simple deployment in docker compose file @@ -103,7 +103,7 @@ services: image: jkaninda/goma-gateway command: server healthcheck: - test: curl -f http://localhost/readyz || exit 1 + test: curl -f http://localhost/heath/live || exit 1 interval: 30s retries: 5 start_period: 20s @@ -119,10 +119,8 @@ Create a config file in this format Example of a configuration file ```yaml -# Goma Gateway configurations +## Goma Gateway configurations gateway: - ########## Global settings - listenAddr: :80 #:443 SSL # Proxy write timeout writeTimeout: 15 # Proxy read timeout @@ -134,7 +132,6 @@ gateway: ## SSL Private Key file sslKeyFile: ''#key.pem # Proxy rate limit, it's In-Memory IP based - # Distributed Rate Limiting for Token based across multiple instances is not yet integrated rateLimiter: 0 accessLog: "/dev/Stdout" errorLog: "/dev/stderr" diff --git a/docs/middleware.md b/docs/middleware.md index c721c0f..0d16cc1 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -19,7 +19,6 @@ Goma Gateway supports : - JWT `client authorization based on the result of a request` - Basic-Auth - Rate limiting middleware - - In-Memory Token Bucket based - In-Memory client IP based - Access middleware diff --git a/docs/quickstart.md b/docs/quickstart.md index f04a3c9..32993d6 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -28,8 +28,8 @@ docker run --rm --name goma-gateway \ ``` ### 4. Healthcheck -- Goma Gateway readiness: `/readyz` -- Routes health check: `/healthz` +- Goma Gateway health check: `/health/live` +- Routes health check: `health/routes` ### 5. Simple deployment in docker compose file @@ -46,6 +46,7 @@ services: timeout: 10s ports: - "80:80" + - "443:443" volumes: - ./config:/config/ ``` @@ -56,8 +57,6 @@ Example of a configuration file ```yaml # Goma Gateway configurations gateway: - ########## Global settings - listenAddr: :80 #:443 SSL # Proxy write timeout writeTimeout: 15 # Proxy read timeout @@ -69,7 +68,6 @@ gateway: ## SSL Private Key file sslKeyFile: ''#key.pem # Proxy rate limit, it's In-Memory IP based - # Distributed Rate Limiting for Token based across multiple instances is not yet integrated rateLimiter: 0 accessLog: "/dev/Stdout" errorLog: "/dev/stderr" diff --git a/docs/route.md b/docs/route.md index 6e6bfda..a8bd2cb 100644 --- a/docs/route.md +++ b/docs/route.md @@ -13,6 +13,46 @@ The Route allows you to match on HTTP traffic and direct it to the backend. ### Create a route ```yaml + # Goma Gateway configurations +gateway: + # Proxy write timeout + writeTimeout: 15 + # Proxy read timeout + readTimeout: 15 + # Proxy idle timeout + idleTimeout: 60 + ## SSL Certificate file + sslCertFile: '' #cert.pem + ## SSL Private Key file + sslKeyFile: ''#key.pem + # Proxy rate limit, it's In-Memory IP based + rateLimiter: 0 + accessLog: "/dev/Stdout" + errorLog: "/dev/stderr" + ## Enable, disable routes health check + disableHealthCheckStatus: false + ## Returns backend route healthcheck errors + disableRouteHealthCheckError: false + # Disable display routes on start + disableDisplayRouteOnStart: false + # disableKeepAlive allows enabling and disabling KeepALive server + disableKeepAlive: false + # interceptErrors intercepts backend errors based on defined the status codes + interceptErrors: + - 405 + - 500 + # - 400 + # Proxy Global HTTP Cors + cors: + # Global routes cors for all routes + origins: + - http://localhost:8080 + - https://example.com + # Global routes cors headers for all routes + headers: + Access-Control-Allow-Headers: 'Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id' + Access-Control-Allow-Credentials: 'true' + Access-Control-Max-Age: 1728000 ##### Define routes routes: # Example of a route | 1 @@ -32,119 +72,32 @@ The Route allows you to match on HTTP traffic and direct it to the backend. healthCheck: '' #/internal/health/ready # Route Cors, global cors will be overridden by route cors: - # Route Origins Cors, global cors will be overridden by route + # Route Origins Cors, route will override global cors origins origins: - https://dev.example.com - http://localhost:3000 - https://example.com - # Route Cors headers, route will override global cors + # Route Cors headers, route will override global cors headers headers: Access-Control-Allow-Methods: 'GET' Access-Control-Allow-Headers: 'Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id' Access-Control-Allow-Credentials: 'true' Access-Control-Max-Age: 1728000 - ##### Define route middlewares from middlewares names + ##### Apply middlewares to the route ## The name must be unique ## List of middleware name middlewares: - api-forbidden-paths - - basic-auth -``` - -### Full example of route - -```yaml -## Goma - simple lightweight API Gateway and Reverse Proxy. -# Goma Gateway configurations -gateway: - ########## Global settings - listenAddr: 0.0.0.0:80 - # Proxy write timeout - writeTimeout: 15 - # Proxy read timeout - readTimeout: 15 - # Proxy idle timeout - idleTimeout: 60 - # Proxy rate limit, it's In-Memory IP based - # Distributed Rate Limiting for Token based across multiple instances is not yet integrated - rateLimiter: 0 - accessLog: "/dev/Stdout" - errorLog: "/dev/stderr" - ## Returns backend route healthcheck errors - disableRouteHealthCheckError: false - # Disable display routes on start - disableDisplayRouteOnStart: false - # disableKeepAlive allows enabling and disabling KeepALive server - disableKeepAlive: false - # interceptErrors intercepts backend errors based on defined the status codes - interceptErrors: - - 405 - - 500 - # - 400 - # Proxy Global HTTP Cors - cors: - # Global routes cors for all routes - origins: - - http://localhost:8080 - - https://example.com - # Global routes cors headers for all routes - headers: - Access-Control-Allow-Headers: 'Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id' - Access-Control-Allow-Credentials: 'true' - Access-Control-Max-Age: 1728000 - ##### Define routes - routes: - # Example of a route | 1 - - name: Public - # host Domain/host based request routing - host: "" # Host is optional - path: /public - ## Rewrite a request path - # e.g rewrite: /store to / - rewrite: / - destination: https://example.com - #DisableHeaderXForward Disable X-forwarded header. - # [X-Forwarded-Host, X-Forwarded-For, Host, Scheme ] - # It will not match the backend route, by default, it's disabled - disableHeaderXForward: false - # Internal health check - healthCheck: '' #/internal/health/ready - # Route Cors, global cors will be overridden by route - cors: - # Route Origins Cors, global cors will be overridden by route - origins: - - https://dev.example.com - - http://localhost:3000 - - https://example.com - # Route Cors headers, route will override global cors - headers: - Access-Control-Allow-Methods: 'GET' - Access-Control-Allow-Headers: 'Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id' - Access-Control-Allow-Credentials: 'true' - Access-Control-Max-Age: 1728000 - ##### Define route middlewares from middlewares names - ## The name must be unique - ## List of middleware name - middlewares: - - api-forbidden-paths - - basic-auth # Example of a route | 2 - - name: Authentication service - path: /auth - rewrite: / - destination: https://example.com - healthCheck: / - cors: {} - middlewares: - - api-forbidden-paths - # Example of a route | 3 - name: Basic auth path: /protected rewrite: / - destination: 'http://notification-service:8080' + destination: https://example.com healthCheck: cors: {} - middlewares: [] + middlewares: + - api-forbidden-paths + - basic-auth #Defines proxy middlewares # middleware name must be unique @@ -176,26 +129,22 @@ middlewares: # Required headers, if not present in the request, the proxy will return 403 requiredHeaders: - Authorization - #Sets the request variable to the given value after the authorization request completes. - # - # Add header to the next request from AuthRequest header, depending on your requirements - # Key is AuthRequest's response header Key, and value is Request's header Key - # In case you want to get headers from the Authentication service and inject them into the next request's headers - #Sets the request variable to the given value after the authorization request completes. - # - # Add header to the next request from AuthRequest header, depending on your requirements - # Key is AuthRequest's response header Key, and value is Request's header Key + # You can also get headers from the authentication request result and inject them into the next request header or params. + # In case you want to get headers from the authentication service and inject them into the next request headers. + # Set the request variable to the given value after the authorization request completes. # In case you want to get headers from the authentication service and inject them into the next request headers. + # Key is authentication request response header Key. Value is the next Request header Key. headers: - userId: X-Auth-UserId - userCountryId: X-Auth-UserCountryId - # In case you want to get headers from the Authentication service and inject them to the next request params. + userId: Auth-UserId + userCountryId: Auth-UserCountryId + # In case you want to get headers from the Authentication service and inject them to the next request params. + #Key is authentication request response header Key. Value is the next Request parameter Key. params: userCountryId: countryId - # The server will return 403 +# The server will return 403 - name: api-forbidden-paths type: access - ## prevents access paths + ## prevents access paths paths: - /swagger-ui/* - /v2/swagger-ui/* diff --git a/examples/compose.yaml b/examples/compose.yaml index beb1c4c..03d6e2b 100644 --- a/examples/compose.yaml +++ b/examples/compose.yaml @@ -10,5 +10,6 @@ services: timeout: 10s ports: - "80:80" + - "443:443" volumes: - ./config:/config/ \ No newline at end of file diff --git a/examples/configMap.yaml b/examples/configMap.yaml index d628a93..7d2efe2 100644 --- a/examples/configMap.yaml +++ b/examples/configMap.yaml @@ -4,20 +4,24 @@ metadata: name: goma-config data: goma.yml: | + # Goma Gateway configurations gateway: - ########## Global settings - listenAddr: 0.0.0.0:80 # Proxy write timeout writeTimeout: 15 # Proxy read timeout readTimeout: 15 # Proxy idle timeout idleTimeout: 60 + ## SSL Certificate file + sslCertFile: '' #cert.pem + ## SSL Private Key file + sslKeyFile: ''#key.pem # Proxy rate limit, it's In-Memory IP based - # Distributed Rate Limiting for Token based across multiple instances is not yet integrated rateLimiter: 0 accessLog: "/dev/Stdout" errorLog: "/dev/stderr" + ## Enable, disable routes health check + disableHealthCheckStatus: false ## Returns backend route healthcheck errors disableRouteHealthCheckError: false # Disable display routes on start @@ -59,23 +63,23 @@ data: healthCheck: '' #/internal/health/ready # Route Cors, global cors will be overridden by route cors: - # Route Origins Cors, global cors will be overridden by route + # Route Origins Cors, route will override global cors origins origins: - https://dev.example.com - http://localhost:3000 - https://example.com - # Route Cors headers, route will override global cors + # Route Cors headers, route will override global cors headers headers: Access-Control-Allow-Methods: 'GET' Access-Control-Allow-Headers: 'Origin, Authorization, Accept, Content-Type, Access-Control-Allow-Headers, X-Client-Id, X-Session-Id' Access-Control-Allow-Credentials: 'true' Access-Control-Max-Age: 1728000 - ##### Define route middlewares from middlewares names + ##### Apply middlewares to the route ## The name must be unique ## List of middleware name middlewares: - api-forbidden-paths - # Example of a route | 3 + # Example of a route | 2 - name: Basic auth path: /protected rewrite: / @@ -116,20 +120,16 @@ data: # Required headers, if not present in the request, the proxy will return 403 requiredHeaders: - Authorization - #Sets the request variable to the given value after the authorization request completes. - # - # Add header to the next request from AuthRequest header, depending on your requirements - # Key is AuthRequest's response header Key, and value is Request's header Key - # In case you want to get headers from the Authentication service and inject them into the next request's headers - #Sets the request variable to the given value after the authorization request completes. - # - # Add header to the next request from AuthRequest header, depending on your requirements - # Key is AuthRequest's response header Key, and value is Request's header Key + # You can also get headers from the authentication request result and inject them into the next request header or params. + # In case you want to get headers from the authentication service and inject them into the next request headers. + # Set the request variable to the given value after the authorization request completes. # In case you want to get headers from the authentication service and inject them into the next request headers. + # Key is authentication request response header Key. Value is the next Request header Key. headers: - userId: X-Auth-UserId - userCountryId: X-Auth-UserCountryId - # In case you want to get headers from the Authentication service and inject them to the next request params. + userId: Auth-UserId + userCountryId: Auth-UserCountryId + # In case you want to get headers from the Authentication service and inject them to the next request params. + #Key is authentication request response header Key. Value is the next Request parameter Key. params: userCountryId: countryId # The server will return 403 diff --git a/examples/kubernetes.yaml b/examples/kubernetes.yaml index 29a76fa..c3b4df1 100644 --- a/examples/kubernetes.yaml +++ b/examples/kubernetes.yaml @@ -23,14 +23,14 @@ spec: - containerPort: 80 livenessProbe: httpGet: - path: /healthz + path: /health/live port: 80 initialDelaySeconds: 15 periodSeconds: 30 timeoutSeconds: 10 readinessProbe: httpGet: - path: /readyz + path: /health/live port: 80 initialDelaySeconds: 15 periodSeconds: 40 diff --git a/goma.yml b/goma.yml index 905e145..04f8f9b 100644 --- a/goma.yml +++ b/goma.yml @@ -1,7 +1,5 @@ # Goma Gateway configurations gateway: - ########## Global settings - listenAddr: :80 #:443 SSL # Proxy write timeout writeTimeout: 15 # Proxy read timeout @@ -13,7 +11,6 @@ gateway: ## SSL Private Key file sslKeyFile: ''#key.pem # Proxy rate limit, it's In-Memory IP based - # Distributed Rate Limiting for Token based across multiple instances is not yet integrated rateLimiter: 0 accessLog: "/dev/Stdout" errorLog: "/dev/stderr" diff --git a/internal/config.go b/internal/config.go index fa4f294..ffdbed9 100644 --- a/internal/config.go +++ b/internal/config.go @@ -85,7 +85,6 @@ func initConfig(configFile string) { } conf := &GatewayConfig{ GatewayConfig: Gateway{ - ListenAddr: ":80", WriteTimeout: 15, ReadTimeout: 15, IdleTimeout: 60, diff --git a/internal/middleware/error-interceptor.go b/internal/middleware/error-interceptor.go index 70a1dbe..d83f37b 100644 --- a/internal/middleware/error-interceptor.go +++ b/internal/middleware/error-interceptor.go @@ -49,6 +49,10 @@ func (intercept InterceptErrors) ErrorInterceptor(next http.Handler) http.Handle logger.Error("Backend error") logger.Error("An error occurred from the backend with the status code: %d", rec.statusCode) w.Header().Set("Content-Type", "application/json") + //Update Origin Cors Headers + if allowedOrigin(intercept.Origins, r.Header.Get("Origin")) { + w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) + } w.WriteHeader(rec.statusCode) err := json.NewEncoder(w).Encode(ProxyResponseError{ Success: false, diff --git a/internal/middleware/helpers.go b/internal/middleware/helpers.go new file mode 100644 index 0000000..65b95c3 --- /dev/null +++ b/internal/middleware/helpers.go @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Jonas Kaninda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package middleware + +import "net/http" + +func getRealIP(r *http.Request) string { + if ip := r.Header.Get("X-Real-IP"); ip != "" { + return ip + } + if ip := r.Header.Get("X-Forwarded-For"); ip != "" { + return ip + } + return r.RemoteAddr +} +func allowedOrigin(origins []string, origin string) bool { + for _, o := range origins { + if o == origin { + return true + } + continue + } + return false + +} diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index ff68ebe..a6c44f8 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -34,6 +34,10 @@ func (jwtAuth JwtAuth) AuthMiddleware(next http.Handler) http.Handler { if r.Header.Get(header) == "" { logger.Error("Proxy error, missing %s header", header) w.Header().Set("Content-Type", "application/json") + //Update Origin Cors Headers + if allowedOrigin(jwtAuth.Origins, r.Header.Get("Origin")) { + w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) + } w.WriteHeader(http.StatusUnauthorized) err := json.NewEncoder(w).Encode(ProxyResponseError{ Message: http.StatusText(http.StatusUnauthorized), diff --git a/internal/middleware/rate-limit.go b/internal/middleware/rate-limit.go index c4d7fad..200b21b 100644 --- a/internal/middleware/rate-limit.go +++ b/internal/middleware/rate-limit.go @@ -69,6 +69,10 @@ func (rl *RateLimiter) RateLimitMiddleware() mux.MiddlewareFunc { logger.Error("Too many requests from IP: %s %s %s", clientID, r.URL, r.UserAgent()) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusTooManyRequests) + //Update Origin Cors Headers + if allowedOrigin(rl.Origins, r.Header.Get("Origin")) { + w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) + } err := json.NewEncoder(w).Encode(ProxyResponseError{ Success: false, Code: http.StatusTooManyRequests, @@ -84,12 +88,3 @@ func (rl *RateLimiter) RateLimitMiddleware() mux.MiddlewareFunc { }) } } -func getRealIP(r *http.Request) string { - if ip := r.Header.Get("X-Real-IP"); ip != "" { - return ip - } - if ip := r.Header.Get("X-Forwarded-For"); ip != "" { - return ip - } - return r.RemoteAddr -} diff --git a/internal/middleware/types.go b/internal/middleware/types.go index 7d2695d..86a5b29 100644 --- a/internal/middleware/types.go +++ b/internal/middleware/types.go @@ -30,6 +30,7 @@ type RateLimiter struct { Window time.Duration ClientMap map[string]*Client mu sync.Mutex + Origins []string } // Client stores request count and window expiration for each client. @@ -39,11 +40,12 @@ type Client struct { } // NewRateLimiterWindow creates a new RateLimiter. -func NewRateLimiterWindow(requests int, window time.Duration) *RateLimiter { +func NewRateLimiterWindow(requests int, window time.Duration, origin []string) *RateLimiter { return &RateLimiter{ Requests: requests, Window: window, ClientMap: make(map[string]*Client), + Origins: origin, } } @@ -69,6 +71,7 @@ type JwtAuth struct { RequiredHeaders []string Headers map[string]string Params map[string]string + Origins []string } // AuthenticationMiddleware Define struct @@ -94,7 +97,8 @@ type AuthBasic struct { // InterceptErrors contains backend status code errors to intercept type InterceptErrors struct { - Errors []int + Errors []int + Origins []string } // responseRecorder intercepts the response body and status code diff --git a/internal/route.go b/internal/route.go index e47c7fb..1ac5912 100644 --- a/internal/route.go +++ b/internal/route.go @@ -35,13 +35,15 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { // Routes health check if !gateway.DisableHealthCheckStatus { r.HandleFunc("/healthz", heath.HealthCheckHandler).Methods("GET") + r.HandleFunc("/health/routes", heath.HealthCheckHandler).Methods("GET") } - // Readiness + // Health check + r.HandleFunc("/health/live", heath.HealthReadyHandler).Methods("GET") r.HandleFunc("/readyz", heath.HealthReadyHandler).Methods("GET") if gateway.RateLimiter != 0 { //rateLimiter := middleware.NewRateLimiter(gateway.RateLimiter, time.Minute) - limiter := middleware.NewRateLimiterWindow(gateway.RateLimiter, time.Minute) // requests per minute + limiter := middleware.NewRateLimiterWindow(gateway.RateLimiter, time.Minute, gateway.Cors.Origins) // requests per minute // Add rate limit middleware to all routes, if defined r.Use(limiter.RateLimitMiddleware()) } @@ -111,6 +113,7 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { RequiredHeaders: jwt.RequiredHeaders, Headers: jwt.Headers, Params: jwt.Params, + Origins: gateway.Cors.Origins, } // Apply JWT authentication middleware secureRouter.Use(amw.AuthMiddleware) @@ -161,7 +164,8 @@ func (gatewayServer GatewayServer) Initialize() *mux.Router { r.Use(CORSHandler(gateway.Cors)) // Apply CORS middleware // Apply errorInterceptor middleware interceptErrors := middleware.InterceptErrors{ - Errors: gateway.InterceptErrors, + Errors: gateway.InterceptErrors, + Origins: gateway.Cors.Origins, } r.Use(interceptErrors.ErrorInterceptor) return r diff --git a/internal/server.go b/internal/server.go index 3508a1a..1fa4295 100644 --- a/internal/server.go +++ b/internal/server.go @@ -29,6 +29,7 @@ import ( func (gatewayServer GatewayServer) Start(ctx context.Context) error { logger.Info("Initializing routes...") route := gatewayServer.Initialize() + logger.Debug("Routes count=%d Middlewares count=%d", len(gatewayServer.gateway.Routes), len(gatewayServer.middlewares)) logger.Info("Initializing routes...done") tlsConfig := &tls.Config{} var listenWithTLS = false @@ -41,8 +42,17 @@ func (gatewayServer GatewayServer) Start(ctx context.Context) error { listenWithTLS = true } - srv := &http.Server{ - Addr: gatewayServer.gateway.ListenAddr, + // HTTP Server + httpServer := &http.Server{ + Addr: ":80", + WriteTimeout: time.Second * time.Duration(gatewayServer.gateway.WriteTimeout), + ReadTimeout: time.Second * time.Duration(gatewayServer.gateway.ReadTimeout), + IdleTimeout: time.Second * time.Duration(gatewayServer.gateway.IdleTimeout), + Handler: route, // Pass our instance of gorilla/mux in. + } + // HTTPS Server + httpsServer := &http.Server{ + Addr: ":443", WriteTimeout: time.Second * time.Duration(gatewayServer.gateway.WriteTimeout), ReadTimeout: time.Second * time.Duration(gatewayServer.gateway.ReadTimeout), IdleTimeout: time.Second * time.Duration(gatewayServer.gateway.IdleTimeout), @@ -53,36 +63,52 @@ func (gatewayServer GatewayServer) Start(ctx context.Context) error { printRoute(gatewayServer.gateway.Routes) } // Set KeepAlive - srv.SetKeepAlivesEnabled(!gatewayServer.gateway.DisableKeepAlive) + httpServer.SetKeepAlivesEnabled(!gatewayServer.gateway.DisableKeepAlive) + httpsServer.SetKeepAlivesEnabled(!gatewayServer.gateway.DisableKeepAlive) + go func() { + logger.Info("Starting HTTP server listen=0.0.0.0:80") + if err := httpServer.ListenAndServe(); err != nil { + logger.Fatal("Error starting Goma Gateway HTTP server: %v", err) + } + }() go func() { - logger.Info("Started Goma Gateway server on %v", gatewayServer.gateway.ListenAddr) if listenWithTLS { - logger.Info("Server is running securely over HTTPS on %v ", gatewayServer.gateway.ListenAddr) - if err := srv.ListenAndServeTLS("", ""); err != nil { - logger.Fatal("Error starting Goma Gateway server: %v", err) - } - } else { - if err := srv.ListenAndServe(); err != nil { - logger.Fatal("Error starting Goma Gateway server: %v", err) + logger.Info("Starting HTTPS server listen=0.0.0.0:443") + if err := httpsServer.ListenAndServeTLS("", ""); err != nil { + logger.Fatal("Error starting Goma Gateway HTTPS server: %v", err) } } }() var wg sync.WaitGroup - wg.Add(1) - + wg.Add(2) go func() { defer wg.Done() <-ctx.Done() shutdownCtx := context.Background() shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second) defer cancel() - if err := srv.Shutdown(shutdownCtx); err != nil { - _, err := fmt.Fprintf(os.Stderr, "error shutting down Goma Gateway server: %s\n", err) + if err := httpServer.Shutdown(shutdownCtx); err != nil { + _, err := fmt.Fprintf(os.Stderr, "error shutting down HTTP server: %s\n", err) if err != nil { return } } }() + go func() { + defer wg.Done() + <-ctx.Done() + shutdownCtx := context.Background() + shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second) + defer cancel() + if listenWithTLS { + if err := httpsServer.Shutdown(shutdownCtx); err != nil { + _, err := fmt.Fprintf(os.Stderr, "error shutting HTTPS server: %s\n", err) + if err != nil { + return + } + } + } + }() wg.Wait() return nil diff --git a/internal/types.go b/internal/types.go index dc7bce8..51a6e06 100644 --- a/internal/types.go +++ b/internal/types.go @@ -133,12 +133,10 @@ type Route struct { // Gateway contains Goma Proxy Gateway's configs type Gateway struct { - // ListenAddr Defines the server listenAddr - // - //e.g: localhost:8080 - ListenAddr string `yaml:"listenAddr" env:"GOMA_LISTEN_ADDR, overwrite"` + // SSLCertFile SSL Certificate file SSLCertFile string `yaml:"sslCertFile" env:"GOMA_SSL_CERT_FILE, overwrite"` - SSLKeyFile string `yaml:"sslKeyFile" env:"GOMA_SSL_KEY_FILE, overwrite"` + // SSLKeyFile SSL Private key file + SSLKeyFile string `yaml:"sslKeyFile" env:"GOMA_SSL_KEY_FILE, overwrite"` // WriteTimeout defines proxy write timeout WriteTimeout int `yaml:"writeTimeout" env:"GOMA_WRITE_TIMEOUT, overwrite"` // ReadTimeout defines proxy read timeout