Commit 63f739a
Changed files (3)
cmd
web
docs
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