Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
24b7c6e
Add demo-app for httpserver pakcage
mishankov Oct 29, 2025
4c0063e
Rename TraceId to TraceIdMiddleware
mishankov Oct 29, 2025
9f5cec8
Remove demo task from Taskfile
mishankov Oct 29, 2025
b7e4297
Fix log message capitalization
mishankov Oct 29, 2025
eca8be5
Improve middleware wrapping documentation
mishankov Oct 29, 2025
ebd5554
Add comment about available API endpoints
mishankov Oct 29, 2025
88346d9
Add /long endpoint for graceful shutdown testing
mishankov Oct 29, 2025
52ce116
Improve HTTP server shutdown error message
mishankov Oct 29, 2025
b975919
Add ServeHTTP method to HttpServer
mishankov Oct 29, 2025
056debc
Refactor HTTP server tests
mishankov Oct 29, 2025
d172ea6
Disable linter for demo-app directory
mishankov Oct 29, 2025
e5a6c1f
Add healthcheck test for HTTP server
mishankov Oct 29, 2025
7cbde5d
Add type assertion for health check data in test
mishankov Oct 29, 2025
42f111c
Wrap handler in middleware before serving HTTP requests
mishankov Oct 29, 2025
bdbefd3
Tests for middleware
mishankov Oct 29, 2025
e144d25
Add HandleGroup method for nested handler groups
mishankov Oct 29, 2025
3546334
Refactor HttpServer to use embedded HandlerGroup
mishankov Oct 29, 2025
462a863
Remove verbose flag from test command
mishankov Oct 29, 2025
908e311
Replace HandlerGroup field with type alias handleGroup
mishankov Oct 29, 2025
d8f1144
Change HttpServer to embed pointer to handleGroup
mishankov Oct 29, 2025
31ae153
Remove middleware application from HandlerGroup methods
mishankov Oct 29, 2025
eec69b1
Add HandlerGroup test case
mishankov Oct 29, 2025
3771e46
Add trace ID middleware tests
mishankov Oct 29, 2025
d762541
Add revive checks to httpserver
mishankov Oct 29, 2025
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
5 changes: 4 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ linters:
rules:
json: camel
exclusions:
paths:
- demo-app

rules:
- path-except: "database/*"
- path-except: "(database|httpserver)/*"
linters:
- revive

Expand Down
7 changes: 1 addition & 6 deletions Taskfile.dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ tasks:

test:
cmds:
- go test -cover -v ./...
- go test -cover ./...

check:
deps:
- lint
- test

demo:
dir: demo-app
cmds:
- go run ./cmd/clock/main.go
2 changes: 1 addition & 1 deletion application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (a *Application) Run(ctx context.Context) error {
ctx = context.Background()
}

log.InfoContext(ctx, "Starting application", "startupTasks", len(a.startupTasks))
log.InfoContext(ctx, "starting application", "startupTasks", len(a.startupTasks))

for dbName, db := range a.databases {
log.InfoContext(ctx, "migrating database", "database", dbName)
Expand Down
65 changes: 65 additions & 0 deletions demo-app/cmd/api/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

import (
"context"
"net/http"
"time"

"github.com/mishankov/platforma/application"
"github.com/mishankov/platforma/httpserver"
"github.com/mishankov/platforma/log"
)

func main() {
ctx := context.Background()

// Initialize new application
app := application.New()

// Create HTTP server
api := httpserver.New("8080", 3*time.Second)

// Add /ping endpoint to `api`
api.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})

// Add /long endpoint to HTTP server to test graceful shutdown
api.HandleFunc("/long", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(10 * time.Second)
w.Write([]byte("pong"))
})

// Add middleware to HTTP server. It will add trace ID to logs and responce headers
api.Use(httpserver.NewTraceIDMiddleware(nil, ""))

// Create handler group
subApiGroup := httpserver.NewHandlerGroup()

// Add /clock endpoint to handler group
subApiGroup.HandleFunc("/clock", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(time.Now().String()))
})

// Add middleware to HTTP server. It will log all incoming requests to this handle group
subApiGroup.UseFunc(func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.InfoContext(r.Context(), "incoming request", "addr", r.RemoteAddr)
h.ServeHTTP(w, r)
})
})

// Add handle group to HTTP server with /subApi path
api.HandleGroup("/subApi", subApiGroup)

// Register HTTP server as application server
app.RegisterService("api", api)

// Run application
if err := app.Run(ctx); err != nil {
log.ErrorContext(ctx, "app finished with error", "error", err)
}

// Now you can access http://localhost:8080/ping, http://localhost:8080/long
// and http://localhost:8080/subApi/clock URLs with GET method
}
11 changes: 7 additions & 4 deletions httpserver/handlergroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,21 @@ func (hg *HandlerGroup) UseFunc(middlewareFuncs ...func(http.Handler) http.Handl
}
}

// Handle registers an http.Handler for the given pattern, applying all
// middlewares in the HandlerGroup's chain.
// Handle registers an http.Handler for the given pattern
func (hg *HandlerGroup) Handle(pattern string, handler http.Handler) {
hg.mux.Handle(pattern, handler)
}

// HandleFunc registers an http.HandlerFunc for the given pattern, applying all
// middlewares in the HandlerGroup's chain.
// HandleFunc registers an http.HandlerFunc for the given pattern
func (hg *HandlerGroup) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
hg.mux.Handle(pattern, http.HandlerFunc(handler))
}

// HandleGroup applies `http.StripPrefix` to http.Handler and registers it for the given pattern
func (hg *HandlerGroup) HandleGroup(pattern string, handler http.Handler) {
hg.mux.Handle(pattern+"/", http.StripPrefix(pattern, handler))
}

// ServeHTTP implements the http.Handler interface, allowing HandlerGroup to
// be used as an HTTP handler itself.
func (hg *HandlerGroup) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Expand Down
45 changes: 14 additions & 31 deletions httpserver/httpserver.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package httpserver provides HTTP server functionality with middleware support.
package httpserver

import (
Expand All @@ -13,40 +14,22 @@ import (
"github.com/mishankov/platforma/log"
)

type HttpServer struct {
mux *http.ServeMux
type handleGroup = HandlerGroup

// HTTPServer represents an HTTP server with middleware support and graceful shutdown.
type HTTPServer struct {
*handleGroup
port string
shutdownTimeout time.Duration
middlewares []Middleware
}

func New(port string, shutdownTimeout time.Duration) *HttpServer {
return &HttpServer{mux: http.NewServeMux(), port: port, shutdownTimeout: shutdownTimeout}
}

func (s *HttpServer) Handle(pattern string, handler http.Handler) {
s.mux.Handle(pattern, handler)
}

func (s *HttpServer) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
s.mux.HandleFunc(pattern, http.HandlerFunc(handler))
}

func (s *HttpServer) HandleGroup(pattern string, handler http.Handler) {
s.mux.Handle(pattern+"/", http.StripPrefix(pattern, handler))
}

func (s *HttpServer) Use(middlewares ...Middleware) {
s.middlewares = append(s.middlewares, middlewares...)
}

func (s *HttpServer) UseFunc(middlewareFuncs ...func(http.Handler) http.Handler) {
for _, middlewareFunc := range middlewareFuncs {
s.middlewares = append(s.middlewares, MiddlewareFunc(middlewareFunc))
}
// New creates a new HTTPServer instance with the specified port and shutdown timeout.
func New(port string, shutdownTimeout time.Duration) *HTTPServer {
return &HTTPServer{handleGroup: NewHandlerGroup(), port: port, shutdownTimeout: shutdownTimeout}
}

func (s *HttpServer) Run(ctx context.Context) error {
// Run starts the HTTP server and handles graceful shutdown on interrupt signals.
func (s *HTTPServer) Run(ctx context.Context) error {
server := &http.Server{
Addr: ":" + s.port,
Handler: wrapHandlerInMiddleware(s.mux, s.middlewares),
Expand All @@ -70,15 +53,15 @@ func (s *HttpServer) Run(ctx context.Context) error {
defer shutdownRelease()

if err := server.Shutdown(shutdownCtx); err != nil {
log.ErrorContext(ctx, "HTTP shutdown error", "error", err)
return fmt.Errorf("failed to shutdown server: %w", err)
return fmt.Errorf("failed to gracefully shutdown HTTP server: %w", err)
}
log.InfoContext(ctx, "graceful shutdown completed.")

return nil
}

func (s *HttpServer) Healthcheck(ctx context.Context) any {
// Healthcheck returns health check information for the HTTP server.
func (s *HTTPServer) Healthcheck(_ context.Context) any {
return map[string]any{
"port": s.port,
}
Expand Down
Loading