lib/request: use async/await, simplify error handling

This commit is contained in:
Jannis R 2022-05-03 17:51:52 +02:00
parent b030eec1f5
commit 7765f9d7a1
No known key found for this signature in database
GPG key ID: 0FE83946296A88A5
3 changed files with 59 additions and 66 deletions

View file

@ -207,23 +207,10 @@ const byErrorCode = Object.assign(Object.create(null), {
} }
}) })
const addErrorInfo = (err, errorCode, errorText, responseId) => {
if (byErrorCode[errorCode]) {
Object.assign(err, byErrorCode[errorCode])
if (errorCode) err.hafasErrorCode = errorCode
if (errorText) err.hafasErrorMessage = errorText
} else {
err.code = errorCode || null
err.message = errorText || errorCode || null
err.responseId = responseId || null
}
}
module.exports = { module.exports = {
ACCESS_DENIED, ACCESS_DENIED,
INVALID_REQUEST, INVALID_REQUEST,
NOT_FOUND, NOT_FOUND,
SERVER_ERROR, SERVER_ERROR,
byErrorCode, byErrorCode,
addErrorInfo,
} }

View file

@ -1,7 +1,5 @@
'use strict' 'use strict'
const DEV = process.env.NODE_ENV === 'dev'
const ProxyAgent = require('https-proxy-agent') const ProxyAgent = require('https-proxy-agent')
const {isIP} = require('net') const {isIP} = require('net')
const {Agent: HttpsAgent} = require('https') const {Agent: HttpsAgent} = require('https')
@ -9,12 +7,11 @@ const roundRobin = require('@derhuerst/round-robin-scheduler')
const {randomBytes} = require('crypto') const {randomBytes} = require('crypto')
const createHash = require('create-hash') const createHash = require('create-hash')
const pick = require('lodash/pick') const pick = require('lodash/pick')
const captureStackTrace = DEV ? require('capture-stack-trace') : () => {}
const {stringify} = require('qs') const {stringify} = require('qs')
const Promise = require('pinkie-promise') const Promise = require('pinkie-promise')
const {Request, fetch} = require('fetch-ponyfill')({Promise}) const {Request, fetch} = require('fetch-ponyfill')({Promise})
const {parse: parseContentType} = require('content-type') const {parse: parseContentType} = require('content-type')
const {addErrorInfo} = require('./errors') const {byErrorCode} = require('./errors')
const proxyAddress = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || null const proxyAddress = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || null
const localAddresses = process.env.LOCAL_ADDRESS || null const localAddresses = process.env.LOCAL_ADDRESS || null
@ -66,16 +63,16 @@ const randomizeUserAgent = (userAgent) => {
const md5 = input => createHash('md5').update(input).digest() const md5 = input => createHash('md5').update(input).digest()
const request = (ctx, userAgent, reqData) => { const request = async (ctx, userAgent, reqData) => {
const {profile, opt} = ctx const {profile, opt} = ctx
const body = profile.transformReqBody(ctx, { const rawReqBody = profile.transformReqBody(ctx, {
// todo: is it `eng` actually? // todo: is it `eng` actually?
// RSAG has `deu` instead of `de` // RSAG has `deu` instead of `de`
lang: opt.language || profile.defaultLanguage || 'en', lang: opt.language || profile.defaultLanguage || 'en',
svcReqL: [reqData] svcReqL: [reqData]
}) })
Object.assign(body, pick(profile, [ Object.assign(rawReqBody, pick(profile, [
'client', // client identification 'client', // client identification
'ext', // ? 'ext', // ?
'ver', // HAFAS protocol version 'ver', // HAFAS protocol version
@ -86,7 +83,7 @@ const request = (ctx, userAgent, reqData) => {
agent: getAgent(), agent: getAgent(),
method: 'post', method: 'post',
// todo: CORS? referrer policy? // todo: CORS? referrer policy?
body: JSON.stringify(body), body: JSON.stringify(rawReqBody),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept-Encoding': 'gzip, br, deflate', 'Accept-Encoding': 'gzip, br, deflate',
@ -129,56 +126,66 @@ const request = (ctx, userAgent, reqData) => {
const fetchReq = new Request(url, req) const fetchReq = new Request(url, req)
profile.logRequest(ctx, fetchReq, reqId) profile.logRequest(ctx, fetchReq, reqId)
// Async stack traces are not supported everywhere yet, so we create our own. const res = await fetch(url, req)
const err = new Error()
err.isHafasError = true // todo: rename to `isHafasClientError`
err.request = req.body // todo: commit as bugfix
err.url = url
captureStackTrace(err)
return fetch(fetchReq) // Async stack traces are not supported everywhere yet, so we create our own.
.then((res) => { const errProps = {
err.statusCode = res.status isHafasError: true, // todo: rename to `isHafasClientError`
if (!res.ok) { statusCode: res.status,
err.message = res.statusText request: req.body, // todo [breaking]: change to fetchReq
url: url,
}
if (!res.ok) {
const err = new Error(res.statusText)
Object.assign(err, errProps)
throw err
}
let cType = res.headers.get('content-type')
if (cType) {
const {type} = parseContentType(cType)
if (type !== 'application/json') {
const err = new Error('invalid response content-type: ' + cType)
err.response = res
throw err throw err
} }
}
let cType = res.headers.get('content-type') const body = await res.text()
if (cType) { profile.logResponse(ctx, res, body, reqId)
const {type} = parseContentType(cType)
if (type !== 'application/json') { const b = JSON.parse(body)
const err = new Error('invalid response content-type: ' + cType) if (b.err) errProps.hafasErrorCode = b.err
err.response = res if (b.errTxt) errProps.hafasErrorMessage = b.errTxt
throw err if (b.id) errProps.responseId = b.id
} if (b.err && b.err !== 'OK') {
const err = new Error(b.errTxt || b.err)
Object.assign(err, errProps)
if (b.err in byErrorCode) {
Object.assign(err, byErrorCode[b.err])
} }
throw err
}
if (!b.svcResL || !b.svcResL[0]) {
const err = new Error('invalid/unsupported response structure')
Object.assign(err, errProps)
throw err
}
if (b.svcResL[0].err !== 'OK') {
const err = new Error(b.svcResL[0].errTxt || b.svcResL[0].err)
Object.assign(err, errProps)
if (b.svcResL[0].err in byErrorCode) {
Object.assign(err, byErrorCode[b.svcResL[0].err])
}
throw err
}
return res.text() const svcRes = b.svcResL[0].res
.then((body) => { return {
profile.logResponse(ctx, res, body, reqId) res: svcRes,
const b = JSON.parse(body) common: profile.parseCommon({...ctx, res: svcRes}),
}
if (b.err && b.err !== 'OK') {
addErrorInfo(err, b.err, b.errTxt, b.id)
throw err
}
if (!b.svcResL || !b.svcResL[0]) {
err.message = 'invalid response'
throw err
}
if (b.svcResL[0].err !== 'OK') {
addErrorInfo(err, b.svcResL[0].err, b.svcResL[0].errTxt, b.id)
throw err
}
const svcRes = b.svcResL[0].res
return {
res: svcRes,
common: profile.parseCommon({...ctx, res: svcRes}),
}
})
})
} }
module.exports = request module.exports = request

View file

@ -46,7 +46,6 @@
"dependencies": { "dependencies": {
"@derhuerst/br2nl": "^1.0.0", "@derhuerst/br2nl": "^1.0.0",
"@derhuerst/round-robin-scheduler": "^1.0.4", "@derhuerst/round-robin-scheduler": "^1.0.4",
"capture-stack-trace": "^1.0.0",
"content-type": "^1.0.4", "content-type": "^1.0.4",
"create-hash": "^1.2.0", "create-hash": "^1.2.0",
"fetch-ponyfill": "^7.0.0", "fetch-ponyfill": "^7.0.0",