framework icon indicating copy to clipboard operation
framework copied to clipboard

feat: Revamp HTTP Client: Multi-client support, Streaming & Optimizations

Open krishankumar01 opened this issue 1 month ago • 1 comments

Description

This PR replaces the single-client singleton (previously initialized via sync.Once) with a Factory Pattern. This enables support for Multiple HTTP Clients while significantly refactoring the engine for performance and safety.

[!Note] We will need to update TestRequest to support testing these HTTP client changes. I’ll create a separate PR for that, as the current PR is already quite large.

Critical: Configuration Update (Breaking Change)

The config/http.go structure has changed to support multiple clients. You must update your configuration file.

Old Config (Single Client) Previously, the configuration only allowed one global client under the "client" key.

// config/http.go

"client": map[string]any{
    "base_url":                config.Env("HTTP_CLIENT_BASE_URL"),
    "timeout":                 config.Env("HTTP_CLIENT_TIMEOUT", 30),
    "max_idle_conns":          config.Env("HTTP_CLIENT_MAX_IDLE_CONNS", 100),
    "max_idle_conns_per_host": config.Env("HTTP_CLIENT_MAX_IDLE_CONNS_PER_HOST", 2),
    "max_conns_per_host":      config.Env("HTTP_CLIENT_MAX_CONN_PER_HOST", 10),
    "idle_conn_timeout":       config.Env("HTTP_CLIENT_IDLE_CONN_TIMEOUT", 90),
},

New Config (Multi-Client) We replace the single "client" key with default_client and a clients map. This avoids conflicts with the HTTP Server configuration (which often uses keys like host or port at the root).

// config/http.go

// The default client to use when facades.Http() is called without a name.
"default_client": config.Env("HTTP_CLIENT_DEFAULT", "default"),

// specific client configurations
"clients": map[string]any{
    "default": map[string]any{
        "base_url":                config.Env("HTTP_CLIENT_BASE_URL"),
        "timeout":                 config.Env("HTTP_CLIENT_TIMEOUT", 30),
        "max_idle_conns":          config.Env("HTTP_CLIENT_MAX_IDLE_CONNS", 100),
        "max_idle_conns_per_host": config.Env("HTTP_CLIENT_MAX_IDLE_CONNS_PER_HOST", 2),
        "max_conns_per_host":      config.Env("HTTP_CLIENT_MAX_CONN_PER_HOST", 10),
        "idle_conn_timeout":       config.Env("HTTP_CLIENT_IDLE_CONN_TIMEOUT", 90),
    },
    // You can add more clients here (e.g., "stripe", "aws")
},

Key Features

1. Multiple Clients Support

Access specific API configurations on the fly using the new Factory.

    facades.Http().Get("/users")             // Uses "default" driver
    facades.Http().Client("stripe").Get("/") // Uses "stripe" driver

2. Streaming (Low Memory Usage)

Introduced Stream() to handle large file downloads efficiently without loading the entire response into RAM.

    resp, err := facades.Http().Get("https://example.com/huge-file.zip")
    stream, _ := resp.Stream() // Stream directly to disk
    defer stream.Close()
    io.Copy(file, stream)

3. Fail-Fast & Lazy Loading

Replaces the strict initialization with a "Lazy Error" pattern. Missing configurations no longer panic at boot; they return a safe error only when a request is attempted.

Usage & Migration

Dependency Injection (Best Practice)

Inject specific client.Client instances into your services instead of the entire Factory.

    // In your ServiceProvider:
    // app.Bind("PaymentService", func() { 
    //     return NewPaymentService(facades.Http().Client("stripe")) 
    // })

    type PaymentService struct {
        request client.Request
    }

    func (s *PaymentService) Charge() {
        // Already configured with Stripe host/timeout
        s.request.Post("/charges", data)
    }

Response Parsing

We are moving to Response.Bind() for safer error handling.

Old Way (Deprecated):

    facades.Http().Bind(&user).Get("/")
    // Unsafe. Parsed before status check.

New Way (Recommended):

    resp, err := facades.Http().Get("/")
    assert(err)
    if resp.Failed() {
       panic()
    }
    resp.Bind(&user)
    // Safe. Check `resp.Failed()` first.

Breaking Changes

  1. Configuration: config/http.go structure must be updated.
  2. Interface (Mocks): client.Response now includes Stream() and Bind(). If you mock this interface in tests, you must implement these methods.

✅ Checks

  • [x] Added test cases for the Factory and Client isolation.
  • [x] Verified thread safety with concurrent request tests.
  • [ ] Updated documentation to reflect the new API.

krishankumar01 avatar Dec 28 '25 12:12 krishankumar01