Skip to content
This repository was archived by the owner on Mar 10, 2020. It is now read-only.

refactor(streams): Refactor response stream handling. #465

Merged
merged 8 commits into from
Dec 21, 2016
Merged

refactor(streams): Refactor response stream handling. #465

merged 8 commits into from
Dec 21, 2016

Conversation

haadcode
Copy link
Contributor

@haadcode haadcode commented Dec 5, 2016

This PR will:

  • Refactor the response stream handling.
  • Add dagnode-stream to transform file results to DAGNode objects.
  • Add stream-to-value to convert a response stream to a single value.
  • Refactor request-api so that chunked JSON objects are not buffered anymore. This touches a bunch of the files pushing the transform function down to the individual commands.

The general idea in this PR is to cleanup the architecture and implementation regards to how the http responses are handled. Previously some of it was done in request-api.js and some in the individual commands making request-api coupled with the commands' response type. I've moved the response transform logic down to the individual commands so that request-api is agnostic to what is returned to the consumer of the commands. I've also refactored individual types of transformations to their own files to make it clear, clean and much more reusable (we can consider moving them to their own modules, but not in this PR).

Copy link
Contributor

@dignifiedquire dignifiedquire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we are at it, could you please use pump instead of .pipe for combining streams, as that will handle errors and cleanup streams on failure. For more details see https://github.com/mafintosh/pump

this._send = opts.send
}

static streamToValue (send, inputStream, callback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is static available in Node4?

Copy link
Contributor Author

@haadcode haadcode Dec 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it? Is it not? And if so, why is that important? Provide context and solutions or guidance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good: https://kangax.github.io/compat-table/es6/#test-class_static_methods

Always usable is

class MyClass {}

MyClass.mystaticmethod = function () {
}

Copy link
Contributor

@daviddias daviddias left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some modifications are not clear to me

isStream.isReadable(files) ||
Array.isArray(files)

if (!good) {
callback(new Error('"files" must be a buffer, readable stream, or array of objects'))
if (!ok) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you think ok is more descriptive and intuitive than good?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine :)

if (!good) {
callback(new Error('"files" must be a buffer, readable stream, or array of objects'))
if (!ok) {
return callback(new Error('"files" must be a buffer, readable stream, or array of objects'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

callback(null, outputStream)
}

_read () {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Class with a constructor and a static method? What is the goal here?

Output object format:
{
path: '/path/to/file/foo.txt',
hash: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hash or multihash?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "Output object format" is not a DAGNode Transforms a stream of objects to DAGNodes and outputs them as objects., what am I missing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛎

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the description. Does that make sense now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here: 1566751

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@diasdavid I'm not sure about the multihash vs. hash. It's a breaking change again and I'd be hesitant to change it in this PR. Have we made the change elsewhere in the code already? Does anything that returns DAGNode-like information return .multihash instead of .hash? If we have, I can change it so that we're consistent, but if not, I would leave it for later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This https://github.com/ipld/js-ipld-dag-pb/blob/master/src/dag-node/index.js#L22-L27 has been there for a while now, that is why I proposed it to be multihash

if (typeof options !== 'object') {
return callback(new Error('no options were passed'))
}

return requestAPI(config, options, callback)
}

// Wraps the 'send' function such that an asynchronous
// transform may be applied to its result before
// passing it on to either its callback or promise.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you preserve the comment please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't make sense, and it doesn't hold true for the new implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was trying to convey that before the response is returned, either through the callback or promise, we transform the response from go-ipfs to match interface-ipfs-core definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the description back and trying to describe as simple as I can what it does. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here: 1566751

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

res.pipe(concat((data) => callback(null, data)))
}

module.exports = streamToValue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of error on the stream (connection dropped) this fails silently

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this why you had all the once('error') calls there previously?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not knowing what is all the, but yes, if you don't capture the error, you don't know it failed

cb(null, res.Strings || [])
}

module.exports = stringlistToArray
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a string list? Is there a better name in this context? Does it has to be a separate file to do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stringList is a Go type (that's what it's called in Go) that the go-ipfs API sends back for some requests. It adds an additional "Strings" field to the response that contains a list of strings, so "{ Strings: [...] }" instead of "[...]".

The correct solution here would be to fix it on go-ipfs side to return an array instead of what it does now, and this is a workaround for that: a type to another type conversion function to isolate it and make it clear that another type gets converted to another.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@whyrusleeping wanna comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't change this on go-ipfs side for this PR. If we do that, it's an API breaking change that we need to handle with proper care. That's why I put it in one file, so that it's wrapped and isolated and if/once we change the return values, we only need to change change it in this file and whichever command is using the current API doesn't need changes. Obviously once you do that, we don't need the stringlist-to-array.js conversion function and can get rid of the file completely, but I recommend we keep it as such now so that we isolate the data conversion to one place. Does that make sense?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood :)

@haadcode haadcode mentioned this pull request Dec 6, 2016
@daviddias daviddias mentioned this pull request Dec 6, 2016
@haadcode
Copy link
Contributor Author

haadcode commented Dec 7, 2016

Thanks for the review @dignifiedquire and @diasdavid. Will work on this soon, mainly change it to use pump as well as address other CR pieces.

path: obj.Name,
hash: obj.Hash,
size: node.size
}
Copy link
Contributor

@daviddias daviddias Dec 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really a dag, it is just a json representation.

let outputStream = new TarStreamToObjects()

inputStream
.pipe(tar.extract())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to use pump

@haadcode
Copy link
Contributor Author

Will update this today and address the CR feedback.

@haadcode
Copy link
Contributor Author

I've refactored the pipes to use pump. I wasn't sure if the pump is needed in dagnode-stream.js since streamToValue internally uses pump (so I take it would handle the errors), but I added it just in case. The tests are not running in the browser atm, but run fully on node (will look into why the browser tests are not running).

I also reworked the comments for the two functions @diasdavid had a confusion about, hope they're now more descriptive. I refactored the TarStreamToObjects according to @diasdavid's question, perhaps now the intent is clearer. I couldn't get the ReadableStream to work when created directly with new Readable() and adding _read() = () => {}` function to it like it's normally done, but by wrapping it in a class it works. If you have an idea how to make it work without the class, please do refactor it.

There's also a problem with browser version now as we're using pump because its dependency on fs. @dignifiedquire suggested we fix it there: mafintosh/pump#6. If you know how, please do.

So while this is still WIP, can you take a look and see if there's anything else to address in this one?

@dignifiedquire
Copy link
Contributor

@haadcode pump fix is here: mafintosh/pump#13 and the issue with cors was correct, you changed them, now they are back: b2c09b2

@haadcode
Copy link
Contributor Author

Should we wait for the PR to get merged or can we use the PR commit as our dep in the meanwhile?

the issue with cors was correct, you changed them, now they are back: b2c09b2

Interesting. I had to change it in the first place because it wasn't working. Good that it's fixed now 👍

@dignifiedquire
Copy link
Contributor

Should we wait for the PR to get merged or can we use the PR commit as our dep in the meanwhile?

Pinged @mafintosh on irc, if it's not merged when we want to ship this lets switch to the fork.

@@ -43,6 +42,7 @@
"peer-id": "^0.8.1",
"peer-info": "^0.8.1",
"promisify-es6": "^1.0.2",
"pump": "^1.0.1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets bump this to 1.0.2 so we are sure to have the fix in


static streamToValue (send, inputStream, callback) {
const outputStream = pump(inputStream, new DAGNodeStream({ send: send }), (err) => {
if (err) callback(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@diasdavid will want this to be

if (err) {
  callback(err)
} 

Copy link
Contributor

@daviddias daviddias left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the hash vs multihash thing, it LGTM. I'm fine if Files API uses hash, although I would prefer consistency.

Add dagnode-stream to transform file results to DAGNode objects.
Add stream-to-value to convert a response stream to a single value.
Refactor request-api so that chunked JSON objects are not buffered anymore. This touches a bunch of the files pushing the transform function down to the individual commands.
@haadcode
Copy link
Contributor Author

I fixed the style issues, updated pump to 1.0.2 and rebased on master.

Other than the hash vs multihash thing, it LGTM. I'm fine if Files API uses hash, although I would prefer consistency.

@diasdavid would you be ok if we leave the change from .hash to .multihash to a separate PR? This is a large PR in itself and I'm afraid of the consequences the aforementioned change would have on users (being an API change). Perhaps you have more confidence to do it as you know the codebase throughout, but I would prefer doing it in one, separate PR where we update all functions (that return .hash currently) at the same time. Let me know what you think.

@daviddias
Copy link
Contributor

@diasdavid would you be ok if we leave the change from .hash to .multihash to a separate PR?

sounds good :)

@daviddias daviddias merged commit 753d19d into ipfs-inactive:master Dec 21, 2016
@haadcode
Copy link
Contributor Author

haadcode commented Jan 2, 2017

Didn't even notice this got merged. Thank you! 😄 🎉

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants