Skip to content

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.