server

package
v2.1.0-alpha.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 2, 2026 License: GPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package server provides an HTTP server for serving generated blog content with support for live content updates via atomic handler hot-swapping.

The server implements thread-safe handler replacement, allowing blog content to be regenerated and swapped in without dropping in-flight requests or requiring server restart.

Basic Usage

Create and start a server:

import (
    "context"
    "log/slog"
    "os"
    "github.com/harrydayexe/GoBlog/v2/pkg/server"
    "github.com/harrydayexe/GoBlog/v2/pkg/config"
)

// Create server with posts from directory
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
postsFS := os.DirFS("posts/")

cfg := config.ServerConfig{
    Server: []config.ServerOption{
        config.WithPort(8080),
    },
}

srv, err := server.New(logger, postsFS, cfg)
if err != nil {
    log.Fatal(err)
}

// Start serving (blocks until interrupted)
if err := srv.Run(context.Background(), os.Stdout); err != nil {
    log.Fatal(err)
}

Middleware

The server supports pluggable HTTP middleware for cross-cutting concerns like logging, metrics, authentication, or rate limiting. Middleware uses the standard pattern from github.com/harrydayexe/GoWebUtilities/middleware.

Adding middleware to a server:

import (
    "github.com/harrydayexe/GoBlog/v2/pkg/config"
    "github.com/harrydayexe/GoBlog/v2/pkg/server"
    "github.com/harrydayexe/GoWebUtilities/logging"
    "github.com/harrydayexe/GoWebUtilities/middleware"
)

// Create server with built-in logging middleware
cfg := config.ServerConfig{
    Server: []config.BaseServerOption{
        config.WithPort(8080),
        config.WithMiddleware(logging.New(logger)),
    },
}

srv, err := server.New(logger, postsFS, cfg)

Custom middleware can be added following the standard pattern:

func customMiddleware(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Pre-request logic
        h.ServeHTTP(w, r)
        // Post-request logic
    })
}

cfg := config.ServerConfig{
    Server: []config.BaseServerOption{
        config.WithMiddleware(
            logging.New(logger),     // Built-in
            customMiddleware,        // Custom
        ),
    },
}

Middleware are applied in order: the first middleware in the list is executed first (outermost wrapper). The middleware chain is reapplied whenever the handler is refreshed via UpdatePosts().

Any middleware compatible with the standard http.Handler interface can be used, including third-party middleware packages following the func(http.Handler) http.Handler pattern.

Live Content Updates

Update blog content while server is running:

// Load updated posts
updatedFS := os.DirFS("posts/")

// Atomically swap in new content
if err := srv.UpdatePosts(updatedFS, context.Background()); err != nil {
    log.Printf("Update failed: %v", err)
}

The handler swap is atomic - in-flight requests complete with the old handler while new requests immediately see the updated content.

Concurrency

All Server methods are safe for concurrent use by multiple goroutines. The handler is stored in an atomic.Value, providing lock-free reads during request serving and atomic writes during updates. This design supports high-concurrency request handling without lock contention.

Server implements http.Handler interface, delegating to the current handler via atomic load operations.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Handler

func Handler(blog *generator.GeneratedBlog, logger *slog.Logger, opts ...config.BaseOption) http.Handler

Handler creates an HTTP handler that serves the generated blog content. It accepts a GeneratedBlog, a logger for error reporting, and optional configuration options to customize the handler behavior, such as setting a custom blog root path.

The handler serves the following routes (assuming default root "/"):

  • GET / and GET /posts - serves the blog index page
  • GET /posts/{postName} - serves individual blog posts
  • GET /tags - serves the tags index page
  • GET /tags/{tagName} - serves tag-specific pages

The handler is safe for concurrent use by multiple goroutines. It does not modify the GeneratedBlog instance.

Types

type Cache

type Cache interface {
	// Get retrieves the cached blog content.
	// It returns nil if no content is cached.
	// Get must be safe to call concurrently with other Cache methods.
	Get(ctx context.Context) (*generator.GeneratedBlog, error)

	// Set stores the provided blog content in the cache.
	// Any previously cached content is replaced.
	// Set must be safe to call concurrently with other Cache methods.
	Set(ctx context.Context, blog *generator.GeneratedBlog) error

	// Clear removes all cached content.
	// After calling Clear, Get will return nil until Set is called again.
	// Clear must be safe to call concurrently with other Cache methods.
	Clear(ctx context.Context) error
}

Cache provides an interface for caching generated blog content. Implementations must be safe for concurrent use by multiple goroutines.

The cache stores a single GeneratedBlog instance and provides methods to get, set, and clear the cached content. This is typically used to avoid regenerating blog content on every request.

type HandlerConfig

type HandlerConfig struct {
	config.BlogRoot
	// contains filtered or unexported fields
}

HandlerConfig holds configuration options for the blog HTTP handler. It embeds config.BlogRoot to specify the root path where the blog is served.

type Server

type Server struct {
	config.BlogRoot
	config.Port
	config.Host
	// contains filtered or unexported fields
}

Server is an HTTP server that serves generated blog content with support for live content updates.

The server uses atomic.Value to store its HTTP handler, enabling thread-safe handler hot-swapping without locks. This allows blog content to be regenerated and swapped in while serving requests, without dropping connections or requiring server restart.

Server implements http.Handler interface, delegating requests to the current handler loaded atomically.

All methods are safe for concurrent use by multiple goroutines.

Example (WithMiddleware)

ExampleServer_withMiddleware demonstrates using middleware with the server.

package main

import (
	"log/slog"
	"net/http"
	"os"
	"testing/fstest"

	"github.com/harrydayexe/GoBlog/v2/pkg/config"
	"github.com/harrydayexe/GoBlog/v2/pkg/server"
)

func main() {
	logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
	postsFS := fstest.MapFS{
		"post1.md": &fstest.MapFile{
			Data: []byte("---\ntitle: Test Post\ndescription: A test post\ndate: 2024-01-01\n---\nContent"),
		},
	}

	// Custom middleware that adds a header
	customMiddleware := func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("X-Powered-By", "GoBlog")
			h.ServeHTTP(w, r)
		})
	}

	cfg := config.ServerConfig{
		Server: []config.BaseServerOption{
			config.WithPort(8080),
			config.WithMiddleware(customMiddleware),
		},
	}

	srv, err := server.New(logger, postsFS, cfg)
	if err != nil {
		logger.Error("failed to create server", "error", err)
		return
	}

	// Server is ready with middleware applied
	_ = srv
}

func New

func New(logger *slog.Logger, posts fs.FS, opts config.ServerConfig) (*Server, error)

New creates a new Server instance with the specified configuration.

The logger is used for structured logging throughout the server lifecycle. The posts filesystem contains the markdown blog posts to be served. The opts parameter configures server behavior via the functional options pattern.

New initializes the server's HTTP handler by generating blog content from the posts filesystem. If initial generation fails, an error is returned.

Returns an error if template rendering or initial blog generation fails. The server is ready to serve requests immediately upon successful return.

func (*Server) Run

func (s *Server) Run(ctx context.Context, stdout io.Writer) error

Run starts the HTTP server and blocks until interrupted via context cancellation or OS signal (SIGINT). It handles graceful shutdown with a 10-second timeout.

The server uses atomic handler swapping, allowing UpdatePosts to be called while the server is running without interrupting in-flight requests.

Run is safe for concurrent use, though typically only called once per Server. It captures OS interrupt signals (Ctrl+C) and initiates graceful shutdown.

Returns an error only if server initialization fails. Shutdown errors are logged to stderr but don't prevent clean exit.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler, delegating requests to the current handler. The handler is loaded atomically, allowing it to be safely updated via UpdatePosts while requests are being served.

ServeHTTP is safe for concurrent use by multiple goroutines. It performs a lock-free atomic load of the current handler on each request.

If the handler has not been initialized (nil), ServeHTTP returns a 503 Service Unavailable error.

func (*Server) UpdatePosts

func (s *Server) UpdatePosts(posts fs.FS, ctx context.Context) error

UpdatePosts updates the posts directory and refreshes the HTTP handler with the new content. This triggers a complete regeneration of the blog and an atomic swap of the HTTP handler.

UpdatePosts is safe to call while the server is running and serving requests. The handler swap is atomic, ensuring that requests see either the old or new content without any intermediate inconsistent state.

If handler refresh fails, an error is returned and the previous handler remains active, continuing to serve the old content.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL