perf(http): reduce data transfer when using HTTP caching (#52347)

This commit reduces the property size in the http transfer cache to reduce the page payload.

Before
```html
<script id="ng-state" type="application/json">
{
  "4155228514": {
    "body": "....",
    "headers": {},
    "status": 200,
    "statusText": "OK",
    "url": "http://foo.com/assets/media.json",
    "responseType": "json"
  },
}
</script>
```

Now
```html
<script id="ng-state" type="application/json">
{
  "4155228514": {
    "b": "....",
    "h": {},
    "s": 200,
    "st": "OK",
    "u": "http://foo.com/assets/media.json",
    "rt": "json"
  },
}
</script>
```

PR Close #52347
This commit is contained in:
Alan Agius
2023-10-24 16:35:36 +00:00
committed by Dylan Hunn
parent 4e00851c04
commit bd9e91ecf7
3 changed files with 78 additions and 34 deletions
+46 -20
View File
@@ -35,13 +35,31 @@ export type HttpTransferCacheOptions = {
includePostRequests?: boolean
};
/**
* Keys within cached response data structure.
*/
export const BODY = 'b';
export const HEADERS = 'h';
export const STATUS = 's';
export const STATUS_TEXT = 'st';
export const URL = 'u';
export const RESPONSE_TYPE = 'rt';
interface TransferHttpResponse {
body: any;
headers: Record<string, string[]>;
status?: number;
statusText?: string;
url?: string;
responseType?: HttpRequest<unknown>['responseType'];
/** body */
[BODY]: any;
/** headers */
[HEADERS]: Record<string, string[]>;
/** status */
[STATUS]?: number;
/** statusText */
[STATUS_TEXT]?: string;
/** url */
[URL]?: string;
/** responseType */
[RESPONSE_TYPE]?: HttpRequest<unknown>['responseType'];
}
interface CacheOptions extends HttpTransferCacheOptions {
@@ -82,22 +100,30 @@ export function transferCacheInterceptorFn(
}
if (response) {
const {
[BODY]: undecodedBody,
[RESPONSE_TYPE]: responseType,
[HEADERS]: httpHeaders,
[STATUS]: status,
[STATUS_TEXT]: statusText,
[URL]: url
} = response;
// Request found in cache. Respond using it.
let body: ArrayBuffer|Blob|string|undefined = response.body;
let body: ArrayBuffer|Blob|string|undefined = undecodedBody;
switch (response.responseType) {
switch (responseType) {
case 'arraybuffer':
body = new TextEncoder().encode(response.body).buffer;
body = new TextEncoder().encode(undecodedBody).buffer;
break;
case 'blob':
body = new Blob([response.body]);
body = new Blob([undecodedBody]);
break;
}
// We want to warn users accessing a header provided from the cache
// That HttpTransferCache alters the headers
// The warning will be logged a single time by HttpHeaders instance
let headers = new HttpHeaders(response.headers);
let headers = new HttpHeaders(httpHeaders);
if (typeof ngDevMode === 'undefined' || ngDevMode) {
// Append extra logic in dev mode to produce a warning when a header
// that was not transferred to the client is accessed in the code via `get`
@@ -110,9 +136,9 @@ export function transferCacheInterceptorFn(
new HttpResponse({
body,
headers,
status: response.status,
statusText: response.statusText,
url: response.url,
status,
statusText,
url,
}),
);
}
@@ -123,12 +149,12 @@ export function transferCacheInterceptorFn(
tap((event: HttpEvent<unknown>) => {
if (event instanceof HttpResponse) {
transferState.set<TransferHttpResponse>(storeKey, {
body: event.body,
headers: getFilteredHeaders(event.headers, headersToInclude),
status: event.status,
statusText: event.statusText,
url: event.url || '',
responseType: req.responseType,
[BODY]: event.body,
[HEADERS]: getFilteredHeaders(event.headers, headersToInclude),
[STATUS]: event.status,
[STATUS_TEXT]: event.statusText,
[URL]: event.url || '',
[RESPONSE_TYPE]: req.responseType,
});
}
}),
@@ -14,7 +14,7 @@ import {withBody} from '@angular/private/testing';
import {BehaviorSubject} from 'rxjs';
import {HttpClient, HttpResponse, provideHttpClient} from '../public_api';
import {withHttpTransferCache} from '../src/transfer_cache';
import {BODY, HEADERS, RESPONSE_TYPE, STATUS, STATUS_TEXT, URL, withHttpTransferCache} from '../src/transfer_cache';
import {HttpTestingController, provideHttpClientTesting} from '../testing';
interface RequestParams {
@@ -84,7 +84,7 @@ describe('TransferCache', () => {
makeRequestAndExpectOne('/test', 'foo');
const key = makeStateKey('432906284');
const transferState = TestBed.inject(TransferState);
expect(transferState.get(key, null)).toEqual(jasmine.objectContaining({body: 'foo'}));
expect(transferState.get(key, null)).toEqual(jasmine.objectContaining({[BODY]: 'foo'}));
});
it('should stop storing HTTP calls in `TransferState` after application becomes stable',
@@ -101,20 +101,20 @@ describe('TransferCache', () => {
const transferState = TestBed.inject(TransferState);
expect(JSON.parse(transferState.toJson()) as Record<string, unknown>).toEqual({
'3706062792': {
'body': 'foo',
'headers': {},
'status': 200,
'statusText': 'OK',
'url': '/test-1',
'responseType': 'json'
[BODY]: 'foo',
[HEADERS]: {},
[STATUS]: 200,
[STATUS_TEXT]: 'OK',
[URL]: '/test-1',
[RESPONSE_TYPE]: 'json'
},
'3706062823': {
'body': 'buzz',
'headers': {},
'status': 200,
'statusText': 'OK',
'url': '/test-2',
'responseType': 'json'
[BODY]: 'buzz',
[HEADERS]: {},
[STATUS]: 200,
[STATUS_TEXT]: 'OK',
[URL]: '/test-2',
[RESPONSE_TYPE]: 'json'
}
});
}));