Skip to content

Commit b9ba569

Browse files
authored
Merge pull request #18 from soyuka/fix-2
Fix #2
2 parents 14d34cb + d2a924e commit b9ba569

File tree

6 files changed

+124
-8
lines changed

6 files changed

+124
-8
lines changed

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ npm install random-access-http
1010

1111
## Why?
1212

13-
Peers in a distributed system tend to come and go over a short period of time in many common p2p scenarios, especially when you are giving away a file without incentivizing the swarm to seed the file for a long time. There are also an abundance of free cloud hosts that let you host large files over http.
13+
Peers in a distributed system tend to come and go over a short period of time in many common p2p scenarios, especially when you are giving away a file without incentivizing the swarm to seed the file for a long time. There are also an abundance of free cloud hosts that let you host large files over http.
1414

1515
This module provides you random access to a file hosted over http so that it can be used by a client in a distributed system (such as hypercore or hyperdrive) to acquire parts of the file for itself and the other peers in the swarm.
1616

@@ -30,13 +30,13 @@ file.read(5, 10, function(err, buffer) {
3030
})
3131
```
3232

33-
file will use a keepalive agent to reduce the number http requests needed for the session. When you are done you should call `file.close()` to destroy the agent.
33+
file will use a keepalive agent to reduce the number http requests needed for the session. When you are done you should call `file.close()` to destroy the agent.
3434

3535
## API
3636

3737
#### `var file = randomAccessHTTP(url, [options])`
3838

39-
Create a new 'file' that reads from the provided `url`. The `url` can be either `http`, `https` or a relative path if url is set in options.
39+
Create a new 'file' that reads from the provided `url`. The `url` can be either `http`, `https` or a relative path if url is set in options.
4040

4141
Options include:
4242
```js
@@ -46,17 +46,20 @@ Options include:
4646
timeout: number, // Optional. Default: 60000
4747
maxRedirects: number, // Optional. Default: 10
4848
maxContentLength: number, // Optional. Default: 50MB
49+
strict: true, // When false, will accept non-ranged response (it will slice the response to the requested offset/length)
4950
}
5051
```
5152

5253
#### `file.write(offset, buffer, [callback])`
5354

54-
**Not implemented!** Please let us know if you have opinions on how to implement this.
55+
**Not implemented!** Please let us know if you have opinions on how to implement this.
5556
This will silently fail with no data being writen.
5657

5758
#### `file.read(offset, length, callback)`
5859

59-
Read a buffer at a specific offset. Callback is called with the buffer read. Currently this will fail if the server returns byte ranges different than what is requested. PRs welcome to expand the flexibility of this method to allow for servers that return fat ranges.
60+
Read a buffer at a specific offset. Callback is called with the buffer read.
61+
By default, this will fail if the server returns byte ranges different than what is requested.
62+
If you want to support uncooperative static file servers (that doesn't use ranges), pass the `strict` with a falsy value.
6063

6164
#### `file.close([callback])`
6265

index.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ var randomAccessHttp = function (filename, options) {
2121
throw new Error('Expect first argument to be a valid URL or a relative path, with url set in options')
2222
}
2323

24+
// If this is falsy, raHttp will read non-ranged data
25+
var strict = true
2426
var axiosConfig = Object.assign({}, defaultOptions)
2527
if (isNode) {
2628
var http = require('http')
@@ -34,6 +36,7 @@ var randomAccessHttp = function (filename, options) {
3436
if (options.timeout) axiosConfig.timeout = options.timeout
3537
if (options.maxRedirects) axiosConfig.maxRedirects = options.maxRedirects
3638
if (options.maxContentLength) axiosConfig.maxContentLength = options.maxContentLength
39+
if (options.strict !== undefined) strict = options.strict
3740
}
3841
var _axios = axios.create(axiosConfig)
3942
var file = filename
@@ -51,7 +54,11 @@ var randomAccessHttp = function (filename, options) {
5154
if (response.headers['content-length']) this.length = response.headers['content-length']
5255
return req.callback(null)
5356
}
54-
return req.callback(new Error('Accept-Ranges does not include "bytes"'))
57+
58+
if (strict) return req.callback(new Error('Accept-Ranges does not include "bytes"'))
59+
60+
logger.warn('Accept-Ranges does not include "bytes" or may not be supported.')
61+
return req.callback(null)
5562
})
5663
.catch((err) => {
5764
if (verbose) logger.log('Error opening', file, '-', err)
@@ -66,7 +73,13 @@ var randomAccessHttp = function (filename, options) {
6673
if (verbose) logger.log('Trying to read', file, headers.Range)
6774
_axios.get(file, { headers: headers })
6875
.then((response) => {
69-
if (!response.headers['content-range']) throw new Error('Server did not return a byte range')
76+
if (!response.headers['content-range']) {
77+
if (strict) throw new Error('No content range. Use the "strict" option to bypass.')
78+
if (req.offset + req.size > response.data.length) throw new Error('Could not satisfy length')
79+
logger.warn('Reading data without content-range.')
80+
return req.callback(null, Buffer.from(response.data.slice(req.offset, req.offset + req.size)))
81+
}
82+
7083
if (response.status !== 206) throw new Error('Bad response: ' + response.status)
7184
var expectedRange = `bytes ${range}/${this.length}`
7285
if (response.headers['content-range'] !== expectedRange) throw new Error('Server returned unexpected range: ' + response.headers['content-range'])

lib/logger.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// This is simply for testing
22
module.exports = {
3-
log: console.log
3+
log: console.log,
4+
warn: console.warn
45
}

package-lock.json

Lines changed: 73 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"nyc": "^11.4.1",
3737
"proxyquire": "^1.8.0",
3838
"sinon": "^4.2.2",
39+
"st": "^1.2.2",
3940
"standard": "^10.0.3",
4041
"stoppable": "^1.0.5",
4142
"tap-spec": "^4.1.1",

tests/random-access-http.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ var proxyquire = require('proxyquire').noPreserveCache()
33
var sinon = require('sinon')
44
var stoppable = require('stoppable')
55
var http = require('http')
6+
var st = require('st')
7+
var path = require('path')
68
var port = 3000
79

810
var server
@@ -187,3 +189,26 @@ test('raHttp.del logs with options.verbose === true', (t) => {
187189
})
188190
})
189191
})
192+
193+
test('raHttp.read() on server that does not support ranges', (t) => {
194+
var raHttp = require('../index.js')
195+
var mount = st({
196+
path: path.join(__dirname, '..')
197+
})
198+
199+
var server = stoppable(http.createServer(function (req, res) {
200+
mount(req, res)
201+
}))
202+
203+
server.listen(port, function () {
204+
var ra = raHttp('LICENSE', { url: 'http://localhost:3000', strict: false })
205+
ra.read(10, 20, (err, data) => {
206+
t.error(err)
207+
t.ok(data instanceof Buffer)
208+
t.same(data.length, 20)
209+
server.stop(function () {
210+
t.end()
211+
})
212+
})
213+
})
214+
})

0 commit comments

Comments
 (0)