mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-07-02 09:27:39 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a06aacfb2 | |||
| 705c4f8001 | |||
| d648614611 | |||
| 3f86eb0f06 | |||
| 3dac55cb19 | |||
| bbb2c6d127 |
+21
-14
@@ -115,32 +115,39 @@ func GetHeadSpecHeaders() []string {
|
|||||||
|
|
||||||
// SetCORSHeaders sets CORS headers on a response writer
|
// SetCORSHeaders sets CORS headers on a response writer
|
||||||
func SetCORSHeaders(w ResponseWriter, r Request, config CORSConfig) {
|
func SetCORSHeaders(w ResponseWriter, r Request, config CORSConfig) {
|
||||||
// Set allowed origins
|
// Reflect the request origin; fall back to wildcard only when no origin is present
|
||||||
// if len(config.AllowedOrigins) > 0 {
|
origin := r.Header("Origin")
|
||||||
// w.SetHeader("Access-Control-Allow-Origin", strings.Join(config.AllowedOrigins, ", "))
|
if origin == "" {
|
||||||
// }
|
origin = "*"
|
||||||
|
} else {
|
||||||
// Todo origin list parsing
|
// Vary must be set so caches don't serve one origin's response to another
|
||||||
w.SetHeader("Access-Control-Allow-Origin", "*")
|
httpW := w.UnderlyingResponseWriter()
|
||||||
|
httpW.Header().Set("Vary", "Origin")
|
||||||
|
}
|
||||||
|
w.SetHeader("Access-Control-Allow-Origin", origin)
|
||||||
|
|
||||||
// Set allowed methods
|
// Set allowed methods
|
||||||
if len(config.AllowedMethods) > 0 {
|
if len(config.AllowedMethods) > 0 {
|
||||||
w.SetHeader("Access-Control-Allow-Methods", strings.Join(config.AllowedMethods, ", "))
|
w.SetHeader("Access-Control-Allow-Methods", strings.Join(config.AllowedMethods, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set allowed headers
|
// Reflect the preflight request headers when present; otherwise use the explicit config list
|
||||||
// if len(config.AllowedHeaders) > 0 {
|
requestedHeaders := r.Header("Access-Control-Request-Headers")
|
||||||
// w.SetHeader("Access-Control-Allow-Headers", strings.Join(config.AllowedHeaders, ", "))
|
if requestedHeaders != "" {
|
||||||
// }
|
w.SetHeader("Access-Control-Allow-Headers", requestedHeaders)
|
||||||
w.SetHeader("Access-Control-Allow-Headers", "*")
|
} else if len(config.AllowedHeaders) > 0 {
|
||||||
|
w.SetHeader("Access-Control-Allow-Headers", strings.Join(config.AllowedHeaders, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
// Set max age
|
// Set max age
|
||||||
if config.MaxAge > 0 {
|
if config.MaxAge > 0 {
|
||||||
w.SetHeader("Access-Control-Max-Age", fmt.Sprintf("%d", config.MaxAge))
|
w.SetHeader("Access-Control-Max-Age", fmt.Sprintf("%d", config.MaxAge))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow credentials
|
// Allow credentials only when a specific origin is reflected (not wildcard)
|
||||||
w.SetHeader("Access-Control-Allow-Credentials", "true")
|
if origin != "*" {
|
||||||
|
w.SetHeader("Access-Control-Allow-Credentials", "true")
|
||||||
|
}
|
||||||
|
|
||||||
// Expose headers that clients can read
|
// Expose headers that clients can read
|
||||||
exposeHeaders := config.AllowedHeaders
|
exposeHeaders := config.AllowedHeaders
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ type ServerInstanceConfig struct {
|
|||||||
// GZIP enables GZIP compression middleware
|
// GZIP enables GZIP compression middleware
|
||||||
GZIP bool `mapstructure:"gzip"`
|
GZIP bool `mapstructure:"gzip"`
|
||||||
|
|
||||||
|
// HTTP2 enables HTTP/2 with the Extended CONNECT protocol (RFC 8441) for WebSocket support.
|
||||||
|
// Requires TLS; pair with SSLCert/SSLKey, SelfSignedSSL, or AutoTLS.
|
||||||
|
HTTP2 bool `mapstructure:"http2"`
|
||||||
|
|
||||||
// TLS/HTTPS configuration options (mutually exclusive)
|
// TLS/HTTPS configuration options (mutually exclusive)
|
||||||
// Option 1: Provide certificate and key files directly
|
// Option 1: Provide certificate and key files directly
|
||||||
SSLCert string `mapstructure:"ssl_cert"`
|
SSLCert string `mapstructure:"ssl_cert"`
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ func FromConfigInstanceToServerConfig(sic *config.ServerInstanceConfig, handler
|
|||||||
Description: sic.Description,
|
Description: sic.Description,
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
GZIP: sic.GZIP,
|
GZIP: sic.GZIP,
|
||||||
|
HTTP2: sic.HTTP2,
|
||||||
|
|
||||||
SSLCert: sic.SSLCert,
|
SSLCert: sic.SSLCert,
|
||||||
SSLKey: sic.SSLKey,
|
SSLKey: sic.SSLKey,
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ type Config struct {
|
|||||||
// GZIP compression support
|
// GZIP compression support
|
||||||
GZIP bool
|
GZIP bool
|
||||||
|
|
||||||
|
// HTTP2 enables HTTP/2 with the Extended CONNECT protocol (RFC 8441) for WebSocket support.
|
||||||
|
// Requires TLS; pair with SSLCert/SSLKey, SelfSignedSSL, or AutoTLS.
|
||||||
|
HTTP2 bool
|
||||||
|
|
||||||
// TLS/HTTPS configuration options (mutually exclusive)
|
// TLS/HTTPS configuration options (mutually exclusive)
|
||||||
// Option 1: Provide certificate and key files directly
|
// Option 1: Provide certificate and key files directly
|
||||||
SSLCert string
|
SSLCert string
|
||||||
@@ -38,6 +42,10 @@ type Config struct {
|
|||||||
// AutoTLSEmail is the email for Let's Encrypt registration (optional but recommended)
|
// AutoTLSEmail is the email for Let's Encrypt registration (optional but recommended)
|
||||||
AutoTLSEmail string
|
AutoTLSEmail string
|
||||||
|
|
||||||
|
// PanicHandler is called when a request handler panics.
|
||||||
|
// If nil, the default middleware.PanicRecovery is used (logs, records metric, returns 500).
|
||||||
|
PanicHandler func(w http.ResponseWriter, r *http.Request, rcv any)
|
||||||
|
|
||||||
// Graceful shutdown configuration
|
// Graceful shutdown configuration
|
||||||
// ShutdownTimeout is the maximum time to wait for graceful shutdown
|
// ShutdownTimeout is the maximum time to wait for graceful shutdown
|
||||||
// Default: 30 seconds
|
// Default: 30 seconds
|
||||||
|
|||||||
+50
-10
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -451,8 +452,19 @@ func newInstance(cfg Config) (*serverInstance, error) {
|
|||||||
handler = gz(handler)
|
handler = gz(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap with the panic recovery middleware
|
// Wrap with panic recovery — use caller-supplied handler if provided
|
||||||
handler = middleware.PanicRecovery(handler)
|
if cfg.PanicHandler != nil {
|
||||||
|
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
if rcv := recover(); rcv != nil {
|
||||||
|
cfg.PanicHandler(w, r, rcv)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
handler = middleware.PanicRecovery(handler)
|
||||||
|
}
|
||||||
|
|
||||||
// Configure TLS if any TLS option is enabled
|
// Configure TLS if any TLS option is enabled
|
||||||
tlsConfig, certFile, keyFile, err := configureTLS(cfg)
|
tlsConfig, certFile, keyFile, err := configureTLS(cfg)
|
||||||
@@ -461,15 +473,43 @@ func newInstance(cfg Config) (*serverInstance, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create gracefulServer
|
// Create gracefulServer
|
||||||
|
httpServer := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: handler,
|
||||||
|
ReadTimeout: cfg.ReadTimeout,
|
||||||
|
WriteTimeout: cfg.WriteTimeout,
|
||||||
|
IdleTimeout: cfg.IdleTimeout,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable HTTP/2 with Extended CONNECT (RFC 8441) for WebSocket-over-H2 support.
|
||||||
|
// The GODEBUG=http2xconnect=1 flag is read by net/http's init(); setting it here
|
||||||
|
// ensures it propagates to subprocesses and any future process restarts.
|
||||||
|
// For the current process, set GODEBUG=http2xconnect=1 in the environment before launch.
|
||||||
|
if httpServer.Protocols == nil {
|
||||||
|
httpServer.Protocols = &http.Protocols{}
|
||||||
|
httpServer.Protocols.SetHTTP1(true)
|
||||||
|
}
|
||||||
|
if cfg.HTTP2 {
|
||||||
|
if existing := os.Getenv("GODEBUG"); !strings.Contains(existing, "http2xconnect=1") {
|
||||||
|
if existing == "" {
|
||||||
|
os.Setenv("GODEBUG", "http2xconnect=1")
|
||||||
|
} else {
|
||||||
|
os.Setenv("GODEBUG", existing+",http2xconnect=1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if httpServer.HTTP2 == nil {
|
||||||
|
httpServer.HTTP2 = &http.HTTP2Config{}
|
||||||
|
}
|
||||||
|
httpServer.Protocols.SetHTTP2(true)
|
||||||
|
httpServer.Protocols.SetUnencryptedHTTP2(true)
|
||||||
|
} else {
|
||||||
|
httpServer.Protocols.SetHTTP1(true)
|
||||||
|
httpServer.Protocols.SetHTTP2(false)
|
||||||
|
}
|
||||||
|
|
||||||
gracefulSrv := &gracefulServer{
|
gracefulSrv := &gracefulServer{
|
||||||
server: &http.Server{
|
server: httpServer,
|
||||||
Addr: addr,
|
|
||||||
Handler: handler,
|
|
||||||
ReadTimeout: cfg.ReadTimeout,
|
|
||||||
WriteTimeout: cfg.WriteTimeout,
|
|
||||||
IdleTimeout: cfg.IdleTimeout,
|
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
},
|
|
||||||
shutdownTimeout: cfg.ShutdownTimeout,
|
shutdownTimeout: cfg.ShutdownTimeout,
|
||||||
drainTimeout: cfg.DrainTimeout,
|
drainTimeout: cfg.DrainTimeout,
|
||||||
shutdownComplete: make(chan struct{}),
|
shutdownComplete: make(chan struct{}),
|
||||||
|
|||||||
Reference in New Issue
Block a user