Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base: master
...
compare: swift
Checking mergeability… Don't worry, you can still create the pull request.
  • 18 commits
  • 6 files changed
  • 0 commit comments
  • 1 contributor
Commits on Feb 22, 2012
@mtyaka mtyaka First pass at an object storage (swift) client.
It's called noox for fun. Its mocked counterpart is called moox.

This is still very rough, the moox response codes and error messages will
need to be adapted to resemble the real world codes/messages more closely.

Auhtentication on OpenStack Object Storage is handled quite differently
than on S3. On S3 you basically sign every request with your credentials,
so there is no separate authentication request.
On Object Storage, you first call an authentication endpoint, which responds
with a security token that is only valid for 24 hours and which you are supposed
to included in headers in every subsequent request, as well as the base URL
pointing to your storage service.

Since creating a token is an asynchronous operation which needs to be done
before any other requests; and because tokens expire, there are some differences
in how the noox client is used compared to nox.

Noox tries to be low level, so you are responsible for authenticating with
the endpoint, as well as for keeping track of token expiry, calling the
authenticate function at least every 24 hours.
I am not completely sold on this idea yet, so noox might do automatic
authentication in the future, but this was easier to implement as the
first step.
fb30548
Commits on Feb 23, 2012
@mtyaka mtyaka Support both http and https storage endpoints.
255fc65
@mtyaka mtyaka Export the n/moox clients on the base module.
f54e77e
@mtyaka mtyaka Use the 'authenticated' boolean attribute on the client.
The 'authenticated' flag is false by default and set to true
the first time the client successfully authenticates through the
'authenticate' function.

Also, remove the authenticated_ts attribute because that was just silly.
f77fe3b
@mtyaka mtyaka Add authenticated function to the moox client.
4ae2cba
Commits on Jul 20, 2012
@mtyaka mtyaka Set a timeout on the authentication request.
db43b2e
@mtyaka mtyaka Stub setTimeout on moox writable streams.
a91fc0a
@mtyaka mtyaka Stub out setTimeout on mox writable streams, too.
c0f81eb
Commits on Mar 05, 2013
@mtyaka mtyaka Merge branch 'master' into swift
Conflicts:
	mox.js
0661966
@mtyaka mtyaka Mirror mox updates to moox.
8234433
Commits on Apr 17, 2013
@mtyaka mtyaka Use new Node 0.10 stream base classes in mo(o)x.
Node 0.10 provides a set of base classes for implementing streams.
Use that instead of building fake readable/writable streams from scratch.
This makes the fake requests/responses in mox/moox comply to the new streams API.

This only works with Node 0.10+. It could be made backwards compatible,
but I don't have any need for that at the moment.
95dc33f
@mtyaka mtyaka Clean up whitespace in moox.js.
cf8ecc8
@mtyaka mtyaka Implement stream._write with the correct signature.
7bdc4df
@mtyaka mtyaka Make wrapReadableStream return a new-style stream object.
c8542d1
@mtyaka mtyaka Make wrapWritableStream return a new-style stream object.
a462e88
Commits on Nov 28, 2013
@mtyaka mtyaka Merge branch 'master' into swift
78ba5db
@mtyaka mtyaka Make client take additional http request options.
This allows you to specify the agent to be used for the request, for example.

client.get(filename, {'User-Agent': 'Node'}, {agent: myAgentInstance});
4178677
@mtyaka mtyaka Add option to force particular protocol for storage url.
Some Object Storage providers always return the X-Storage-Url header with
a https url, even though the objects in storage can be accessed via plain http.

In those cases, one can now use the 'protocol': 'http' option to always force
the interaction with Object Storage to go via plain http.
This of course does not include authentication requests, which are always https.
ad05855
Showing with 714 additions and 31 deletions.
  1. +6 −0 index.js
  2. +304 −0 moox.js
  3. +12 −16 mox.js
  4. +154 −0 noox.js
  5. +20 −15 nox.js
  6. +218 −0 test-nooxmoox.js
View
6 index.js
@@ -3,3 +3,9 @@ exports.nox = require('./nox.js');
// mox S3 mock client
exports.mox = require('./mox.js');
+
+// noox object storage (swift) client
+exports.noox = require('./noox.js');
+
+// moox object storage (swift) client
+exports.moox = require('./moox.js');
View
304 moox.js
@@ -0,0 +1,304 @@
+
+// moox - OpenStack Object Storage mock-up for node.js
+//
+// Copyright(c) 2011 Nephics AB
+// MIT Licensed
+//
+
+var crypto = require('crypto');
+var fs = require('fs');
+var events = require('events');
+var path = require('path');
+var util = require('util');
+var stream = require('stream');
+
+
+function fakeReadableStream() {
+ var rs = new stream.Readable();
+ rs._read = function(size) {};
+ return rs;
+}
+
+function wrapReadableStream(rs) {
+ var self = new events.EventEmitter();
+
+ self.readable = true;
+ self.setEncoding = rs.setEncoding;
+ self.pause = rs.pause;
+ self.resume = rs.resume;
+ self.destroy = function() { self.readable = false; rs.destroy(); };
+ self.destroySoon = function() { self.readable = false; rs.destroySoon(); };
+ self.pipe = rs.pipe;
+
+ rs.on('data', function(chunk) { self.emit('data', chunk); });
+ rs.on('end', function() { self.readable = false; self.emit('end'); });
+ rs.on('error', function(err) { self.readable = false; });
+ rs.on('close', function() { self.emit('close'); });
+
+ return new stream.Readable().wrap(self);
+}
+
+function fakeWritableStream() {
+ var ws = new stream.Writable();
+ ws._write = function(chunk, encoding, callback) { callback(); };
+ ws.setTimeout = function() {};
+ return ws;
+}
+
+function wrapWritableStream(ws) {
+ var self = new stream.Writable();
+
+ self.writable = true;
+ self._write = ws._write;
+ self.write = function(chunk, enc) { return ws.write(chunk, enc); };
+ self.end = function(chunk, enc) { self.writable = false; if (chunk) self.write(chunk, enc); ws.end(); };
+ self.destroy = function() { self.writable = false; ws.destroy(); }
+ self.destroySoon = function() { self.writable = false; ws.destroySoon(); };
+ self.setTimeout = function() {};
+
+ ws.on('drain', function() { self.emit('drain'); });
+ ws.on('error', function(err) { self.writable = false; });
+ ws.on('close', function() { self.emit('close'); });
+ ws.on('pipe', function(src) { self.emit('pipe', src); });
+
+ return self;
+}
+
+exports.createClient = function createClient(options) {
+
+ if (!options.container) throw new Error('"container" required');
+
+ if (!options.prefix) {
+ options.prefix = '/tmp/moox';
+ }
+
+ // create storage dir, if it doesn't exists
+ if (!fs.existsSync(options.prefix)) {
+ fs.mkdirSync(options.prefix, 0777);
+ }
+
+ // create container dir, if it does not exists
+ var containerPath = path.join(options.prefix, options.container);
+ if (!fs.existsSync(containerPath)) {
+ fs.mkdirSync(containerPath, 0777);
+ }
+
+
+ function getFilePath(filename, createPath) {
+ var filePath = path.join(containerPath, filename);
+ if (createPath) {
+ // ensure that the path to the file exists
+ createRecursive(path.dirname(filePath));
+ function createRecursive(p) {
+ if (fs.existsSync(p) || p === containerPath) return;
+ createRecursive(path.join(p, '..'));
+ fs.mkdirSync(p, 0777);
+ }
+ }
+ return filePath;
+ }
+
+
+ function emitResponse(request, opts) {
+ if (request.responseEmitted) {
+ console.error('Response already emitted.')
+ return;
+ }
+
+ var xml;
+ var response = opts.response || fakeReadableStream();
+
+ response.httpVersion = '1.1';
+
+ response.headers = response.headers || (opts.headers || {});
+ response.headers.date = (new Date()).toUTCString();
+ response.headers['server'] = 'Moox';
+ response.headers['connection'] = 'close';
+
+ response.statusCode = opts.code || 200;
+ if (opts.err) {
+ response.headers['content-type'] = 'application/xml';
+ response.headers['transfer-encoding'] = 'chunked';
+ xml = ['<?xml version="1.0" encoding="UTF-8"?><Error><Code>',
+ opts.err.code, '</Code><Message>', opts.err.msg, '<Message></Error>'].join('');
+ }
+ response.headers['content-length'] = response.headers['content-length'] || (xml && xml.length || 0);
+
+ request.writable = false;
+ request.emit('response', response);
+ request.responseEmitted = true;
+ if (opts.err) {
+ response.emit('data', xml);
+ }
+ if (!opts.hasbody) {
+ response.emit('end');
+ response.readable = false;
+ response.emit('close');
+ }
+ }
+
+
+ var client = new function() {};
+
+ client.put = function put(filename, headers) {
+ var filePath = getFilePath(filename, true);
+ var fileLength = 0;
+ var md5 = crypto.createHash('md5');
+
+ // create file stream to write the file data
+ var ws = fs.createWriteStream(filePath);
+ var request = wrapWritableStream(ws);
+
+ ws.on('open', function() { request.emit('continue'); });
+ ws.on('error', function(err) {
+ emitResponse(request, {code:403, err:{code:'AccessDenied', msg:err.message}});
+ });
+
+ // wrap request.write() to allow calculation of MD5 hash
+ request._write = request.write;
+ request.write = function write(chunk, enc) {
+ fileLength += chunk.length;
+ md5.update(chunk);
+ return request._write(chunk, enc);
+ };
+
+ // wrap request.end() to write meta-data file and emit response
+ request._end = request.end;
+ request.end = function end(chunk, enc) {
+ request._end(chunk, enc);
+
+ // write the meta data file
+ headers['content-length'] = fileLength;
+ headers.Date = (new Date()).toUTCString();
+ headers.ETag = md5.digest('hex');
+
+ var meta = {};
+ Object.keys(headers).forEach(function(key) {
+ meta[key.toLowerCase()] = headers[key];
+ });
+
+ fs.writeFile(filePath + '.meta', JSON.stringify(meta), 'utf8', function(err) {
+ if (err) {
+ emitResponse(request, {code:403, err:{code:'AccessDenied', msg:err.message}});
+ return;
+ }
+ // when all data is written, the response is emitted
+ emitResponse(request, {code:201, headers:{etag:headers.ETag}});
+ });
+ };
+
+ request.abort = request.destroy;
+
+ return request;
+ };
+
+
+ client.get = function get(filename, headers) {
+ var request = fakeWritableStream();
+ var filePath = getFilePath(filename);
+
+ // read meta data
+ fs.readFile(filePath + '.meta', 'utf8', function(err, data) {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ // no such file
+ emitResponse(request, {code:404, err:{code:'NoSuchKey', msg:'The specified key does not exist.'}});
+ }
+ else {
+ // other error
+ emitResponse(request, {code:500, err:{code:'InternalError', msg:err.message}});
+ }
+ return;
+ }
+
+ // create file stream for reading the requested file
+ var rs = fs.createReadStream(filePath);
+ var response = wrapReadableStream(rs);
+ response.headers = JSON.parse(data);
+ response.headers['last-modified'] = response.headers['date'];
+
+ rs.on('open', function() {
+ // file is ready, emit response
+ emitResponse(request, {response:response, hasbody:true})
+ });
+ rs.on('error', function(err) {
+ // emit response indicating the file read error
+ emitResponse(request, {code:500, err:{code:'InternalError', msg:err.message}});
+ });
+ });
+
+ request.abort = request.destroy;
+
+ return request;
+ };
+
+
+ client.head = function head(filename, headers) {
+ var request = fakeWritableStream();
+ var filePath = getFilePath(filename);
+
+ // read meta data
+ fs.readFile(filePath + '.meta', 'utf8', function(err, data) {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ // no such file
+ emitResponse(request, {code:404, err:{code:'NoSuchKey', msg:'The specified key does not exist.'}});
+ }
+ else {
+ // other error
+ emitResponse(request, {code:500, err:{code:'InternalError', msg:err.message}});
+ }
+ return;
+ }
+
+ headers = JSON.parse(data);
+ headers['last-modified'] = headers['date'];
+ emitResponse(request, {headers:headers});
+ });
+
+ request.abort = request.destroy;
+
+ return request;
+ };
+
+
+ client.del = function del(filename) {
+ var request = fakeWritableStream();
+ var filePath = getFilePath(filename);
+
+ // remove the file
+ fs.unlink(filePath, function(err) {
+ // ignore "no such file" errors
+ if (err && err.code !== 'ENOENT') {
+ emitResponse(request, {code:500, err:{code:'InternalError', msg:err.message}});
+ return;
+ }
+ // remove meta data file
+ fs.unlink(filePath + '.meta', function(err) {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ emitResponse(request, {code:404, err:{code:'NoSuchKey', msg:err.message}});
+ } else {
+ emitResponse(request, {code:500, err:{code:'InternalError', msg:err.message}});
+ }
+ } else {
+ // when files are deleted, emit the response
+ emitResponse(request, {code:204})
+ }
+ });
+ });
+
+ request.abort = request.destroy;
+
+ return request;
+ };
+
+ client.authenticated = false;
+
+ client.authenticate = function(callback) {
+ client.authenticated = true;
+ callback();
+ };
+
+ return client;
+};
View
28 mox.js
@@ -10,16 +10,13 @@ var fs = require('fs');
var events = require('events');
var path = require('path');
var util = require('util');
+var stream = require('stream');
function fakeReadableStream() {
- var self = new events.EventEmitter();
-
- self.readable = true;
- self.setEncoding = self.pause = self.resume = self.pipe = function() {};
- self.destroy = self.destroySoon = function() { self.readable = false; }
-
- return self;
+ var rs = new stream.Readable();
+ rs._read = function(size) {};
+ return rs;
}
function wrapReadableStream(rs) {
@@ -38,27 +35,26 @@ function wrapReadableStream(rs) {
rs.on('error', function(err) { self.readable = false; });
rs.on('close', function() { self.emit('close'); });
- return self;
+ return new stream.Readable().wrap(self);
}
function fakeWritableStream() {
- var self = new events.EventEmitter();
-
- self.writable = true;
- self.write = function(chunk, enc) { return true; };
- self.end = self.destroy = self.destroySoon = function() { self.writable = false; };
-
- return self;
+ var ws = new stream.Writable();
+ ws._write = function(chunk, encoding, callback) { callback(); };
+ ws.setTimeout = function() {};
+ return ws;
}
function wrapWritableStream(ws) {
- var self = new events.EventEmitter();
+ var self = new stream.Writable();
self.writable = true;
+ self._write = ws._write;
self.write = function(chunk, enc) { return ws.write(chunk, enc); };
self.end = function(chunk, enc) { self.writable = false; if (chunk) self.write(chunk, enc); ws.end() };
self.destroy = function() { self.writable = false; ws.destroy(); }
self.destroySoon = function() { self.writable = false; ws.destroySoon(); };
+ self.setTimeout = function() {};
ws.on('drain', function() { self.emit('drain'); });
ws.on('error', function(err) { self.writable = false; });
View
154 noox.js
@@ -0,0 +1,154 @@
+
+// noox - OpenStack Object Storage Client for node.js
+//
+// Copyright(c) 2011 Nephics AB
+// MIT Licensed
+//
+// Some code parts derived from knox:
+// https://github.com/LearnBoost/knox
+// Copyright(c) 2010 LearnBoost <dev@learnboost.com>
+// MIT Licensed
+
+var http = require('http');
+var https = require('https');
+var url = require('url');
+
+var http_modules = {http: http, https: https};
+
+
+function merge(a, b) {
+ var o = {};
+ Object.keys(a).forEach(function(key) {
+ o[key] = a[key];
+ });
+ Object.keys(b).forEach(function(key) {
+ o[key] = b[key];
+ });
+ return o;
+}
+
+
+// Create a Object Storage client
+//
+// Required options:
+// host: authentication endpoint host
+// user: username
+// key: api key
+// container: container name
+//
+// Optional:
+// protocol: 'http' or 'https' - force use of this protocol instead of
+// the one specified in X-Storage-Url
+exports.createClient = function(options) {
+ if (!options.host) throw new Error('"host" required');
+ if (!options.user) throw new Error('"user" required');
+ if (!options.key) throw new Error('"key" required');
+ if (!options.container) throw new Error('"container" required');
+
+ var client = new function() {};
+
+ client.authenticate = function(callback) {
+ callback = callback || function() {};
+ var opts = {
+ host: options.host,
+ port: options.port,
+ path: '/auth/v1.0/',
+ headers: {
+ 'X-Auth-User': options.user,
+ 'X-Auth-Key': options.key
+ }
+ };
+
+ var req = https.request(opts, function(res) {
+ var buffer = [];
+
+ res.on('data', function(chunk) {
+ buffer.push(chunk);
+ });
+
+ res.on('end', function() {
+ var storage_url = res.headers['x-storage-url'];
+ var auth_token = res.headers['x-auth-token'];
+ var body = buffer.join('');
+ var err;
+
+ if (res.statusCode >= 400) {
+ err = new Error('Bad auth server response');
+ err.statusCode = res.statusCode;
+ err.body = body;
+ callback(err);
+ } else if (!(storage_url && auth_token)) {
+ err = new Error('Missing X-Storage-Url or X-Auth-Token headers');
+ err.statusCode = res.statusCode;
+ err.body = body;
+ err.headers = res.headers;
+ callback(err);
+ } else {
+ client.authenticated = true;
+ client.storage_url = url.parse(storage_url);
+ client.auth_token = auth_token;
+ callback();
+ }
+ });
+ });
+
+ req.setTimeout(15 * 1000, function() {
+ req.abort();
+ var err = new Error('Object storage authentication request timed out.');
+ callback(err);
+ });
+
+ req.on('error', function(err) {
+ callback(err);
+ });
+
+ req.end();
+ };
+
+ var request = function(method, filename, headers, http_opts) {
+ if (!client.authenticated) { throw new Error('noox client not authenticated. Call the .authenticate() function.'); }
+ var date = new Date;
+ var url_path = client.storage_url.path + '/'+ options.container + '/' + filename;
+ headers = headers || {};
+ http_opts = http_opts || {};
+
+ // Default headers
+ headers = merge(headers, {
+ 'Date': date.toUTCString(),
+ 'Host': options.host,
+ 'X-Auth-Token': client.auth_token
+ });
+
+ // Issue request
+ var opts = merge(http_opts, {
+ host: client.storage_url.host,
+ port: client.storage_url.port,
+ method: method,
+ path: url_path,
+ headers: headers
+ });
+
+ var protocol = options.protocol || client.storage_url.protocol.replace(':', '');
+ return http_modules[protocol].request(opts);
+ };
+
+ client.put = function put(filename, headers, opts) {
+ headers.Expect = '100-continue';
+ return request('PUT', filename, headers, opts);
+ };
+
+ client.get = function get(filename, headers, opts) {
+ return request('GET', filename, headers, opts);
+ };
+
+ client.head = function head(filename, headers, opts) {
+ return request('HEAD', filename, headers, opts);
+ };
+
+ // Delete file
+ client.del = function del(filename, headers, opts) {
+ return request('DELETE', filename, headers, opts);
+ };
+
+ return client;
+};
View
35 nox.js
@@ -17,10 +17,14 @@ var auth = require('./auth')
function merge(a, b) {
+ var o = {};
+ Object.keys(a).forEach(function(key) {
+ o[key] = a[key];
+ });
Object.keys(b).forEach(function(key) {
- a[key] = b[key]
+ o[key] = b[key];
});
- return a;
+ return o;
}
@@ -49,12 +53,13 @@ exports.createClient = function(options) {
endpoint = bucket + '.s3.amazonaws.com';
}
- function request(method, filename, headers) {
+ function request(method, filename, headers, http_opts) {
var date = new Date;
- var headers = headers || {};
+ headers = headers || {};
+ http_opts = http_opts || {};
// Default headers
- merge(headers, {
+ headers = merge(headers, {
Date:date.toUTCString(),
Host:endpoint
});
@@ -72,35 +77,35 @@ exports.createClient = function(options) {
});
// Issue request
- var opts = {
+ var opts = merge(http_opts, {
host:endpoint,
port:80,
method:method,
path:path.join('/', filename),
headers:headers
- };
+ });
return http.request(opts);
}
var client = new function() {};
- client.put = function put(filename, headers) {
+ client.put = function put(filename, headers, opts) {
headers.Expect = '100-continue';
- return request('PUT', filename, headers);
+ return request('PUT', filename, headers, opts);
};
- client.get = function get(filename, headers) {
- return request('GET', filename, headers);
+ client.get = function get(filename, headers, opts) {
+ return request('GET', filename, headers, opts);
};
- client.head = function head(filename, headers) {
- return request('HEAD', filename, headers);
+ client.head = function head(filename, headers, opts) {
+ return request('HEAD', filename, headers, opts);
};
// Delete file
- client.del = function del(filename, headers) {
- return request('DELETE', filename, headers);
+ client.del = function del(filename, headers, opts) {
+ return request('DELETE', filename, headers, opts);
};
// Return an S3 presigned url to the given `filename`.
View
218 test-nooxmoox.js
@@ -0,0 +1,218 @@
+
+//
+// Copyright(c) 2011 Nephics AB
+// MIT Licensed
+//
+
+// To run the tests you will need a file called awsauth.json in the parent path.
+// The JSON file shall contain an object with the aws key, secret and bucketname.
+
+var fs = require('fs');
+var crypto = require('crypto');
+var util = require('util');
+var assert = require('assert');
+
+var noox = require('./noox.js');
+var moox = require('./moox.js');
+
+runTests();
+
+function runTests() {
+ fs.readFile('../swiftauth.json', 'utf8', function(err, data) {
+ if (err) {
+ console.log(err.message);
+ return;
+ }
+ var options = JSON.parse(data);
+
+ console.log('\nTesting moox client');
+ var mooxclient = moox.createClient(options);
+ test(mooxclient, function() {
+ var nooxclient = noox.createClient(options);
+ console.log('\nTesting noox client');
+ nooxclient.authenticate(function(err) {
+ if (err) { throw 'Authentication failed!'; }
+ test(nooxclient, function(){
+ console.log('\nAll tests completed');
+ });
+ });
+ });
+ });
+}
+
+
+function test(client, callback) {
+ var name = 'test-nooxmoox.txt';
+ t1();
+ function t1() {
+ var buf = new Buffer('Testing the nooxmoox lib.');
+ upload(client, name, buf, t2);
+ }
+ function t2() {
+ stat(client, name, t3);
+ }
+ function t3() {
+ download(client, name, t4);
+ }
+ function t4() {
+ remove(client, name, t5);
+ }
+ function t5() {
+ statRemoved(client, name, t6);
+ }
+ function t6() {
+ downloadRemoved(client, name, t7);
+ }
+ function t7() {
+ removeRemoved(client, name, callback);
+ }
+}
+
+function logErrors(req) {
+ req.on('error', function(err) {
+ console.log(err.message || err);
+ });
+}
+
+function logResponse(res) {
+ console.log('status code: ' + res.statusCode);
+ console.log('headers: ' + util.inspect(res.headers));
+}
+
+
+function upload(client, name, buf, callback) {
+ console.log('\nFile upload');
+ var req = client.put(name, {
+ 'Content-Type':'text/plain',
+ 'Content-Length':buf.length,
+ 'ETag': crypto.createHash('md5').update(buf).digest('hex')
+ });
+ logErrors(req);
+ req.on('continue', function() {
+ req.end(buf);
+ });
+ req.on('response', function(res) {
+ logResponse(res);
+ res.on('data', function(chunk) {
+ console.log(chunk);
+ });
+ res.on('end', function() {
+ console.log('Response finished');
+ assert.equal(res.statusCode, 201);
+ callback();
+ });
+ });
+}
+
+
+function stat(client, name, callback) {
+ var req = client.head(name);
+ logErrors(req);
+ console.log('\nFile stat');
+ req.on('response', function(res) {
+ logResponse(res);
+ res.on('data', function(chunk) {
+ console.log(chunk);
+ });
+ res.on('end', function() {
+ console.log('Response finished');
+ assert.equal(res.statusCode, 200);
+ callback();
+ });
+ });
+ req.end();
+}
+
+
+function statRemoved(client, name, callback) {
+ var req = client.head(name);
+ logErrors(req);
+ console.log('\nNonexistent file stat');
+ req.on('response', function(res) {
+ logResponse(res);
+ res.on('data', function(chunk) {
+ console.log(chunk);
+ });
+ res.on('end', function() {
+ console.log('Response finished');
+ assert.equal(res.statusCode, 404);
+ callback();
+ });
+ });
+ req.end();
+}
+
+
+function download(client, name, callback) {
+ var req = client.get(name);
+ logErrors(req);
+ console.log('\nFile download');
+ req.on('response', function(res) {
+ logResponse(res);
+ var len = 0;
+ res.on('data', function(chunk) {
+ len += chunk.length;
+ });
+ res.on('end', function() {
+ console.log('Downloaded ' + len + ' bytes of file data');
+ assert.equal(res.statusCode, 200);
+ callback();
+ });
+ });
+ req.end();
+}
+
+function downloadRemoved(client, name, callback) {
+ var req = client.get(name);
+ logErrors(req);
+ console.log('\nNonexistent file download');
+ req.on('response', function(res) {
+ logResponse(res);
+ res.on('data', function(chunk) {
+ console.log(chunk.toString());
+ });
+ res.on('end', function() {
+ assert.equal(res.statusCode, 404);
+ callback();
+ });
+ });
+ req.end();
+}
+
+
+function remove(client, name, callback) {
+ var req = client.del(name);
+ logErrors(req);
+ console.log('\nFile delete');
+ req.on('response', function(res) {
+ logResponse(res);
+ res.on('data', function(chunk) {
+ console.log(chunk);
+ });
+ res.on('end', function() {
+ console.log('Response finished');
+ assert.equal(res.statusCode, 204);
+ callback();
+ });
+ });
+ req.end();
+}
+
+
+function removeRemoved(client, name, callback) {
+ var req = client.del(name);
+ logErrors(req);
+ console.log('\nFile delete');
+ req.on('response', function(res) {
+ logResponse(res);
+ res.on('data', function(chunk) {
+ console.log(chunk);
+ });
+ res.on('end', function() {
+ console.log('Response finished');
+ assert.equal(res.statusCode, 404);
+ callback();
+ });
+ });
+ req.end();
+}

No commit comments for this range

Something went wrong with that request. Please try again.