Go
Go foi feita pra HTTP. A stdlib net/http aceita qualquer string como método em http.NewRequest — não precisa de monkey-patch, flag experimental, nem dependência extra. QUERY funciona out-of-the-box desde Go 1.0. Com o roteador melhorado do Go 1.22+, o ServeMux aceita "QUERY /path" direto no pattern. Frameworks como Chi também suportam métodos customizados com uma linha de registro.
Servidor net/http (stdlib)
Seção intitulada “Servidor net/http (stdlib)”O handler mais idiomático possível: ServeMux do Go 1.22+ com pattern "QUERY /rota".
package main
import ( "encoding/json" "log" "net/http")
// SearchRequest representa o body de uma requisição QUERY.type SearchRequest struct { Filter map[string]string `json:"filter"` Fields []string `json:"fields"` Limit int `json:"limit,omitempty"`}
// SearchResponse é o payload de resposta.type SearchResponse struct { Results []map[string]any `json:"results"` Total int `json:"total"`}
func queryHandler(w http.ResponseWriter, r *http.Request) { var req SearchRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid json"}`, http.StatusBadRequest) return }
// Simula busca filtrada results := []map[string]any{ {"id": 1, "name": "Teclado Mecânico", "status": "active"}, {"id": 2, "name": "Monitor 4K", "status": "active"}, }
resp := SearchResponse{Results: results, Total: len(results)}
w.Header().Set("Content-Type", "application/json") // QUERY é cacheable — adicione headers de cache w.Header().Set("Cache-Control", "public, max-age=60") json.NewEncoder(w).Encode(resp)}
func main() { mux := http.NewServeMux() mux.HandleFunc("QUERY /products", queryHandler)
log.Println("servidor rodando em :8080") log.Fatal(http.ListenAndServe(":8080", mux))}Pontos-chave:
"QUERY /products"no pattern — Go 1.22+ roteia pelo método automaticamente.- Sem framework, sem dependência. Só stdlib.
Cache-Controlsinaliza que a resposta é cacheável (semântica QUERY).
Cliente http.NewRequest
Seção intitulada “Cliente http.NewRequest”O cliente Go já suporta "QUERY" como método — é só uma string. Nenhum hack.
package main
import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http")
func main() { payload := map[string]any{ "filter": map[string]string{"status": "active", "category": "electronics"}, "fields": []string{"id", "name", "price"}, "limit": 10, }
body, err := json.Marshal(payload) if err != nil { log.Fatal(err) }
req, err := http.NewRequest("QUERY", "http://localhost:8080/products", bytes.NewReader(body)) if err != nil { log.Fatal(err) } req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body) fmt.Printf("Status: %s\nBody: %s\n", resp.Status, data)}Por que funciona: http.NewRequest aceita qualquer string no primeiro parâmetro. O transport HTTP/1.1 do Go envia o método literal no request line: QUERY /products HTTP/1.1.
Chi Router (método customizado)
Seção intitulada “Chi Router (método customizado)”Chi precisa de chi.RegisterMethod no init() pra reconhecer métodos não-padrão no roteamento.
package main
import ( "encoding/json" "log" "net/http"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware")
func init() { // Registra QUERY como método reconhecido pelo roteador chi.RegisterMethod("QUERY")}
type SearchRequest struct { Filter map[string]string `json:"filter"` Fields []string `json:"fields"` Limit int `json:"limit,omitempty"`}
func main() { r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.Recoverer)
// MethodFunc registra handler para método customizado r.MethodFunc("QUERY", "/products", func(w http.ResponseWriter, r *http.Request) { var req SearchRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid json"}`, http.StatusBadRequest) return }
results := map[string]any{ "results": []map[string]any{ {"id": 1, "name": "Teclado Mecânico", "price": 349.90}, }, "total": 1, "query": req, }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "public, max-age=60") json.NewEncoder(w).Encode(results) })
log.Println("chi server em :8080") log.Fatal(http.ListenAndServe(":8080", r))}Detalhes:
chi.RegisterMethod("QUERY")noinit()— obrigatório antes de criar o router.r.MethodFunc("QUERY", "/products", handler)— registra a rota.- Todo middleware do Chi funciona normalmente (Logger, Recoverer, etc).
Teste com httptest
Seção intitulada “Teste com httptest”Teste de integração idiomático sem subir servidor real.
package main
import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing")
func TestQueryHandler(t *testing.T) { // Monta o handler mux := http.NewServeMux() mux.HandleFunc("QUERY /products", queryHandler)
// Cria request QUERY com body JSON payload := map[string]any{ "filter": map[string]string{"status": "active"}, "fields": []string{"id", "name"}, } body, _ := json.Marshal(payload)
req := httptest.NewRequest("QUERY", "/products", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json")
// Grava a resposta rec := httptest.NewRecorder() mux.ServeHTTP(rec, req)
// Verifica status if rec.Code != http.StatusOK { t.Fatalf("esperava 200, recebeu %d", rec.Code) }
// Verifica Content-Type ct := rec.Header().Get("Content-Type") if ct != "application/json" { t.Fatalf("esperava application/json, recebeu %s", ct) }
// Verifica Cache-Control (QUERY é cacheable) cc := rec.Header().Get("Cache-Control") if cc == "" { t.Fatal("esperava Cache-Control header pra método QUERY") }
// Decodifica resposta var resp SearchResponse if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { t.Fatalf("erro decodificando resposta: %v", err) }
if resp.Total == 0 { t.Fatal("esperava resultados, recebeu zero") }}
func TestQueryHandler_InvalidJSON(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("QUERY /products", queryHandler)
req := httptest.NewRequest("QUERY", "/products", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder() mux.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest { t.Fatalf("esperava 400 pra JSON inválido, recebeu %d", rec.Code) }}
func TestQueryHandler_WrongMethod(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("QUERY /products", queryHandler)
// GET não deve atingir o handler QUERY req := httptest.NewRequest("GET", "/products", nil) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req)
if rec.Code == http.StatusOK { t.Fatal("GET não deveria retornar 200 em rota QUERY") }}httptest.NewRequest aceita qualquer método — mesmo princípio do http.NewRequest. Sem mágica.
Testando com curl
Seção intitulada “Testando com curl”curl suporta qualquer método via -X. Aqui os cenários mais úteis:
# Requisição QUERY básica com body JSONcurl -X QUERY http://localhost:8080/products \ -H "Content-Type: application/json" \ -d '{"filter":{"status":"active"},"fields":["id","name"],"limit":10}'
curl -X QUERY http://localhost:8080/products \ -H "Content-Type: application/json" \ -d '{"filter":{"category":"electronics"}}' \ -v
echo '{"filter":{"status":"active","price_min":100},"fields":["id","name","price"]}' > query.jsoncurl -X QUERY http://localhost:8080/products \ -H "Content-Type: application/json" \ -d @query.json
curl -X QUERY http://localhost:8080/products \ -H "Content-Type: application/json" \ -d '{"filter":{"status":"active"}}' \ -s -o /dev/null -D -Saída esperada do verbose:
> QUERY /products HTTP/1.1> Host: localhost:8080> Content-Type: application/json> Content-Length: 65>< HTTP/1.1 200 OK< Content-Type: application/json< Cache-Control: public, max-age=60Por que Go é ideal pra QUERY
Seção intitulada “Por que Go é ideal pra QUERY”| Aspecto | Como Go resolve |
|---|---|
| Método customizado | http.NewRequest("QUERY", ...) — é só string |
| Roteamento | ServeMux Go 1.22+ aceita "QUERY /path" |
| Body em safe method | Transport envia body normalmente |
| Cache semântico | Headers padrão, sem hack |
| Teste | httptest.NewRequest("QUERY", ...) direto |
| Frameworks | Chi: RegisterMethod + MethodFunc |
Sem reflection, sem build tag, sem flag. O design do net/http já contemplava métodos arbitrários desde o dia zero.