Interface HttpClientFactory

All Known Implementing Classes:
DefaultHttpClientFactory

public interface HttpClientFactory
Factory for creating HttpClient instances.

This interface allows external modules (such as karate-gatling) to provide custom HttpClient implementations, for example with a shared connection pool for performance testing scenarios.

The default implementation DefaultHttpClientFactory creates a new ApacheHttpClient instance for each call, which is the standard behavior for functional tests where isolation is preferred.

See Also:
  • Method Summary

    Modifier and Type
    Method
    Description
    Creates a new HttpClient instance.
    default void
    Called when the scenario that owns client has finished with it.
  • Method Details

    • create

      HttpClient create()
      Creates a new HttpClient instance.
      Returns:
      a new HttpClient instance
    • release

      default void release(HttpClient client)
      Called when the scenario that owns client has finished with it.

      The default does nothing, and that is the conservative choice rather than the obvious one. A factory exists so a caller can supply its own policy — this interface's own javadoc offers "a shared connection pool for performance testing scenarios" — and a factory handing out one shared instance is the common shape, including the () -> client lambda form. Closing that at the end of one scenario would break every other scenario using it, so a custom factory keeps today's behaviour unless it opts in.

      DefaultHttpClientFactory overrides this to close, because it mints one client per scenario that nothing else can reach — and until this existed, not one of them was ever closed. Each abandoned its connection manager and pooled sockets to the collector, released at a time no test controlled; a file descriptor is not heap, so no heap-based check could see them accumulate.

      What this does and does not guarantee, stated because an implementation that assumes more will break:

      • Not exactly-once per create(). A client is minted eagerly when a scenario runtime is constructed, and a few runtimes never run a scenario at all — a dynamic-outline template, the synthetic runtime behind config-only loading, a mock's cached per-feature runtime. Those release where they can, but a factory that counts checkouts must tolerate a create with no matching release rather than deadlock.
      • A released client may still be used. An afterFeature hook runs after the last scenario released its client, and a closure returned from a called feature can outlive the scenario that made it. DefaultHttpClientFactory's client tolerates this by rebuilding lazily; a pooled implementation must decide what a late request should do rather than assume it cannot happen.
      • Called from the scenario's own thread, and scenarios run in parallel, so an implementation holding shared state needs its own synchronization.

      Pool underneath, one wrapper per scenario — do not return one client instance to concurrent scenarios. This is the rule for the pooling case this interface exists to enable, and it is not merely stylistic. HttpClient.apply(io.karatelabs.core.KarateConfig) is invoked on the returned instance whenever configuration changes and unconditionally after a shared-scope call, and ApacheHttpClient implements that by closing its transport and rebuilding lazily. A second scenario holding the same instance therefore has its in-flight request killed — SocketException: Socket closed — by the first scenario's configure step. Independently of that, ApacheHttpClient keeps per-request state in instance fields, so concurrent callers already attach one request's headers to another's response. Give each scenario its own lightweight wrapper and share the connection manager beneath it, which is where the pooling win actually is.

      Parameters:
      client - a client previously returned by create()