Upgrade to Pro — share decks privately, control downloads, hide ads and more …

From Zero to 400 webhooks/second in 24 hours

From Zero to 400 webhooks/second in 24 hours

A brief intro about the main three things I believe make Go so good. for the cloud: simplicity, a great standard library, and performance.

Ernesto Jiménez

August 28, 2017
Tweet

More Decks by Ernesto Jiménez

Other Decks in Programming

Transcript

  1. FROM golang:1.8 as builder WORKDIR /go/src/github.com/gocardless/whebhook-dispatcher ADD . . RUN

    CGO_ENABLED=0 GOOS=linux \ go install -a -installsuffix cgo \ github.com/gocardless/whebhook-dispatcher/cmd/... FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /go/bin/wh-* /go/bin/demo-webhook /bin/ CMD ["wh-receiver"]
  2. r := receiving.NewService(endpointsRepo, webhooksRepo, webhooksQueue) http.HandleFunc( "/webhooks/send", func(w http.ResponseWriter, req

    *http.Request) { err := r.Receive( req.Header.Get("X-WH-Endpoint-Key"), forwardedHeaders(req.Header), req.Body, ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } w.WriteHeader(http.StatusCreated) }, ) http.ListenAndServe(":80", http.DefaultServeMux)
  3. io

  4. package io type Reader interface { Read(p []byte) (n int,

    err error) } type Writer interface { Write(p []byte) (n int, err error) }
  5. // calculate hash of the request body h := sha1.New()

    io.Copy(h, request.Body) h.Sum(nil)
  6. // calculate hash and decode json var user User h

    := sha1.New() r := io.TeeReader(request.Body, h) json.NewDecoder(r).Decode(&user) h.Sum(nil)
  7. r := receiving.NewService(endpointsRepo, webhooksRepo, webhooksQueue) http.HandleFunc( "/webhooks/send", func(w http.ResponseWriter, req

    *http.Request) { err := r.Receive( req.Header.Get("X-WH-Endpoint-Key"), forwardedHeaders(req.Header), req.Body, ⚠ ) // ... }, ) http.ListenAndServe(":80", http.DefaultServeMux)
  8. r := receiving.NewService(endpointsRepo, webhooksRepo, webhooksQueue) http.HandleFunc( "/webhooks/send", func(w http.ResponseWriter, req

    *http.Request) { err := r.Receive( req.Header.Get("X-WH-Endpoint-Key"), forwardedHeaders(req.Header), io.LimitReader(req.Body, 10 * 1024), ! ✅ ) // ... }, ) http.ListenAndServe(":80", http.DefaultServeMux)
  9. r := receiving.NewService(endpointsRepo, webhooksRepo, webhooksQueue) http.HandleFunc( "/webhooks/send", func(w http.ResponseWriter, req

    *http.Request) { err := r.Receive( req.Context(),❓" req.Header.Get("X-WH-Endpoint-Key"), forwardedHeaders(req.Header), io.LimitReader(req.Body, 10 * 1024), ) // ... }, ) http.ListenAndServe(":80", http.DefaultServeMux)
  10. func (s *receiver) Receive( ctx context.Context, ! ✅ key string,

    headers http.Header, body io.Reader, ) error { e, err := s.endpoints.FindByKey(ctx, key) ✅ if err != nil { return err } wh, err := webhooks.New(e.ID) if err != nil { return err } wh.Reader = body wh.Headers = headers err = s.webhooks.Put(ctx, wh) ✅ if err != nil { return err } err = s.queue.Enqueue(ctx, wh) ✅ if err != nil { return err } return nil }
  11. r := receiving.NewService(endpointsRepo, webhooksRepo, webhooksQueue) http.HandleFunc("/webhooks/send", func(w http.ResponseWriter, req *http.Request)

    { ctx, cancel := context.WithTimeout(req.Context(), 20 * time.Second) defer cancel() err := r.Receive( ctx, req.Header.Get("X-WH-Endpoint-Key"), forwardedHeaders(req.Header), io.LimitReader(req.Body, 10 * 1024), ) // ... } http.ListenAndServe(":80", http.DefaultServeMux)
  12. r := receiving.NewService(endpointsRepo, webhooksRepo, webhooksQueue) http.HandleFunc("/webhooks/send", func(w http.ResponseWriter, req *http.Request)

    { // ... } http.ListenAndServe( ":80", nethttp.Middleware( opentracing.GlobalTracer(), http.DefaultServeMux, ), ✅ )
  13. func (s *dispatcher) Dispatch(ctx context.Context, e *endpoints.Endpoint, id string) error

    { wh, err := s.webhooks.Get(ctx, id) if err != nil { return err } defer wh.Close() ctx, cancel := context.WithTimeout(ctx, 10 * time.Second) defer cancel() req, err := http.NewRequest(http.MethodPost, e.URL, wh.Body) if err != nil { return err } req = req.WithContext(ctx) res, err := s.client.Do(req) if err != nil { return err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return errors.Errorf("invalid status response: %s", res.Status) } return nil }
  14. // package cloud.google.com/go/storage type Writer struct { // ChunkSize controls

    the maximum number of bytes of the object that the // Writer will attempt to send to the server in a single request. Objects // smaller than the size will be sent in a single request, while larger // objects will be split over multiple requests. The size will be rounded up // to the nearest multiple of 256K. If zero, chunking will be disabled and // the object will be uploaded in a single request. // // ChunkSize will default to a reasonable value. Any custom configuration // must be done before the first Write call. ChunkSize int // ... }