Servidor HTTP básico con `net/http`
Go fue pensado para servicios de red. `net/http` te permite levantar un servidor productivo en pocas líneas, sin frameworks ni dependencias externas.
Hola, mundo (HTTP edition)
Un servidor HTTP en Go cabe en menos de 15 líneas. http.HandleFunc registra un handler en el mux por defecto y http.ListenAndServe arranca el servidor.
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hola desde Go!")
})
log.Println("Escuchando en :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
Escuchando en :8080
# curl http://localhost:8080/
Hola desde Go!Lo que pasa por debajo:
HandleFuncregistra una función con la firmafunc(w http.ResponseWriter, r *http.Request)en el DefaultServeMux global.ListenAndServe(":8080", nil)arranca el servidor. Elnilindica «usa el DefaultServeMux».wes donde escribes la respuesta;rcontiene toda la información de la petición entrante.
`ResponseWriter` y `Request`
Estos dos tipos son el corazón de cualquier handler.
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
// Headers de respuesta (deben ir ANTES de escribir el body)
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
// Status code (opcional; default 200)
w.WriteHeader(http.StatusOK)
// Body
fmt.Fprintf(w, "Method: %s\n", r.Method)
fmt.Fprintf(w, "Path: %s\n", r.URL.Path)
fmt.Fprintf(w, "Host: %s\n", r.Host)
}
Una vez que escribes en el body (con Write, Fprintf, etc.), los headers se envían y ya no se pueden cambiar. Configura headers y status code antes de escribir cualquier byte de respuesta.
Leer query params y body
r.URL.Query() da acceso a los parámetros del query string y r.Body al body (típicamente JSON).
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// GET /greet?name=Ada
func greetHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "desconocido"
}
fmt.Fprintf(w, "Hola, %s!\n", name)
}
// POST /users con JSON {"name": "...", "age": 30}
type CreateUserRequest struct {
Name string `json:"name"`
Age int `json:"age"`
}
func createUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
defer r.Body.Close()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]any{
"message": "usuario creado",
"user": req,
})
}
# curl 'http://localhost:8080/greet?name=Ada'
Hola, Ada!
# curl -X POST -d '{"name":"Ada","age":36}' http://localhost:8080/users
{"message":"usuario creado","user":{"name":"Ada","age":36}}http.Error es atajo para escribir un mensaje de error con su código de estado. json.NewEncoder(w).Encode(v) escribe directamente en el ResponseWriter, evitando intermedios.
`http.ServeMux`: tu propio router
El DefaultServeMux global es práctico para empezar, pero un servidor real define el suyo. Esto facilita testing, configuración por entorno y composición de servicios.
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "ok")
})
// Desde Go 1.22 se puede prefijar con método y usar variables de ruta
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "user id = %s\n", id)
})
log.Println("listening on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
# curl http://localhost:8080/health
ok
# curl http://localhost:8080/users/42
user id = 42A partir de Go 1.22, el mux estándar soporta métodos HTTP en los patrones (GET /path) y parámetros de ruta ({id}). Para muchísimos servicios, ya no necesitas un router externo como chi o gorilla/mux.
`http.Server` con timeouts
http.ListenAndServe es el shortcut. Para producción usa http.Server directamente y configura timeouts explícitos: sin ellos, una conexión lenta puede mantenerse abierta indefinidamente.
package main
import (
"log"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
log.Println("listening on", srv.Addr)
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
listening on :8080Los valores por defecto de http.Server son infinitos. Un cliente malicioso o una conexión rota pueden mantener goroutines vivas para siempre. Define ReadTimeout, WriteTimeout e IdleTimeout en cualquier servicio expuesto a internet.