mirror of
https://github.com/public-transport/db-vendo-client.git
synced 2025-02-22 22:59:35 +02:00
request() via profile 💥
This commit is contained in:
parent
4652c1694e
commit
6d5c6081ce
8 changed files with 67 additions and 82 deletions
|
@ -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
|
||||
|
|
27
index.js
27
index.js
|
@ -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'),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
const types = {
|
||||
locale: 'string',
|
||||
timezone: 'string',
|
||||
|
||||
request: 'function',
|
||||
transformReq: 'function',
|
||||
transformReqBody: 'function',
|
||||
transformJourneysQuery: 'function',
|
||||
|
|
46
retry.js
46
retry.js
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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++) {
|
||||
|
|
17
throttle.js
17
throttle.js
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue