Send a CloudEvent over HTTP¶
http.Header is also map[string][]string, so nothing about the format changes. HTTP carries the
content type in Content-Type, so set it from Wire.ContentType:
w, err := cloudevents.Binary(cloudevents.HTTP).Encode(e)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, target, bytes.NewReader(w.Body))
if err != nil {
return err
}
req.Header = http.Header(w.Headers)
if w.ContentType != "" {
req.Header.Set("Content-Type", w.ContentType)
}
The case-folding wrinkle¶
http.Header canonicalises keys when you use its Set and Add methods: ce-id becomes Ce-Id,
content-type becomes Content-Type. Assigning the map directly, as above, skips that for the
attribute headers. Go's HTTP server and client both handle lower-case keys correctly on the wire.
Either way round, Binary(...).Decode matches every key without regard to case, so it accepts both
spellings.
Header.Get cannot see Encode's output
Get canonicalises the key it is given, so req.Header.Get("Ce-Id") returns "" against the
map Encode produced. Assign the map as above and read it back with Decode, or iterate it.
Do not reach for Get in a test or a middleware.
On the receiving side r.Header carries Content-Type itself, so pass the headers and the body:
func handler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "unreadable body", http.StatusBadRequest)
return
}
e, foreign, err := cloudevents.Binary(cloudevents.HTTP).Decode(
envelope.Wire{Headers: r.Header, Body: body},
)
if err != nil {
http.Error(w, "not a CloudEvent this service accepts", http.StatusBadRequest)
return
}
_ = foreign // Accept, User-Agent and the rest: everything that is not the event.
handle(e)
}
The fixture at testdata/sdk-binary-1.0.txt in
go/envelope-cloudevents
was produced by the CloudEvents SDK's own HTTP binding and carries Ce-Id rather than ce-id. It
is checked in precisely so that the folding cannot be removed without a test noticing.
What if two spellings disagree¶
Decode returns
ErrAmbiguousHeader. It does not pick one. An event
whose ce-id and CE-ID differ means two different things depending on which transport carried it,
and choosing either would make that difference invisible. The same goes for a Wire.ContentType
that disagrees with a content-type header.
Only the binding's prefix and content-type are compared. An ordinary proxy's repeated
X-Forwarded-For, Via or Accept is none of the event's business: it is returned untouched in
Foreign.Headers, not treated as an ambiguous event.