-
Notifications
You must be signed in to change notification settings - Fork 444
/
curl.go
134 lines (117 loc) · 3.82 KB
/
curl.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
package transforms
import (
"bytes"
"io"
"net/http"
"strconv"
"strings"
"github.com/solo-io/gloo/pkg/utils/kubeutils/kubectl"
)
const (
requestHeaderPrefix = "> "
responseHeaderPrefix = "< "
infoPrefix = "* "
bytesDataSuffix = " bytes data]"
responseStatusPrefix1dot1 = "< HTTP/1.1 "
responseStatusPrefix2 = "< HTTP/2 "
)
// WithCurlHttpResponse is a Gomega Transform that converts the string returned by an exec.Curl
// and transforms it into an http.Response. This is useful to be used in tandem with matchers.HaveHttpResponse
// NOTE: This is not feature complete, as we do not convert the entire response.
// For now, we handle HTTP/1.1 response headers, status, and body.
// The curl must be executed with verbose=true to include both the response headers/status
// and response body.
func WithCurlHttpResponse(curlResponse string) *http.Response {
headers := make(http.Header)
statusCode := 0
var bodyBuf bytes.Buffer
for _, line := range strings.Split(curlResponse, "\n") {
k, v := processResponseHeader(line)
if k != "" {
headers.Add(k, v)
continue
}
code := processResponseCode(line)
if code != 0 {
statusCode = code
continue
}
if isResponseBody(line) {
if bodyBuf.Len() > 0 {
bodyBuf.WriteString("\n")
}
bodyBuf.WriteString(line)
}
}
return &http.Response{
StatusCode: statusCode,
Header: headers,
Body: bytesBody(bodyBuf.Bytes()),
}
}
func WithCurlResponse(curlResponse *kubectl.CurlResponse) *http.Response {
headers := make(http.Header)
statusCode := 0
var bodyBuf bytes.Buffer
// Curl writes the body to stdout and the headers/status to stderr
// Headers/response code
for _, line := range strings.Split(curlResponse.StdErr, "\n") {
k, v := processResponseHeader(line)
if k != "" {
headers.Add(k, v)
continue
}
code := processResponseCode(line)
if code != 0 {
statusCode = code
}
}
// Body
bodyBuf.WriteString(curlResponse.StdOut)
return &http.Response{
StatusCode: statusCode,
Header: headers,
Body: bytesBody(bodyBuf.Bytes()),
}
}
// processResponseHeader processes the current line if it's a response header.
// Returns header key and value if the line was processed, otherwise returns empty strings.
func processResponseHeader(line string) (string, string) {
// check for response headers
if strings.HasPrefix(line, responseHeaderPrefix) {
headerParts := strings.Split(line[len(responseHeaderPrefix):], ": ")
if len(headerParts) == 2 {
// strip "\r" from the end of the value
return strings.ToLower(headerParts[0]), strings.TrimSuffix(headerParts[1], "\r")
}
}
return "", ""
}
// processResponseCode processes the current line if it's a response status code.
// Returns the status code if the line was processed, otherwise returns 0.
func processResponseCode(line string) int {
// check for response status. the line with the response code will be in the format
// `< HTTP/1.1 <code> <message>` or `< HTTP/2 <code> <message>`
if strings.HasPrefix(line, responseStatusPrefix1dot1) || strings.HasPrefix(line, responseStatusPrefix2) {
statusParts := strings.Split(line, " ")
if len(statusParts) >= 4 {
statusCode, err := strconv.Atoi(statusParts[2])
if err == nil {
return statusCode
}
}
}
return 0
}
// isResponseBody returns true if the current line is part of the response body, false otherwise.
func isResponseBody(line string) bool {
// if there is no special prefix/suffix, assume this is part of the response body
// (this may not work reliably for all curl outputs)
return !strings.HasPrefix(line, infoPrefix) &&
!strings.HasPrefix(line, requestHeaderPrefix) &&
!strings.HasPrefix(line, responseHeaderPrefix) &&
!strings.HasSuffix(line, bytesDataSuffix)
}
func bytesBody(bodyBytes []byte) io.ReadCloser {
return io.NopCloser(bytes.NewReader(bodyBytes))
}