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: add OPTIONS as an allowed HTTP Method #265

Merged
merged 2 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/command/http-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const getInitialResult = () => ({
});

const allowedHttpProtocols = [ 'HTTP', 'HTTPS', 'HTTP2' ];
const allowedHttpMethods = [ 'GET', 'HEAD' ];
const allowedHttpMethods = [ 'GET', 'HEAD', 'OPTIONS' ];
const allowedIpVersions = [ 4, 6 ];

export const httpOptionsSchema = Joi.object<HttpOptions>({
Expand Down Expand Up @@ -145,7 +145,7 @@ export const urlBuilder = (options: HttpOptions): string => {
return url;
};

export const httpCmd = (options: HttpOptions, resolverFn?: ResolverType): Request => {
export const httpCmd = (options: HttpOptions, resolverFn?: ResolverType): Request => {
const url = urlBuilder(options);
const dnsResolver = callbackify(dnsLookup(options.resolver, resolverFn), true);

Expand All @@ -166,6 +166,7 @@ export const httpCmd = (options: HttpOptions, resolverFn?: ResolverType): Reques
'User-Agent': 'globalping probe (https://github.com/jsdelivr/globalping)',
'host': options.request.host ?? options.target,
},
...(options.request.method === 'OPTIONS' && { body: '' }), // https://github.com/sindresorhus/got/issues/2394
setHost: false,
throwHttpErrors: false,
context: {
Expand Down Expand Up @@ -215,7 +216,7 @@ export class HttpCommand implements CommandInterface<HttpOptions> {
rawOutput = result.error;
} else if (result.error) {
rawOutput = `HTTP/${result.httpVersion} ${result.statusCode}\n${result.rawHeaders}\n\n${result.error}`;
} else if (cmdOptions.request.method === 'HEAD') {
} else if (cmdOptions.request.method === 'HEAD' || !result.rawBody) {
rawOutput = `HTTP/${result.httpVersion} ${result.statusCode}\n${result.rawHeaders}`;
} else {
rawOutput = `HTTP/${result.httpVersion} ${result.statusCode}\n${result.rawHeaders}\n\n${result.rawBody}`;
Expand Down
109 changes: 109 additions & 0 deletions test/unit/command/http-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,115 @@ describe('http command', () => {
]);
});

it('should respond with 200 on OPTIONS request and response with body', async () => {
nock('http://google.com').options('/').reply(200, function () {
const request = this.req as typeof this.req & {response: Response & {socket: { getPeerCertificate }}};
request.response.httpVersion = '1.1';
request.response.socket.getPeerCertificate = false;
return 'response body';
});

const options = {
type: 'http' as const,
target: 'google.com',
inProgressUpdates: false,
protocol: 'HTTP',
request: {
method: 'OPTIONS',
path: '/',
query: '',
},
ipVersion: 4,
};

const http = new HttpCommand(httpCmd);
await http.run(mockedSocket as any, 'measurement', 'test', options);

expect(mockedSocket.emit.callCount).to.equal(1);

expect(mockedSocket.emit.firstCall.args).to.deep.equal([
'probe:measurement:result',
{
testId: 'test',
measurementId: 'measurement',
result: {
status: 'finished',
resolvedAddress: '127.0.0.1',
headers: {},
rawHeaders: null,
rawBody: 'response body',
rawOutput: 'HTTP/1.1 200\n\n\nresponse body',
truncated: false,
statusCode: 200,
statusCodeName: 'OK',
timings: {
total: 0,
download: 0,
firstByte: 0,
dns: 0,
tls: null,
tcp: 0,
},
tls: null,
},
},
]);
});

it('should respond with 200 on OPTIONS request and response without body', async () => {
nock('http://google.com').options('/').reply(200, function () {
const request = this.req as typeof this.req & {response: Response & {socket: { getPeerCertificate }}};
request.response.httpVersion = '1.1';
request.response.socket.getPeerCertificate = false;
});

const options = {
type: 'http' as const,
target: 'google.com',
inProgressUpdates: false,
protocol: 'HTTP',
request: {
method: 'OPTIONS',
path: '/',
query: '',
},
ipVersion: 4,
};

const http = new HttpCommand(httpCmd);
await http.run(mockedSocket as any, 'measurement', 'test', options);

expect(mockedSocket.emit.callCount).to.equal(1);

expect(mockedSocket.emit.firstCall.args).to.deep.equal([
'probe:measurement:result',
{
testId: 'test',
measurementId: 'measurement',
result: {
status: 'finished',
resolvedAddress: '127.0.0.1',
headers: {},
rawHeaders: null,
rawBody: null,
rawOutput: 'HTTP/1.1 200\n',
truncated: false,
statusCode: 200,
statusCodeName: 'OK',
timings: {
total: 0,
download: 0,
firstByte: 0,
dns: 0,
tls: null,
tcp: 0,
},
tls: null,
},
},
]);
});

it('should respond with 400 with progress messages', async () => {
nock('http://google.com').get('/400').times(3).reply(400, function () {
const request = this.req as typeof this.req & {response: Response & {socket: { getPeerCertificate }}};
Expand Down
3 changes: 1 addition & 2 deletions wallaby.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as url from 'node:url';

export default function wallaby () {
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));

return {
testFramework: 'mocha',
files: [
Expand All @@ -12,7 +13,6 @@ export default function wallaby () {
'test/plugins/**/*',
'test/utils.ts',
'test/hooks.ts',
'test/setup.ts',
'test/snapshots/**/*.json',
'package.json',
],
Expand All @@ -22,7 +22,6 @@ export default function wallaby () {
setup (w) {
const path = require('path');
w.testFramework.files.unshift(path.resolve(process.cwd(), 'test/hooks.js'));
w.testFramework.files.unshift(path.resolve(process.cwd(), 'test/setup.js'));
const mocha = w.testFramework;
mocha.timeout(5000);
},
Expand Down
Loading