mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(http): add options to allow caching of credentialed and non-cacheable HTTP requests
Adds `includeRequestsWithCredentials` and `includeNonCacheableRequests` options to `HttpTransferCacheOptions`.
This commit is contained in:
@@ -90,9 +90,7 @@ The Event Replay is divided into three main phases:
|
||||
|
||||
Event replay supports _native browser events_, for example `click`, `mouseover`, and `focusin`. If you'd like to learn more about JSAction, the library that powers event replay, you can read more [on the readme](https://github.com/angular/angular/tree/main/packages/core/primitives/event-dispatch#readme).
|
||||
|
||||
---
|
||||
|
||||
This feature ensures a consistent user experience, preventing user actions performed before Hydration from being ignored.
|
||||
This feature ensures a consistent user experience, preventing user actions performed before hydration from being ignored.
|
||||
|
||||
NOTE: If you have [incremental hydration](guide/incremental-hydration) enabled, event replay is automatically enabled under the hood.
|
||||
|
||||
|
||||
@@ -478,8 +478,6 @@ bootstrapApplication(App, {
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `includeHeaders`
|
||||
|
||||
Specifies which headers from the server response should be included in cached entries.
|
||||
@@ -495,8 +493,6 @@ IMPORTANT: Avoid including sensitive headers like authentication tokens. These c
|
||||
|
||||
Including `Cache-Control` in `includeHeaders` only makes that header available on the hydrated response. Angular already evaluates `Cache-Control` headers automatically when deciding whether a request or response is eligible for transfer cache.
|
||||
|
||||
---
|
||||
|
||||
### `includePostRequests`
|
||||
|
||||
By default, only `GET` and `HEAD` requests are cached.
|
||||
@@ -510,12 +506,10 @@ withHttpTransferCacheOptions({
|
||||
|
||||
Use this only when `POST` requests are **idempotent** and safe to reuse between server and client renders.
|
||||
|
||||
---
|
||||
|
||||
### `includeRequestsWithAuthHeaders`
|
||||
|
||||
Determines whether requests containing `Authorization`, `Proxy‑Authorization`, or `Cookie` headers are eligible for caching.
|
||||
By default, these are excluded to prevent caching user‑specific responses. Requests sent with `withCredentials` or Fetch API `credentials` set to `include` or `same-origin` are also excluded by default.
|
||||
By default, these are excluded to prevent caching user‑specific responses.
|
||||
|
||||
```ts
|
||||
withHttpTransferCacheOptions({
|
||||
@@ -525,6 +519,32 @@ withHttpTransferCacheOptions({
|
||||
|
||||
Enable only when authentication headers do **not** affect the response content (for example, public tokens for analytics APIs).
|
||||
|
||||
### `includeRequestsWithCredentials`
|
||||
|
||||
Determines whether requests sent using `withCredentials` or Fetch API `credentials` modes (`include` or `same-origin`) are eligible for caching.
|
||||
By default, these are excluded to prevent caching user‑specific responses.
|
||||
|
||||
```ts
|
||||
withHttpTransferCacheOptions({
|
||||
includeRequestsWithCredentials: true,
|
||||
});
|
||||
```
|
||||
|
||||
Enable only when credentialed requests return responses that are safe to cache.
|
||||
|
||||
### `includeNonCacheableRequests`
|
||||
|
||||
Determines whether requests and responses containing `Cache-Control` directives that forbid caching (`no-store`, `no-cache`, or `private`), responses with a `Set-Cookie` header, or requests using Fetch API `cache` options (`no-store` or `no-cache`), are eligible for caching.
|
||||
By default, these are excluded to respect HTTP caching controls.
|
||||
|
||||
```ts
|
||||
withHttpTransferCacheOptions({
|
||||
includeNonCacheableRequests: true,
|
||||
});
|
||||
```
|
||||
|
||||
Enable only when you need to bypass cache-control restrictions for transfer caching.
|
||||
|
||||
### Per‑request overrides
|
||||
|
||||
You can override caching behavior for a specific request using the `transferCache` request option.
|
||||
|
||||
@@ -1164,12 +1164,14 @@ export enum HttpStatusCode {
|
||||
}
|
||||
|
||||
// @public
|
||||
export type HttpTransferCacheOptions = {
|
||||
includeHeaders?: string[];
|
||||
export interface HttpTransferCacheOptions {
|
||||
filter?: (req: HttpRequest<unknown>) => boolean;
|
||||
includeHeaders?: string[];
|
||||
includeNonCacheableRequests?: boolean;
|
||||
includePostRequests?: boolean;
|
||||
includeRequestsWithAuthHeaders?: boolean;
|
||||
};
|
||||
includeRequestsWithCredentials?: boolean;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface HttpUploadProgressEvent extends HttpProgressEvent {
|
||||
|
||||
@@ -33,28 +33,47 @@ import {HttpParams} from './params';
|
||||
/**
|
||||
* Options to configure how TransferCache should be used to cache requests made via HttpClient.
|
||||
*
|
||||
* @param includeHeaders Specifies which headers should be included into cached responses. No
|
||||
* headers are included by default.
|
||||
* @param filter A function that receives a request as an argument and returns a boolean to indicate
|
||||
* whether a request should be included into the cache.
|
||||
* @param includePostRequests Enables caching for POST requests. By default, only GET and HEAD
|
||||
* requests are cached. This option can be enabled if POST requests are used to retrieve data
|
||||
* (for example using GraphQL).
|
||||
* @param includeRequestsWithAuthHeaders Enables caching of requests containing `Authorization`,
|
||||
* `Proxy-Authorization`, or `Cookie` headers. By default, these requests are excluded from
|
||||
* caching. Requests sent using `withCredentials` or Fetch API `credentials` modes that can send
|
||||
* credentials are also excluded by default.
|
||||
*
|
||||
* @see [Configuring the caching options](guide/ssr#configuring-the-caching-options)
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export type HttpTransferCacheOptions = {
|
||||
includeHeaders?: string[];
|
||||
export interface HttpTransferCacheOptions {
|
||||
/**
|
||||
* A function that receives a request as an argument and returns a boolean to indicate
|
||||
* whether a request should be included into the cache.
|
||||
*/
|
||||
filter?: (req: HttpRequest<unknown>) => boolean;
|
||||
|
||||
/**
|
||||
* Specifies which headers should be included into cached responses. No headers are included by default.
|
||||
*/
|
||||
includeHeaders?: string[];
|
||||
|
||||
/**
|
||||
* Enables caching for `POST` requests. By default, only `GET` and `HEAD` requests are cached.
|
||||
* This option can be enabled if `POST` requests are used to retrieve data (for example using `GraphQL`).
|
||||
*/
|
||||
includePostRequests?: boolean;
|
||||
|
||||
/**
|
||||
* Enables caching of requests containing `Authorization`, `Proxy-Authorization`, or `Cookie` headers.
|
||||
* By default, these requests are excluded from caching.
|
||||
*/
|
||||
includeRequestsWithAuthHeaders?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enables caching of requests sent using `withCredentials` or Fetch API `credentials` modes (`include` or `same-origin`).
|
||||
* By default, these requests are excluded from caching.
|
||||
*/
|
||||
includeRequestsWithCredentials?: boolean;
|
||||
|
||||
/**
|
||||
* Enables caching of requests and responses with `Cache-Control` directives that forbid caching
|
||||
* (such as `no-cache`, `no-store`, or `private`), responses with a `Set-Cookie` header, or requests using Fetch API `no-cache` or `no-store` modes.
|
||||
* By default, these requests/responses are excluded from caching.
|
||||
*/
|
||||
includeNonCacheableRequests?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* If your application uses different HTTP origins to make API calls (via `HttpClient`) on the server and
|
||||
@@ -126,24 +145,31 @@ export const CACHE_OPTIONS = new InjectionToken<CacheOptions>(
|
||||
const ALLOWED_METHODS = ['GET', 'HEAD'];
|
||||
|
||||
function canUseOrCacheRequest(req: HttpRequest<unknown>, options: CacheOptions): boolean {
|
||||
const {isCacheActive, ...globalOptions} = options;
|
||||
const {
|
||||
isCacheActive,
|
||||
filter,
|
||||
includePostRequests,
|
||||
includeRequestsWithAuthHeaders,
|
||||
includeRequestsWithCredentials,
|
||||
includeNonCacheableRequests,
|
||||
} = options;
|
||||
const {transferCache: requestOptions, method: requestMethod} = req;
|
||||
|
||||
if (
|
||||
!isCacheActive ||
|
||||
requestOptions === false ||
|
||||
// Do not cache requests sent with credentials.
|
||||
hasOutgoingCredentials(req) ||
|
||||
// POST requests are allowed either globally or at request level
|
||||
(requestMethod === 'POST' && !globalOptions.includePostRequests && !requestOptions) ||
|
||||
(requestMethod === 'POST' && !includePostRequests && !requestOptions) ||
|
||||
(requestMethod !== 'POST' && !ALLOWED_METHODS.includes(requestMethod)) ||
|
||||
// Do not cache requests with authentication or cookie headers unless explicitly enabled.
|
||||
(!globalOptions.includeRequestsWithAuthHeaders && hasAuthHeaders(req)) ||
|
||||
(!includeRequestsWithAuthHeaders && hasAuthHeaders(req)) ||
|
||||
// Do not cache requests sent with credentials unless explicitly enabled.
|
||||
(!includeRequestsWithCredentials && hasOutgoingCredentials(req)) ||
|
||||
// Do not cache requests that explicitly forbid caching via Cache-Control
|
||||
// or Fetch API cache mode.
|
||||
hasUncacheableCacheControl(req.headers) ||
|
||||
isNonCacheableRequest(req.cache) ||
|
||||
globalOptions.filter?.(req) === false
|
||||
// or Fetch API cache mode unless explicitly enabled.
|
||||
(!includeNonCacheableRequests &&
|
||||
(hasUncacheableCacheControl(req.headers) || isNonCacheableRequest(req.cache))) ||
|
||||
filter?.(req) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -287,11 +313,11 @@ export function transferCacheInterceptorFn(
|
||||
if (event instanceof HttpResponse) {
|
||||
const {headers, body, status, statusText} = event;
|
||||
|
||||
// Only cache successful HTTP responses that do not have Cache-Control
|
||||
// directives that forbid shared caching (no-store or private) and do not
|
||||
// carry a Set-Cookie header. A Set-Cookie header marks the response as
|
||||
// user-specific.
|
||||
if (hasUncacheableCacheControl(headers) || hasSetCookieHeader(headers)) {
|
||||
// Only cache successful HTTP responses that are not non-cacheable.
|
||||
if (
|
||||
!options.includeNonCacheableRequests &&
|
||||
(hasUncacheableCacheControl(headers) || hasSetCookieHeader(headers))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -960,6 +960,97 @@ describe('TransferCache', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('caching with includeRequestsWithCredentials and includeNonCacheableRequests', () => {
|
||||
beforeEach(
|
||||
withBody('<test-app-http></test-app-http>', () => {
|
||||
TestBed.resetTestingModule();
|
||||
isStable = new BehaviorSubject<boolean>(false);
|
||||
|
||||
@Injectable()
|
||||
class ApplicationRefPatched extends ApplicationRef {
|
||||
override get isStable() {
|
||||
return new BehaviorSubject<boolean>(false);
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SomeComponent],
|
||||
providers: [
|
||||
{provide: PLATFORM_ID, useValue: PLATFORM_SERVER_ID},
|
||||
{provide: DOCUMENT, useFactory: () => document},
|
||||
{provide: ApplicationRef, useClass: ApplicationRefPatched},
|
||||
withHttpTransferCache({
|
||||
includeRequestsWithCredentials: true,
|
||||
includeNonCacheableRequests: true,
|
||||
}),
|
||||
provideHttpClient(),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
});
|
||||
|
||||
const appRef = TestBed.inject(ApplicationRef);
|
||||
appRef.bootstrap(SomeComponent);
|
||||
isStable = appRef.isStable as BehaviorSubject<boolean>;
|
||||
}),
|
||||
);
|
||||
|
||||
it(`should cache requests with credentials when 'includeRequestsWithCredentials' is 'true'`, async () => {
|
||||
makeRequestAndExpectOne('/test-cred', 'foo', {
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
makeRequestAndExpectNone('/test-cred');
|
||||
});
|
||||
|
||||
it(`should cache requests with included credentials mode when 'includeRequestsWithCredentials' is 'true'`, async () => {
|
||||
makeRequestAndExpectOne('/test-cred', 'foo', {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
makeRequestAndExpectNone('/test-cred');
|
||||
});
|
||||
|
||||
it(`should cache requests with same-origin credentials mode when 'includeRequestsWithCredentials' is 'true'`, async () => {
|
||||
makeRequestAndExpectOne('/test-cred', 'foo', {
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
makeRequestAndExpectNone('/test-cred');
|
||||
});
|
||||
|
||||
it(`should cache responses with Cache-Control: no-store when 'includeNonCacheableRequests' is 'true'`, async () => {
|
||||
makeRequestAndExpectOne('/test-cache-control', 'foo', {
|
||||
responseHeaders: {'Cache-Control': 'no-store'},
|
||||
});
|
||||
|
||||
makeRequestAndExpectNone('/test-cache-control');
|
||||
});
|
||||
|
||||
it(`should cache responses with Cache-Control: private when 'includeNonCacheableRequests' is 'true'`, async () => {
|
||||
makeRequestAndExpectOne('/test-cache-control', 'foo', {
|
||||
responseHeaders: {'Cache-Control': 'private'},
|
||||
});
|
||||
|
||||
makeRequestAndExpectNone('/test-cache-control');
|
||||
});
|
||||
|
||||
it(`should cache requests with Cache-Control: no-cache when 'includeNonCacheableRequests' is 'true'`, async () => {
|
||||
makeRequestAndExpectOne('/test-cache-control', 'foo', {
|
||||
headers: {'Cache-Control': 'no-cache'},
|
||||
});
|
||||
|
||||
makeRequestAndExpectNone('/test-cache-control');
|
||||
});
|
||||
|
||||
it(`should cache requests with cache: 'no-store' mode when 'includeNonCacheableRequests' is 'true'`, async () => {
|
||||
makeRequestAndExpectOne('/test-cache-mode', 'foo', {
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
makeRequestAndExpectNone('/test-cache-mode');
|
||||
});
|
||||
});
|
||||
|
||||
describe('caching with public origins', () => {
|
||||
beforeEach(
|
||||
withBody('<test-app-http></test-app-http>', () => {
|
||||
|
||||
@@ -311,7 +311,6 @@
|
||||
"__getOwnPropDescs",
|
||||
"__getOwnPropSymbols",
|
||||
"__hasOwnProp",
|
||||
"__objRest",
|
||||
"__propIsEnum",
|
||||
"__spreadProps",
|
||||
"__spreadValues",
|
||||
|
||||
Reference in New Issue
Block a user