forked from target/flottbot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
http.go
165 lines (137 loc) · 3.97 KB
/
http.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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package handlers
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/target/flottbot/models"
"github.com/target/flottbot/utils"
)
// HTTPReq handles 'http' actions for rules
func HTTPReq(args models.Action, msg *models.Message, bot *models.Bot) (*models.HTTPResponse, error) {
bot.Log.Info().Msgf("executing http request for action '%s'", args.Name)
if args.Timeout == 0 {
// Default HTTP Timeout of 10 seconds
args.Timeout = 10
}
client := &http.Client{
Timeout: time.Duration(args.Timeout) * time.Second,
}
// check the URL string from defined action has a variable, try to substitute it
url, err := utils.Substitute(args.URL, msg.Vars)
if err != nil {
bot.Log.Error().Msg("failed substituting variables in url parameter")
return nil, err
}
// TODO: refactor querydata
// this is a temp fix for scenarios where
// substitution above may have introduced spaces in the URL
url = strings.Replace(url, " ", "%20", -1)
url, payload, err := prepRequestData(url, args.Type, args.QueryData, msg)
if err != nil {
bot.Log.Error().Msg("failed preparing the request data for the http request")
return nil, err
}
req, err := http.NewRequest(args.Type, url, payload)
if err != nil {
bot.Log.Error().Msg("failed to create a new http request")
return nil, err
}
req.Close = true
// Add custom headers to request
for k, v := range args.CustomHeaders {
value, err := utils.Substitute(v, msg.Vars)
if err != nil {
bot.Log.Error().Msg("failed substituting variables in custom headers")
return nil, err
}
req.Header.Add(k, value)
}
resp, err := client.Do(req)
if err != nil {
bot.Log.Error().Msg("failed to execute the http request")
return nil, err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
bot.Log.Error().Msg("failed to read response from http request")
return nil, err
}
fields, err := extractFields(bodyBytes)
if err != nil {
bot.Log.Error().Msg("failed to extract the fields from the http response")
return nil, err
}
result := models.HTTPResponse{
Status: resp.StatusCode,
Raw: string(bodyBytes),
Data: fields,
}
bot.Log.Info().Msgf("http request for action '%s' completed", args.Name)
return &result, nil
}
// Depending on the type of request we want to deal with the payload accordingly
func prepRequestData(url, actionType string, data map[string]interface{}, msg *models.Message) (string, io.Reader, error) {
if len(data) > 0 {
if actionType == http.MethodGet {
query, err := createGetQuery(data, msg)
if err != nil {
return url, nil, err
}
url = fmt.Sprintf("%s?%s", url, query)
return url, nil, nil
}
query, err := createJSONPayload(data, msg)
if err != nil {
return url, nil, err
}
return url, strings.NewReader(query), nil
}
return url, nil, nil
}
// Unmarshal arbitrary JSON
func extractFields(raw []byte) (interface{}, error) {
var resp map[string]interface{}
err := json.Unmarshal(raw, &resp)
if err != nil {
var arrResp []map[string]interface{}
err := json.Unmarshal(raw, &arrResp)
if err != nil {
return string(raw), nil
}
return arrResp, nil
}
return resp, nil
}
// Create GET query string
func createGetQuery(data map[string]interface{}, msg *models.Message) (string, error) {
u := url.Values{}
for k, v := range data {
subv, err := utils.Substitute(v.(string), msg.Vars)
if err != nil {
return "", err
}
u.Add(k, subv)
}
encoded := u.Encode() // uses QueryEscape
encoded = strings.Replace(encoded, "+", "%20", -1) // replacing + with more reliable %20
return encoded, nil
}
// Create querydata payload for non-GET requests
func createJSONPayload(data map[string]interface{}, msg *models.Message) (string, error) {
dataNice := utils.MakeNiceJSON(data)
str, err := json.Marshal(dataNice)
if err != nil {
return "", err
}
payload, err := utils.Substitute(string(str), msg.Vars)
if err != nil {
return "", err
}
return payload, nil
}