Skip to content

Commit 7f810d2

Browse files
author
Benjamin Forster
committed
merge with http-random-access
1 parent d8bd457 commit 7f810d2

15 files changed

+535
-199
lines changed

.coveralls.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
repo_token: Ol7Ap8LS2ER32hLdx9evIMFgCSChJNueO

.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "extends": ["standard"] }

.travis.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
language: node_js
2-
node_js:
3-
- 'node'
4-
- '4'
52
sudo: false
6-
cache:
7-
directories:
8-
- node_modules
3+
node_js:
4+
- 6
5+
- 8
6+
- 9
7+
script:
8+
- npm run test-travis
9+
after_script:
10+
- npm run report-coverage

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2016, Bret Comnes
1+
Copyright (c) 2016, Bret Comnes and contributors
22

33
Permission to use, copy, modify, and/or distribute this software for any
44
purpose with or without fee is hereby granted, provided that the above

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Continuous reading from a http(s) url using random offsets and lengths
66
npm install random-access-http
77
```
88

9-
[![Build Status](https://travis-ci.org/bcomnes/random-access-http.svg?branch=master)](https://travis-ci.org/bcomnes/random-access-http)
9+
[![Build Status](https://travis-ci.org/random-access-storage/random-access-http.svg?branch=master)](https://travis-ci.org/random-access-storage/random-access-http) [![Coverage Status](https://coveralls.io/repos/github/random-access-storage/random-access-http/badge.svg?branch=master)](https://coveralls.io/github/random-access-storage/random-access-http?branch=master)
1010

1111
## Why?
1212

@@ -34,13 +34,25 @@ file will use a keepalive agent to reduce the number http requests needed for th
3434

3535
## API
3636

37-
#### `var file = randomAccessHTTP(url)`
37+
#### `var file = randomAccessHTTP(url, [options])`
3838

39-
Create a new 'file' that reads from the provided `url`. The `url` can be either `http` or `https`.
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.
40+
41+
Options include:
42+
```js
43+
{
44+
url: string // Optionsal. The base url if first argument is relative
45+
verbose: boolean, // Optional. Default: false.
46+
timeout: number, // Optional. Default: 60000
47+
maxRedirects: number, // Optional. Default: 10
48+
maxContentLength: number, // Optional. Default: 50MB
49+
}
50+
```
4051

4152
#### `file.write(offset, buffer, [callback])`
4253

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

4557
#### `file.read(offset, length, callback)`
4658

_index.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
var events = require('events')
2+
var inherits = require('inherits')
3+
var http = require('http')
4+
var https = require('https')
5+
var url = require('url')
6+
var xtend = require('xtend')
7+
var concat = require('concat-stream')
8+
var pump = require('pump')
9+
var limitStream = require('size-limit-stream')
10+
11+
module.exports = RandomAccessHTTP
12+
13+
function RandomAccessHTTP (fileUrl, opts) {
14+
if (!(this instanceof RandomAccessHTTP)) return new RandomAccessHTTP(fileUrl, opts)
15+
if (!opts) opts = {}
16+
17+
events.EventEmitter.call(this)
18+
19+
this.url = fileUrl
20+
this.urlObj = url.parse(fileUrl)
21+
this.client = {
22+
http: http,
23+
https: https
24+
}[this.urlObj.protocol.split(':')[0]]
25+
this.readable = opts.readable !== false
26+
this.writable = false
27+
this.length = opts.length || 0
28+
this.opened = false
29+
}
30+
31+
inherits(RandomAccessHTTP, events.EventEmitter)
32+
33+
RandomAccessHTTP.prototype.open = function (cb) {
34+
var self = this
35+
36+
this.keepAliveAgent = new this.client.Agent({ keepAlive: true })
37+
var reqOpts = xtend(this.urlObj, {
38+
method: 'HEAD',
39+
agent: this.keepAliveAgent
40+
})
41+
var req = this.client.request(reqOpts, onres)
42+
43+
function onres (res) {
44+
if (res.statusCode !== 200) return cb(new Error('Bad response: ' + res.statusCode))
45+
if (headersInvalid(res.headers)) {
46+
return cb(new Error("Source doesn't support 'accept-ranges'"))
47+
}
48+
self.opened = true
49+
if (res.headers['content-length']) self.length = res.headers['content-length']
50+
self.emit('open')
51+
cb()
52+
}
53+
54+
req.on('error', (e) => {
55+
return cb(new Error(`problem with request: ${e.message}`))
56+
})
57+
58+
req.end()
59+
}
60+
61+
function headersInvalid (headers) {
62+
if (!headers['accept-ranges']) return true
63+
if (headers['accept-ranges'] !== 'bytes') return true
64+
}
65+
66+
RandomAccessHTTP.prototype.write = function (offset, buf, cb) {
67+
if (!cb) cb = noop
68+
if (!this.opened) return openAndWrite(this, offset, buf, cb)
69+
if (!this.writable) return cb(new Error('URL is not writable'))
70+
cb(new Error('Write Not Implemented'))
71+
}
72+
73+
RandomAccessHTTP.prototype.read = function (offset, length, cb) {
74+
if (!this.opened) return openAndRead(this, offset, length, cb)
75+
if (!this.readable) return cb(new Error('URL is not readable'))
76+
77+
var self = this
78+
79+
var range = `${offset}-${offset + length - 1}` // 0 index'd
80+
var reqOpts = xtend(this.urlObj, {
81+
method: 'GET',
82+
agent: this.keepAliveAgent,
83+
headers: {
84+
Accept: '*/*',
85+
Range: `bytes=${range}`
86+
}
87+
})
88+
89+
var req = this.client.request(reqOpts, onres)
90+
91+
req.on('error', (e) => {
92+
return cb(new Error(`problem with read request: ${e.message}`))
93+
})
94+
95+
req.end()
96+
97+
function onres (res) {
98+
if (!res.headers['content-range']) return cb(new Error('Server did not return a byte range'))
99+
if (res.statusCode !== 206) return cb(new Error('Bad response: ' + res.statusCode))
100+
var expectedRange = `bytes ${range}/${self.length}`
101+
if (res.headers['content-range'] !== expectedRange) return cb(new Error('Server returned unexpected range: ' + res.headers['content-range']))
102+
if (offset + length > self.length) return cb(new Error('Could not satisfy length'))
103+
var concatStream = concat(onBuf)
104+
var limiter = limitStream(length + 1) // blow up if we get more data back than needed
105+
106+
pump(res, limiter, concatStream, function (err) {
107+
if (err) return cb(new Error(`problem while reading stream: ${err}`))
108+
})
109+
}
110+
111+
function onBuf (buf) {
112+
return cb(null, buf)
113+
}
114+
}
115+
116+
// function parseRangeHeader (rangeHeader) {
117+
// var range = {}
118+
// var byteRangeArr = rangeHeader.split(' ')
119+
// range.unit = byteRangeArr[0]
120+
// var ranges = byteRangeArr[1].split('/')
121+
// range.totalLength = ranges[1]
122+
// var startStop = ranges[0].split('-')
123+
// range.offset = startStop[0]
124+
// range.end = startStop[1]
125+
// range.length = range.end - range.offset
126+
// return range
127+
// }
128+
129+
RandomAccessHTTP.prototype.close = function (cb) {
130+
this.opened = false
131+
this.keepAliveAgent.destroy()
132+
this.emit('close')
133+
cb(null)
134+
}
135+
136+
function noop () {}
137+
138+
function openAndRead (self, offset, length, cb) {
139+
self.open(function (err) {
140+
if (err) return cb(err)
141+
self.read(offset, length, cb)
142+
})
143+
}
144+
145+
function openAndWrite (self, offset, buf, cb) {
146+
self.open(function (err) {
147+
if (err) return cb(err)
148+
self.write(offset, buf, cb)
149+
})
150+
}

example.js

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,12 @@
1-
var http = require('http')
2-
var https = require('https')
3-
var url = require('url')
1+
var raHttp = require('./')
42

5-
var urlObj = url.parse('https://ia800309.us.archive.org/2/items/Popeye_Nearlyweds/Popeye_Nearlyweds_512kb.mp4')
3+
var file = raHttp('/readme.md', { url: 'https://raw.githubusercontent.com/e-e-e/http-random-access/master/' })
64

7-
var options = Object.assign({}, urlObj, {
8-
method: 'HEAD',
9-
headers: {
10-
Connection: 'keep-alive'
5+
file.read(2, 10, (err, data) => {
6+
if (err) {
7+
console.log('Something went wrong!')
8+
console.log(err)
9+
return
1110
}
11+
console.log(data.toString())
1212
})
13-
14-
var client = {
15-
http: http,
16-
https: https
17-
}[urlObj.protocol.split(':')[0]]
18-
19-
var req = client.request(options, res => {
20-
console.log(`STATUS: ${res.statusCode}`)
21-
console.log(`HEADERS: ${JSON.stringify(res.headers, null, '\t')}`)
22-
res.setEncoding('utf8')
23-
res.on('data', (chunk) => {
24-
console.log(`BODY: ${chunk}`)
25-
})
26-
res.on('end', () => {
27-
console.log('No more data in response.')
28-
})
29-
})
30-
31-
req.on('error', (e) => {
32-
console.log(`problem with request: ${e.message}`)
33-
})
34-
35-
// write data to request body
36-
req.end()

0 commit comments

Comments
 (0)