Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 25 additions & 27 deletions lib/handlers/allow.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module.exports = allow
var ACL = require('../acl-checker')
var $rdf = require('rdflib')
var url = require('url')
var async = require('async')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👏

var utils = require('../utils')

function allow (mode) {
Expand Down Expand Up @@ -59,31 +58,30 @@ function allow (mode) {
*/
function fetchDocument (host, ldp, baseUri) {
return function fetch (uri, callback) {
var graph = $rdf.graph()
async.waterfall([
function readFile (cb) {
// If local request, slice off the initial baseUri
// S(uri).chompLeft(baseUri).s
var newPath = uri.startsWith(baseUri)
? uri.slice(baseUri.length)
: uri
// Determine the root file system folder to look in
// TODO prettify this
var root = !ldp.idp ? ldp.root : ldp.root + host + '/'
// Derive the file path for the resource
var documentPath = utils.uriToFilename(newPath, root)
var documentUri = url.parse(documentPath)
documentPath = documentUri.pathname
return ldp.readFile(documentPath, cb)
},
function parseFile (body, cb) {
try {
$rdf.parse(body, graph, uri, 'text/turtle')
} catch (err) {
return cb(err, graph)
}
return cb(null, graph)
}
], callback)
readFile(uri, host, ldp, baseUri).then(body => {
const graph = $rdf.graph()
$rdf.parse(body, graph, uri, 'text/turtle')
return graph

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mostly unrelated to this PR, but these 3 lines come up again and again when working with rdflib.js. I'd love to have an alternative API which takes a string and returns a graph.

})
.then(graph => callback(null, graph), callback)
}
}

// Reads the given file, returning its contents
function readFile (uri, host, ldp, baseUri) {
return new Promise((resolve, reject) => {
// If local request, slice off the initial baseUri
// S(uri).chompLeft(baseUri).s
var newPath = uri.startsWith(baseUri)
? uri.slice(baseUri.length)
: uri
// Determine the root file system folder to look in
// TODO prettify this
var root = !ldp.idp ? ldp.root : ldp.root + host + '/'
// Derive the file path for the resource
var documentPath = utils.uriToFilename(newPath, root)
var documentUri = url.parse(documentPath)
documentPath = documentUri.pathname
ldp.readFile(documentPath, (e, c) => e ? reject(e) : resolve(c))
})
}
26 changes: 9 additions & 17 deletions lib/handlers/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ var glob = require('glob')
var _path = require('path')
var $rdf = require('rdflib')
var S = require('string')
var async = require('async')
var Negotiator = require('negotiator')
const url = require('url')
const mime = require('mime-types')
Expand Down Expand Up @@ -161,35 +160,28 @@ function globHandler (req, res, next) {
let reqOrigin = utils.getBaseUri(req)

debugGlob('found matches ' + matches)
async.each(matches, function (match, done) {
Promise.all(matches.map(match => new Promise((resolve, reject) => {
var baseUri = utils.filenameToBaseUri(match, reqOrigin, root)
fs.readFile(match, {encoding: 'utf8'}, function (err, fileData) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Again probably not in scope for the PR, but I'm confused as to why we need a readFile declared here vs fs.readFile.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, unsure. I just ported it.

if (err) {
debugGlob('error ' + err)
return done(null)
return resolve()
}
aclAllow(match, req, res, function (allowed) {
if (!S(match).endsWith('.ttl') || !allowed) {
return done(null)
return resolve()
}
try {
$rdf.parse(
fileData,
globGraph,
baseUri,
'text/turtle')
$rdf.parse(fileData, globGraph, baseUri, 'text/turtle')
} catch (parseErr) {
debugGlob('error in parsing the files' + parseErr)
debugGlob(`error parsing ${match}: ${parseErr}`)
}
return done(null)
return resolve()
})
})
}, function () {
var data = $rdf.serialize(
undefined,
globGraph,
requestUri,
'text/turtle')
})))
.then(() => {
var data = $rdf.serialize(undefined, globGraph, requestUri, 'text/turtle')
// TODO this should be added as a middleware in the routes
res.setHeader('Content-Type', 'text/turtle')
debugGlob('returning turtle')
Expand Down
119 changes: 49 additions & 70 deletions lib/ldp.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ var path = require('path')
const url = require('url')
var fs = require('fs')
var $rdf = require('rdflib')
var async = require('async')
// var url = require('url')
var mkdirp = require('fs-extra').mkdirp
var uuid = require('uuid')
Expand All @@ -13,7 +12,6 @@ var error = require('./http-error')
var stringToStream = require('./utils').stringToStream
var serialize = require('./utils').serialize
var extend = require('extend')
var doWhilst = require('async').doWhilst

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

doWhilst?? haha!

var rimraf = require('rimraf')
var ldpContainer = require('./ldp-container')
var parse = require('./utils').parse
Expand Down Expand Up @@ -156,42 +154,40 @@ class LDP {
return callback(error(500, "Can't parse container"))
}

async.waterfall(
[
// add container stats
function (next) {
ldpContainer.addContainerStats(ldp, reqUri, filename, resourceGraph, next)
},
// reading directory
function (next) {
ldpContainer.readdir(filename, next)
},
// Iterate through all the files
function (files, next) {
async.each(
files,
function (file, cb) {
let fileUri = url.resolve(reqUri, encodeURIComponent(file))
ldpContainer.addFile(ldp, resourceGraph, reqUri, fileUri, uri,
filename, file, cb)
},
next)
}
],
function (err, data) {
// add container stats
new Promise((resolve, reject) =>
ldpContainer.addContainerStats(ldp, reqUri, filename, resourceGraph,
err => err ? reject(err) : resolve())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a nitpick comment (don't need to address) but the indentation here through me off as to which expression the anonymous function was being passed off to.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can address this, as there are some other points as well. What indentation would you use here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's definitely a matter of taste but this is how I generally like things: when breaking function arguments up into several lines, I like to keep the closing paren ) on its own line, indented to match the start of the line of the function call.

For example:

ldpContainer.addContainerStats(
  ldp, reqUri, filename, resourceGraph,
  err => err ? reject(err) : resolve()
)

I also see this a lot when using callback functions - breaking the line after the arguments to the callback:

ldpContainer.addContainerStats(ldp, reqUri, filename, resourceGraph, err =>
  err ? reject(err) : resolve()
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We should really consider using a linter that goes beyond standard. There are still lots of variations in the code.

This particular instance can perhaps be solved with promisify (see below).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, fair point. A code formatter would be nice. I thought I saw you discussing prettier at some point in gitter. I'd be in favor of adding that, for no other reason than not having to think about style ever again :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Won't have been me 😄 I'm in favor of a strict .eslintrc (and really dislike standard 😉).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm leaving this as-is, since other parts in the same file follow the same indentation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's fine. Consistency is more important :)

)
// read directory
.then(() => new Promise((resolve, reject) =>
ldpContainer.readdir(filename,
(err, files) => err ? reject(err) : resolve(files))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like a node convention for pre-Promise API async functions to take callbacks which themselves take a possible error as the first argument and a successful value as the next argument. We could write a higher-order function to make these kinds of functions return promises:

const promisify = (fn) =>
  (...args) =>
    new Promise((resolve, reject) =>
      fn(...args, (err, success) =>
        err
          ? reject(err)
          : resolve(success)
      )
    )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We could do that—and there are a couple of libraries that expose such a function (starting with Node 8 even in core). I've used that in other projects.

I was hesitant to do this here and in other places of the Solid code, because callbacks are sometimes used inconsistently (they don't always have an error argument). So I could start using it here, but then it would perhaps be good to keep an eye on it in other places of the code as well. And eventually, we should migrate to an await/async style, I suppose.

So, what do you think, should we start the promisify practice here?
Perhaps we should consider switching to async/await (with build step) for node-solid-server v5? Or already start it here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good points!

I was hesitant to do this here and in other places of the Solid code, because callbacks are sometimes used inconsistently (they don't always have an error argument).

Oof yeah that complicates things!

And eventually, we should migrate to an await/async style, I suppose.

Sure thing. Though async/await is syntactic sugar on top of Promises, so I think that "promisifying" our interfaces goes hand-in-hand with async/await-ifying them :)

I like your point that this change is more of an interface redesign than simply "promisifying" the existing APIs. How about we simply leave things as they are here, and I'll create a ticket for making the async APIs more consistent.

@RubenVerborgh RubenVerborgh Aug 22, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

👍 I'll make the other changes and merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(created issue #557)

))
// iterate through all the files
.then(files => {
return Promise.all(files.map(file =>
new Promise((resolve, reject) => {
const fileUri = url.resolve(reqUri, encodeURIComponent(file))
ldpContainer.addFile(ldp, resourceGraph, reqUri, fileUri, uri,
filename, file, err => err ? reject(err) : resolve())
})
))
})
.catch(() => { throw error(500, "Can't list container") })
.then(() => new Promise((resolve, reject) => {
// TODO 'text/turtle' is fixed, should be contentType instead
// This forces one more translation turtle -> desired
serialize(resourceGraph, reqUri, 'text/turtle', function (err, result) {
if (err) {
return callback(error(500, "Can't list container"))
debug.handlers('GET -- Error serializing container: ' + err)
reject(error(500, "Can't serialize container"))
} else {
resolve(result)
}
// TODO 'text/turtle' is fixed, should be contentType instead
// This forces one more translation turtle -> desired
serialize(resourceGraph, reqUri, 'text/turtle', function (err, result) {
if (err) {
debug.handlers('GET -- Error serializing container: ' + err)
return callback(error(500, "Can't serialize container"))
}
return callback(null, result)
})
})
}))
.then(result => callback(null, result), callback)
}

post (hostname, containerPath, slug, stream, container, callback) {
Expand Down Expand Up @@ -351,18 +347,10 @@ class LDP {
var root = ldp.idp ? ldp.root + host + '/' : ldp.root
var filename = utils.uriToFilename(reqPath, root)

async.waterfall([
// Read file
function (cb) {
return ldp.readFile(filename, cb)
},
// Parse file
function (body, cb) {
parse(body, baseUri, contentType, function (err, graph) {
cb(err, graph)
})
}
], callback)
ldp.readFile(filename, (err, body) => {
if (err) return callback(err)
parse(body, baseUri, contentType, callback)
})
}

get (options, callback) {
Expand Down Expand Up @@ -395,7 +383,7 @@ class LDP {
if (err) {
metaFile = ''
}
let absContainerUri = url.resolve(baseUri, reqPath)
let absContainerUri = baseUri + reqPath
ldp.listContainer(filename, absContainerUri, baseUri, metaFile, contentType,
function (err, data) {
if (err) {
Expand Down Expand Up @@ -498,34 +486,25 @@ class LDP {

getAvailablePath (host, containerURI, slug, callback) {
var self = this
var exists

if (!slug) {
slug = uuid.v1()
}
slug = slug || uuid.v1()

var newPath = path.join(containerURI, slug)

// TODO: maybe a nicer code
doWhilst(
function (next) {
function ensureNotExists (newPath) {
return new Promise(resolve => {
self.exists(host, newPath, function (err) {
exists = !err

if (exists) {
var id = uuid.v1().split('-')[ 0 ] + '-'
// If an error occurred, the resource does not exist yet
if (err) {
resolve(newPath)
// Otherwise, generate a new path
} else {
const id = uuid.v1().split('-')[ 0 ] + '-'
newPath = path.join(containerURI, id + slug)
resolve(ensureNotExists(newPath))
}

next()
})
},
function () {
return exists === true
},
function () {
callback(newPath)
})
}

return ensureNotExists(path.join(containerURI, slug)).then(callback)
}
}
module.exports = LDP
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
"homepage": "http://github.com/solid/node-solid-server",
"bugs": "http://github.com/solid/node-solid-server/issues",
"dependencies": {
"async": "^1.3.0",
"body-parser": "^1.14.2",
"busboy": "^0.2.12",
"camelize": "^1.0.0",
Expand Down
38 changes: 13 additions & 25 deletions test/integration/cors-proxy-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ var assert = require('chai').assert
var supertest = require('supertest')
var path = require('path')
var nock = require('nock')
var async = require('async')

var ldnode = require('../../index')

Expand Down Expand Up @@ -108,30 +107,19 @@ describe('CORS Proxy', () => {
.expect(200, done)
})

it('should return the same HTTP status code as the uri', (done) => {
async.parallel([
// 500
(next) => {
nock('https://example.org').get('/404').reply(404)
server.get('/proxy/?uri=https://example.org/404')
.expect(404, next)
},
(next) => {
nock('https://example.org').get('/401').reply(401)
server.get('/proxy/?uri=https://example.org/401')
.expect(401, next)
},
(next) => {
nock('https://example.org').get('/500').reply(500)
server.get('/proxy/?uri=https://example.org/500')
.expect(500, next)
},
(next) => {
nock('https://example.org').get('/').reply(200)
server.get('/proxy/?uri=https://example.org/')
.expect(200, next)
}
], done)
it('should return the same HTTP status code as the uri', () => {
nock('https://example.org')
.get('/404').reply(404)
.get('/401').reply(401)
.get('/500').reply(500)
.get('/200').reply(200)

return Promise.all([
server.get('/proxy/?uri=https://example.org/404').expect(404),
server.get('/proxy/?uri=https://example.org/401').expect(401),
server.get('/proxy/?uri=https://example.org/500').expect(500),
server.get('/proxy/?uri=https://example.org/200').expect(200)
])
})

it('should work with cors', (done) => {
Expand Down