/
http_egress.go
116 lines (94 loc) · 3.6 KB
/
http_egress.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package strategies
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strings"
"time"
pb "github.com/buoyantio/bb/gen"
"github.com/buoyantio/bb/service"
log "github.com/sirupsen/logrus"
)
const (
// HTTPEgressStrategyName is the user-friendly name of this strategy
HTTPEgressStrategyName = "http-egress"
// HTTPEgressURLToInvokeArgName is the parameter used to supply the URL to fetch from
HTTPEgressURLToInvokeArgName = "url"
// HTTPEgressHTTPMethodToUseArgName is the parameter used to supply the HTTP 1.1 method used when fetching the URL
HTTPEgressHTTPMethodToUseArgName = "method"
// HTTPEgressHTTPTimeoutArgName is the timeout used to configure the HTTP client when fetching the URL
HTTPEgressHTTPTimeoutArgName = "http-client-timeout"
)
var validHTTPMethods = map[string]bool{"GET": true, "POST": true, "PUT": true, "DELETE": true, "PATCH": true}
// HTTPEgressStrategy a strategy that makes a HTTP 1.1 call to a pre-configured URL
type HTTPEgressStrategy struct {
httpClientToUse *http.Client
urlToInvoke string
methodToUse string
}
// Do executes the request
func (s *HTTPEgressStrategy) Do(_ context.Context, req *pb.TheRequest) (*pb.TheResponse, error) {
httpRequest, err := http.NewRequest(s.methodToUse, s.urlToInvoke, strings.NewReader(req.RequestUID))
if err != nil {
return nil, err
}
log.Infof("Making [%s] request to [%s] for requestUID [%s]", s.methodToUse, s.urlToInvoke, req.GetRequestUID())
httpResp, err := s.httpClientToUse.Do(httpRequest)
if err != nil {
return nil, err
}
log.Infof("Response from [%s] for requestUID [%s] was: %+v", s.urlToInvoke, req.GetRequestUID(), httpResp)
statusCode := httpResp.StatusCode
if statusCode < 200 || statusCode > 299 {
return nil, fmt.Errorf("unexpected status returned by [%s]for requestUID [%s]: %d", s.urlToInvoke, req.GetRequestUID(), statusCode)
}
bytes, err := ioutil.ReadAll(httpResp.Body)
if err != nil {
return nil, err
}
resp := &pb.TheResponse{
Payload: string(bytes),
}
return resp, err
}
// NewHTTPEgress creates a new HTTPEgressStrategy
func NewHTTPEgress(config *service.Config, servers []service.Server, clients []service.Client) (service.Strategy, error) {
if len(clients) != 0 || len(servers) == 0 {
return nil, fmt.Errorf("strategy [%s] requires at least one server port and exactly zero downstream services, but was configured as: %+v", HTTPEgressStrategyName, config)
}
urlToInvoke := config.ExtraArguments[HTTPEgressURLToInvokeArgName]
if urlToInvoke == "" {
return nil, fmt.Errorf("URL to invoke is nil")
}
isHTTP, err := regexp.MatchString("https?://", urlToInvoke)
if err != nil {
return nil, fmt.Errorf("error while validating URL [%s]: %v", urlToInvoke, err)
}
if !isHTTP {
return nil, fmt.Errorf("url must be HTTP or HTTPS, was [%s]", urlToInvoke)
}
_, err = url.Parse(urlToInvoke)
if err != nil {
return nil, fmt.Errorf("error while parsing URL [%s]: %v", urlToInvoke, err)
}
httpMethodToUse := config.ExtraArguments[HTTPEgressHTTPMethodToUseArgName]
if !validHTTPMethods[httpMethodToUse] {
return nil, fmt.Errorf("HTTP method [%s] isn't supported [%v]", httpMethodToUse, validHTTPMethods)
}
timeout, err := time.ParseDuration(config.ExtraArguments[HTTPEgressHTTPTimeoutArgName])
if err != nil {
return nil, fmt.Errorf("error while parsing timeout [%s]: %v", config.ExtraArguments[HTTPEgressHTTPTimeoutArgName], err)
}
httpClient := &http.Client{
Timeout: timeout,
}
log.Infof("HTTP client being used is: %+v", httpClient)
return &HTTPEgressStrategy{
urlToInvoke: urlToInvoke,
methodToUse: httpMethodToUse,
httpClientToUse: httpClient,
}, nil
}