Building a Basic Web Framework with Go's net/http Package
Go's standard library includes net/http, which provides foundational tools for HTTP programming. This framework, named Gee, builds upon net/http to create a simple web framework. Below is an example demonstrating basic usage of the standard library to start a web server.
Example: Standard Library Server Setup
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", pathHandler)
http.HandleFunc("/greet", headerHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func pathHandler(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintf(resp, "Path: %s\n", req.URL.Path)
}
func headerHandler(resp http.ResponseWriter, req *http.Request) {
for key, values := range req.Header {
fmt.Fprintf(resp, "%s: %v\n", key, values)
}
}
This code sets up two routes: / and /greet, each linked to a handler function. The server listens on port 8080. Testing with a tool like curl yields responses based on the request path and headers.
To customize request handling, implement the http.Handler enterface, which requires a ServeHTTP method. This allows intercepting all HTTP requests through a single instance.
Example: Custom Handler Implementation
package main
import (
"fmt"
"log"
"net/http"
)
type Server struct{}
func (s *Server) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/":
fmt.Fprintf(resp, "Path: %s\n", req.URL.Path)
case "/greet":
for key, values := range req.Header {
fmt.Fprintf(resp, "%s: %v\n", key, values)
}
default:
resp.WriteHeader(http.StatusNotFound)
fmt.Fprintf(resp, "404: %s\n", req.URL)
}
}
func main() {
server := &Server{}
log.Fatal(http.ListenAndServe(":8080", server))
}
By passing a Server instance to ListenAndServe, all requests are directed to ServeHTTP, enabling centralized control for routing and logic like error handling.
Next, structure this into a basic framework. The project layout includes:
gee/gee.gomain.gogo.modfiles
Main Application File
package main
import (
"fmt"
"net/http"
"gee"
)
func main() {
app := gee.Create()
app.GET("/", func(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintf(resp, "Path: %s\n", req.URL.Path)
})
app.GET("/greet", func(resp http.ResponseWriter, req *http.Request) {
for key, values := range req.Header {
fmt.Fprintf(resp, "%s: %v\n", key, values)
}
})
app.Start(":8080")
}
This mirrors frameworks like Gin, using methods to add routes and start the server. Currently, it supports static routes only.
Framework Core Implemantation
package gee
import (
"fmt"
"net/http"
)
type RouteHandler func(http.ResponseWriter, *http.Request)
type App struct {
routes map[string]RouteHandler
}
func Create() *App {
return &App{routes: make(map[string]RouteHandler)}
}
func (a *App) registerRoute(method, path string, handler RouteHandler) {
key := method + "-" + path
a.routes[key] = handler
}
func (a *App) GET(path string, handler RouteHandler) {
a.registerRoute("GET", path, handler)
}
func (a *App) POST(path string, handler RouteHandler) {
a.registerRoute("POST", path, handler)
}
func (a *App) Start(address string) error {
return http.ListenAndServe(address, a)
}
func (a *App) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
routeKey := req.Method + "-" + req.URL.Path
if handler, exists := a.routes[routeKey]; exists {
handler(resp, req)
} else {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprintf(resp, "404: %s\n", req.URL)
}
}
In this implementation, RouteHandler defines user-provided functions for routes. The App struct maintains a routing map where keys combine HTTP methods and paths. Methods like GET and POST register handlers, and ServeHTTP looks up and executes them or returns a 404 error.
Running this setup with go run main.go and testing with curl confirms it handles requests as expected, providing a foundation for adding features like dynamic routing in future iterations.