This repository has been archived by the owner on Feb 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: limit concurrent HTTP requests in browser (#2304)
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
- Loading branch information
1 parent
3878f0f
commit cf38aea
Showing
12 changed files
with
88 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,22 @@ | ||
'use strict' | ||
|
||
const { URL } = require('iso-url') | ||
const fetch = require('../../runtime/fetch-nodejs') | ||
|
||
module.exports = (self) => { | ||
return async (url, options, callback) => { | ||
if (typeof options === 'function') { | ||
callback = options | ||
options = {} | ||
} | ||
|
||
let files | ||
|
||
try { | ||
const parsedUrl = new URL(url) | ||
const res = await fetch(url) | ||
|
||
if (!res.ok) { | ||
throw new Error('unexpected status code: ' + res.status) | ||
} | ||
|
||
// TODO: use res.body when supported | ||
const content = Buffer.from(await res.arrayBuffer()) | ||
const path = decodeURIComponent(parsedUrl.pathname.split('/').pop()) | ||
|
||
files = await self.add({ content, path }, options) | ||
} catch (err) { | ||
if (callback) { | ||
return callback(err) | ||
} | ||
throw err | ||
} | ||
const nodeify = require('promise-nodeify') | ||
const { default: ky } = require('ky-universal') | ||
|
||
module.exports = (ipfs) => { | ||
const addFromURL = async (url, opts = {}) => { | ||
const res = await ky.get(url) | ||
const path = decodeURIComponent(new URL(res.url).pathname.split('/').pop()) | ||
const content = Buffer.from(await res.arrayBuffer()) | ||
return ipfs.add({ content, path }, opts) | ||
} | ||
|
||
if (callback) { | ||
callback(null, files) | ||
return (name, opts = {}, cb) => { | ||
if (typeof opts === 'function') { | ||
cb = opts | ||
opts = {} | ||
} | ||
|
||
return files | ||
return nodeify(addFromURL(name, opts), cb) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,62 @@ | ||
/* global self */ | ||
/* eslint-env browser */ | ||
'use strict' | ||
|
||
module.exports = (domain, opts, callback) => { | ||
const TLRU = require('../../utils/tlru') | ||
const { default: PQueue } = require('p-queue') | ||
const { default: ky } = require('ky-universal') | ||
const nodeify = require('promise-nodeify') | ||
|
||
// 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 }) | ||
|
||
// Delegated HTTP resolver sending DNSLink queries to ipfs.io | ||
// TODO: replace hardcoded host with configurable DNS over HTTPS: https://github.com/ipfs/js-ipfs/issues/2212 | ||
const api = ky.create({ | ||
prefixUrl: 'https://ipfs.io/api/v0/', | ||
hooks: { | ||
afterResponse: [ | ||
async (input, options, response) => { | ||
const query = new URL(response.url).search.slice(1) | ||
const json = await response.json() | ||
cache.set(query, json, ttl) | ||
} | ||
] | ||
} | ||
}) | ||
|
||
const ipfsPath = (response) => { | ||
if (response.Path) return response.Path | ||
throw new Error(response.Message) | ||
} | ||
|
||
module.exports = (fqdn, opts = {}, cb) => { | ||
if (typeof opts === 'function') { | ||
callback = opts | ||
cb = opts | ||
opts = {} | ||
} | ||
const resolveDnslink = async (fqdn, opts = {}) => { | ||
const searchParams = new URLSearchParams(opts) | ||
searchParams.set('arg', fqdn) | ||
|
||
opts = opts || {} | ||
|
||
domain = encodeURIComponent(domain) | ||
let url = `https://ipfs.io/api/v0/dns?arg=${domain}` | ||
// try cache first | ||
const query = searchParams.toString() | ||
if (!opts.nocache && cache.has(query)) { | ||
const response = cache.get(query) | ||
return ipfsPath(response) | ||
} | ||
|
||
Object.keys(opts).forEach(prop => { | ||
url += `&${encodeURIComponent(prop)}=${encodeURIComponent(opts[prop])}` | ||
}) | ||
// fallback to delegated DNS resolver | ||
const response = await httpQueue.add(() => api.get('dns', { searchParams }).json()) | ||
return ipfsPath(response) | ||
} | ||
|
||
self.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)) | ||
} | ||
}) | ||
.catch((error) => { | ||
callback(error) | ||
}) | ||
return nodeify(resolveDnslink(fqdn, opts), cb) | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters