Skip to content

Commit

Permalink
feat(node.js fetch): hyperjumptech#1268 implement redirect for fetch (h…
Browse files Browse the repository at this point in the history
…yperjumptech#1271)

* handle 3xx on using nodejs fetch

* fix operator and add tests

* add documentatation
  • Loading branch information
syamsudotdev authored Apr 16, 2024
1 parent 6a29470 commit f05e7cb
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 38 deletions.
6 changes: 6 additions & 0 deletions src/components/probe/prober/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* SOFTWARE. *
**********************************************************************************/

import { monikaFlagsDefaultValue } from '../../../../flag'
import { BaseProber, NotificationType, type ProbeParams } from '..'
import { getContext } from '../../../../context'
import events from '../../../../events'
Expand Down Expand Up @@ -56,6 +57,11 @@ export class HTTPProber extends BaseProber {
responses.push(
// eslint-disable-next-line no-await-in-loop
await httpRequest({
isVerbose: getContext().flags.verbose,
maxRedirects:
getContext().flags['follow-redirects'] ||
monikaFlagsDefaultValue['follow-redirects'],
isEnableFetch: getContext().flags['native-fetch'],
requestConfig: { ...requestConfig, signal },
responses,
})
Expand Down
94 changes: 94 additions & 0 deletions src/components/probe/prober/http/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe('probingHTTP', () => {
try {
// eslint-disable-next-line no-await-in-loop
const resp = await httpRequest({
maxRedirects: 0,
requestConfig: request,
responses,
})
Expand Down Expand Up @@ -138,6 +139,7 @@ describe('probingHTTP', () => {
}

const result = await httpRequest({
maxRedirects: 0,
requestConfig: request,
responses: [],
})
Expand Down Expand Up @@ -176,6 +178,7 @@ describe('probingHTTP', () => {

// act
const res = await httpRequest({
maxRedirects: 0,
requestConfig: request,
responses: [],
})
Expand Down Expand Up @@ -218,6 +221,7 @@ describe('probingHTTP', () => {

// act
const res = await httpRequest({
maxRedirects: 0,
requestConfig: request,
responses: [],
})
Expand Down Expand Up @@ -260,6 +264,7 @@ describe('probingHTTP', () => {

// act
const res = await httpRequest({
maxRedirects: 0,
requestConfig: request,
responses: [],
})
Expand Down Expand Up @@ -302,6 +307,7 @@ describe('probingHTTP', () => {

// act
const res = await httpRequest({
maxRedirects: 0,
requestConfig: request,
responses: [],
})
Expand All @@ -310,6 +316,93 @@ describe('probingHTTP', () => {
expect(res.status).to.eq(200)
})

it('Should handle HTTP redirect with axios', async () => {
// arrange
server.use(
http.get(
'https://example.com/get',
() => new HttpResponse(null, { status: 200 })
),
http.get(
'https://example.com/redirect/:nredirect',
async ({ params }) => {
const { nredirect } = params
const castRedirect = Number(nredirect)
return new HttpResponse(null, {
status: 302,
headers: [
[
'Location',
castRedirect === 1
? 'https://example.com/get'
: `https://example.com/redirect/${castRedirect - 1}`,
],
],
})
}
)
)

const requestConfig: RequestConfig = {
url: 'https://example.com/redirect/3',
method: 'GET',
body: '',
timeout: 10_000,
}

const res = await httpRequest({
isEnableFetch: true,
maxRedirects: 3,
requestConfig,
responses: [],
})

expect(res.status).to.eq(200)
})
it('Should handle HTTP redirect with fetch', async () => {
// arrange
server.use(
http.get(
'https://example.com/get',
() => new HttpResponse(null, { status: 200 })
),
http.get(
'https://example.com/redirect/:nredirect',
async ({ params }) => {
const { nredirect } = params
const castRedirect = Number(nredirect)
return new HttpResponse(null, {
status: 302,
headers: [
[
'Location',
castRedirect === 1
? 'https://example.com/get'
: `https://example.com/redirect/${castRedirect - 1}`,
],
],
})
}
)
)

const requestConfig: RequestConfig = {
url: 'https://example.com/redirect/3',
method: 'GET',
body: '',
timeout: 10_000,
}

const res = await httpRequest({
isEnableFetch: false,
maxRedirects: 3,
requestConfig,
responses: [],
})

expect(res.status).to.eq(200)
})

it('should send request with text-plain content-type even with allowUnauthorized option', async () => {
// arrange
server.use(
Expand Down Expand Up @@ -345,6 +438,7 @@ describe('probingHTTP', () => {

// act
const res = await httpRequest({
maxRedirects: 0,
requestConfig: request,
responses: [],
})
Expand Down
43 changes: 28 additions & 15 deletions src/components/probe/prober/http/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@ import {
// eslint-disable-next-line no-restricted-imports
import * as qs from 'querystring'

import { getContext } from '../../../../context'
import { icmpRequest } from '../icmp/request'
import registerFakes from '../../../../utils/fakes'
import { sendHttpRequest, sendHttpRequestFetch } from '../../../../utils/http'
import { log } from '../../../../utils/pino'
import { AxiosError } from 'axios'
import { MonikaFlags } from 'src/flag'
import { getErrorMessage } from '../../../../utils/catch-error-handler'
import { errors as undiciErrors } from 'undici'
import Joi from 'joi'
Expand All @@ -49,8 +47,11 @@ import Joi from 'joi'
registerFakes(Handlebars)

type probingParams = {
maxRedirects: number
requestConfig: Omit<RequestConfig, 'saveBody' | 'alert'> // is a config object
responses: Array<ProbeRequestResponse> // an array of previous responses
isVerbose?: boolean
isEnableFetch?: boolean
}

const UndiciErrorValidator = Joi.object({
Expand All @@ -63,17 +64,18 @@ const UndiciErrorValidator = Joi.object({
* @returns ProbeRequestResponse, response to the probe request
*/
export async function httpRequest({
maxRedirects,
requestConfig,
responses,
isEnableFetch,
isVerbose,
}: probingParams): Promise<ProbeRequestResponse> {
// Compile URL using handlebars to render URLs that uses previous responses data
const { method, url, headers, timeout, body, ping, allowUnauthorized } =
requestConfig
const newReq = { method, headers, timeout, body, ping }
const renderURL = Handlebars.compile(url)
const renderedURL = renderURL({ responses })

const { flags } = getContext()
newReq.headers = compileHeaders(headers, body, responses as never)
// compile body needs to modify headers if necessary
const { headers: newHeaders, body: newBody } = compileBody(
Expand All @@ -97,10 +99,11 @@ export async function httpRequest({
}

// Do the request using compiled URL and compiled headers (if exists)
if (flags['native-fetch']) {
if (isEnableFetch) {
return await probeHttpFetch({
startTime,
flags,
isVerbose,
maxRedirects,
renderedURL,
requestParams: { ...newReq, headers: requestHeaders },
allowUnauthorized,
Expand All @@ -109,7 +112,7 @@ export async function httpRequest({

return await probeHttpAxios({
startTime,
flags,
maxRedirects,
renderedURL,
requestParams: { ...newReq, headers: requestHeaders },
allowUnauthorized,
Expand Down Expand Up @@ -228,15 +231,17 @@ function compileBody(

async function probeHttpFetch({
startTime,
flags,
renderedURL,
requestParams,
allowUnauthorized,
maxRedirects,
isVerbose,
}: {
startTime: number
flags: MonikaFlags
renderedURL: string
allowUnauthorized: boolean | undefined
maxRedirects: number
isVerbose?: boolean
requestParams: {
method: string | undefined
headers: Headers | undefined
Expand All @@ -245,13 +250,21 @@ async function probeHttpFetch({
ping: boolean | undefined
}
}): Promise<ProbeRequestResponse> {
if (flags.verbose) log.info(`Probing ${renderedURL} with Node.js fetch`)
if (isVerbose) log.info(`Probing ${renderedURL} with Node.js fetch`)
console.log(
'renderedURL =',
renderedURL,
'| requestParams =',
JSON.stringify(requestParams),
'| maxRedirects =',
maxRedirects
)
const response = await sendHttpRequestFetch({
...requestParams,
allowUnauthorizedSsl: allowUnauthorized,
keepalive: true,
url: renderedURL,
maxRedirects: flags['follow-redirects'],
maxRedirects,
body:
typeof requestParams.body === 'string'
? requestParams.body
Expand Down Expand Up @@ -286,15 +299,15 @@ async function probeHttpFetch({

async function probeHttpAxios({
startTime,
flags,
renderedURL,
requestParams,
allowUnauthorized,
maxRedirects,
requestParams,
}: {
startTime: number
flags: MonikaFlags
renderedURL: string
allowUnauthorized: boolean | undefined
maxRedirects: number
requestParams: {
method: string | undefined
headers: Headers | undefined
Expand All @@ -308,7 +321,7 @@ async function probeHttpAxios({
allowUnauthorizedSsl: allowUnauthorized,
keepalive: true,
url: renderedURL,
maxRedirects: flags['follow-redirects'],
maxRedirects,
body:
typeof requestParams.body === 'string'
? requestParams.body
Expand Down
Loading

0 comments on commit f05e7cb

Please sign in to comment.