Commit 63f739a

bryfry <bryon@fryer.io>
2025-07-24 09:08:50
Task 1.10: Implement HTTP Handler Interface patterns
- Create application struct to hold shared dependencies and configuration - Implement TemplateHandler custom type that satisfies http.Handler interface - Convert method handlers to use http.HandlerFunc for interface compatibility - Demonstrate different handler patterns in route registration: 1. Method handlers converted with http.HandlerFunc (home, processSubmit) 2. Custom handler types with ServeHTTP methods (TemplateHandler for submit/about) 3. Simple function handlers (confirmWithToken) - Update main.go to create application instance and use mux.Handle() with proper interfaces - Maintain all existing functionality while improving code organization - Prepare foundation for future middleware and advanced handler patterns - Update project_plan.md status to Completed Handler patterns demonstrated: - application.home: Method on application struct - TemplateHandler: Custom type implementing http.Handler interface - confirmWithToken: Stateless function handler - Shared dependencies via application struct (logger, serviceName) See: docs/todo/task_1.10.md
1 parent f5139c4
Changed files (3)
cmd/web/handlers.go
@@ -14,69 +14,91 @@ type PageData struct {
 	Content     interface{}
 }
 
-// home displays the buylater.email landing page
-func home(w http.ResponseWriter, r *http.Request) {
-	w.Header().Add("Server", "buylater.email")
+// application holds the application-wide dependencies and configuration
+// This demonstrates a custom handler type that can hold state and dependencies
+type application struct {
+	logger      *log.Logger
+	serviceName string
+}
+
+// TemplateHandler represents handlers that render templates
+// This is a custom handler type that implements http.Handler interface
+type TemplateHandler struct {
+	app          *application
+	templateName string
+	pageName     string
+	title        string
+}
+
+// ServeHTTP makes TemplateHandler satisfy the http.Handler interface
+func (th *TemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	w.Header().Add("Server", th.app.serviceName)
 
 	// Parse templates with inheritance
 	ts, err := template.ParseFiles(
 		"./ui/html/layouts/base.tmpl",
-		"./ui/html/pages/home.tmpl",
+		"./ui/html/pages/"+th.templateName,
 	)
 	if err != nil {
-		log.Println(err.Error())
+		th.app.logger.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 		return
 	}
 
 	// Prepare template data
 	data := PageData{
-		Title:       "Home",
-		ServiceName: "buylater.email",
+		Title:       th.title,
+		ServiceName: th.app.serviceName,
 		Content:     nil,
 	}
 
-	// Execute template - specify the base template name
+	// Execute template
 	err = ts.ExecuteTemplate(w, "base", data)
 	if err != nil {
-		log.Println(err.Error())
+		th.app.logger.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 	}
 }
 
-// submitForm displays the email submission form for scheduling purchase reminders
-func submitForm(w http.ResponseWriter, r *http.Request) {
-	w.Header().Add("Server", "buylater.email")
+// home displays the buylater.email landing page
+// This demonstrates using http.HandlerFunc to convert a regular function
+// into something that satisfies the http.Handler interface
+func (app *application) home(w http.ResponseWriter, r *http.Request) {
+	w.Header().Add("Server", app.serviceName)
 
 	// Parse templates with inheritance
 	ts, err := template.ParseFiles(
 		"./ui/html/layouts/base.tmpl",
-		"./ui/html/pages/submit.tmpl",
+		"./ui/html/pages/home.tmpl",
 	)
 	if err != nil {
-		log.Println(err.Error())
+		app.logger.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 		return
 	}
 
 	// Prepare template data
 	data := PageData{
-		Title:       "Submit",
-		ServiceName: "buylater.email",
+		Title:       "Home",
+		ServiceName: app.serviceName,
 		Content:     nil,
 	}
 
-	// Execute template - specify the base template name
+	// Execute template
 	err = ts.ExecuteTemplate(w, "base", data)
 	if err != nil {
-		log.Println(err.Error())
+		app.logger.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 	}
 }
 
+// submitForm displays the email submission form for scheduling purchase reminders
+// This will be handled by the TemplateHandler type to demonstrate custom handler types
+
 // processSubmit handles the form submission and schedules the email reminder
-func processSubmit(w http.ResponseWriter, r *http.Request) {
-	w.Header().Add("Server", "buylater.email")
+// This demonstrates a method handler with business logic
+func (app *application) processSubmit(w http.ResponseWriter, r *http.Request) {
+	w.Header().Add("Server", app.serviceName)
 
 	// Parse templates with inheritance
 	ts, err := template.ParseFiles(
@@ -84,7 +106,7 @@ func processSubmit(w http.ResponseWriter, r *http.Request) {
 		"./ui/html/pages/complete.tmpl",
 	)
 	if err != nil {
-		log.Println(err.Error())
+		app.logger.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 		return
 	}
@@ -92,19 +114,20 @@ func processSubmit(w http.ResponseWriter, r *http.Request) {
 	// Prepare template data
 	data := PageData{
 		Title:       "Submission Complete",
-		ServiceName: "buylater.email",
+		ServiceName: app.serviceName,
 		Content:     nil,
 	}
 
 	// Execute template
 	err = ts.ExecuteTemplate(w, "base", data)
 	if err != nil {
-		log.Println(err.Error())
+		app.logger.Println(err.Error())
 		http.Error(w, "Internal Server Error", 500)
 	}
 }
 
 // confirmWithToken handles magic link confirmation with token validation
+// This demonstrates a stateless handler function that can be converted with http.HandlerFunc
 func confirmWithToken(w http.ResponseWriter, r *http.Request) {
 	token := r.PathValue("token")
 
@@ -131,31 +154,4 @@ func isValidToken(token string) bool {
 }
 
 // about displays information about the buylater.email service
-func about(w http.ResponseWriter, r *http.Request) {
-	w.Header().Add("Server", "buylater.email")
-
-	// Parse templates with inheritance
-	ts, err := template.ParseFiles(
-		"./ui/html/layouts/base.tmpl",
-		"./ui/html/pages/about.tmpl",
-	)
-	if err != nil {
-		log.Println(err.Error())
-		http.Error(w, "Internal Server Error", 500)
-		return
-	}
-
-	// Prepare template data
-	data := PageData{
-		Title:       "About",
-		ServiceName: "buylater.email",
-		Content:     nil,
-	}
-
-	// Execute template
-	err = ts.ExecuteTemplate(w, "base", data)
-	if err != nil {
-		log.Println(err.Error())
-		http.Error(w, "Internal Server Error", 500)
-	}
-}
+// This will be handled by the TemplateHandler type to demonstrate reusable custom handlers
cmd/web/main.go
@@ -6,6 +6,13 @@ import (
 )
 
 func main() {
+	// Create an instance of the application struct, containing the application-wide
+	// dependencies and configuration settings.
+	app := &application{
+		logger:      log.New(log.Writer(), "", log.LstdFlags),
+		serviceName: "buylater.email",
+	}
+
 	// Initialize a new servemux (router) - this stores the mapping between
 	// URL patterns and their corresponding handlers.
 	mux := http.NewServeMux()
@@ -20,15 +27,35 @@ func main() {
 	// "/static" prefix before the request reaches the file server.
 	mux.Handle("GET /static/", http.StripPrefix("/static", fileServer))
 
-	// Register handlers for buylater.email routes
-	mux.HandleFunc("GET /{$}", home)                         // Exact match for home page
-	mux.HandleFunc("GET /submit", submitForm)                // Display email submission form
-	mux.HandleFunc("POST /submit", processSubmit)            // Process form submission
-	mux.HandleFunc("GET /confirm/{token}", confirmWithToken) // Magic link confirmation with token
-	mux.HandleFunc("GET /about", about)                      // About page
+	// Register handlers for buylater.email routes - demonstrating different handler patterns:
+
+	// 1. Method handler converted to http.Handler using http.HandlerFunc
+	mux.Handle("GET /{$}", http.HandlerFunc(app.home))
+
+	// 2. Custom handler type that implements http.Handler interface directly
+	mux.Handle("GET /submit", &TemplateHandler{
+		app:          app,
+		templateName: "submit.tmpl",
+		pageName:     "submit",
+		title:        "Submit",
+	})
+
+	// 3. Method handler with business logic
+	mux.Handle("POST /submit", http.HandlerFunc(app.processSubmit))
+
+	// 4. Simple function handler converted to http.Handler
+	mux.Handle("GET /confirm/{token}", http.HandlerFunc(confirmWithToken))
+
+	// 5. Another custom handler type instance for the about page
+	mux.Handle("GET /about", &TemplateHandler{
+		app:          app,
+		templateName: "about.tmpl",
+		pageName:     "about",
+		title:        "About",
+	})
 
 	// Print a log message to indicate the server is starting.
-	log.Printf("Starting server on http://localhost:4000")
+	app.logger.Printf("Starting server on http://localhost:4000")
 
 	// Start the web server on port 4000. If ListenAndServe returns an error
 	// we use log.Fatal() to log the error and terminate the program.
docs/project_plan.md
@@ -17,7 +17,7 @@ Phase 1 lays the groundwork by methodically working through the Let's Go book, c
 | 1.7  | Project Structure and Organization       | Completed | Medium | 2.7     | [task_1.7.md](todo/task_1.7.md) |
 | 1.8  | HTML Templating and Inheritance         | Completed | Medium | 2.8     | [task_1.8.md](todo/task_1.8.md) |
 | 1.9  | Serving Static Files                    | Completed | Small  | 2.9     | [task_1.9.md](todo/task_1.9.md) |
-| 1.10 | The HTTP Handler Interface              | In Progress | Medium | 2.10    | [task_1.10.md](todo/task_1.10.md) |
+| 1.10 | The HTTP Handler Interface              | Completed | Medium | 2.10    | [task_1.10.md](todo/task_1.10.md) |
 
 ## Status Legend
 - **Completed** - Implemented and verified