Skip to content

Commit e257db3

Browse files
committed
Initial work
Signed-off-by: John Howard <[email protected]>
1 parent a952d05 commit e257db3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1073
-978
lines changed

perf.data

4.01 MB
Binary file not shown.
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
package curl
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"crypto/tls"
7+
"fmt"
8+
"io"
9+
"net"
10+
"net/http"
11+
"net/url"
12+
"strings"
13+
"time"
14+
)
15+
16+
// ExecuteRequest accepts a set of Option and executes a native Go HTTP request
17+
// If multiple Option modify the same parameter, the last defined one will win
18+
//
19+
// Example:
20+
//
21+
// resp, err := ExecuteRequest(WithMethod("GET"), WithMethod("POST"))
22+
// will executeNative a POST request
23+
//
24+
// A notable exception is the WithHeader option, which accumulates headers
25+
func ExecuteRequest(options ...Option) (*http.Response, error) {
26+
config := &requestConfig{
27+
verbose: false,
28+
ignoreServerCert: false,
29+
connectionTimeout: 0,
30+
headersOnly: false,
31+
method: "GET",
32+
host: "127.0.0.1",
33+
port: 80,
34+
headers: make(map[string][]string),
35+
scheme: "http",
36+
sni: "",
37+
caFile: "",
38+
path: "",
39+
retry: 0,
40+
retryDelay: -1,
41+
retryMaxTime: 0,
42+
ipv4Only: false,
43+
ipv6Only: false,
44+
cookie: "",
45+
queryParameters: make(map[string]string),
46+
}
47+
48+
for _, opt := range options {
49+
opt(config)
50+
}
51+
52+
return config.executeNative()
53+
}
54+
55+
func (c *requestConfig) executeNative() (*http.Response, error) {
56+
// Build URL
57+
fullURL := c.buildURL()
58+
59+
// Create HTTP client with custom transport
60+
client := c.buildHTTPClient()
61+
62+
// Prepare request body
63+
var bodyReader io.Reader
64+
if c.body != "" {
65+
bodyReader = bytes.NewBufferString(c.body)
66+
}
67+
68+
// Create context with timeout
69+
ctx := context.Background()
70+
if c.connectionTimeout > 0 {
71+
var cancel context.CancelFunc
72+
ctx, cancel = context.WithTimeout(ctx, time.Duration(c.connectionTimeout)*time.Second)
73+
defer cancel()
74+
}
75+
76+
// Create request
77+
req, err := http.NewRequestWithContext(ctx, c.method, fullURL, bodyReader)
78+
if err != nil {
79+
return nil, fmt.Errorf("failed to create request: %w", err)
80+
}
81+
82+
// Add headers
83+
for key, values := range c.headers {
84+
for _, value := range values {
85+
// Host header must be set on req.Host, not in req.Header
86+
if strings.EqualFold(key, "Host") {
87+
req.Host = value
88+
} else {
89+
req.Header.Add(key, value)
90+
}
91+
}
92+
}
93+
94+
// Add cookies
95+
if c.cookie != "" {
96+
req.Header.Add("Cookie", c.cookie)
97+
}
98+
99+
// Handle HEAD-only requests
100+
if c.headersOnly {
101+
req.Method = "HEAD"
102+
}
103+
104+
// Execute request
105+
if c.verbose {
106+
fmt.Printf("> %s %s\n", req.Method, req.URL.String())
107+
fmt.Printf("> Host: %s\n", req.Host)
108+
for k, v := range req.Header {
109+
fmt.Printf("> %s: %s\n", k, strings.Join(v, ", "))
110+
}
111+
}
112+
113+
resp, err := client.Do(req)
114+
if err != nil {
115+
if c.verbose {
116+
fmt.Printf("Request failed: %v\n", err)
117+
}
118+
return nil, err
119+
}
120+
121+
if c.verbose {
122+
fmt.Printf("< HTTP %s\n", resp.Status)
123+
for k, v := range resp.Header {
124+
fmt.Printf("< %s: %s\n", k, strings.Join(v, ", "))
125+
}
126+
}
127+
128+
return resp, nil
129+
}
130+
131+
func (c *requestConfig) buildURL() string {
132+
path := c.path
133+
if path != "" && !strings.HasPrefix(path, "/") {
134+
path = "/" + path
135+
}
136+
137+
baseURL := fmt.Sprintf("%s://%s:%d%s", c.scheme, c.host, c.port, path)
138+
139+
if len(c.queryParameters) > 0 {
140+
values := url.Values{}
141+
for k, v := range c.queryParameters {
142+
values.Add(k, v)
143+
}
144+
return fmt.Sprintf("%s?%s", baseURL, values.Encode())
145+
}
146+
147+
return baseURL
148+
}
149+
150+
func (c *requestConfig) buildHTTPClient() *http.Client {
151+
transport := &http.Transport{
152+
DialContext: c.buildDialer(),
153+
}
154+
155+
// Configure TLS
156+
if c.scheme == "https" || c.ignoreServerCert || c.sni != "" {
157+
tlsConfig := &tls.Config{
158+
InsecureSkipVerify: c.ignoreServerCert,
159+
}
160+
161+
if c.sni != "" {
162+
tlsConfig.ServerName = c.sni
163+
}
164+
165+
// Configure TLS version
166+
if c.tlsVersion != "" {
167+
tlsConfig.MinVersion = parseTLSVersion(c.tlsVersion)
168+
}
169+
if c.tlsMaxVersion != "" {
170+
tlsConfig.MaxVersion = parseTLSVersion(c.tlsMaxVersion)
171+
}
172+
173+
// Configure cipher suites (simplified)
174+
if c.ciphers != "" {
175+
// Note: Go's TLS implementation uses predefined cipher suites
176+
// This would require parsing the cipher string and mapping to Go's constants
177+
// For simplicity, this is left as a placeholder
178+
}
179+
180+
// Configure curves (simplified)
181+
if c.curves != "" {
182+
// Similar to ciphers, this would require parsing and mapping
183+
}
184+
185+
transport.TLSClientConfig = tlsConfig
186+
}
187+
188+
// Configure HTTP version
189+
if c.http2 {
190+
// HTTP/2 is enabled by default in Go's transport
191+
transport.ForceAttemptHTTP2 = true
192+
} else if c.http11 {
193+
// Disable HTTP/2 to force HTTP/1.1
194+
transport.ForceAttemptHTTP2 = false
195+
transport.TLSNextProto = make(map[string]func(string, *tls.Conn) http.RoundTripper)
196+
}
197+
198+
client := &http.Client{
199+
Transport: transport,
200+
}
201+
202+
// Set timeout (client-level timeout)
203+
if c.connectionTimeout > 0 {
204+
client.Timeout = time.Duration(c.connectionTimeout) * time.Second
205+
}
206+
207+
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
208+
// Disable redirects
209+
return http.ErrUseLastResponse
210+
}
211+
return client
212+
}
213+
214+
func (c *requestConfig) buildDialer() func(context.Context, string, string) (net.Conn, error) {
215+
dialer := &net.Dialer{
216+
Timeout: 30 * time.Second,
217+
}
218+
219+
if c.connectionTimeout > 0 {
220+
dialer.Timeout = time.Duration(c.connectionTimeout) * time.Second
221+
}
222+
223+
// Handle IPv4/IPv6 restrictions
224+
if c.ipv4Only {
225+
return func(ctx context.Context, network, addr string) (net.Conn, error) {
226+
return dialer.DialContext(ctx, "tcp4", addr)
227+
}
228+
}
229+
if c.ipv6Only {
230+
return func(ctx context.Context, network, addr string) (net.Conn, error) {
231+
return dialer.DialContext(ctx, "tcp6", addr)
232+
}
233+
}
234+
235+
// Handle SNI with custom host resolution
236+
// TODO
237+
if c.sni != "" {
238+
panic("sni is not implemented")
239+
}
240+
241+
return dialer.DialContext
242+
}
243+
244+
func parseTLSVersion(version string) uint16 {
245+
switch version {
246+
case "1.0":
247+
return tls.VersionTLS10
248+
case "1.1":
249+
return tls.VersionTLS11
250+
case "1.2":
251+
return tls.VersionTLS12
252+
case "1.3":
253+
return tls.VersionTLS13
254+
default:
255+
return tls.VersionTLS12 // default
256+
}
257+
}

profile.json.gz

12.3 KB
Binary file not shown.

test/e2e/common/base.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package common
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"testing"
8+
9+
"istio.io/istio/pkg/log"
10+
"istio.io/istio/pkg/test/util/assert"
11+
"istio.io/istio/pkg/test/util/retry"
12+
"k8s.io/apimachinery/pkg/types"
13+
14+
"github.com/kgateway-dev/kgateway/v2/pkg/utils/requestutils/curl"
15+
"github.com/kgateway-dev/kgateway/v2/test/e2e"
16+
"github.com/kgateway-dev/kgateway/v2/test/gomega/matchers"
17+
testmatchers "github.com/kgateway-dev/kgateway/v2/test/gomega/matchers"
18+
)
19+
20+
func SetupBaseConfig(ctx context.Context, t *testing.T, installation *e2e.TestInstallation, manifests ...string) {
21+
for _, s := range log.Scopes() {
22+
s.SetOutputLevel(log.DebugLevel)
23+
}
24+
err := installation.ClusterContext.IstioClient.ApplyYAMLFiles("", manifests...)
25+
assert.NoError(t, err)
26+
//for _, manifest := range manifests {
27+
//err := installation.Actions.Kubectl().ApplyFile(ctx, manifest)
28+
//}
29+
}
30+
31+
func SetupBaseGateway(ctx context.Context, installation *e2e.TestInstallation, name types.NamespacedName) {
32+
address := installation.Assertions.EventuallyGatewayAddress(
33+
ctx,
34+
name.Name,
35+
name.Namespace,
36+
)
37+
BaseGateway = Gateway{
38+
NamespacedName: name,
39+
Address: address,
40+
}
41+
}
42+
43+
type Gateway struct {
44+
types.NamespacedName
45+
Address string
46+
}
47+
48+
var BaseGateway Gateway
49+
50+
func (g *Gateway) Send(t *testing.T, match *testmatchers.HttpResponse, opts ...curl.Option) *http.Response {
51+
fullOpts := append([]curl.Option{curl.WithHost(g.Address)}, opts...)
52+
var resp *http.Response
53+
retry.UntilSuccessOrFail(t, func() error {
54+
r, err := curl.ExecuteRequest(fullOpts...)
55+
if err != nil {
56+
return err
57+
}
58+
resp = r
59+
mm := matchers.HaveHttpResponse(match)
60+
success, err := mm.Match(resp)
61+
if err != nil {
62+
return err
63+
}
64+
if !success {
65+
return fmt.Errorf("match failed: %v", mm.FailureMessage(resp))
66+
}
67+
return nil
68+
})
69+
return resp
70+
}

0 commit comments

Comments
 (0)