Skip to content

Commit 26abe7c

Browse files
authored
Fix: Monika sends null request body as string null (#1293)
* fix: request body send 'null' instead of null
1 parent 2663934 commit 26abe7c

File tree

6 files changed

+62
-37
lines changed

6 files changed

+62
-37
lines changed

src/components/config/__tests__/expected.textfile.yml

-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ probes:
55
- url: https://monika.hyperjump.tech
66
method: GET
77
timeout: 10000
8-
body: {}
98
followRedirects: 21
109
interval: 900
1110
alerts:
@@ -19,7 +18,6 @@ probes:
1918
- url: https://github.com
2019
method: GET
2120
timeout: 10000
22-
body: {}
2321
followRedirects: 21
2422
interval: 900
2523
alerts:

src/components/config/parser/text.ts

-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export const parseConfigFromText = (configString: string): Config => {
6767
url,
6868
method: 'GET',
6969
timeout: 10_000,
70-
body: {} as JSON,
7170
followRedirects: getContext().flags['follow-redirects'],
7271
},
7372
],

src/components/probe/prober/http/request.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,34 @@ describe('probingHTTP', () => {
537537
// assert
538538
expect(res.status).to.eq(302)
539539
})
540+
541+
it('should send no body', async () => {
542+
// arrange
543+
let body
544+
server.use(
545+
http.get('https://example.com', async ({ request }) => {
546+
body = request.body
547+
548+
return new HttpResponse(null, {
549+
status: 200,
550+
})
551+
})
552+
)
553+
const request = {
554+
url: 'https://example.com',
555+
body: null,
556+
} as RequestConfig
557+
558+
// act
559+
const res = await httpRequest({
560+
requestConfig: request,
561+
responses: [],
562+
})
563+
564+
// assert
565+
expect(res.status).to.eq(200)
566+
expect(body).to.eq(null)
567+
})
540568
})
541569

542570
describe('Unit test', () => {

src/components/probe/prober/http/request.ts

+32-31
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import FormData from 'form-data'
2727
import Joi from 'joi'
2828
// eslint-disable-next-line no-restricted-imports
2929
import * as qs from 'querystring'
30-
import { errors as undiciErrors } from 'undici'
30+
import { type BodyInit, errors as undiciErrors } from 'undici'
3131
import YAML from 'yaml'
3232
import {
3333
type ProbeRequestResponse,
@@ -73,17 +73,17 @@ export async function httpRequest({
7373
ping,
7474
allowUnauthorized,
7575
followRedirects,
76+
signal,
7677
} = requestConfig
77-
const newReq = { method, headers, timeout, body, ping }
78+
const newReq = { method, headers, timeout, body, ping, signal }
7879
const renderURL = Handlebars.compile(url)
7980
const renderedURL = renderURL({ responses })
80-
newReq.headers = compileHeaders(headers, body, responses as never)
8181
// compile body needs to modify headers if necessary
82-
const { headers: newHeaders, body: newBody } = compileBody(
83-
newReq.headers,
82+
const { headers: newHeaders, body: newBody } = compileBody({
83+
responses,
8484
body,
85-
responses
86-
)
85+
headers: compileHeaders({ headers, responses, body }),
86+
})
8787
newReq.headers = newHeaders
8888
newReq.body = newBody
8989

@@ -149,11 +149,13 @@ export async function httpRequest({
149149
}
150150
}
151151

152-
function compileHeaders(
153-
headers: object | undefined,
154-
body: string | object,
155-
responses: never
156-
) {
152+
type ChainingRequest = {
153+
responses: Array<ProbeRequestResponse>
154+
body?: BodyInit
155+
headers?: object
156+
}
157+
158+
function compileHeaders({ headers, responses, body }: ChainingRequest) {
157159
// return as-is if falsy
158160
if (!headers) return headers
159161
// Compile headers using handlebars to render URLs that uses previous responses data.
@@ -193,18 +195,18 @@ function compileHeaders(
193195
return newHeaders
194196
}
195197

196-
function compileBody(
197-
headers: object | undefined,
198-
body: object | string,
199-
responses: ProbeRequestResponse[]
200-
): {
201-
headers: object | undefined
202-
body: object | string
203-
} {
198+
function compileBody({
199+
responses,
200+
body,
201+
headers,
202+
}: ChainingRequest): Pick<ChainingRequest, 'body' | 'headers'> {
204203
// return as-is if falsy
205204
if (!body) return { headers, body }
206205
let newHeaders = headers
207-
let newBody = generateRequestChainingBody(body, responses)
206+
let newBody: BodyInit | undefined = generateRequestChainingBody(
207+
body,
208+
responses
209+
)
208210

209211
if (newHeaders) {
210212
const contentTypeKey = Object.keys(newHeaders || {}).find(
@@ -247,7 +249,7 @@ async function probeHttpFetch({
247249
method: string | undefined
248250
headers: Headers | undefined
249251
timeout: number
250-
body: string | object
252+
body?: BodyInit
251253
ping: boolean | undefined
252254
}
253255
}): Promise<ProbeRequestResponse> {
@@ -302,7 +304,7 @@ type ProbeHTTPAxiosParams = {
302304
method: string | undefined
303305
headers: Headers | undefined
304306
timeout: number
305-
body: string | object
307+
body?: BodyInit
306308
ping: boolean | undefined
307309
}
308310
}
@@ -320,10 +322,6 @@ async function probeHttpAxios({
320322
keepalive: true,
321323
url: renderedURL,
322324
maxRedirects,
323-
body:
324-
typeof requestParams.body === 'string'
325-
? requestParams.body
326-
: JSON.stringify(requestParams.body),
327325
})
328326

329327
const responseTime = Date.now() - startTime
@@ -343,7 +341,7 @@ async function probeHttpAxios({
343341
export function generateRequestChainingBody(
344342
body: object | string,
345343
responses: ProbeRequestResponse[]
346-
): object | string {
344+
): BodyInit {
347345
const isString = typeof body === 'string'
348346
const template = Handlebars.compile(isString ? body : JSON.stringify(body))
349347
const renderedBody = template({ responses })
@@ -352,9 +350,13 @@ export function generateRequestChainingBody(
352350
}
353351

354352
function transformContentByType(
355-
content: object | string,
353+
content?: BodyInit,
356354
contentType?: string | number | boolean
357355
) {
356+
if (!content) {
357+
return { content, contentType }
358+
}
359+
358360
switch (contentType) {
359361
case 'application/json': {
360362
return { content: JSON.stringify(content), contentType }
@@ -369,9 +371,8 @@ function transformContentByType(
369371

370372
case 'multipart/form-data': {
371373
const form = new FormData()
372-
373374
for (const contentKey of Object.keys(content)) {
374-
form.append(contentKey, (content as Record<string, never>)[contentKey])
375+
form.append(contentKey, (content as any)[contentKey])
375376
}
376377

377378
return { content: form, contentType: form.getHeaders()['content-type'] }

src/interfaces/request.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,10 @@ export interface ProbeRequestResponse<T = unknown> {
5656
}
5757

5858
// ProbeRequest is used to define the requests that is being made.
59-
export interface RequestConfig extends Omit<RequestInit, 'headers' | 'body'> {
59+
export interface RequestConfig extends Omit<RequestInit, 'headers'> {
6060
id?: string
6161
saveBody?: boolean // save response body to db?
6262
url: string
63-
body: object | string
6463
followRedirects: number
6564
timeout: number // request timeout
6665
alerts?: ProbeAlert[]

src/symon/index.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ describe('Symon initiate', () => {
206206
})
207207
),
208208
http.get('http://localhost:4000/api/v1/monika/1234/probes', () => {
209-
throw new Error('Failed')
209+
throw new Error('Failed to get probes from Symon')
210210
})
211211
)
212212

0 commit comments

Comments
 (0)