From 7e2085664115dda4d2dc41a1f1a314ac191cae76 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Wed, 22 Sep 2021 17:37:34 +0300 Subject: [PATCH] use browser fetch on renderer Signed-off-by: Jari Kolehmainen --- src/common/k8s-api/index.ts | 12 +---- src/common/k8s-api/json-api.ts | 66 +++++++++++++++++-------- src/common/k8s-api/kube-api.ts | 12 +++-- src/common/k8s-api/kube-object.store.ts | 2 +- 4 files changed, 57 insertions(+), 35 deletions(-) diff --git a/src/common/k8s-api/index.ts b/src/common/k8s-api/index.ts index e464c307d7..1c0f32e0f2 100644 --- a/src/common/k8s-api/index.ts +++ b/src/common/k8s-api/index.ts @@ -48,25 +48,17 @@ if (typeof window === "undefined") { }); } else { apiBase = new JsonApi({ - serverAddress: `http://127.0.0.1:${window.location.port}`, + serverAddress: `http://localhost:${window.location.port}`, apiBase: apiPrefix, debug: isDevelopment || isDebugging, - }, { - headers: { - "Host": window.location.host - } }); } if (isClusterPageContext()) { apiKube = new KubeJsonApi({ - serverAddress: `http://127.0.0.1:${window.location.port}`, + serverAddress: `http://${window.location.host}:${window.location.port}`, apiBase: apiKubePrefix, debug: isDevelopment - }, { - headers: { - "Host": window.location.host - } }); } diff --git a/src/common/k8s-api/json-api.ts b/src/common/k8s-api/json-api.ts index 8690521099..00dc35cef2 100644 --- a/src/common/k8s-api/json-api.ts +++ b/src/common/k8s-api/json-api.ts @@ -21,8 +21,9 @@ // Base http-service / json-api class +import { randomBytes } from "crypto"; import { merge } from "lodash"; -import fetch, { Response, RequestInit } from "node-fetch"; +import nodeFetch, { Response as NodeResponse, RequestInit as NodeRequestInit } from "node-fetch"; import { stringify } from "querystring"; import { EventEmitter } from "../../common/event-emitter"; import logger from "../../common/logger"; @@ -44,11 +45,14 @@ export interface JsonApiParams { export interface JsonApiLog { method: string; reqUrl: string; - reqInit: RequestInit; + reqInit: RequestInit | NodeRequestInit; data?: any; error?: any; } +export type JsonRequestInit = RequestInit | NodeRequestInit; +export type JsonResponse = Response | NodeResponse; + export interface JsonApiConfig { apiBase: string; serverAddress: string; @@ -65,22 +69,22 @@ export class JsonApi { debug: false }; - constructor(public readonly config: JsonApiConfig, protected reqInit?: RequestInit) { + constructor(public readonly config: JsonApiConfig, protected reqInit?: JsonRequestInit) { this.config = Object.assign({}, JsonApi.configDefault, config); this.reqInit = merge({}, JsonApi.reqInitDefault, reqInit); this.parseResponse = this.parseResponse.bind(this); } - public onData = new EventEmitter<[D, Response]>(); - public onError = new EventEmitter<[JsonApiErrorParsed, Response]>(); + public onData = new EventEmitter<[D, JsonResponse]>(); + public onError = new EventEmitter<[JsonApiErrorParsed, JsonResponse]>(); - get(path: string, params?: P, reqInit: RequestInit = {}) { + get(path: string, params?: P, reqInit: RequestInit | NodeRequestInit = {}) { return this.request(path, params, { ...reqInit, method: "get" }); } - getResponse(path: string, params?: P, init: RequestInit = {}): Promise { - let reqUrl = `${this.config.serverAddress}${this.config.apiBase}${path}`; - const reqInit: RequestInit = merge({}, this.reqInit, init); + getResponse(path: string, params?: P, init: JsonRequestInit = {}): Promise { + let reqUrl = this.buildRequestUrl(path, true); + const reqInit = merge({}, this.reqInit, init); const { query } = params || {} as P; if (!reqInit.method) { @@ -93,28 +97,42 @@ export class JsonApi { reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString; } - return fetch(reqUrl, reqInit); + if (window) { + return fetch(reqUrl, reqInit as RequestInit); + } else { + return nodeFetch(reqUrl, reqInit as NodeRequestInit); + } } - post(path: string, params?: P, reqInit: RequestInit = {}) { + post(path: string, params?: P, reqInit: JsonRequestInit = {}) { return this.request(path, params, { ...reqInit, method: "post" }); } - put(path: string, params?: P, reqInit: RequestInit = {}) { + put(path: string, params?: P, reqInit: JsonRequestInit = {}) { return this.request(path, params, { ...reqInit, method: "put" }); } - patch(path: string, params?: P, reqInit: RequestInit = {}) { + patch(path: string, params?: P, reqInit: JsonRequestInit = {}) { return this.request(path, params, { ...reqInit, method: "PATCH" }); } - del(path: string, params?: P, reqInit: RequestInit = {}) { + del(path: string, params?: P, reqInit: JsonRequestInit = {}) { return this.request(path, params, { ...reqInit, method: "delete" }); } - protected async request(path: string, params?: P, init: RequestInit = {}) { - let reqUrl = `${this.config.serverAddress}${this.config.apiBase}${path}`; - const reqInit: RequestInit = merge({}, this.reqInit, init); + protected buildRequestUrl(path: string, useSubdomain = false) { + if (window) { + const subdomain = useSubdomain ? `${randomBytes(2).toString("hex")}.` : ""; + + return `http://${subdomain}${window.location.host}${this.config.apiBase}${path}`; + } else { + return `${this.config.serverAddress}${this.config.apiBase}${path}`; + } + } + + protected async request(path: string, params?: P, init: JsonRequestInit = {}) { + let reqUrl = this.buildRequestUrl(path); + const reqInit = merge({}, this.reqInit, init); const { data, query } = params || {} as P; if (data && !reqInit.body) { @@ -132,12 +150,20 @@ export class JsonApi { reqInit, }; - const res = await fetch(reqUrl, reqInit); + const res = await this.fetch(reqUrl, reqInit); return this.parseResponse(res, infoLog); } - protected async parseResponse(res: Response, log: JsonApiLog): Promise { + protected fetch(reqUrl: string, reqInit: JsonRequestInit) { + if (window) { + return fetch(reqUrl, reqInit as RequestInit); + } else { + return nodeFetch(reqUrl, reqInit as NodeRequestInit); + } + } + + protected async parseResponse(res: JsonResponse, log: JsonApiLog): Promise { const { status } = res; const text = await res.text(); @@ -169,7 +195,7 @@ export class JsonApi { throw error; } - protected parseError(error: JsonApiError | string, res: Response): string[] { + protected parseError(error: JsonApiError | string, res: JsonResponse): string[] { if (typeof error === "string") { return [error]; } diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index 54b0e5726f..ef339315fe 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -36,6 +36,7 @@ import { noop } from "../utils"; import type { RequestInit } from "node-fetch"; import AbortController from "abort-controller"; import { Agent, AgentOptions } from "https"; +import { ReadableWebToNodeStream } from "readable-web-to-node-stream"; export interface IKubeApiOptions { /** @@ -117,7 +118,7 @@ export interface IRemoteKubeApiConfig { export function forCluster(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor): KubeApi { const url = new URL(apiBase.config.serverAddress); const request = new KubeJsonApi({ - serverAddress: apiBase.config.serverAddress, + serverAddress: window ? `${cluster.metadata.uid}.localhost:${url.port}` : apiBase.config.serverAddress, apiBase: apiKubePrefix, debug: isDevelopment, }, { @@ -507,21 +508,24 @@ export class KubeApi { return callback(null, response); } + const body = window ? new ReadableWebToNodeStream(response.body as ReadableStream): response.body as NodeJS.ReadableStream; + ["end", "close", "error"].forEach((eventName) => { - response.body.on(eventName, () => { + body.on(eventName, () => { + clearTimeout(timedRetry); + if (errorReceived) return; // kubernetes errors should be handled in a callback if (signal.aborted) return; logger.info(`[KUBE-API] watch (${watchId}) ${eventName} ${watchUrl}`); - clearTimeout(timedRetry); timedRetry = setTimeout(() => { // we did not get any kubernetes errors so let's retry this.watch({ ...opts, namespace, callback, watchId, retry: true }); }, 1000); }); }); - byline(response.body).on("data", (line) => { + byline(body).on("data", (line) => { try { const event: IKubeWatchEvent = JSON.parse(line); diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index dc428eb08b..e0b2bf304c 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -348,7 +348,7 @@ export abstract class KubeObjectStore extends ItemStore const { signal } = abortController; const callback = (data: IKubeWatchEvent, error: any) => { - if (!this.isLoaded || error?.type === "aborted") return; + if (!this.isLoaded || error instanceof DOMException || error?.type === "aborted") return; if (error instanceof Response) { if (error.status === 404 || error.status === 401) {