diff --git a/README.md b/README.md index c0aa514..6d62459 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ The GrowthBook Proxy repository is a mono-repo containing the following packages ### What's new +**Version 1.1.2** +- Fix max payload size bug +- Deprecate `CLUSTER_ROOT_NODES` in favor of `CLUSTER_ROOT_NODES_JSON` + **Version 1.1.1** - Multi organization support - Support paginated SDK Connection polling @@ -110,9 +114,8 @@ Redis-specific options for cluster mode:
_(Note that CACHE_CONNECTION_URL is ignored when using cluster mode)_ - `USE_CLUSTER` - "true" or "1" to enable (default: `false`) -- `CLUSTER_ROOT_NODES` - simple: comma-separated URLs to your cluster seed nodes -- `CLUSTER_ROOT_NODES_JSON` - advanced: JSON array of ClusterNode objects (ioredis) -- `CLUSTER_OPTIONS_JSON` - advanced: JSON object of ClusterOptions (ioredis) +- `CLUSTER_ROOT_NODES_JSON` - JSON array of ClusterNode objects (ioredis) +- `CLUSTER_OPTIONS_JSON` - JSON object of ClusterOptions (ioredis) #### MongoDB diff --git a/packages/apps/proxy/package.json b/packages/apps/proxy/package.json index 815d819..9e078a4 100644 --- a/packages/apps/proxy/package.json +++ b/packages/apps/proxy/package.json @@ -4,7 +4,7 @@ "node": ">=18" }, "description": "GrowthBook proxy server for caching, realtime updates, telemetry, etc", - "version": "1.1.1", + "version": "1.1.2", "main": "dist/app.js", "license": "MIT", "repository": { diff --git a/packages/apps/proxy/src/controllers/adminController.ts b/packages/apps/proxy/src/controllers/adminController.ts index 6ce127f..7e2f4f8 100644 --- a/packages/apps/proxy/src/controllers/adminController.ts +++ b/packages/apps/proxy/src/controllers/adminController.ts @@ -2,6 +2,7 @@ import express, { Request, Response } from "express"; import { registrar } from "../services/registrar"; import { adminMiddleware } from "../middleware/adminMiddleware"; import logger from "../services/logger"; +import { MAX_PAYLOAD_SIZE } from "../init"; const postConnection = (req: Request, res: Response) => { const apiKey = req.body.apiKey; @@ -57,7 +58,7 @@ adminRouter.post( "/connection", adminMiddleware, express.json({ - limit: process.env.MAX_PAYLOAD_SIZE ?? "2mb", + limit: process.env.MAX_PAYLOAD_SIZE ?? MAX_PAYLOAD_SIZE, }), postConnection, ); diff --git a/packages/apps/proxy/src/controllers/featuresController.ts b/packages/apps/proxy/src/controllers/featuresController.ts index 871017a..db81f05 100644 --- a/packages/apps/proxy/src/controllers/featuresController.ts +++ b/packages/apps/proxy/src/controllers/featuresController.ts @@ -11,6 +11,7 @@ import { sseSupportMiddleware } from "../middleware/sseSupportMiddleware"; import logger from "../services/logger"; import { fetchFeatures } from "../services/features"; import { Context } from "../types"; +import { MAX_PAYLOAD_SIZE } from "../init"; const getFeatures = async (req: Request, res: Response, next: NextFunction) => { if (!registrar?.growthbookApiHost) { @@ -181,7 +182,9 @@ export const featuresRouter = (ctx: Context) => { router.post( "/api/eval/*", apiKeyMiddleware, - express.json(), + express.json({ + limit: process.env.MAX_PAYLOAD_SIZE ?? MAX_PAYLOAD_SIZE, + }), sseSupportMiddleware, getEvaluatedFeatures, ); @@ -192,6 +195,7 @@ export const featuresRouter = (ctx: Context) => { "/proxy/features", apiKeyMiddleware, express.json({ + limit: process.env.MAX_PAYLOAD_SIZE ?? MAX_PAYLOAD_SIZE, verify: (req: Request, res: Response, buf: Buffer) => (res.locals.rawBody = buf), }), diff --git a/packages/apps/proxy/src/init.ts b/packages/apps/proxy/src/init.ts index 5902677..c409557 100644 --- a/packages/apps/proxy/src/init.ts +++ b/packages/apps/proxy/src/init.ts @@ -4,6 +4,8 @@ import dotenv from "dotenv"; import { CacheEngine, Context } from "./types"; dotenv.config({ path: "./.env.local" }); +export const MAX_PAYLOAD_SIZE = "2mb"; + export default async () => { const context: Partial = { growthbookApiHost: process.env.GROWTHBOOK_API_HOST, @@ -15,7 +17,7 @@ export default async () => { verboseDebugging: ["true", "1"].includes( process.env.VERBOSE_DEBUGGING ?? "0", ), - maxPayloadSize: process.env.MAX_PAYLOAD_SIZE ?? "2mb", + maxPayloadSize: process.env.MAX_PAYLOAD_SIZE ?? MAX_PAYLOAD_SIZE, // SDK Connections settings: createConnectionsFromEnv: ["true", "1"].includes(process.env.CREATE_CONNECTIONS_FROM_ENV ?? "1"), pollForConnections: ["true", "1"].includes(process.env.POLL_FOR_CONNECTIONS ?? "1"), @@ -37,9 +39,6 @@ export default async () => { ), // Redis only - cluster: useCluster: ["true", "1"].includes(process.env.USE_CLUSTER ?? "0"), - clusterRootNodes: process.env.CLUSTER_ROOT_NODES - ? process.env.CLUSTER_ROOT_NODES.replace(" ", "").split(",") - : undefined, clusterRootNodesJSON: process.env.CLUSTER_ROOT_NODES_JSON ? JSON.parse(process.env.CLUSTER_ROOT_NODES_JSON) : undefined, diff --git a/packages/apps/proxy/src/services/cache/RedisCache.ts b/packages/apps/proxy/src/services/cache/RedisCache.ts index 3643f77..ff51a90 100644 --- a/packages/apps/proxy/src/services/cache/RedisCache.ts +++ b/packages/apps/proxy/src/services/cache/RedisCache.ts @@ -22,7 +22,7 @@ export class RedisCache { public readonly allowStale: boolean; private readonly useCluster: boolean; - private readonly clusterRootNodes?: ClusterNode[]; + private readonly clusterRootNodesJSON?: ClusterNode[]; private readonly clusterOptions?: ClusterOptions; private readonly appContext?: Context; @@ -36,7 +36,6 @@ export class RedisCache { useAdditionalMemoryCache, publishPayloadToChannel = false, useCluster = false, - clusterRootNodes, clusterRootNodesJSON, clusterOptionsJSON, }: CacheSettings = {}, @@ -48,8 +47,7 @@ export class RedisCache { this.allowStale = allowStale; this.publishPayloadToChannel = publishPayloadToChannel; this.useCluster = useCluster; - this.clusterRootNodes = - clusterRootNodesJSON ?? this.transformRootNodes(clusterRootNodes); + this.clusterRootNodesJSON = clusterRootNodesJSON; this.clusterOptions = clusterOptionsJSON; this.appContext = appContext; @@ -69,9 +67,9 @@ export class RedisCache { ? new Redis(this.connectionUrl) : new Redis(); } else { - if (this.clusterRootNodes) { + if (this.clusterRootNodesJSON) { this.client = new Redis.Cluster( - this.clusterRootNodes, + this.clusterRootNodesJSON, this.clusterOptions, ); } else { @@ -268,21 +266,4 @@ export class RedisCache { public getsubscriberClient() { return this.subscriberClient; } - - private transformRootNodes(rootNodes?: string[]): ClusterNode[] | undefined { - if (!rootNodes) return undefined; - return rootNodes - .map((node) => { - try { - const url = new URL(node); - const host = url.protocol + "//" + url.hostname + url.pathname; - const port = parseInt(url.port); - return { host, port }; - } catch (e) { - logger.error(e, "Error parsing Redis cluster node"); - return undefined; - } - }) - .filter(Boolean) as ClusterNode[]; - } } diff --git a/packages/apps/proxy/src/services/cache/index.ts b/packages/apps/proxy/src/services/cache/index.ts index aa30d7b..1ec9408 100644 --- a/packages/apps/proxy/src/services/cache/index.ts +++ b/packages/apps/proxy/src/services/cache/index.ts @@ -22,7 +22,6 @@ export interface CacheSettings { useAdditionalMemoryCache?: boolean; publishPayloadToChannel?: boolean; // for RedisCache pub/sub useCluster?: boolean; // for RedisCache - clusterRootNodes?: string[]; // for RedisCache clusterRootNodesJSON?: ClusterNode[]; // for RedisCache clusterOptionsJSON?: ClusterOptions; // for RedisCache }