mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-20 03:51:00 +00:00
refactor to use caching interceptor
This commit is contained in:
parent
15c2556e06
commit
9589f25e57
@ -29,7 +29,6 @@ import { InMemoryConfigDataService } from './in-memory-config-data.service';
|
|||||||
import { deviceID } from './api-utils';
|
import { deviceID } from './api-utils';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { ChartItemComponent } from './charts/chart-item/chart-item.component';
|
import { ChartItemComponent } from './charts/chart-item/chart-item.component';
|
||||||
import { CSRFInterceptor } from './http-interceptors/csrf-intercepor';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -38,7 +38,6 @@ export class FolderChartComponent implements OnInit {
|
|||||||
if (s.label === state) {
|
if (s.label === state) {
|
||||||
s.count = s.count + 1;
|
s.count = s.count + 1;
|
||||||
found = true;
|
found = true;
|
||||||
console.log("increase count", s.count);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,18 +14,10 @@ import Folder from './folder'
|
|||||||
})
|
})
|
||||||
export class DbStatusService {
|
export class DbStatusService {
|
||||||
private dbStatusUrl = environment.production ? apiURL + 'rest/db/status' : 'api/dbStatus';
|
private dbStatusUrl = environment.production ? apiURL + 'rest/db/status' : 'api/dbStatus';
|
||||||
private statuses: Map<string, Folder.Status>;
|
|
||||||
|
|
||||||
constructor(private http: HttpClient, private cookieService: CookieService) {
|
constructor(private http: HttpClient, private cookieService: CookieService) { }
|
||||||
this.statuses = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
getFolderStatus(id: string): Observable<Folder.Status> {
|
getFolderStatus(id: string): Observable<Folder.Status> {
|
||||||
// First check to see if we have a cached value
|
|
||||||
if (this.statuses.has(id)) {
|
|
||||||
return of(this.statuses.get(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
let httpOptions: { params: HttpParams };
|
let httpOptions: { params: HttpParams };
|
||||||
if (id) {
|
if (id) {
|
||||||
httpOptions = {
|
httpOptions = {
|
||||||
@ -46,8 +38,6 @@ export class DbStatusService {
|
|||||||
res = res[0];
|
res = res[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cache result
|
|
||||||
this.statuses.set(id, res)
|
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
16
src/app/http-interceptors/caching.interceptor.spec.ts
Normal file
16
src/app/http-interceptors/caching.interceptor.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CachingInterceptor } from './caching.interceptor';
|
||||||
|
|
||||||
|
describe('CachingInterceptor', () => {
|
||||||
|
beforeEach(() => TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
CachingInterceptor
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
const interceptor: CachingInterceptor = TestBed.inject(CachingInterceptor);
|
||||||
|
expect(interceptor).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
58
src/app/http-interceptors/caching.interceptor.ts
Normal file
58
src/app/http-interceptors/caching.interceptor.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {
|
||||||
|
HttpRequest,
|
||||||
|
HttpHandler,
|
||||||
|
HttpEvent,
|
||||||
|
HttpInterceptor,
|
||||||
|
HttpHeaders,
|
||||||
|
HttpResponse
|
||||||
|
} from '@angular/common/http';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
import { RequestCacheService } from '../request-cache.service'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CachingInterceptor implements HttpInterceptor {
|
||||||
|
constructor(private cache: RequestCacheService) { }
|
||||||
|
|
||||||
|
intercept(req: HttpRequest<any>, next: HttpHandler) {
|
||||||
|
// continue if not cachable.
|
||||||
|
if (!isCachable(req)) { return next.handle(req); }
|
||||||
|
|
||||||
|
const cachedResponse = this.cache.get(req);
|
||||||
|
return cachedResponse ?
|
||||||
|
of(cachedResponse) : sendRequest(req, next, this.cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Is this request cachable? */
|
||||||
|
function isCachable(req: HttpRequest<any>) {
|
||||||
|
// Only GET requests are cachable
|
||||||
|
return req.method === 'GET';
|
||||||
|
/*
|
||||||
|
return req.method === 'GET' &&
|
||||||
|
-1 < req.url.indexOf("url");
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get server response observable by sending request to `next()`.
|
||||||
|
* Will add the response to the cache on the way out.
|
||||||
|
*/
|
||||||
|
function sendRequest(
|
||||||
|
req: HttpRequest<any>,
|
||||||
|
next: HttpHandler,
|
||||||
|
cache: RequestCacheService): Observable<HttpEvent<any>> {
|
||||||
|
|
||||||
|
// No headers allowed in npm search request
|
||||||
|
const noHeaderReq = req.clone({ headers: new HttpHeaders() });
|
||||||
|
|
||||||
|
return next.handle(noHeaderReq).pipe(
|
||||||
|
tap(event => {
|
||||||
|
// There may be other events besides the response.
|
||||||
|
if (event instanceof HttpResponse) {
|
||||||
|
// cache.put(req, event); // Update the cache.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { deviceID } from '../api-utils';
|
|
||||||
import {
|
|
||||||
HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders
|
|
||||||
} from '@angular/common/http';
|
|
||||||
|
|
||||||
import { CookieService } from '../cookie.service';
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CSRFInterceptor implements HttpInterceptor {
|
|
||||||
|
|
||||||
constructor(private cookieService: CookieService) { }
|
|
||||||
|
|
||||||
intercept(req: HttpRequest<any>, next: HttpHandler) {
|
|
||||||
const dID: String = deviceID();
|
|
||||||
const csrfCookie = 'CSRF-Token-' + dID
|
|
||||||
|
|
||||||
// Clone the request and replace the original headers with
|
|
||||||
// cloned headers, updated with the CSRF information.
|
|
||||||
const csrfReq = req.clone({
|
|
||||||
headers: req.headers.set('X-CSRF-Token-' + dID,
|
|
||||||
this.cookieService.getCookie(csrfCookie))
|
|
||||||
});
|
|
||||||
|
|
||||||
// send cloned request with header to the next handler.
|
|
||||||
return next.handle(csrfReq);
|
|
||||||
}
|
|
||||||
}
|
|
16
src/app/http-interceptors/csrf.interceptor.spec.ts
Normal file
16
src/app/http-interceptors/csrf.interceptor.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CSRFInterceptor } from './csrf.interceptor';
|
||||||
|
|
||||||
|
describe('CsrfInterceptor', () => {
|
||||||
|
beforeEach(() => TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
CSRFInterceptor
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
const interceptor: CSRFInterceptor = TestBed.inject(CSRFInterceptor);
|
||||||
|
expect(interceptor).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
29
src/app/http-interceptors/csrf.interceptor.ts
Normal file
29
src/app/http-interceptors/csrf.interceptor.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { deviceID } from '../api-utils';
|
||||||
|
import {
|
||||||
|
HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders
|
||||||
|
} from '@angular/common/http';
|
||||||
|
|
||||||
|
import { CookieService } from '../cookie.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CSRFInterceptor implements HttpInterceptor {
|
||||||
|
|
||||||
|
constructor(private cookieService: CookieService) { }
|
||||||
|
|
||||||
|
intercept(req: HttpRequest<any>, next: HttpHandler) {
|
||||||
|
const dID: String = deviceID();
|
||||||
|
const csrfCookie = 'CSRF-Token-' + dID
|
||||||
|
|
||||||
|
// Clone the request and replace the original headers with
|
||||||
|
// cloned headers, updated with the CSRF information.
|
||||||
|
const csrfReq = req.clone({
|
||||||
|
headers: req.headers.set('X-CSRF-Token-' + dID,
|
||||||
|
this.cookieService.getCookie(csrfCookie))
|
||||||
|
});
|
||||||
|
|
||||||
|
// send cloned request with header to the next handler.
|
||||||
|
return next.handle(csrfReq);
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,12 @@
|
|||||||
/* "Barrel" of Http Interceptors */
|
/* "Barrel" of Http Interceptors */
|
||||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
import { CSRFInterceptor } from './csrf-intercepor';
|
|
||||||
|
import { CSRFInterceptor } from './csrf.interceptor';
|
||||||
|
import { CachingInterceptor } from './caching.interceptor';
|
||||||
|
|
||||||
/** Http interceptor providers in outside-in order */
|
/** Http interceptor providers in outside-in order */
|
||||||
export const httpInterceptorProviders = [
|
export const httpInterceptorProviders = [
|
||||||
|
{ provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true },
|
||||||
|
// CSRFInterceptor needs to be last
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: CSRFInterceptor, multi: true },
|
{ provide: HTTP_INTERCEPTORS, useClass: CSRFInterceptor, multi: true },
|
||||||
];
|
];
|
@ -34,7 +34,6 @@ export class DeviceListComponent implements AfterViewInit, OnInit {
|
|||||||
|
|
||||||
this.systemConfigService.getDevices().subscribe(
|
this.systemConfigService.getDevices().subscribe(
|
||||||
data => {
|
data => {
|
||||||
console.log("get data??", data)
|
|
||||||
this.dataSource.data = data;
|
this.dataSource.data = data;
|
||||||
this.dataSource.dataSubject.next(data);
|
this.dataSource.dataSubject.next(data);
|
||||||
}
|
}
|
||||||
|
16
src/app/request-cache.service.spec.ts
Normal file
16
src/app/request-cache.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { RequestCacheService } from './request-cache.service';
|
||||||
|
|
||||||
|
describe('RequestCacheService', () => {
|
||||||
|
let service: RequestCacheService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(RequestCacheService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
50
src/app/request-cache.service.ts
Normal file
50
src/app/request-cache.service.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpResponse, HttpRequest } from '@angular/common/http';
|
||||||
|
|
||||||
|
export interface RequestCacheEntry {
|
||||||
|
url: string;
|
||||||
|
response: HttpResponse<any>;
|
||||||
|
lastRead: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxAge = 30000; // milliseconds
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class RequestCacheService {
|
||||||
|
private cache: Map<string, RequestCacheEntry> = new Map();
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
get(req: HttpRequest<any>): HttpResponse<any> | undefined {
|
||||||
|
const url = req.urlWithParams;
|
||||||
|
const cached = this.cache.get(url);
|
||||||
|
|
||||||
|
if (!cached) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isExpired = cached.lastRead < (Date.now() - maxAge);
|
||||||
|
return isExpired ? undefined : cached.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
put(req: HttpRequest<any>, response: HttpResponse<any>): void {
|
||||||
|
const url = req.urlWithParams;
|
||||||
|
|
||||||
|
const entry = { url, response, lastRead: Date.now() };
|
||||||
|
this.cache.set(url, entry);
|
||||||
|
|
||||||
|
// Remove expired cache entries
|
||||||
|
const expired = Date.now() - maxAge;
|
||||||
|
this.cache.forEach(entry => {
|
||||||
|
if (entry.lastRead < expired) {
|
||||||
|
this.cache.delete(entry.url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAll(): void {
|
||||||
|
this.cache = new Map();
|
||||||
|
}
|
||||||
|
}
|
@ -60,6 +60,7 @@ export class SystemConfigService {
|
|||||||
return folderObservable;
|
return folderObservable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO switch to devices
|
||||||
getDevices(): Observable<Device[]> {
|
getDevices(): Observable<Device[]> {
|
||||||
const deviceObserverable: Observable<Device[]> = new Observable((observer) => {
|
const deviceObserverable: Observable<Device[]> = new Observable((observer) => {
|
||||||
if (this.folders) {
|
if (this.folders) {
|
||||||
|
Loading…
Reference in New Issue
Block a user