@@ -38,43 +38,86 @@ func New(options ...PolicyOption) (Client, error) {
3838 }
3939
4040 dialer := newPolicyDialer (policy )
41- transport := newResponseValidatingTransport (
42- newPolicyTransport (dialer , policy .maxResponseHeaderSize ),
43- )
4441
45- return & defaultHTTPClient {
42+ return newDefaultHTTPClient (policy , stdhttp.Client {
43+ Transport : newPolicyTransport (dialer , policy .MaxResponseHeaderSize ()),
44+ }), nil
45+ }
46+
47+ // NewWithTransport constructs a policy-aware client that uses transport. The
48+ // supplied transport is shared with the returned Client, so closing idle
49+ // connections affects its connection pool.
50+ //
51+ // A nil transport, including a typed-nil *net/http.Transport, selects Ferret's
52+ // policy-aware transport. A non-nil transport remains responsible for proxy
53+ // behavior, DNS and concrete-address enforcement, and response-header limits.
54+ func NewWithTransport (
55+ transport stdhttp.RoundTripper ,
56+ options ... PolicyOption ,
57+ ) (Client , error ) {
58+ return NewWithClient (& stdhttp.Client {Transport : transport }, options ... )
59+ }
60+
61+ // NewWithClient constructs a policy-aware client from a standard-library
62+ // client. It snapshots the supplied client's fields without mutating it.
63+ // Policy timeout and redirect settings take precedence over the corresponding
64+ // client fields.
65+ //
66+ // A nil Transport is replaced with Ferret's policy-aware transport. A non-nil
67+ // Transport is preserved and remains responsible for proxy behavior, DNS and
68+ // concrete-address enforcement, and response-header limits. The transport and
69+ // cookie jar remain shared with the supplied client; closing idle connections
70+ // through the returned Client affects the shared transport pool.
71+ func NewWithClient (client * stdhttp.Client , options ... PolicyOption ) (Client , error ) {
72+ if client == nil {
73+ return nil , ErrNilClient
74+ }
75+
76+ policy , err := NewPolicy (options ... )
77+ if err != nil {
78+ return nil , err
79+ }
80+
81+ stdClient := * client
82+ transport , isStandardTransport := stdClient .Transport .(* stdhttp.Transport )
83+
84+ if stdClient .Transport == nil || (isStandardTransport && transport == nil ) {
85+ dialer := newPolicyDialer (policy )
86+ stdClient .Transport = newPolicyTransport (dialer , policy .MaxResponseHeaderSize ())
87+ }
88+
89+ return newDefaultHTTPClient (policy , stdClient ), nil
90+ }
91+
92+ // newDefaultHTTPClient snapshots and normalizes a standard-library client so
93+ // the stored client can be reused safely across concurrent requests.
94+ func newDefaultHTTPClient (policy * Policy , client stdhttp.Client ) * defaultHTTPClient {
95+ result := & defaultHTTPClient {
4696 policy : policy ,
47- client : stdhttp.Client {
48- Transport : transport ,
49- Timeout : policy .timeout ,
50- },
51- }, nil
97+ client : client ,
98+ }
99+ result .client .Transport = newNilResponseGuardTransport (result .client .Transport )
100+ result .client .Timeout = policy .Timeout ()
101+ result .client .CheckRedirect = policy .CheckRedirect
102+
103+ return result
52104}
53105
54106func (d * defaultHTTPClient ) Do (ctx context.Context , req * Request ) (* Response , error ) {
55107 if ctx == nil {
56108 ctx = context .Background ()
57109 }
58110
59- p := d .policy
60- if p == nil {
61- p = & Policy {}
62- }
63-
64111 stdReq , err := toStdRequest (ctx , req )
65112 if err != nil {
66113 return nil , err
67114 }
68- if err := p .Prepare (stdReq ); err != nil {
115+
116+ if err := d .policy .Prepare (stdReq ); err != nil {
69117 return nil , err
70118 }
71119
72- client := d .client
73- client .Transport = newResponseValidatingTransport (client .Transport )
74- client .Timeout = p .timeout
75- client .CheckRedirect = d .checkRedirect
76-
77- res , err := client .Do (stdReq )
120+ res , err := d .client .Do (stdReq )
78121 if err != nil {
79122 var policyErr * PolicyError
80123 if errors .As (err , & policyErr ) {
@@ -84,28 +127,9 @@ func (d *defaultHTTPClient) Do(ctx context.Context, req *Request) (*Response, er
84127 return nil , err
85128 }
86129
87- return fromStdResponse (res , p )
130+ return fromStdResponse (res , d . policy )
88131}
89132
90133func (d * defaultHTTPClient ) CloseIdleConnections () {
91134 d .client .CloseIdleConnections ()
92135}
93-
94- func (d * defaultHTTPClient ) checkRedirect (req * stdhttp.Request , via []* stdhttp.Request ) error {
95- p := d .policy
96-
97- if p == nil {
98- p = & Policy {}
99- }
100-
101- if ! p .followRedirects {
102- return stdhttp .ErrUseLastResponse
103- }
104-
105- limit := p .maxRedirects
106- if len (via ) > limit {
107- return & RedirectLimitError {Limit : limit }
108- }
109-
110- return p .eval (req , PolicyTargetRedirect )
111- }
0 commit comments