diff --git a/.env.development.local.template b/.env.development.local.template index 17248068a..c1ba5363b 100644 --- a/.env.development.local.template +++ b/.env.development.local.template @@ -17,6 +17,7 @@ NODE_TLS_REJECT_UNAUTHORIZED=0 [gateway] RUCIO_AUTH_HOST=https://rucio-devmaany.cern.ch:443 RUCIO_HOST=https://rucio-devmaany.cern.ch:443 +PARAMS_ENCODING_ENABLED=false [oidc] OIDC_ENABLED=true diff --git a/src/lib/core/port/secondary/env-config-gateway-output-port.ts b/src/lib/core/port/secondary/env-config-gateway-output-port.ts index 8a8fcaf16..9974fc92d 100644 --- a/src/lib/core/port/secondary/env-config-gateway-output-port.ts +++ b/src/lib/core/port/secondary/env-config-gateway-output-port.ts @@ -60,4 +60,9 @@ export default interface EnvConfigGatewayOutputPort { * @returns the URL of the VO/Experiment community that maintains the Rucio WebUI instance */ projectURL(): Promise; + + /** + * @returns whether the query parameters should get URI encoded + */ + paramsEncodingEnabled(): Promise; } diff --git a/src/lib/infrastructure/gateway/env-config-gateway.ts b/src/lib/infrastructure/gateway/env-config-gateway.ts index 0a4a3a3ed..d3f6c0638 100644 --- a/src/lib/infrastructure/gateway/env-config-gateway.ts +++ b/src/lib/infrastructure/gateway/env-config-gateway.ts @@ -172,6 +172,15 @@ class EnvConfigGateway implements EnvConfigGatewayOutputPort { } return Promise.resolve(undefined); } + + async paramsEncodingEnabled(): Promise { + const value = await this.get('PARAMS_ENCODING_ENABLED'); + if (value === 'true' || value === 'True' || value === 'TRUE') { + return Promise.resolve(true); + } else { + return Promise.resolve(false); + } + } } export default EnvConfigGateway; diff --git a/src/lib/sdk/gateway-endpoints.ts b/src/lib/sdk/gateway-endpoints.ts index ce58c29e3..5483163e0 100644 --- a/src/lib/sdk/gateway-endpoints.ts +++ b/src/lib/sdk/gateway-endpoints.ts @@ -206,7 +206,8 @@ export abstract class BaseEndpoint { throw new Error(`Request not initialized for ${this.constructor.name}`); } - const preparedRequest = prepareRequestArgs(this.request); + const encodeParams = await this.envConfigGateway.paramsEncodingEnabled(); + const preparedRequest = prepareRequestArgs(this.request, encodeParams); const response: Response = await fetch(preparedRequest.url, preparedRequest.requestArgs); if (!response.ok) { diff --git a/src/lib/sdk/http.ts b/src/lib/sdk/http.ts index 64319fca6..fb2218c4b 100644 --- a/src/lib/sdk/http.ts +++ b/src/lib/sdk/http.ts @@ -17,17 +17,21 @@ export type HTTPRequest = { /** * Prepares the request arguments for an HTTP request. * @param {HTTPRequest} request - The HTTP request to prepare arguments for. + * @param {boolean} encodeParams - Whether the query parameters should get URI encoded * @returns {{ url: string | URL; requestArgs: RequestInit }} - An object containing the URL and request arguments. */ -export function prepareRequestArgs(request: HTTPRequest): { +export function prepareRequestArgs( + request: HTTPRequest, + encodeParams: boolean = false, +): { url: string | URL; requestArgs: RequestInit; } { if (request.params) { const url = new URL(request.url); Object.keys(request.params).forEach(key => { - const encodedValue = encodeURIComponent(request.params![key]); - url.searchParams.append(key, encodedValue); + const value = request.params![key]; + url.searchParams.append(key, encodeParams ? encodeURIComponent(value) : value); }); request.url = url.toString(); } diff --git a/src/lib/sdk/streaming-gateway.ts b/src/lib/sdk/streaming-gateway.ts index 0c9b1da1a..b73369a7f 100644 --- a/src/lib/sdk/streaming-gateway.ts +++ b/src/lib/sdk/streaming-gateway.ts @@ -1,12 +1,17 @@ import 'reflect-metadata'; import StreamGatewayOutputPort from '@/lib/core/port/secondary/stream-gateway-output-port'; -import { injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; import fetch, { Response } from 'node-fetch'; import { PassThrough, Transform } from 'node:stream'; import { HTTPRequest, prepareRequestArgs } from '@/lib/sdk/http'; import { BytesToStringifiedJSONTransform, NewlineDelimittedDataParser } from '@/lib/sdk/stream-transformers'; +import type EnvConfigGatewayOutputPort from '@/lib/core/port/secondary/env-config-gateway-output-port'; +import GATEWAYS from '@/lib/infrastructure/ioc/ioc-symbols-gateway'; + @injectable() export default class StreamingGateway implements StreamGatewayOutputPort { + constructor(@inject(GATEWAYS.ENV_CONFIG) private envConfigGateway: EnvConfigGatewayOutputPort) {} + private convertChunkBytesToString = new Transform({ transform(chunk, encoding, callback) { callback(null, chunk.toString()); @@ -14,7 +19,8 @@ export default class StreamingGateway implements StreamGatewayOutputPort { }); async getTextStream(request: HTTPRequest): Promise { - const { url, requestArgs } = prepareRequestArgs(request); + const encodeParams = await this.envConfigGateway.paramsEncodingEnabled(); + const { url, requestArgs } = prepareRequestArgs(request, encodeParams); const response = await fetch(url, requestArgs); if (!response.ok || response.body === null) { @@ -27,7 +33,8 @@ export default class StreamingGateway implements StreamGatewayOutputPort { } async getJSONChunks(request: HTTPRequest, ndjson: boolean = false): Promise<{ type: 'response' | 'stream'; content: PassThrough | Response }> { - const { url, requestArgs } = prepareRequestArgs(request); + const encodeParams = await this.envConfigGateway.paramsEncodingEnabled(); + const { url, requestArgs } = prepareRequestArgs(request, encodeParams); const response = await fetch(url, requestArgs); if (!response.ok || response.body === null) {