Skip to content

Commit

Permalink
feat(actions/http): allow custom timeout message
Browse files Browse the repository at this point in the history
  • Loading branch information
wass3r committed Oct 4, 2023
1 parent 67cb26e commit af08ee9
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 24 deletions.
29 changes: 25 additions & 4 deletions core/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"html"
"html/template"
"net"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -365,15 +366,20 @@ func doRuleActions(message models.Message, outputMsgs chan<- models.Message, rul
// After running through all the actions, compose final message
val, err := craftResponse(rule, message)
if err != nil {
// error message will be ephemeral to not bother all users
message.IsEphemeral = true

log.Error().Msg(err.Error())

message.Output = err.Error()
outputMsgs <- message
} else {
message.Output = val
// Override out with an error message, if one was set
// if there was an error message defined, use it
if message.Error != "" {
message.Output = message.Error
}

outputMsgs <- message
} else {
message.Output = val
// Pass along whether the message should be a direct message
message.DirectMessageOnly = rule.DirectMessageOnly
outputMsgs <- message
Expand Down Expand Up @@ -458,7 +464,22 @@ func handleHTTP(action models.Action, msg *models.Message) error {

resp, err := handlers.HTTPReq(action, msg)
if err != nil {
// check if error is a timeout and handle custom timeout messaging
var netError net.Error
if errors.As(err, &netError) && err.(net.Error).Timeout() {

Check failure on line 469 in core/matcher.go

View workflow job for this annotation

GitHub Actions / build

type assertion on error will fail on wrapped errors. Use errors.As to check for specific errors (errorlint)

Check failure on line 469 in core/matcher.go

View workflow job for this annotation

GitHub Actions / validate

type assertion on error will fail on wrapped errors. Use errors.As to check for specific errors (errorlint)
timeoutMsg := action.TimeoutMessage

if timeoutMsg == "" {
timeoutMsg = fmt.Sprintf("request for %#q action timed out", action.Name)
}

msg.Error = timeoutMsg

return err
}

msg.Error = fmt.Sprintf("error in request made by action %#q - see bot admin for more information", action.Name)

return err
}

Expand Down
32 changes: 19 additions & 13 deletions handlers/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
)

// HTTPReq handles 'http' actions for rules.
func HTTPReq(args models.Action, msg *models.Message) (*models.HTTPResponse, error) {
func HTTPReq(args models.Action, msg *models.Message) (models.HTTPResponse, error) {
log.Info().Msgf("executing http request for action %#q", args.Name)

result := models.HTTPResponse{}

if args.Timeout == 0 {
// Default HTTP Timeout of 10 seconds
args.Timeout = 10
Expand All @@ -37,7 +39,7 @@ func HTTPReq(args models.Action, msg *models.Message) (*models.HTTPResponse, err
url, err := utils.Substitute(args.URL, msg.Vars)
if err != nil {
log.Error().Msg("failed substituting variables in url parameter")
return nil, err
return result, err
}

// TODO: refactor querydata
Expand All @@ -48,13 +50,13 @@ func HTTPReq(args models.Action, msg *models.Message) (*models.HTTPResponse, err
url, payload, err := prepRequestData(url, args.Type, args.QueryData, msg)
if err != nil {
log.Error().Msg("failed preparing the request data for the http request")
return nil, err
return result, err
}

req, err := http.NewRequestWithContext(context.Background(), args.Type, url, payload)
if err != nil {
log.Error().Msg("failed to create a new http request")
return nil, err
return result, err
}

req.Close = true
Expand All @@ -64,7 +66,7 @@ func HTTPReq(args models.Action, msg *models.Message) (*models.HTTPResponse, err
value, err := utils.Substitute(v, msg.Vars)
if err != nil {
log.Error().Msg("failed substituting variables in custom headers")
return nil, err
return result, err
}

req.Header.Add(k, value)
Expand All @@ -73,28 +75,32 @@ func HTTPReq(args models.Action, msg *models.Message) (*models.HTTPResponse, err
resp, err := client.Do(req)
if err != nil {
log.Error().Msg("failed to execute the http request")
return nil, err

// if we have a response, at least capture its status code
if resp != nil {
result.Status = resp.StatusCode
}

return result, err
}

defer resp.Body.Close()

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Error().Msg("failed to read response from http request")
return nil, err
return result, err
}

fields := extractFields(bodyBytes)

result := models.HTTPResponse{
Status: resp.StatusCode,
Raw: string(bodyBytes),
Data: fields,
}
result.Status = resp.StatusCode
result.Raw = string(bodyBytes)
result.Data = fields

log.Info().Msgf("http request for action %#q completed", args.Name)

return &result, nil
return result, nil
}

// Depending on the type of request we want to deal with the payload accordingly.
Expand Down
14 changes: 7 additions & 7 deletions handlers/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,15 @@ func TestHTTPReq(t *testing.T) {
tests := []struct {
name string
args args
want *models.HTTPResponse
want models.HTTPResponse
wantErr bool
}{
{"HTTPReq GET", args{args: TestGETAction, msg: &TestMessage}, &models.HTTPResponse{Status: 200, Raw: "", Data: ""}, false},
{"HTTPReq POST", args{args: TestPOSTAction, msg: &TestMessage}, &models.HTTPResponse{Status: 200, Raw: "", Data: ""}, false},
{"HTTPReq No Query", args{args: TestEmptyQueryAction, msg: &TestMessage}, &models.HTTPResponse{Status: 200, Raw: "", Data: ""}, false},
{"HTTPReq Error Response", args{args: TestErrorResponseAction, msg: &TestMessage}, &models.HTTPResponse{Status: 502, Raw: "", Data: ""}, false},
{"HTTPReq with Sub", args{args: TestQueryWithSubsAction, msg: &TestMessage}, nil, true},
{"HTTPReq with Error", args{args: TestWithError, msg: &TestMessage}, nil, true},
{"HTTPReq GET", args{args: TestGETAction, msg: &TestMessage}, models.HTTPResponse{Status: 200, Raw: "", Data: ""}, false},
{"HTTPReq POST", args{args: TestPOSTAction, msg: &TestMessage}, models.HTTPResponse{Status: 200, Raw: "", Data: ""}, false},
{"HTTPReq No Query", args{args: TestEmptyQueryAction, msg: &TestMessage}, models.HTTPResponse{Status: 200, Raw: "", Data: ""}, false},
{"HTTPReq Error Response", args{args: TestErrorResponseAction, msg: &TestMessage}, models.HTTPResponse{Status: 502, Raw: "", Data: ""}, false},
{"HTTPReq with Sub", args{args: TestQueryWithSubsAction, msg: &TestMessage}, models.HTTPResponse{Status: 0, Raw: "", Data: nil}, true},
{"HTTPReq with Error", args{args: TestWithError, msg: &TestMessage}, models.HTTPResponse{Status: 0, Raw: "", Data: nil}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions models/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Action struct {
URL string `mapstructure:"url"`
Cmd string `mapstructure:"cmd"`
Timeout int `mapstructure:"timeout"`
TimeoutMessage string `mapstructure:"timeout_message" binding:"omitempty"`
QueryData map[string]any `mapstructure:"query_data"`
CustomHeaders map[string]string `mapstructure:"custom_headers"`
Auth []Auth `mapstructure:"auth"`
Expand Down

0 comments on commit af08ee9

Please sign in to comment.