...

Source file src/github.com/chaos-mesh/chaos-mesh/controllers/statuscheck/http/http.go

Documentation: github.com/chaos-mesh/chaos-mesh/controllers/statuscheck/http

     1  // Copyright Chaos Mesh Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  
    16  package http
    17  
    18  import (
    19  	"bytes"
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/go-logr/logr"
    28  	"github.com/pkg/errors"
    29  
    30  	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
    31  )
    32  
    33  type httpExecutor struct {
    34  	logger logr.Logger
    35  
    36  	timeoutSeconds  int
    37  	httpStatusCheck v1alpha1.HTTPStatusCheck
    38  }
    39  
    40  func NewExecutor(logger logr.Logger, timeoutSeconds int, httpStatusCheck v1alpha1.HTTPStatusCheck) *httpExecutor {
    41  	return &httpExecutor{logger: logger, timeoutSeconds: timeoutSeconds, httpStatusCheck: httpStatusCheck}
    42  }
    43  
    44  type response struct {
    45  	statusCode int
    46  	body       string
    47  }
    48  
    49  func (e *httpExecutor) Type() string {
    50  	return "HTTP"
    51  }
    52  
    53  func (e *httpExecutor) Do() (bool, string, error) {
    54  	client := &http.Client{
    55  		Timeout: time.Duration(e.timeoutSeconds) * time.Second,
    56  	}
    57  
    58  	httpStatusCheck := e.httpStatusCheck
    59  	return e.DoHTTPRequest(client,
    60  		httpStatusCheck.RequestUrl,
    61  		string(httpStatusCheck.RequestMethod),
    62  		httpStatusCheck.RequestHeaders,
    63  		[]byte(httpStatusCheck.RequestBody),
    64  		httpStatusCheck.Criteria)
    65  }
    66  
    67  func (e *httpExecutor) DoHTTPRequest(client *http.Client, url, method string,
    68  	headers http.Header, body []byte, criteria v1alpha1.HTTPCriteria) (bool, string, error) {
    69  	req, err := http.NewRequest(method, url, bytes.NewReader(body))
    70  	if err != nil {
    71  		return false, errors.Wrap(err, "new http request").Error(), nil
    72  	}
    73  	req.Header = headers
    74  	resp, err := client.Do(req)
    75  	if err != nil {
    76  		return false, errors.Wrap(err, "do http request").Error(), nil
    77  	}
    78  	defer resp.Body.Close()
    79  
    80  	responseBody, err := io.ReadAll(resp.Body)
    81  	if err != nil {
    82  		return false, "", errors.Wrap(err, "read response body")
    83  	}
    84  
    85  	return validate(e.logger.WithValues("url", url),
    86  		criteria, response{statusCode: resp.StatusCode, body: string(responseBody)})
    87  }
    88  
    89  func validate(logger logr.Logger, criteria v1alpha1.HTTPCriteria, resp response) (bool, string, error) {
    90  	ok := validateStatusCode(criteria.StatusCode, resp)
    91  	if !ok {
    92  		logger.Info("validate status code failed",
    93  			"criteria", criteria.StatusCode,
    94  			"statusCode", resp.statusCode)
    95  		return false, fmt.Sprintf("unexpected status code: %d", resp.statusCode), nil
    96  	}
    97  	return ok, "", nil
    98  }
    99  
   100  // validateStatusCode validate whether the result is as expected.
   101  // A criteria(statusCode) string could be a single code (e.g. 200), or
   102  // an inclusive range (e.g. 200-400, both `200` and `400` are included).
   103  // The format of the criteria field will be validated in webhook.
   104  func validateStatusCode(criteria string, resp response) bool {
   105  	if code, err := strconv.Atoi(criteria); err == nil {
   106  		return code == resp.statusCode
   107  	}
   108  	index := strings.Index(criteria, "-")
   109  	if index == -1 {
   110  		return false
   111  	}
   112  	start := criteria[:index]
   113  	end := criteria[index+1:]
   114  	startStatusCode, err := strconv.Atoi(start)
   115  	if err != nil {
   116  		return false
   117  	}
   118  	endStatusCode, err := strconv.Atoi(end)
   119  	if err != nil {
   120  		return false
   121  	}
   122  	return resp.statusCode >= startStatusCode &&
   123  		resp.statusCode <= endStatusCode
   124  }
   125