Skip to content

Commit

Permalink
fix: native fetch implementation (#1256)
Browse files Browse the repository at this point in the history
  • Loading branch information
haricnugraha authored Mar 21, 2024
1 parent f6ecb27 commit e958048
Show file tree
Hide file tree
Showing 9 changed files with 1,021 additions and 1,036 deletions.
1,832 changes: 911 additions & 921 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
"@types/js-yaml": "^4.0.2",
"@types/mocha": "^5.2.7",
"@types/mongodb-uri": "^0.9.1",
"@types/node": "^18.19.3",
"@types/node": "^20.11.30",
"@types/node-cron": "^2.0.4",
"@types/sinon": "^10.0.2",
"@types/smtp-server": "^3.5.6",
Expand Down
3 changes: 1 addition & 2 deletions src/components/config/parse-insomnia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

import type { Config } from '../../interfaces/config'
import yml from 'js-yaml'
import type { Probe } from '../../interfaces/probe'
import { compile as compileTemplate } from 'handlebars'
import type { AxiosRequestHeaders, Method } from 'axios'
import Joi from 'joi'
Expand Down Expand Up @@ -144,7 +143,7 @@ function mapInsomniaToConfig(data: unknown): Config {
return { probes }
}

function mapInsomniaRequestToConfig(req: unknown): Probe {
function mapInsomniaRequestToConfig(req: unknown) {
const { value: res } = resourceValidator.validate(req, { allowUnknown: true })
// eslint-disable-next-line camelcase
const url = compileTemplate(res.url)({ base_url: baseUrl })
Expand Down
37 changes: 0 additions & 37 deletions src/components/logger/startup-message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,41 +427,4 @@ describe('Startup message', () => {
expect(logMessage).include('config file')
})
})

// delete tests on removing toggle
describe('Node fetch toggle', () => {
it('should show experimental fetch toggle', () => {
// act
logStartupMessage({
config: defaultConfig,
flags: {
config: ['./monika.yaml'],
verbose: false,
'native-fetch': true,
},
isFirstRun: false,
})

// assert
expect(logMessage).include('Using native Node.js fetch for HTTP client')
})

it('should hide experimental fetch toggle', () => {
// act
logStartupMessage({
config: defaultConfig,
flags: {
config: ['./monika.yaml'],
verbose: false,
'native-fetch': false,
},
isFirstRun: false,
})

// assert
expect(logMessage).not.include(
'Using native Node.js fetch for HTTP client'
)
})
})
})
4 changes: 0 additions & 4 deletions src/components/logger/startup-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ export function logStartupMessage({
}
}

if (flags['native-fetch']) {
log.info('Using native Node.js fetch for HTTP client')
}

const startupMessage = generateStartupMessage({
config,
flags,
Expand Down
6 changes: 1 addition & 5 deletions src/components/probe/prober/http/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@
**********************************************************************************/

import { expect } from '@oclif/test'
import { AxiosError } from 'axios'
import { HttpResponse, http } from 'msw'
import { setupServer } from 'msw/node'
import sinon from 'sinon'
import * as httpRequest from '../../../../utils/http'
import { doProbe } from '../..'
import { initializeProbeStates } from '../../../../utils/probe-state'
import type { Probe } from '../../../../interfaces/probe'
Expand Down Expand Up @@ -199,9 +197,7 @@ describe('HTTP Probe processing', () => {

it('should send incident notification if the request is failed', async () => {
// arrange
sinon.stub(httpRequest, 'sendHttpRequest').callsFake(async () => {
throw new AxiosError('ECONNABORTED', undefined, undefined, {})
})
server.use(http.get('https://example.com', () => HttpResponse.error()))
const probe = {
...probes[0],
id: '2md9a',
Expand Down
78 changes: 46 additions & 32 deletions src/components/probe/prober/http/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ 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 { type HeadersInit, errors as undiciErrors } from 'undici'
import Joi from 'joi'

// Register Handlebars helpers
Expand All @@ -69,19 +69,21 @@ export async function httpRequest({
// 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 newReq = { method, headers: new Headers(headers), timeout, body, ping }
const renderURL = Handlebars.compile(url)
const renderedURL = renderURL({ responses })

const { flags } = getContext()
newReq.headers = compileHeaders(headers, body, responses as never)
newReq.headers = new Headers(
compileHeaders(headers, body, responses as never)
)
// compile body needs to modify headers if necessary
const { headers: newHeaders, body: newBody } = compileBody(
newReq.headers,
body,
responses
)
newReq.headers = newHeaders
newReq.headers = new Headers(newHeaders)
newReq.body = newBody

const startTime = Date.now()
Expand Down Expand Up @@ -139,10 +141,10 @@ export async function httpRequest({
}

function compileHeaders(
headers: object | undefined,
headers: HeadersInit | undefined,
body: string | object,
responses: never
) {
): HeadersInit | undefined {
// return as-is if falsy
if (!headers) return headers
// Compile headers using handlebars to render URLs that uses previous responses data.
Expand Down Expand Up @@ -183,14 +185,16 @@ function compileHeaders(
return newHeaders
}

type CompiledBody = {
headers: HeadersInit | undefined
body: object | string
}

function compileBody(
headers: object | undefined,
headers: HeadersInit | undefined,
body: object | string,
responses: ProbeRequestResponse[]
): {
headers: object | undefined
body: object | string
} {
): CompiledBody {
// return as-is if falsy
if (!body) return { headers, body }
let newHeaders = headers
Expand Down Expand Up @@ -235,23 +239,24 @@ async function probeHttpFetch({
allowUnauthorized: boolean | undefined
requestParams: {
method: string | undefined
headers: object | undefined
headers: Headers | undefined
timeout: number
body: string | object
ping: boolean | undefined
}
}): Promise<ProbeRequestResponse> {
if (flags.verbose) log.info(`Probing ${renderedURL} with Node.js fetch`)

const { body, headers, method, timeout } = requestParams
const { content } = transformContentByType(body, headers?.get('content-type'))
const response = await sendHttpRequestFetch({
...requestParams,
allowUnauthorizedSsl: allowUnauthorized,
keepalive: true,
url: renderedURL,
body: content,
headers,
maxRedirects: flags['follow-redirects'],
body:
typeof requestParams.body === 'string'
? requestParams.body
: JSON.stringify(requestParams.body),
method,
timeout,
url: renderedURL,
})

const responseTime = Date.now() - startTime
Expand All @@ -263,11 +268,11 @@ async function probeHttpFetch({
}
}

const responseBody = response.headers
const responseBody = await (response.headers
.get('Content-Type')
?.includes('application/json')
? response.json()
: response.text()
: response.text())

return {
requestType: 'HTTP',
Expand All @@ -293,33 +298,34 @@ async function probeHttpAxios({
allowUnauthorized: boolean | undefined
requestParams: {
method: string | undefined
headers: object | undefined
headers: Headers | undefined
timeout: number
body: string | object
ping: boolean | undefined
}
}): Promise<ProbeRequestResponse> {
const { body, headers, method, timeout } = requestParams
const { content } = transformContentByType(body, headers?.get('content-type'))
const resp = await sendHttpRequest({
...requestParams,
allowUnauthorizedSsl: allowUnauthorized,
body: content,
headers,
keepalive: true,
url: renderedURL,
maxRedirects: flags['follow-redirects'],
body:
typeof requestParams.body === 'string'
? requestParams.body
: JSON.stringify(requestParams.body),
method,
timeout,
url: renderedURL,
})

const responseTime = Date.now() - startTime
const { data, headers, status } = resp
const { data, headers: requestHeaders, status } = resp

return {
requestType: 'HTTP',
data,
body: data,
status,
headers,
headers: requestHeaders,
responseTime,
result: probeRequestResult.success,
}
Expand All @@ -338,9 +344,13 @@ export function generateRequestChainingBody(

function transformContentByType(
content: object | string,
contentType?: string | number | boolean
contentType?: string | null | undefined
) {
switch (contentType) {
case 'application/json': {
return { content: JSON.stringify(content), contentType }
}

case 'application/x-www-form-urlencoded': {
return {
content: qs.stringify(content as never),
Expand All @@ -366,7 +376,11 @@ function transformContentByType(
}

default: {
return { content, contentType }
return {
content:
typeof content === 'object' ? JSON.stringify(content) : content,
contentType,
}
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/interfaces/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
* SOFTWARE. *
**********************************************************************************/

import { ProbeAlert } from './probe'
import type { HeadersInit } from 'undici'
import type { ProbeAlert } from './probe'

// RequestTypes are used to define the type of request that is being made.
export type RequestTypes =
Expand Down Expand Up @@ -63,7 +64,7 @@ export interface RequestConfig extends Omit<RequestInit, 'headers' | 'body'> {
body: object | string
timeout: number // request timeout
alerts?: ProbeAlert[]
headers?: object
headers?: HeadersInit
ping?: boolean // is this request for a ping?
allowUnauthorized?: boolean // ignore ssl cert?
}
Loading

0 comments on commit e958048

Please sign in to comment.