Skip to content

Commit 6c70ad6

Browse files
committed
chore: migrate from request to got + fetch #3
1 parent 8076ba2 commit 6c70ad6

10 files changed

Lines changed: 278 additions & 136 deletions

File tree

packages/krawler/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"feathers-hooks-common": "^3.0.0",
7878
"fs-blob-store": "^5.2.1",
7979
"fs-extra": "catalog:",
80+
"got": "^14.4.5",
8081
"helmet": "catalog:",
8182
"imapflow": "^1.0.162",
8283
"js-yaml": "^3.13.1",
@@ -88,13 +89,11 @@
8889
"moment": "catalog:",
8990
"mongodb": "catalog:",
9091
"mubsub-es": "^2.0.0",
91-
"node-fetch": "^3.2.6",
9292
"osmtogeojson": "^3.0.0-beta.3",
9393
"papaparse": "catalog:",
9494
"pg": "^8.7.3",
9595
"proj4": "catalog:",
9696
"reproject": "^1.2.1",
97-
"request": "^2.88.0",
9897
"s3-blob-store": "^2.1.0",
9998
"sift": "catalog:",
10099
"socket.io-client": "catalog:",

packages/krawler/src/healthcheck.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import request from 'request'
21
import path, { dirname } from 'path'
3-
import utils from 'util'
42

53
import fs from 'fs-extra'
64
import _ from 'lodash'
@@ -68,8 +66,8 @@ export async function publishToSlack (slackWebhook, data, compilers, posttext =
6866
const message = compilers.message(data)
6967
const link = compilers.link(data)
7068
const text = link ? `<${link}|${message}${posttext}>` : `${message}${posttext}`
71-
await utils.promisify(request.post)({
72-
url: slackWebhook,
69+
await fetch(slackWebhook, {
70+
method: 'POST',
7371
body: JSON.stringify({
7472
attachments: [
7573
{
@@ -106,10 +104,14 @@ export async function healthcheck (options) {
106104
if (options.debug) {
107105
logger.info(`Requesting healthcheck endpoint ${endpoint}`)
108106
}
109-
const response = await utils.promisify(request.get)(endpoint)
110-
const data = JSON.parse(response.body)
107+
const response = await fetch(endpoint)
108+
// krawler healthcheck endpoint returns either 200 or 500
109+
if (response.status !== 200 && response.status !== 500) {
110+
throw new Error('Healthcheck rejected with unexpected HTTP code ' + response.status)
111+
}
112+
const data = await response.json()
111113
if (options.debug) {
112-
logger.info(`Current healthcheck status from service ${response.statusCode}`)
114+
logger.info(`Current healthcheck status from service ${response.status}`)
113115
logger.info('Current healthcheck output read from service', data)
114116
logger.info('Previous healthcheck output read from log', previousHealthcheck)
115117
}

packages/krawler/src/hooks/hooks.auth.js

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
import _ from 'lodash'
2-
import utils from 'util'
3-
import request from 'request'
42
import makeDebug from 'debug'
53

64
const debug = makeDebug('krawler:hooks:auth')
75

6+
// Apply a tough-cookie-compatible CookieJar (if provided) to fetch headers and persist Set-Cookie back to it.
7+
// Feature-detects getCookieString/setCookie so non-jar truthy values (e.g. `jar: true`, legacy compat) are tolerated.
8+
async function fetchWithJar (url, init, jar) {
9+
const headers = { ...(init.headers || {}) }
10+
if (jar && typeof jar.getCookieString === 'function') {
11+
const cookieString = await jar.getCookieString(url)
12+
if (cookieString) headers.cookie = cookieString
13+
}
14+
const response = await fetch(url, { ...init, headers })
15+
if (jar && typeof jar.setCookie === 'function') {
16+
const setCookies = response.headers.getSetCookie ? response.headers.getSetCookie() : []
17+
for (const c of setCookies) {
18+
await jar.setCookie(c, url)
19+
}
20+
}
21+
return response
22+
}
23+
824
// Add headers for basic/proxy auth
925
export function basicAuth (options = {}) {
1026
return async function (hook) {
@@ -19,7 +35,10 @@ export function basicAuth (options = {}) {
1935
// Post auth information as form data ?
2036
if (form) {
2137
// Set as well if we use cookie to store the session
22-
await utils.promisify(request.post)({ url, form, jar: options.jar })
38+
await fetchWithJar(url, {
39+
method: 'POST',
40+
body: new URLSearchParams(form)
41+
}, options.jar)
2342
_.set(requestOptions, 'jar', options.jar)
2443
} else { // Default method is to directly set basic auth as header
2544
if (!requestOptions.headers) requestOptions.headers = {}
@@ -49,18 +68,21 @@ export function OAuth (options = {}) {
4968
let response
5069
if (!requestOptions.headers) requestOptions.headers = {}
5170
if (method === 'client_secret_basic') {
52-
response = await utils.promisify(request.post)({
53-
url,
71+
response = await fetch(url, {
72+
method: 'POST',
5473
headers: { Authorization: 'Basic ' + Buffer.from(client_id + ':' + client_secret).toString('base64') },
5574
body: JSON.stringify(_.omit(oauth, ['client_id', 'client_secret', 'method', 'url']))
5675
})
5776
} else if (method === 'client_secret_post') {
58-
response = await utils.promisify(request.post)({
59-
url,
77+
response = await fetch(url, {
78+
method: 'POST',
6079
body: JSON.stringify(_.omit(oauth, ['method', 'url']))
6180
})
6281
}
63-
const { access_token, token_type } = JSON.parse(response.body)
82+
if (!response.ok) {
83+
throw new Error('OAuth rejected with HTTP code ' + response.status)
84+
}
85+
const { access_token, token_type } = await response.json()
6486
// Defaults to Bearer Auth
6587
const type = options.type || 'Authorization'
6688
requestOptions.headers[type] = `${token_type} ${access_token}`

packages/krawler/src/hooks/hooks.feathers.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import _ from 'lodash'
22
import feathers from '@feathersjs/client'
33
import io from 'socket.io-client'
4-
import fetch from 'node-fetch'
54
import makeDebug from 'debug'
65
// import { getItems } from 'feathers-hooks-common'
76
import {
Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import _ from 'lodash'
2-
import request from 'request'
32
import xml2js from 'xml2js'
43
import makeDebug from 'debug'
54

@@ -9,38 +8,29 @@ const debug = makeDebug('krawler:hooks:ogc')
98
export function getCapabilities (options = {}) {
109
return async function (hook) {
1110
const queryParameters = _.merge({ request: 'GetCapabilities' }, _.omit(options, ['url', 'headers']))
12-
const requestParameters = {
13-
method: 'GET',
14-
url: options.url,
15-
headers: options.headers,
16-
qs: queryParameters,
17-
qsStringifyOptions: { arrayFormat: 'repeat' }
18-
}
1911

20-
debug('Requesting ' + options.url + ' with following parameters', queryParameters)
21-
return new Promise((resolve, reject) => {
22-
const onParsed = (err, result) => {
23-
if (err) {
24-
reject(err instanceof Error ? err : new Error(String(err)))
25-
return
26-
}
27-
// feed the hook with the parsed result
28-
_.set(hook, options.dataPath || 'result.data', result)
29-
resolve(hook)
12+
const url = new URL(options.url)
13+
Object.keys(queryParameters).forEach((param) => {
14+
const value = queryParameters[param]
15+
if (Array.isArray(value)) {
16+
value.forEach((val) => url.searchParams.append(param, val))
17+
} else {
18+
url.searchParams.append(param, value)
3019
}
31-
const onResponse = (error, response, body) => {
32-
if (error) {
33-
reject(error instanceof Error ? error : new Error(String(error)))
34-
return
35-
}
36-
if (response.statusCode !== 200) {
37-
reject(new Error('Request rejected with HTTP code ' + response.statusCode))
38-
return
39-
}
40-
const parser = new xml2js.Parser({ explicitArray: false })
41-
parser.parseString(body, onParsed)
42-
}
43-
request(requestParameters, onResponse)
4420
})
21+
22+
debug('Requesting ' + options.url + ' with following parameters', queryParameters)
23+
const response = await fetch(url, {
24+
method: 'GET',
25+
headers: options.headers
26+
})
27+
if (!response.ok) {
28+
throw new Error('Request rejected with HTTP code ' + response.status)
29+
}
30+
const body = await response.text()
31+
const parser = new xml2js.Parser({ explicitArray: false })
32+
const result = await parser.parseStringPromise(body)
33+
_.set(hook, options.dataPath || 'result.data', result)
34+
return hook
4535
}
4636
}

packages/krawler/src/services/tasks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class TasksService extends Service {
6363
return new Promise((resolve, reject) => {
6464
let statusCode
6565
taskStream
66-
.on('timeout', reject)
66+
.on('error', reject)
6767
.on('response', (response) => {
6868
statusCode = response.statusCode
6969
if (response.statusCode > 299) {
Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,56 @@
11
import _ from 'lodash'
2-
import request from 'request'
2+
import got from 'got'
33
import makeDebug from 'debug'
44

55
const debug = makeDebug('krawler:tasks')
66

7+
// Build URLSearchParams with array values expanded as repeated keys
8+
// (replicates request's qsStringifyOptions: { arrayFormat: 'repeat' })
9+
function buildSearchParams (queryParameters) {
10+
const searchParams = new URLSearchParams()
11+
for (const key of Object.keys(queryParameters)) {
12+
const value = queryParameters[key]
13+
if (value === undefined || value === null) continue
14+
if (Array.isArray(value)) {
15+
value.forEach((v) => searchParams.append(key, v))
16+
} else {
17+
searchParams.append(key, value)
18+
}
19+
}
20+
return searchParams
21+
}
22+
723
// Build the request parameters to download data from input data source
824
export function getRequestParameters (options) {
925
const queryParameters = _.merge({}, _.omit(options, ['url', 'method', 'body', 'headers', 'timeout', 'auth', 'oauth', 'jar']))
1026
const requestParameters = {
11-
url: options.url,
12-
body: options.body,
27+
method: options.method || 'GET',
1328
headers: options.headers,
14-
timeout: options.timeout,
15-
jar: options.jar,
16-
qs: queryParameters,
17-
qsStringifyOptions: { arrayFormat: 'repeat' }
29+
body: options.body,
30+
searchParams: buildSearchParams(queryParameters),
31+
cookieJar: options.jar,
32+
// Do not throw on non-2xx so the downstream consumer can read statusCode from the 'response' event
33+
throwHttpErrors: false
34+
}
35+
if (options.timeout) {
36+
requestParameters.timeout = { request: options.timeout }
1837
}
19-
// Setup request with URL & params
2038
debug('Requesting with the following parameters', requestParameters)
2139
return requestParameters
2240
}
2341

42+
const METHODS_WITH_BODY = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])
43+
2444
// Create the request stream for a task
2545
export function createRequestStream (options) {
26-
const method = options.method || 'GET'
27-
return request({ method, ...getRequestParameters(options) })
46+
const params = getRequestParameters(options)
47+
const stream = got.stream(options.url, params)
48+
// got.stream returns a Duplex; for body-bearing methods without an explicit body,
49+
// it would hang waiting for written data. Signal end-of-input explicitly.
50+
if (METHODS_WITH_BODY.has(params.method) && params.body === undefined) {
51+
stream.end()
52+
}
53+
return stream
2854
}
2955

3056
export default createRequestStream

packages/krawler/test/cli.test.js

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import path, { dirname } from 'path'
22
import _ from 'lodash'
33
import moment from 'moment'
4-
import request from 'request'
54
import utils from 'util'
65
import fs from 'fs-extra'
76
import mongodb from 'mongodb'
@@ -66,9 +65,10 @@ describe('krawler:cli', () => {
6665
fs.removeSync(path.join(outputPath, 'RJTT-30-18000-2-1.tif.csv'))
6766
appServer = await cli(jobfile, { mode: 'setup', api: true, apiPrefix: '/api', port: 3030, messageTemplate: process.env.MESSAGE_TEMPLATE, debug: true, slackWebhook: process.env.SLACK_WEBHOOK_URL })
6867
// Submit a job to be run
69-
const response = await utils.promisify(request.post)({
70-
url: 'http://localhost:3030/api/jobs',
71-
body: {
68+
const response = await fetch('http://localhost:3030/api/jobs', {
69+
method: 'POST',
70+
headers: { 'content-type': 'application/json' },
71+
body: JSON.stringify({
7272
id: 'job',
7373
store: 'job-store',
7474
tasks: [{
@@ -78,10 +78,9 @@ describe('krawler:cli', () => {
7878
store: 'task-store'
7979
}
8080
}]
81-
},
82-
json: true
81+
})
8382
})
84-
const tasks = response.body
83+
const tasks = await response.json()
8584
await appServer.close()
8685
expect(tasks.length).toBe(1)
8786
// Check intermediate products have been erased and final product are here
@@ -134,10 +133,9 @@ describe('krawler:cli', () => {
134133
// Only run as we already setup the app
135134
await cli(jobfile, { mode: 'runJob', port: 3030, cron: '*/10 * * * * *', run: true, messageTemplate: process.env.MESSAGE_TEMPLATE, debug: false, slackWebhook: process.env.SLACK_WEBHOOK_URL })
136135
expect(runCount).toBe(1) // First run
137-
const response = await utils.promisify(request.get)('http://localhost:3030/healthcheck')
138-
// console.log(response.body)
139-
expect(response.statusCode).toBe(200)
140-
const healthcheck = JSON.parse(response.body)
136+
const response = await fetch('http://localhost:3030/healthcheck')
137+
expect(response.status).toBe(200)
138+
const healthcheck = await response.json()
141139
// console.log(healthcheck)
142140
const { error } = await runCommand('node ' + path.join(__dirname, '..', 'healthcheck.js'))
143141
expect(error).toBeNull()
@@ -162,10 +160,9 @@ describe('krawler:cli', () => {
162160
await utils.promisify(setTimeout)((1 + remainingSecondsForNextRun) * 1000)
163161
try {
164162
expect(runCount).toBeGreaterThanOrEqual(2) // 2 runs
165-
const response = await utils.promisify(request.get)('http://localhost:3030/healthcheck')
166-
// console.log(response.body)
167-
expect(response.statusCode).toBe(500)
168-
const healthcheck = JSON.parse(response.body)
163+
const response = await fetch('http://localhost:3030/healthcheck')
164+
expect(response.status).toBe(500)
165+
const healthcheck = await response.json()
169166
// console.log(healthcheck)
170167
const { error } = await runCommand('node ' + path.join(__dirname, '..', 'healthcheck.js'))
171168
expect(error).toBeTruthy()

packages/krawler/test/jobs.cron.test.js

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import path, { dirname } from 'path'
2-
import request from 'request'
32
import utils from 'util'
43
import moment from 'moment'
54
import { cli } from '../src/index.js'
@@ -56,10 +55,9 @@ describe('krawler:jobs:cron', () => {
5655
await utils.promisify(setTimeout)((6 + remainingSecondsForNextRun) * 1000)
5756
// Check for error with healthcheck
5857
{
59-
const response = await utils.promisify(request.get)('http://localhost:3030/healthcheck')
60-
const healthcheck = JSON.parse(response.body)
61-
// console.log(healthcheck)
62-
expect(response.statusCode).toBe(500)
58+
const response = await fetch('http://localhost:3030/healthcheck')
59+
const healthcheck = await response.json()
60+
expect(response.status).toBe(500)
6361
expect(healthcheck.isRunning).toBe(true)
6462
expect(healthcheck.duration).toBeUndefined()
6563
expect(healthcheck.nbSkippedJobs).toBeGreaterThanOrEqual(1)
@@ -73,10 +71,9 @@ describe('krawler:jobs:cron', () => {
7371
await utils.promisify(setTimeout)(5000)
7472
// Now it should have finished
7573
{
76-
const response = await utils.promisify(request.get)('http://localhost:3030/healthcheck')
77-
const healthcheck = JSON.parse(response.body)
78-
// console.log(healthcheck)
79-
expect(response.statusCode).toBe(200)
74+
const response = await fetch('http://localhost:3030/healthcheck')
75+
const healthcheck = await response.json()
76+
expect(response.status).toBe(200)
8077
expect(healthcheck.isRunning).toBe(false)
8178
expect(healthcheck.duration).toBeTruthy()
8279
expect(healthcheck.nbSkippedJobs).toBe(0)

0 commit comments

Comments
 (0)