Skip to content

Commit

Permalink
fix: limit concurrent HTTP requests in browser
Browse files Browse the repository at this point in the history
Adds limit of concurrent HTTP requests sent to remote API
by dns and preload calls when running in web browser contexts.

Browsers limit connections per host (~6). This change mitigates the
problem of expensive and long running calls of one type exhausting
connection pool for other uses.

It additionally limits the number of DNS lookup calls by introducing
time-bound cache with eviction rules following what browser already do.

This is similar to:
libp2p/js-libp2p-delegated-content-routing#12

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>
  • Loading branch information
lidel committed Sep 5, 2019
1 parent 039675e commit 5c5f891
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 11 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
"multihashes": "~0.4.14",
"multihashing-async": "~0.6.0",
"node-fetch": "^2.3.0",
"p-queue": "^6.1.0",
"peer-book": "~0.9.0",
"peer-id": "~0.12.3",
"peer-info": "~0.15.0",
Expand Down
44 changes: 34 additions & 10 deletions src/core/runtime/dns-browser.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,57 @@
/* global self */
/* eslint-env browser */
'use strict'

const TLRU = require('../../utils/tlru')
const { default: PQueue } = require('p-queue')

// Avoid sending multiple queries for the same hostname by caching results
const cache = new TLRU(1000)
// TODO: /api/v0/dns does not return TTL yet: https://github.com/ipfs/go-ipfs/issues/5884
// However we know browsers themselves cache DNS records for at least 1 minute,
// which acts a provisional default ttl: https://stackoverflow.com/a/36917902/11518426
const ttl = 60 * 1000

// browsers limit concurrent connections per host,
// we don't want preload calls to exhaust the limit (~6)
const _httpQueue = new PQueue({ concurrency: 4 })

function unpackResponse (domain, response, callback) {
if (response.Path) {
return callback(null, response.Path)
} else {
const err = new Error(response.Message)
return callback(err)
}
}

module.exports = (domain, opts, callback) => {
if (typeof opts === 'function') {
callback = opts
opts = {}
}

opts = opts || {}

domain = encodeURIComponent(domain)
let url = `https://ipfs.io/api/v0/dns?arg=${domain}`

if (cache.has(domain)) {
const response = cache.get(domain)
return unpackResponse(domain, response, callback)
}

let url = `https://ipfs.io/api/v0/dns?arg=${domain}`
Object.keys(opts).forEach(prop => {
url += `&${encodeURIComponent(prop)}=${encodeURIComponent(opts[prop])}`
})

self.fetch(url, { mode: 'cors' })
_httpQueue.add(() => fetch(url, { mode: 'cors' })
.then((response) => {
return response.json()
})
.then((response) => {
if (response.Path) {
return callback(null, response.Path)
} else {
return callback(new Error(response.Message))
}
cache.set(domain, response, ttl)
return unpackResponse(domain, response, callback)
})
.catch((error) => {
callback(error)
})
)
}
8 changes: 7 additions & 1 deletion src/core/runtime/preload-browser.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
/* eslint-env browser */
'use strict'

const { default: PQueue } = require('p-queue')
const debug = require('debug')

const log = debug('ipfs:preload')
log.error = debug('ipfs:preload:error')

// browsers limit concurrent connections per host,
// we don't want preload calls to exhaust the limit (~6)
const _httpQueue = new PQueue({ concurrency: 4 })

module.exports = function preload (url, callback) {
log(url)

const controller = new AbortController()
const signal = controller.signal

fetch(url, { signal })
_httpQueue.add(() => fetch(url, { signal })
.then(res => {
if (!res.ok) {
log.error('failed to preload', url, res.status, res.statusText)
Expand All @@ -22,6 +27,7 @@ module.exports = function preload (url, callback) {
})
.then(() => callback())
.catch(callback)
).catch(callback)

return {
cancel: () => controller.abort()
Expand Down

0 comments on commit 5c5f891

Please sign in to comment.