request() via profile 💥

This commit is contained in:
Jannis R 2019-10-31 18:48:11 +01:00
parent 4652c1694e
commit 6d5c6081ce
No known key found for this signature in database
GPG key ID: 0FE83946296A88A5
8 changed files with 67 additions and 82 deletions

View file

@ -21,13 +21,12 @@
There's opt-in support for throttling requests to the endpoint.
```js
const withThrottling = require('hafas-client/throttle')
const createClient = require('hafas-client')
const withThrottling = require('hafas-client/throttle')
const dbProfile = require('hafas-client/p/db')
// create a throttled HAFAS client with Deutsche Bahn profile
const createThrottledClient = withThrottling(createClient)
const client = createThrottledClient(dbProfile, 'my-awesome-program')
const client = createClient(withThrottling(dbProfile), 'my-awesome-program')
// Berlin Jungfernheide to München Hbf
client.journeys('8011167', '8000261', {results: 1})
@ -39,8 +38,8 @@ You can pass custom values for the nr of requests (`limit`) per interval into `w
```js
// 2 requests per second
const createThrottledClient = withThrottling(createClient, 2, 1000)
const client = createThrottledClient(dbProfile, 'my-awesome-program')
const throttledDbProfile = withThrottling(dbProfile, 2, 1000)
const client = createClient(throttledDbProfile, 'my-awesome-program')
```
## Retrying failed requests
@ -48,13 +47,12 @@ const client = createThrottledClient(dbProfile, 'my-awesome-program')
There's opt-in support for retrying failed requests to the endpoint.
```js
const withRetrying = require('hafas-client/retry')
const createClient = require('hafas-client')
const withRetrying = require('hafas-client/retry')
const dbProfile = require('hafas-client/p/db')
// create a client with Deutsche Bahn profile that will retry on HAFAS errors
const createRetryingClient = withRetrying(createClient)
const client = createRetryingClient(dbProfile, 'my-awesome-program')
const client = createClient(withRetrying(dbProfile), 'my-awesome-program')
// Berlin Jungfernheide to München Hbf
client.journeys('8011167', '8000261', {results: 1})
@ -66,12 +64,12 @@ You can pass custom options into `withRetrying`. They will be passed into [`retr
```js
// retry 2 times, after 10 seconds & 30 seconds
const createRetryingClient = withRetrying(createClient, {
const retryingDbProfile = withRetrying(dbProfile, {
retries: 2,
minTimeout: 10 * 1000,
factor: 3
})
const client = createRetryingClient(dbProfile, 'my-awesome-program')
const client = createClient(retryingDbProfile, 'my-awesome-program')
```
## Writing a profile

View file

@ -9,7 +9,6 @@ const pRetry = require('p-retry')
const defaultProfile = require('./lib/default-profile')
const createFormatProductsFilter = require('./format/products-filter')
const validateProfile = require('./lib/validate-profile')
const _request = require('./lib/request')
const isNonEmptyString = str => 'string' === typeof str && str.length > 0
@ -25,10 +24,6 @@ const validateLocation = (loc, name = 'location') => {
}
}
const defaults = {
request: _request
}
const createClient = (profile, userAgent, opt = {}) => {
profile = Object.assign({}, defaultProfile, profile)
if (!profile.formatProductsFilter) {
@ -40,10 +35,6 @@ const createClient = (profile, userAgent, opt = {}) => {
throw new TypeError('userAgent must be a string');
}
const {
request
} = Object.assign({}, defaults, opt)
const _stationBoard = (station, type, parse, opt = {}) => {
if (isObj(station)) station = profile.formatStation(station.id)
else if ('string' === typeof station) station = profile.formatStation(station)
@ -88,7 +79,7 @@ const createClient = (profile, userAgent, opt = {}) => {
if (profile.departuresGetPasslist) req.getPasslist = !!opt.stopovers
if (profile.departuresStbFltrEquiv) req.stbFltrEquiv = !opt.includeRelatedStations
return request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
meth: 'StationBoard',
req
})
@ -230,7 +221,7 @@ const createClient = (profile, userAgent, opt = {}) => {
if (profile.journeysNumF && opt.results !== null) query.numF = opt.results
if (profile.journeysOutFrwd) query.outFrwd = outFrwd
return request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
cfg: {polyEnc: 'GPA'},
meth: 'TripSearch',
req: profile.transformJourneysQuery({profile, opt}, query)
@ -277,7 +268,7 @@ const createClient = (profile, userAgent, opt = {}) => {
remarks: true // parse & expose hints & warnings?
}, opt)
return request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
meth: 'Reconstruction',
req: {
ctxRecon: refreshToken,
@ -311,7 +302,7 @@ const createClient = (profile, userAgent, opt = {}) => {
}, opt)
const f = profile.formatLocationFilter(opt.stops, opt.addresses, opt.poi)
return request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
cfg: {polyEnc: 'GPA'},
meth: 'LocMatch',
req: {input: {
@ -339,7 +330,7 @@ const createClient = (profile, userAgent, opt = {}) => {
opt = Object.assign({
linesOfStops: false // parse & expose lines at the stop/station?
}, opt)
return request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
meth: 'LocDetails',
req: {
locL: [stop]
@ -367,7 +358,7 @@ const createClient = (profile, userAgent, opt = {}) => {
linesOfStops: false // parse & expose lines at each stop/station?
}, opt)
return request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
cfg: {polyEnc: 'GPA'},
meth: 'LocGeoPos',
req: {
@ -405,7 +396,7 @@ const createClient = (profile, userAgent, opt = {}) => {
remarks: true // parse & expose hints & warnings?
}, opt)
return request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
cfg: {polyEnc: 'GPA'},
meth: 'JourneyDetails',
req: {
@ -454,7 +445,7 @@ const createClient = (profile, userAgent, opt = {}) => {
if (Number.isNaN(+opt.when)) throw new TypeError('opt.when is invalid')
const durationPerStep = opt.duration / Math.max(opt.frames, 1) * 1000
return request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
meth: 'JourneyGeoPos',
req: {
maxJny: opt.results,
@ -492,7 +483,7 @@ const createClient = (profile, userAgent, opt = {}) => {
if (Number.isNaN(+opt.when)) throw new TypeError('opt.when is invalid')
const refetch = () => {
return request({profile, opt}, userAgent, {
return profile.request({profile, opt}, userAgent, {
meth: 'LocGeoReach',
req: {
loc: profile.formatLocation(profile, address, 'address'),

View file

@ -1,5 +1,7 @@
'use strict'
const request = require('../lib/request')
const parseDateTime = require('../parse/date-time')
const parsePlatform = require('../parse/platform')
const parseProductsBitmask = require('../parse/products-bitmask')
@ -34,13 +36,13 @@ const filters = require('../format/filters')
const id = (ctx, x) => x
const defaultProfile = {
request,
transformReqBody: id,
transformReq: id,
salt: null,
addChecksum: false,
addMicMac: false,
transformReqBody: id,
transformReq: id,
transformJourneysQuery: id,
parseDateTime,

View file

@ -3,6 +3,8 @@
const types = {
locale: 'string',
timezone: 'string',
request: 'function',
transformReq: 'function',
transformReqBody: 'function',
transformJourneysQuery: 'function',

View file

@ -2,42 +2,36 @@
const retry = require('p-retry')
const _request = require('./lib/request')
const retryDefaults = {
retries: 3,
factor: 3,
minTimeout: 5 * 1000
}
const withRetrying = (createClient, retryOpts = {}) => {
const withRetrying = (profile, retryOpts = {}) => {
retryOpts = Object.assign({}, retryDefaults, retryOpts)
const {request} = profile
const createRetryingClient = (profile, userAgent, opt = {}) => {
const request = 'request' in opt ? opt.request : _request
const retryingRequest = (profile, userAgent, opt, data) => {
const attempt = () => {
return request(profile, userAgent, opt, data)
.catch((err) => {
if (err.isHafasError) throw err // continue
if (err.code === 'ENOTFOUND') { // abort
const abortErr = new retry.AbortError(err)
Object.assign(abortErr, err)
throw abortErr
}
throw err // continue
})
}
return retry(attempt, retryOpts)
const retryingRequest = (...args) => {
const attempt = () => {
return request(...args)
.catch((err) => {
if (err.isHafasError) throw err // continue
if (err.code === 'ENOTFOUND') { // abort
const abortErr = new retry.AbortError(err)
Object.assign(abortErr, err)
throw abortErr
}
throw err // continue
})
}
return createClient(profile, userAgent, {
...opt,
request: retryingRequest
})
return retry(attempt, retryOpts)
}
return {
...profile,
request: retryingRequest
}
return createRetryingClient
}
module.exports = withRetrying

View file

@ -2,8 +2,8 @@
const test = require('tape')
const withRetrying = require('../retry')
const createClient = require('..')
const withRetrying = require('../retry')
const vbbProfile = require('../p/vbb')
const userAgent = 'public-transport/hafas-client:test'
@ -12,7 +12,7 @@ const spichernstr = '900000042101'
test('withRetrying works', (t) => {
// for the first 3 calls, return different kinds of errors
let calls = 0
const failingRequest = (profile, userAgent, opt, data) => {
const failingRequest = async (ctx, userAgent, reqData) => {
calls++
if (calls === 1) {
const err = new Error('HAFAS error')
@ -25,18 +25,22 @@ test('withRetrying works', (t) => {
return Promise.reject(err)
}
if (calls < 4) return Promise.reject(new Error('generic error'))
return Promise.resolve([])
return {
res: [],
common: {}
}
}
const createRetryingClient = withRetrying(createClient, {
const profile = withRetrying({
...vbbProfile,
request: failingRequest
}, {
retries: 3,
minTimeout: 100,
factor: 2,
randomize: false
})
const client = createRetryingClient(vbbProfile, userAgent, {
request: failingRequest
})
const client = createClient(profile, userAgent)
t.plan(1 + 4)
client.departures(spichernstr, {duration: 1})

View file

@ -3,8 +3,8 @@
const tapePromise = require('tape-promise').default
const tape = require('tape')
const withThrottling = require('../throttle')
const createClient = require('..')
const withThrottling = require('../throttle')
const vbbProfile = require('../p/vbb')
const depsRes = require('./fixtures/vbb-departures.json')
@ -14,10 +14,8 @@ const spichernstr = '900000042101'
const test = tapePromise(tape)
test('withThrottling works', async (t) => {
const ctx = {profile: vbbProfile, opt: {}}
let calls = 0
const mockedRequest = async (ctx, _, reqData) => {
const mockedRequest = async (ctx, userAgent, reqData) => {
calls++
return {
res: depsRes,
@ -25,8 +23,11 @@ test('withThrottling works', async (t) => {
}
}
const createThrottledClient = withThrottling(createClient, 2, 1000)
const client = createThrottledClient(vbbProfile, ua, mockedRequest)
const profile = withThrottling({
...vbbProfile,
request: mockedRequest
}, 2, 1000)
const client = createClient(profile, ua)
t.plan(3)
for (let i = 0; i < 10; i++) {

View file

@ -2,20 +2,13 @@
const throttle = require('p-throttle')
const _request = require('./lib/request')
const withThrottling = (profile, limit = 5, interval = 1000) => {
const {request} = profile
const withThrottling = (createClient, limit = 5, interval = 1000) => {
const createThrottledClient = (profile, userAgent, opt = {}) => {
const request = 'request' in opt ? opt.request : _request
const throttledRequest = throttle(request, limit, interval)
return createClient(profile, userAgent, {
...opt,
request: throttledRequest
})
return {
...profile,
request: throttle(request, limit, interval)
}
return createThrottledClient
}
module.exports = withThrottling