Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: set content-length header #1381

Merged
merged 10 commits into from
Jan 2, 2024
Merged

Conversation

cristiand391
Copy link
Member

@cristiand391 cristiand391 commented Nov 24, 2023

feat: automatically set content-length header on REST/SOAP requests.

We've been getting a few reports that some internal envs started to enforce this header on requests, adding support in jsforce as a safety measure.

Tests

while working on this I found the HTTP module doesn't any UT, there's some coverage for session refresh in different e2e tests but we today we don't have an easy way to test this behavior, I'll add some tests for this when working on
[W-14580746: [jsforce]: increase HTTP module test coverage]
(https://gus.lightning.force.com/lightning/r/ADM_Work__c/a07EE00001fXgwbYAC/view)

QA

not really an easy way to QA this without getting one of those internal envs, but you can try running tests with this env var:
JSFORCE_LOG_LEVEL=DEBUG to get the body size added in each request (I tried adding to mock the logger to grab the value and compare it in e2e tests but couldn't get it to work on browser/tests (you can't use jest specific stuff).

@W-14177773@

@cristiand391 cristiand391 changed the base branch from master to 3.0 November 24, 2023 21:10
@cristiand391 cristiand391 marked this pull request as ready for review December 1, 2023 15:13
// only retry on CI
if (process.env.CI) {
jest.retryTimes(3, {logErrorsBeforeRetry: true})
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retries are annoying when working locally, should always be enabled on CI.

@@ -207,6 +209,7 @@
"dependencies": {
"@babel/runtime": "^7.23.1",
"@babel/runtime-corejs3": "^7.23.1",
"@sindresorhus/is": "^4",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's already a v6:
https://github.com/sindresorhus/is/releases

but had to use v4 because it's the latest CJS release.

When using ESM modules via await import webpack creates a new chunk just for it instead of keeping it in the main bundle:
https://webpack.js.org/guides/code-splitting/#dynamic-imports

I spent a few hours trying to find some workarounds but couldn't get it to inline this into the main bundle so keeping it at v4 until I find a solution for this (will need to for node-fetch v3 (ESM): #1356)

`missing 'content-length' header, setting it to: ${bodySize}`,
);
headers['content-length'] = String(bodySize);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`missing 'content-length' header, setting it to: ${bodySize}`,
);
request.headers['content-length'] = String(bodySize);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as this but for SOAP requests:
https://github.com/jsforce/jsforce/pull/1381/files#r1412264591

one difference here is that this only checks if method is POST
while GET is possible, I haven't seen any examples of using non-POST in API docs. We can revert it later if it's not true.

type FormData = {
getBoundary: () => string;
getLength: (callback: (error: Error | null, length: number) => void) => void;
} & Readable;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

): number | undefined {
function isFormData(body: unknown): body is FormData {
return is.nodeStream(body) && is.function_((body as FormData).getBoundary);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks specifically for this FormData, not the web native or node native versions.

we use FormData in jsforce.

}

return undefined;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only case not handled here is readable streams:

| NodeJS.ReadableStream

got stopped handling this in v12 too, see:
sindresorhus/got#1486
sindresorhus/got#1510

src/http-api.ts Outdated
Comment on lines 130 to 141
// remove the `content-length` header after token refresh
//
// SOAP requests include the access token their the body,
// if the first req had an invalid token and jsforce successfully
// refreshed it we need to remove the `content-length` header
// so that it get's re-calculated again with the new body.
//
// REST request aren't affected by this because the access token
// is sent via HTTP headers
//
//
// `_message` is only present in SOAP requests
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit: I prefer /**/ style for more than like 2 lines of comments

src/http-api.ts Outdated
Comment on lines 143 to 145
Object.hasOwn(request, '_message') &&
request.headers &&
request.headers['content-length']
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'_message' in request && request.headers?.['content-length']

src/http-api.ts Outdated
!cannotHaveBody &&
!!request.body &&
!headers['transfer-encoding'] &&
!headers['content-length'] &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if 'content-length' is set to 0, does this still do what you want?

src/soap.ts Outdated
request.method === 'POST' &&
request.headers &&
!request.headers['transfer-encoding'] &&
!request.headers['content-length'] &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question here about 0

import { HttpBody } from '../types';
import is from '@sindresorhus/is';

export function getBodySize(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for using an exported function. It should be simple to write some true UT, which jsforce lacks, for this.

@@ -233,6 +234,21 @@ export class SOAP<S extends Schema> extends HttpApi<S> {
/** @override */
beforeSend(request: HttpRequest & { _message: object }) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this design (pass an object for mutation). I know you didn't start it.

But if http-api isn't exported and is only used via the 2 classes that extend it (soap and bulk?) maybe it's possible to fix it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe that's too big and it's easier to stay in line with what jsforce is already doing?

@mshanemc
Copy link
Collaborator

mshanemc commented Jan 2, 2024

QA: created a record via sf data create record (uses POST) and JSFORCE_LOG_LEVEL=DEBUG

missing 'content-length' header, setting it to: 18

Seems fine

@mshanemc mshanemc merged commit 442d045 into 3.0 Jan 2, 2024
15 checks passed
@cristiand391 cristiand391 deleted the cd/add-http-content-length-header branch January 2, 2024 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants