diff --git a/api/api.go b/api/api.go index 37ab599..282740d 100644 --- a/api/api.go +++ b/api/api.go @@ -24,7 +24,7 @@ import ( "strings" "time" - "github.com/blinklabs-io/cardano-models" + models "github.com/blinklabs-io/cardano-models" "github.com/blinklabs-io/gouroboros/ledger" "github.com/fxamacker/cbor/v2" ginzap "github.com/gin-contrib/zap" @@ -113,6 +113,9 @@ func handleSubmitTx(c *gin.Context) { } } } + // Create custom HTTP client + client := createHTTPClient(cfg) + // Send request to each backend for _, backend := range cfg.Backends { go func(backend string) { @@ -138,7 +141,7 @@ func handleSubmitTx(c *gin.Context) { return } req.Header.Add("Content-Type", "application/cbor") - resp, err := http.DefaultClient.Do(req) + resp, err := client.Do(req) if err != nil { logger.Errorf( "failed to send request to backend %s: %s", @@ -175,3 +178,17 @@ func handleSubmitTx(c *gin.Context) { // Return transaction ID c.String(202, tx.Hash()) } + +// createHTTPClient with custom timeout +func createHTTPClient(cfg *config.Config) *http.Client { + return &http.Client{ + Timeout: time.Duration(cfg.Api.ClientTimeout) * time.Millisecond, + Transport: &http.Transport{ + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + DisableCompression: false, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }, + } +} diff --git a/api/api_test.go b/api/api_test.go new file mode 100644 index 0000000..06978c5 --- /dev/null +++ b/api/api_test.go @@ -0,0 +1,64 @@ +package api + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/blinklabs-io/tx-submit-api-mirror/config" +) + +func TestHTTPClientTimeout(t *testing.T) { + client := createHTTPClient(&config.Config{ + Api: config.ApiConfig{ + ClientTimeout: 100, + }, + }) + + // Create a test server that introduces a delay + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(200 * time.Millisecond) // Delay longer than client timeout + w.WriteHeader(http.StatusOK) + })) + defer testServer.Close() + + // Make a request to the test server + resp, err := client.Get(testServer.URL) + + // Verify that the request timed out + if err == nil { + t.Errorf("Expected timeout error, but got nil") + } + if resp != nil { + t.Errorf("Expected no response, but got %v", resp) + } +} + +func TestHTTPClientTimeoutPass(t *testing.T) { + client := createHTTPClient(&config.Config{ + Api: config.ApiConfig{ + ClientTimeout: 300, + }, + }) + + // Create a test server that introduces a delay + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(200 * time.Millisecond) // Delay shorter than client timeout + w.WriteHeader(http.StatusOK) + })) + defer testServer.Close() + + // Make a request to the test server + resp, err := client.Get(testServer.URL) + + // Verify that the request did not time out + if err != nil { + t.Errorf("Expected no error, but got %v", err) + } + if resp == nil { + t.Errorf("Expected response, but got nil") + } else if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status OK, but got %v", resp.StatusCode) + } +} diff --git a/config/config.go b/config/config.go index 6a05caa..4ab2bee 100644 --- a/config/config.go +++ b/config/config.go @@ -16,9 +16,10 @@ package config import ( "fmt" + "os" + "github.com/kelseyhightower/envconfig" "gopkg.in/yaml.v2" - "os" ) type Config struct { @@ -34,6 +35,7 @@ type LoggingConfig struct { type ApiConfig struct { ListenAddress string `yaml:"address" envconfig:"API_LISTEN_ADDRESS"` ListenPort uint `yaml:"port" envconfig:"API_LISTEN_PORT"` + ClientTimeout uint `yaml:"client_timeout" envconfig:"CLIENT_TIMEOUT"` } // Singleton config instance with default values @@ -44,6 +46,7 @@ var globalConfig = &Config{ Api: ApiConfig{ ListenAddress: "", ListenPort: 8090, + ClientTimeout: 60000, // [ms] }, }