Repository: chimurai/http-proxy-middleware
Pull request #1090 (2025-04-07 19:56:54)

fix(fixRequestBody): prevent multiple .write() calls
related #1089


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 42160f4..7e196d8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,13 +8,13 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v2
-      - name: Use Node.js 16.x
-        uses: actions/setup-node@v2
+      - uses: actions/checkout@v4
+      - name: Use Node.js 22.x
+        uses: actions/setup-node@v4
         with:
-          node-version: 16.x
+          node-version: 22.x
 
-      - uses: actions/cache@v2
+      - uses: actions/cache@v4
         id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
         with:
           path: '**/node_modules'
@@ -38,16 +38,16 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [12.x, 14.x, 16.x]
+        node-version: [18.x, 20.x, 22.x]
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Use Node.js ${{ matrix.node-version }}
-        uses: actions/setup-node@v2
+        uses: actions/setup-node@v4
         with:
           node-version: ${{ matrix.node-version }}
 
-      - uses: actions/cache@v2
+      - uses: actions/cache@v4
         id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
         with:
           path: '**/node_modules'
@@ -70,13 +70,13 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v2
-      - name: Use Node.js 16.x
-        uses: actions/setup-node@v2
+      - uses: actions/checkout@v4
+      - name: Use Node.js 22.x
+        uses: actions/setup-node@v4
         with:
-          node-version: 16.x
+          node-version: 22.x
 
-      - uses: actions/cache@v2
+      - uses: actions/cache@v4
         id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
         with:
           path: '**/node_modules'
@@ -100,7 +100,7 @@ jobs:
     name: Spellcheck
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - uses: streetsidesoftware/cspell-action@main
         with:
           # Github token used to fetch the list of changed files in the commit.
diff --git a/src/handlers/fix-request-body.ts b/src/handlers/fix-request-body.ts
index 0f45a2f..bfd4cce 100644
--- a/src/handlers/fix-request-body.ts
+++ b/src/handlers/fix-request-body.ts
@@ -13,17 +13,20 @@ export function fixRequestBody(proxyReq: http.ClientRequest, req: http.IncomingM
   }
 
   const contentType = proxyReq.getHeader('Content-Type') as string;
+
+  if (!contentType) {
+    return;
+  }
+
   const writeBody = (bodyData: string) => {
     // deepcode ignore ContentLengthInCode: bodyParser fix
     proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
     proxyReq.write(bodyData);
   };
 
-  if (contentType && contentType.includes('application/json')) {
+  if (contentType.includes('application/json')) {
     writeBody(JSON.stringify(requestBody));
-  }
-
-  if (contentType && contentType.includes('application/x-www-form-urlencoded')) {
+  } else if (contentType.includes('application/x-www-form-urlencoded')) {
     writeBody(querystring.stringify(requestBody));
   }
 }


Test patch:
diff --git a/test/unit/fix-request-body.spec.ts b/test/unit/fix-request-body.spec.ts
index 5bc42f8..4415b7e 100644
--- a/test/unit/fix-request-body.spec.ts
+++ b/test/unit/fix-request-body.spec.ts
@@ -78,4 +78,19 @@ describe('fixRequestBody', () => {
     expect(proxyRequest.setHeader).toHaveBeenCalledWith('Content-Length', expectedBody.length);
     expect(proxyRequest.write).toHaveBeenCalledWith(expectedBody);
   });
+
+  it('should parse json and call write() once with incorrect content-type application/x-www-form-urlencoded+application/json', () => {
+    const proxyRequest = fakeProxyRequest();
+    proxyRequest.setHeader('content-type', 'application/x-www-form-urlencoded+application/json');
+
+    jest.spyOn(proxyRequest, 'setHeader');
+    jest.spyOn(proxyRequest, 'write');
+
+    fixRequestBody(proxyRequest, { body: { someField: 'some value' } } as Request);
+
+    const expectedBody = JSON.stringify({ someField: 'some value' });
+    expect(proxyRequest.setHeader).toHaveBeenCalledWith('Content-Length', expectedBody.length);
+    expect(proxyRequest.write).toHaveBeenCalledTimes(1);
+    expect(proxyRequest.write).toHaveBeenCalledWith(expectedBody);
+  });
 });