Server Package
This package implements the HTTP server for the member-console application, including HTMX partial handlers.
Overview
The server uses Go's standard net/http package with:
- Bootstrap 5 for styling
- HTMX 2.0 for dynamic content updates
- gorilla/csrf for CSRF protection
- gorilla/sessions for session management
Architecture
The HTTP server handles several types of requests:
- Static Files (
/static/*) - CSS, JS, images served from embedded assets - Page Handlers (
/) - Full HTML page rendering - Partial Handlers (
/partials/*) - HTMX partial HTML responses - Auth Handlers (
/login,/logout,/callback) - OAuth2/OIDC authentication
Partial handlers interact with Temporal workflows for async operations like site provisioning.
Files
server.go- Main server setup, middleware chain, route registrationfedwiki_partials.go- HTMX partial handlers for FedWiki site management
HTMX Integration
Partial Handler Pattern
Partial handlers return HTML fragments (not full pages) that HTMX swaps into the DOM:
// Handler returns HTML fragment
func (h *Handler) GetSites(w http.ResponseWriter, r *http.Request) {
// ... fetch data ...
w.Header().Set("Content-Type", "text/html")
h.Templates.ExecuteTemplate(w, "fedwiki_sites.html", data)
}
<!-- Template returns partial HTML -->
<div class="table-responsive">
<table class="table">
{{ range .Sites }}
<tr>...</tr>
{{ end }}
</table>
</div>
HTMX Triggers
Use HX-Trigger header to trigger events that refresh other parts of the page:
// After successful operation, trigger refresh
w.Header().Set("HX-Trigger", `{"refreshSites": true}`)
<!-- Element listens for the trigger -->
<div hx-get="/partials/fedwiki/sites"
hx-trigger="load, refreshSites from:body">
</div>
CSRF with HTMX
CSRF tokens are included via hx-headers on forms:
<form hx-post="/partials/fedwiki/sites"
hx-headers='{"X-CSRF-Token": "{{ .CSRFToken }}"}'>
Or globally in the page template using hx-vals or a meta tag approach.
Synchronous Workflow Execution
Unlike fire-and-forget patterns, this server waits for Temporal workflows to complete:
func (h *Handler) CreateSite(w http.ResponseWriter, r *http.Request) {
// 1. Start the workflow
we, err := h.TemporalClient.ExecuteWorkflow(ctx, options, workflow, input)
// 2. Wait for completion (BLOCKING)
var result WorkflowOutput
err = we.Get(ctx, &result)
// 3. Render result based on workflow outcome
if !result.Success {
h.renderError(w, result.ErrorMessage)
return
}
h.renderSuccess(w, result)
}
Benefits:
- User sees accurate success/failure immediately
- Spinner shows during actual operation
- Error messages come from workflow
Trade-offs:
- Request blocks until workflow completes
- Long operations may timeout (mitigated by workflow retry handling)
Route Organization
Routes are organized by feature using handler structs:
// FedWikiPartialsHandler groups related routes
type FedWikiPartialsHandler struct { ... }
func (h *FedWikiPartialsHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /partials/fedwiki/sites", h.GetSites)
mux.HandleFunc("POST /partials/fedwiki/sites", h.CreateSite)
mux.HandleFunc("DELETE /partials/fedwiki/sites/{domain}", h.DeleteSite)
// ...
}
Error Rendering
Errors are rendered back into the same partial/modal that initiated the request:
func (h *Handler) renderCreateFormError(w http.ResponseWriter, userID int64, errMsg string) {
// Re-fetch form data
data := h.getCreateFormData(userID)
data.Error = errMsg
// Re-render the form partial with error displayed
w.Header().Set("Content-Type", "text/html")
h.Templates.ExecuteTemplate(w, "fedwiki_create_form.html", data)
}
This keeps the user in context - they see the error in the same modal/form they were using.
Middleware Stack
handler = middleware.Logging(logger)(handler) // Request logging
handler = middleware.Recover(logger)(handler) // Panic recovery
handler = middleware.Compress()(handler) // Gzip compression
handler = middleware.Decompress()(handler) // Gzip decompression
handler = middleware.SecurityHeaders()(handler) // Security headers
handler = middleware.RequestID()(handler) // Request ID generation
handler = csrf.Protect(key, opts...)(handler) // CSRF protection