Roteamento e Middlewares sem Framework em Golang
1. Fundamentos do Servidor HTTP Padrão
O pacote net/http da biblioteca padrão do Go fornece tudo que precisamos para construir servidores web robustos sem frameworks externos. A interface central é o http.Handler:
type Handler interface {
ServeHTTP(http.ResponseWriter, *http.Request)
}
Para iniciar um servidor básico:
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Olá, mundo!"))
})
http.ListenAndServe(":8080", nil)
}
O multiplexador padrão (http.DefaultServeMux) é limitado — não suporta parâmetros de rota dinâmicos nem métodos HTTP específicos. Para superar essas limitações, construímos nosso próprio roteador.
2. Construindo um Roteador Manual
Criamos uma estrutura que mapeia padrões de URL a handlers, armazenando também o método HTTP esperado:
type route struct {
method string
pattern string
handler http.HandlerFunc
}
type Router struct {
routes []route
}
func (r *Router) Handle(method, pattern string, handler http.HandlerFunc) {
r.routes = append(r.routes, route{method, pattern, handler})
}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
for _, route := range r.routes {
if route.method == req.Method && route.pattern == req.URL.Path {
route.handler(w, req)
return
}
}
http.NotFound(w, req)
}
Para extrair parâmetros de query string, usamos r.URL.Query():
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
for _, route := range r.routes {
if route.method == req.Method && route.pattern == req.URL.Path {
// Parâmetros de query
id := req.URL.Query().Get("id")
if id != "" {
req = req.WithContext(context.WithValue(req.Context(), "id", id))
}
route.handler(w, req)
return
}
}
http.NotFound(w, req)
}
3. Implementação de Middlewares
Um middleware é uma função que recebe um http.Handler e retorna outro http.Handler. Essa assinatura permite encadeamento:
type Middleware func(http.Handler) http.Handler
Exemplo de middleware de logging:
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
Middleware de recuperação de panics:
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
Composição de middlewares:
func ApplyMiddleware(handler http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
4. Roteamento com Métodos HTTP
Evoluímos nosso roteador para tratar métodos HTTP explicitamente:
type Router struct {
routes map[string]map[string]http.HandlerFunc
}
func NewRouter() *Router {
return &Router{
routes: make(map[string]map[string]http.HandlerFunc),
}
}
func (r *Router) Handle(method, pattern string, handler http.HandlerFunc) {
if r.routes[pattern] == nil {
r.routes[pattern] = make(map[string]http.HandlerFunc)
}
r.routes[pattern][method] = handler
}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if handlers, ok := r.routes[req.URL.Path]; ok {
if handler, ok := handlers[req.Method]; ok {
handler(w, req)
return
}
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
http.NotFound(w, req)
}
// Métodos auxiliares
func (r *Router) GET(pattern string, handler http.HandlerFunc) { r.Handle("GET", pattern, handler) }
func (r *Router) POST(pattern string, handler http.HandlerFunc) { r.Handle("POST", pattern, handler) }
5. Parâmetros de Rota e Validação
Suporte a parâmetros dinâmicos como /users/:id:
type Router struct {
routes []route
middleware []Middleware
}
type route struct {
method string
pattern string
handler http.HandlerFunc
params []string
}
func (r *Router) addRoute(method, pattern string, handler http.HandlerFunc) {
params := extractParams(pattern)
r.routes = append(r.routes, route{
method: method,
pattern: pattern,
handler: handler,
params: params,
})
}
func extractParams(pattern string) []string {
var params []string
segments := strings.Split(pattern, "/")
for _, seg := range segments {
if strings.HasPrefix(seg, ":") {
params = append(params, seg[1:])
}
}
return params
}
func matchPath(pattern, path string) (map[string]string, bool) {
patternSegs := strings.Split(pattern, "/")
pathSegs := strings.Split(path, "/")
if len(patternSegs) != len(pathSegs) {
return nil, false
}
params := make(map[string]string)
for i, seg := range patternSegs {
if strings.HasPrefix(seg, ":") {
params[seg[1:]] = pathSegs[i]
} else if seg != pathSegs[i] {
return nil, false
}
}
return params, true
}
Validação de parâmetros:
func validateID(id string) (int, error) {
n, err := strconv.Atoi(id)
if err != nil || n <= 0 {
return 0, fmt.Errorf("invalid ID: %s", id)
}
return n, nil
}
6. Agrupamento de Rotas e Sub-roteadores
Criamos sub-roteadores com prefixo comum:
type SubRouter struct {
prefix string
router *Router
}
func (r *Router) Group(prefix string) *SubRouter {
return &SubRouter{
prefix: prefix,
router: r,
}
}
func (sr *SubRouter) Handle(method, pattern string, handler http.HandlerFunc) {
sr.router.Handle(method, sr.prefix+pattern, handler)
}
func (sr *SubRouter) Use(mw Middleware) {
sr.router.Use(mw)
}
Uso prático:
router := NewRouter()
api := router.Group("/api/v1")
api.Use(AuthMiddleware)
api.GET("/users", listUsers)
api.POST("/users", createUser)
7. Tratamento de Erros e Respostas Consistentes
Helpers para respostas JSON padronizadas:
type APIResponse struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
func JSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(APIResponse{
Success: status < 400,
Data: data,
})
}
func ErrorJSON(w http.ResponseWriter, status int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(APIResponse{
Success: false,
Error: message,
})
}
Middleware global de tratamento de erros:
func ErrorHandlerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
ErrorJSON(w, http.StatusInternalServerError, "internal server error")
}
}()
next.ServeHTTP(w, r)
})
}
8. Testes do Roteador e Middlewares
Testes unitários com httptest:
func TestRouter_GET(t *testing.T) {
router := NewRouter()
router.GET("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("world"))
})
req := httptest.NewRequest("GET", "/hello", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "world", rec.Body.String())
}
func TestRouter_MethodNotAllowed(t *testing.T) {
router := NewRouter()
router.GET("/resource", handler)
req := httptest.NewRequest("POST", "/resource", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
assert.Equal(t, http.StatusMethodNotAllowed, rec.Code)
}
func TestMiddlewareChain(t *testing.T) {
var log []string
mw1 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log = append(log, "mw1")
next.ServeHTTP(w, r)
})
}
mw2 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log = append(log, "mw2")
next.ServeHTTP(w, r)
})
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log = append(log, "handler")
})
final := ApplyMiddleware(handler, mw1, mw2)
final.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", "/", nil))
assert.Equal(t, []string{"mw1", "mw2", "handler"}, log)
}
func TestRouter_Params(t *testing.T) {
router := NewRouter()
router.GET("/users/:id", func(w http.ResponseWriter, r *http.Request) {
params := r.Context().Value("params").(map[string]string)
w.Write([]byte(params["id"]))
})
req := httptest.NewRequest("GET", "/users/42", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
assert.Equal(t, "42", rec.Body.String())
}
Conclusão
Construir roteamento e middlewares sem frameworks em Go não apenas é possível, como também oferece controle total sobre o comportamento do servidor. A abordagem apresentada demonstra como:
- O pacote
net/httpfornece todos os blocos de construção necessários - Middlewares como funções de alta ordem permitem composição elegante
- O roteador manual pode ser estendido para suportar parâmetros, validação e sub-roteadores
- Testes com
httptestgarantem a confiabilidade do sistema
Para aplicações que exigem simplicidade e desempenho, essa abordagem supera muitos frameworks, mantendo o código enxuto e compreensível.
Referências
- Documentação oficial do pacote net/http — Referência completa da biblioteca padrão para construção de servidores HTTP.
- Writing Web Applications - Golang Docs — Tutorial oficial do Go sobre criação de aplicações web com roteamento manual.
- Go by Example: HTTP Servers — Exemplos práticos de servidores HTTP, middlewares e handlers.
- Building a Router in Go (Alex Edwards) — Artigo técnico detalhado sobre construção de roteadores manuais.
- Middleware Patterns in Go (Mat Ryer) — Padrões de design para middlewares em Go.
- httptest package documentation — Documentação oficial para testes de handlers HTTP.
- Go Web Examples: Middleware — Exemplos avançados de encadeamento de middlewares.