11package models
22
33import (
4+ "bytes"
45 "errors"
56 "fmt"
67 "io"
78 "io/ioutil"
89 "net/http"
10+ "time"
911
1012 "github.com/apex/log"
1113 "github.com/script-development/RT-CV/db"
@@ -87,7 +89,7 @@ func (k DataKind) contentTypeAndDataKind() (contentType string, dataKind string)
8789
8890// CallAndLogResult calls the hook defined in OnMatchHook and logs the result
8991func (h * OnMatchHook ) CallAndLogResult (body io.Reader , dataKind DataKind , logger * log.Entry ) {
90- _ , err := h .Call (body , dataKind )
92+ _ , err := h .CallWithRetry (body , dataKind )
9193
9294 loggerWithFields := logger .WithField ("hook" , h .URL ).WithField ("hook_id" , h .ID .Hex ())
9395 if err != nil {
@@ -97,15 +99,74 @@ func (h *OnMatchHook) CallAndLogResult(body io.Reader, dataKind DataKind, logger
9799 }
98100}
99101
102+ // CallWithRetry executes (*OnMatchHook).Call() with a retry if it failes with spesific reasons
103+ func (h * OnMatchHook ) CallWithRetry (body io.Reader , dataKind DataKind ) (http.Header , error ) {
104+ reqID := primitive .NewObjectID ().String ()
105+ // do 5 retries
106+ var headers http.Header
107+ var err error
108+ for i := 0 ; i < 5 ; i ++ {
109+ // Is retry, do a backoff
110+ switch i {
111+ case 1 :
112+ time .Sleep (time .Millisecond * 100 )
113+ case 2 :
114+ time .Sleep (time .Second )
115+ case 3 :
116+ time .Sleep (time .Second * 2 )
117+ case 4 :
118+ time .Sleep (time .Second * 3 )
119+ }
120+
121+ if body == nil {
122+ body = bytes .NewReader (nil )
123+ }
124+ headers , err = h .Call (body , dataKind , reqID )
125+ if err == nil {
126+ break
127+ }
128+ statusCodeErr , ok := err .(* StatusCodeError )
129+ if ! ok {
130+ break
131+ }
132+
133+ retry := false
134+ switch statusCodeErr .code {
135+ case http .StatusBadGateway , http .StatusServiceUnavailable , http .StatusGatewayTimeout :
136+ retry = true
137+ }
138+
139+ if ! retry {
140+ break
141+ }
142+ }
143+ return headers , err
144+ }
145+
146+ // StatusCodeError is an error thrown by (*OnMatchHook).Call() when the status code is >= 400
147+ type StatusCodeError struct {
148+ status string
149+ code int
150+ body []byte
151+ }
152+
153+ func (e * StatusCodeError ) Error () string {
154+ if len (e .body ) == 0 {
155+ return fmt .Sprintf ("hook returned status code \" %s\" with a unreadable message" , e .status )
156+ }
157+ return fmt .Sprintf ("hook returned status code \" %s\" with message: %s" , e .status , string (e .body ))
158+ }
159+
100160// Call calls the hook defined in OnMatchHook
101- func (h * OnMatchHook ) Call (body io.Reader , dataKind DataKind ) (http.Header , error ) {
161+ func (h * OnMatchHook ) Call (body io.Reader , dataKind DataKind , reqID string ) (http.Header , error ) {
102162 req , err := http .NewRequest (h .Method , h .URL , body )
103163 if err != nil {
104164 return nil , err
105165 }
106166
107167 req .Header .Set ("Content-Type" , "application/json" )
108168 req .Header .Set ("User-Agent" , "RT-CV" )
169+ req .Header .Set ("X-Request-ID" , reqID )
109170
110171 contentTypeHeader , dataKindHeader := dataKind .contentTypeAndDataKind ()
111172 req .Header .Set ("Content-Type" , contentTypeHeader )
@@ -123,12 +184,12 @@ func (h *OnMatchHook) Call(body io.Reader, dataKind DataKind) (http.Header, erro
123184 }
124185
125186 if resp .StatusCode >= 400 {
126- respBody , err := ioutil .ReadAll (resp .Body )
127- if err != nil {
128- return req .Header , fmt .Errorf ("hook returned status code \" %s\" with a unreadable message, error: %s" , resp .Status , err .Error ())
187+ respBody , _ := ioutil .ReadAll (resp .Body )
188+ return req .Header , & StatusCodeError {
189+ status : resp .Status ,
190+ code : resp .StatusCode ,
191+ body : respBody ,
129192 }
130-
131- return req .Header , fmt .Errorf ("hook returned status code \" %s\" with message: %s" , resp .Status , string (respBody ))
132193 }
133194
134195 return req .Header , nil
0 commit comments