The server package provides utilities for serving HTTP and gRPC servers with graceful shutdown support.
Package: mux
The mux package provides a multiplexer that can serve HTTP and gRPC servers on different ports simultaneously with coordinated graceful shutdown.
Serve
func Serve(ctx context.Context, opts ...Option) error
Starts TCP listeners and serves the registered protocol servers of the given serveTarget(s) and blocks until the servers exit. Context can be cancelled to perform graceful shutdown.
Context for controlling server lifecycle. Cancel this context to trigger graceful shutdown.
Variable number of Option functions to configure the server targets and settings.
Returns an error if no serve targets are configured or if server startup fails.
Example
package main
import (
"context"
"net/http"
"time"
"github.com/raystack/salt/server/mux"
"google.golang.org/grpc"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
httpServer := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, HTTP!"))
}),
}
grpcServer := grpc.NewServer()
// Register your gRPC services here
err := mux.Serve(ctx,
mux.WithHTTPTarget(":8080", httpServer),
mux.WithGRPCTarget(":9090", grpcServer),
mux.WithGracePeriod(10*time.Second),
)
if err != nil {
panic(err)
}
}
Options
WithHTTPTarget
func WithHTTPTarget(addr string, srv *http.Server) Option
Configures an HTTP server to be served on the specified address.
The address to bind the HTTP server to (e.g., “:8080”, “localhost:8080”).
The HTTP server instance to serve. The server’s Addr field will be set to the provided address.
Returns an Option function that configures the mux server with this HTTP target.
WithGRPCTarget
func WithGRPCTarget(addr string, srv *grpc.Server) Option
Configures a gRPC server to be served on the specified address.
The address to bind the gRPC server to (e.g., “:9090”, “localhost:9090”).
The gRPC server instance to serve.
Returns an Option function that configures the mux server with this gRPC target.
WithGracePeriod
func WithGracePeriod(d time.Duration) Option
Sets the wait duration for graceful shutdown. If the duration is less than or equal to 0, the default grace period of 10 seconds is used.
The duration to wait for graceful shutdown. Must be positive, otherwise defaults to 10 seconds.
Returns an Option function that configures the graceful shutdown period.
Package: spa
The spa package provides a simple and efficient HTTP handler for serving Single Page Applications (SPAs) from embedded file systems.
Features
- Serves static assets from an embedded file system
- Fallback to an index file for client-side routing
- Optional gzip compression for supported clients
- Prevents directory traversal attacks
Handler
func Handler(build embed.FS, dir string, index string, gzip bool) (http.Handler, error)
Returns an HTTP handler for serving a Single Page Application (SPA). The handler serves static files from the specified directory in the embedded file system and falls back to serving the index file if a requested file is not found. This is useful for client-side routing in SPAs.
An embedded file system containing the build assets.
The directory within the embedded file system where the static files are located.
The name of the index file (usually “index.html”).
If true, the response body will be compressed using gzip for clients that support it.
An HTTP handler that serves the SPA with optional gzip compression.
Returns an error if the file system or index file cannot be initialized.
Example
package main
import (
"embed"
"log"
"net/http"
"github.com/raystack/salt/server/spa"
)
//go:embed build/*
var build embed.FS
func main() {
handler, err := spa.Handler(build, "build", "index.html", true)
if err != nil {
log.Fatalf("Failed to initialize SPA handler: %v", err)
}
log.Println("Serving SPA on http://localhost:8080")
http.ListenAndServe(":8080", handler)
}
Best Practices
Graceful Shutdown
Always provide a context for controlling server lifecycle:
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
err := mux.Serve(ctx,
mux.WithHTTPTarget(":8080", httpServer),
mux.WithGracePeriod(15*time.Second),
)
Multiple Protocols
Serve HTTP and gRPC on different ports:
err := mux.Serve(ctx,
mux.WithHTTPTarget(":8080", httpServer),
mux.WithGRPCTarget(":9090", grpcServer),
)
SPA with Embedded Assets
Use Go’s embed package to bundle your frontend:
//go:embed dist/*
var staticFiles embed.FS
handler, _ := spa.Handler(staticFiles, "dist", "index.html", true)