Skip to content

Commit

Permalink
Merge pull request #69 from anuraaga/configurable-timeout
Browse files Browse the repository at this point in the history
feat: make request timeout configurable
  • Loading branch information
fzipi authored Sep 6, 2022
2 parents 61a73cf + fa9612c commit 67881bf
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 188 deletions.
25 changes: 24 additions & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"fmt"
"os"
"regexp"
"time"

"github.com/kyokomi/emoji"
"github.com/rs/zerolog"
Expand All @@ -25,6 +27,8 @@ var runCmd = &cobra.Command{
dir, _ := cmd.Flags().GetString("dir")
showTime, _ := cmd.Flags().GetBool("time")
quiet, _ := cmd.Flags().GetBool("quiet")
connectTimeout, _ := cmd.Flags().GetDuration("connect-timeout")
readTimeout, _ := cmd.Flags().GetDuration("read-timeout")
if !quiet {
log.Info().Msgf(emoji.Sprintf(":hammer_and_wrench: Starting tests!\n"))
} else {
Expand All @@ -43,7 +47,24 @@ var runCmd = &cobra.Command{
log.Fatal().Err(err)
}

currentRun := runner.Run(include, exclude, showTime, quiet, tests)
var includeRE *regexp.Regexp
if include != "" {
includeRE = regexp.MustCompile(include)
}
var excludeRE *regexp.Regexp
if exclude != "" {
excludeRE = regexp.MustCompile(exclude)
}

currentRun := runner.Run(tests, runner.Config{
Include: includeRE,
Exclude: excludeRE,
ShowTime: showTime,
Quiet: quiet,
ConnectTimeout: connectTimeout,
ReadTimeout: readTimeout,
})

os.Exit(currentRun.Stats.TotalFailed())
},
}
Expand All @@ -56,4 +77,6 @@ func init() {
runCmd.Flags().StringP("dir", "d", ".", "recursively find yaml tests in this directory")
runCmd.Flags().BoolP("quiet", "q", false, "do not show test by test, only results")
runCmd.Flags().BoolP("time", "t", false, "show time spent per test")
runCmd.Flags().Duration("connect-timeout", 3*time.Second, "timeout for connecting to endpoints during test execution")
runCmd.Flags().Duration("read-timeout", 1*time.Second, "timeout for receiving responses during test execution")
}
26 changes: 17 additions & 9 deletions ftwhttp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,24 @@ import (
"golang.org/x/net/publicsuffix"
)

// NewClientConfig returns a new ClientConfig with reasonable defaults.
func NewClientConfig() ClientConfig {
return ClientConfig{
ConnectTimeout: 3 * time.Second,
ReadTimeout: 1 * time.Second,
}
}

// NewClient initializes the http client, creating the cookiejar
func NewClient() *Client {
func NewClient(config ClientConfig) *Client {
// All users of cookiejar should import "golang.org/x/net/publicsuffix"
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
log.Fatal().Err(err)
}
c := &Client{
Jar: jar,
// default Timeout
Timeout: 3 * time.Second,
Jar: jar,
config: config,
}
return c
}
Expand All @@ -44,16 +51,17 @@ func (c *Client) NewConnection(d Destination) error {
// strings.HasSuffix(err.String(), "connection refused") {
if strings.ToLower(d.Protocol) == "https" {
// Commenting InsecureSkipVerify: true.
netConn, err = tls.DialWithDialer(&net.Dialer{Timeout: c.Timeout}, "tcp", hostPort, &tls.Config{MinVersion: tls.VersionTLS12})
netConn, err = tls.DialWithDialer(&net.Dialer{Timeout: c.config.ConnectTimeout}, "tcp", hostPort, &tls.Config{MinVersion: tls.VersionTLS12})
} else {
netConn, err = net.DialTimeout("tcp", hostPort, c.Timeout)
netConn, err = net.DialTimeout("tcp", hostPort, c.config.ConnectTimeout)
}

if err == nil {
c.Transport = &Connection{
connection: netConn,
protocol: d.Protocol,
duration: NewRoundTripTime(),
connection: netConn,
protocol: d.Protocol,
readTimeout: c.config.ReadTimeout,
duration: NewRoundTripTime(),
}
}

Expand Down
200 changes: 100 additions & 100 deletions ftwhttp/client_test.go
Original file line number Diff line number Diff line change
@@ -1,152 +1,152 @@
package ftwhttp

import (
"testing"
"testing"
)

func TestNewClient(t *testing.T) {
c := NewClient()
c := NewClient(NewClientConfig())

if c.Jar == nil {
t.Logf("Error creating Client")
}
if c.Jar == nil {
t.Logf("Error creating Client")
}
}

func TestConnectDestinationHTTPS(t *testing.T) {
d := &Destination{
DestAddr: "example.com",
Port: 443,
Protocol: "https",
}

c := NewClient()

err := c.NewConnection(*d)
if err != nil {
t.Logf("This should not error")
}

if c.Transport.protocol != "https" {
t.Logf("Error connecting to example.com using https")
}
d := &Destination{
DestAddr: "example.com",
Port: 443,
Protocol: "https",
}

c := NewClient(NewClientConfig())

err := c.NewConnection(*d)
if err != nil {
t.Logf("This should not error")
}

if c.Transport.protocol != "https" {
t.Logf("Error connecting to example.com using https")
}
}

func TestDoRequest(t *testing.T) {
d := &Destination{
DestAddr: "httpbin.org",
Port: 443,
Protocol: "https",
}
d := &Destination{
DestAddr: "httpbin.org",
Port: 443,
Protocol: "https",
}

c := NewClient()
c := NewClient(NewClientConfig())

req := generateBaseRequestForTesting()
req := generateBaseRequestForTesting()

err := c.NewConnection(*d)
if err != nil {
t.Logf("This should not error")
}
err := c.NewConnection(*d)
if err != nil {
t.Logf("This should not error")
}

_, err = c.Do(*req)
_, err = c.Do(*req)

if err == nil {
t.Logf("This should return error")
}
if err == nil {
t.Logf("This should return error")
}
}

func TestGetTrackedTime(t *testing.T) {
d := &Destination{
DestAddr: "httpbin.org",
Port: 443,
Protocol: "https",
}
d := &Destination{
DestAddr: "httpbin.org",
Port: 443,
Protocol: "https",
}

c := NewClient()
c := NewClient(NewClientConfig())

rl := &RequestLine{
Method: "POST",
URI: "/post",
Version: "HTTP/1.1",
}
rl := &RequestLine{
Method: "POST",
URI: "/post",
Version: "HTTP/1.1",
}

h := Header{"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost"}
h := Header{"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost"}

data := []byte(`test=me&one=two&one=twice`)
req := NewRequest(rl, h, data, true)
data := []byte(`test=me&one=two&one=twice`)
req := NewRequest(rl, h, data, true)

err := c.NewConnection(*d)
if err != nil {
t.Logf("This should not error")
}
err := c.NewConnection(*d)
if err != nil {
t.Logf("This should not error")
}

c.StartTrackingTime()
c.StartTrackingTime()

resp, err := c.Do(*req)
resp, err := c.Do(*req)

c.StopTrackingTime()
c.StopTrackingTime()

if err != nil {
t.Logf("This should not error")
}
if err != nil {
t.Logf("This should not error")
}

if resp.Parsed.StatusCode != 200 {
t.Logf("Error in calling website")
}
if resp.Parsed.StatusCode != 200 {
t.Logf("Error in calling website")
}

rtt := c.GetRoundTripTime()
rtt := c.GetRoundTripTime()

if rtt.RoundTripDuration() < 0 {
t.Logf("Error getting RTT")
}
if rtt.RoundTripDuration() < 0 {
t.Logf("Error getting RTT")
}
}

func TestClientMultipartFormDataRequest(t *testing.T) {
d := &Destination{
DestAddr: "httpbin.org",
Port: 443,
Protocol: "https",
}

c := NewClient()

rl := &RequestLine{
Method: "POST",
URI: "/post",
Version: "HTTP/1.1",
}

h := Header{
"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost",
"Content-Type": "multipart/form-data; boundary=--------397236876",
}

data := []byte(`----------397236876
d := &Destination{
DestAddr: "httpbin.org",
Port: 443,
Protocol: "https",
}

c := NewClient(NewClientConfig())

rl := &RequestLine{
Method: "POST",
URI: "/post",
Version: "HTTP/1.1",
}

h := Header{
"Accept": "*/*", "User-Agent": "go-ftw test agent", "Host": "localhost",
"Content-Type": "multipart/form-data; boundary=--------397236876",
}

data := []byte(`----------397236876
Content-Disposition: form-data; name="fileRap"; filename="test.txt"
Content-Type: text/plain
Some-file-test-here
----------397236876--`)

req := NewRequest(rl, h, data, true)
req := NewRequest(rl, h, data, true)

err := c.NewConnection(*d)
err := c.NewConnection(*d)

if err != nil {
t.Logf("This should not error")
}
if err != nil {
t.Logf("This should not error")
}

c.StartTrackingTime()
c.StartTrackingTime()

resp, err := c.Do(*req)
resp, err := c.Do(*req)

c.StopTrackingTime()
c.StopTrackingTime()

if err != nil {
t.Logf("This should not error")
}
if err != nil {
t.Logf("This should not error")
}

if resp.Parsed.StatusCode != 200 {
t.Logf("Error in calling website")
if resp.Parsed.StatusCode != 200 {
t.Logf("Error in calling website")
}

}
6 changes: 1 addition & 5 deletions ftwhttp/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,9 @@ func (c *Connection) receive() ([]byte, error) {
var err error
var buf []byte

// Set a deadline for reading. Read operation will fail if no data
// is received after deadline.
timeoutDuration := 1000 * time.Millisecond

// We assume the response body can be handled in memory without problems
// That's why we use io.ReadAll
if err = c.connection.SetReadDeadline(time.Now().Add(timeoutDuration)); err == nil {
if err = c.connection.SetReadDeadline(time.Now().Add(c.readTimeout)); err == nil {
buf, err = io.ReadAll(c.connection)
}

Expand Down
6 changes: 3 additions & 3 deletions ftwhttp/response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func TestResponse(t *testing.T) {
}
req := generateRequestForTesting(true)

client := NewClient()
client := NewClient(NewClientConfig())
err = client.NewConnection(*d)

if err != nil {
Expand All @@ -98,7 +98,7 @@ func TestResponse(t *testing.T) {
response, err := client.Do(*req)

if err != nil {
t.Logf("Failed !")
t.Fatal(err)
}

if response.GetBodyAsString() != "Hello, client\n" {
Expand All @@ -118,7 +118,7 @@ func TestResponseWithCookies(t *testing.T) {
}
req := generateRequestForTesting(true)

client := NewClient()
client := NewClient(NewClientConfig())
err = client.NewConnection(*d)

if err != nil {
Expand Down
Loading

0 comments on commit 67881bf

Please sign in to comment.