Negotiation in plain net/http
The
server-middleware module ships content
negotiation as a standalone, dependency-free package. You can drop
it into any net/http application — no spec, no analyzer, no
go-openapi/runtime import.
Install
The full module pulls only the standard library at runtime
(testify is _test.go-only).
Pick a response Content-Type
const mediaTypeXML = "application/xml"
// Pet is the demo resource served by the negotiation handler.
type Pet struct {
XMLName xml.Name `json:"-" xml:"pet"`
Name string `json:"name" xml:"name"`
}
func pickContentType() {
pet := Pet{Name: "Lassie"}
offers := []string{"application/json", mediaTypeXML}
http.HandleFunc("/pet", func(w http.ResponseWriter, r *http.Request) {
chosen := negotiate.ContentType(r, offers, "application/json")
w.Header().Set("Content-Type", chosen)
switch chosen {
case mediaTypeXML:
_ = xml.NewEncoder(w).Encode(pet)
default:
_ = json.NewEncoder(w).Encode(pet)
}
})
srv := &http.Server{
Addr: ":8080",
ReadHeaderTimeout: readHeaderTimeout,
}
log.Fatal(srv.ListenAndServe())
}Full source: docs/examples/contenttypes/negotiatestandalone/main.go
ContentType returns the most-acceptable offer per the request’s
Accept header (q-values, specificity, position-as-tiebreaker). If
no offer is acceptable, the third argument (the default offer) is
returned.
Pick a Content-Encoding
chosen := negotiate.ContentEncoding(r, []string{"gzip", "deflate"})
if chosen != "" {
w.Header().Set("Content-Encoding", chosen)
}Full source: docs/examples/contenttypes/negotiatestandalone/main.go
"" means “no offer is acceptable” — let your handler decide
whether to send the unencoded body or 406.
Exercise
MIME-parameter behaviour
As of v0.30 the negotiator honours MIME parameters by default — an
Accept of text/plain;charset=utf-8 does not match an offer of
text/plain;charset=ascii. Pre-v0.30 the parameters were stripped
before matching. Opt out per call to restore the old behaviour:
chosen := negotiate.ContentType(r, offers, "",
negotiate.WithIgnoreParameters(true),
)Full source: docs/examples/contenttypes/negotiatestandalone/main.go
Full algorithm and rationale: standalone / content negotiation.
Adding a Swagger UI to the same server
The same module ships
docui — stdlib-only handlers for
Swagger UI / RapiDoc / Redoc. Combining the two gives you a small
spec-served, doc-UI-equipped HTTP server with no OpenAPI runtime
dependency at all. See docui standalone
(queued) once we write that example.