sendProviderRequest function
- Client httpClient,
- Request request,
- CancelToken? cancelToken
Sends request, racing cancelToken (abort wins), and validates the
response status.
Redirects are decided HERE, never by the underlying client
(followRedirects is forced off): a same-origin hop is re-issued
verbatim (method/body/headers — credentials intact), while a
cross-origin 3xx FAILS as ProviderHttpError carrying the
Location. An endpoint that bounces an API call to another host
(expired SSO portal, moved URL) never gets the request re-sent, so
Authorization: Bearer … can never leak to a redirect target — the
SEC-01 trust boundary lives in this shared layer, not per relay.
Throws AbortedError when the token fires before the headers arrive and ProviderHttpError on a non-200 status (with the body consumed for the error message). The adapter's try/catch turns both into error events.
Implementation
Future<http.StreamedResponse> sendProviderRequest(
http.Client httpClient,
http.Request request,
CancelToken? cancelToken,
) async {
request.followRedirects = false;
var current = request;
for (var redirects = 0; ; redirects++) {
final response = await sendWatchedProviderRequest(
httpClient,
current,
cancelToken,
);
final location = response.headers['location'];
final target = _redirectTarget(current.url, response.statusCode, location);
if (target == null) return _validateStreamResponse(current, response);
if (redirects >= _maxProviderRedirects ||
!_sameOrigin(current.url, target)) {
final body = await response.stream.bytesToString();
throw ProviderHttpError(
response.statusCode,
body,
requestUrl: current.url,
redirectLocation: location,
);
}
// Consume the (tiny) redirect body so the connection can be reused.
await response.stream.drain<void>();
current = _reissue(current, target, response.statusCode);
}
}