6

I've been writing an app in Node.js that stores images in MongoDB's GridFS file system.

I've uploaded images through the app, and the images appear to be stored correctly:

$ mongofiles -v -d speaker-karaoke get howard-basement-100x115.jpg
Tue Jul 17 12:14:16 creating new connection to:127.0.0.1
Tue Jul 17 12:14:16 BackgroundJob starting: ConnectBG
Tue Jul 17 12:14:16 connected connection!
connected to: 127.0.0.1
done write to: howard-basement-100x115.jpg

This grabbed the .jpg out of MongoDB and I was able to open it no problem, so it looks like what I'm uploading is being stored correctly.

However, in my running application, when I attempt to read the same file, I get:

12:15:44 web.1     | started with pid 89621
12:15:45 web.1     | Connecting to mongodb://localhost/speaker-karaoke
12:15:45 web.1     | Speaker Karaoke express app started on 5000
12:15:48 web.1     | DEBUG: Get review thumbnail for 5005b7550333650000000001
12:15:48 web.1     | 
12:15:48 web.1     | node.js:201
12:15:48 web.1     |         throw e; // process.nextTick error, or 'error' event on first tick
12:15:48 web.1     |               ^
12:15:48 web.1     | Error: Illegal chunk format
12:15:48 web.1     |     at Error (unknown source)
12:15:48 web.1     |     at new <anonymous> (/Users/hlship/workspaces/github/speaker-karaoke/node_modules/mongodb/lib/mongodb/gridfs/chunk.js:43:11)
12:15:48 web.1     |     at /Users/hlship/workspaces/github/speaker-karaoke/node_modules/mongodb/lib/mongodb/gridfs/gridstore.js:488:24
12:15:48 web.1     |     at Cursor.nextObject (/Users/hlship/workspaces/github/speaker-karaoke/node_modules/mongoose/node_modules/mongodb/lib/mongodb/cursor.js:462:5)
12:15:48 web.1     |     at [object Object].<anonymous> (/Users/hlship/workspaces/github/speaker-karaoke/node_modules/mongoose/node_modules/mongodb/lib/mongodb/cursor.js:456:12)
12:15:48 web.1     |     at [object Object].g (events.js:156:14)
12:15:48 web.1     |     at [object Object].emit (events.js:88:20)
12:15:48 web.1     |     at Db._callHandler (/Users/hlship/workspaces/github/speaker-karaoke/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1290:25)
12:15:48 web.1     |     at /Users/hlship/workspaces/github/speaker-karaoke/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:329:30
12:15:48 web.1     |     at [object Object].parseBody (/Users/hlship/workspaces/github/speaker-karaoke/node_modules/mongoose/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js:118:5)
12:15:48 web.1     | process terminated
12:15:48 system    | sending SIGTERM to all processes

Using this code (CoffeeScript, for Express):

  app.get "/images/review-thumbnail/:id", (req, res) ->

    id = req.params.id

    util.debug "Get review thumbnail for #{id}"

    store = new GridStore mongoose.connection.db, new ObjectID(id), null, "r"

    store.open (err, file) ->

      throw err if err

      util.debug "Store open for #{id}, type = #{file.contentType}"

      # TODO: Scale the image before sending it!

      res.header "Content-Type", file.contentType

      store.stream(true).pipe res

So it doesn't seem to be even making it to the callback passed to store.open().

Is there a problem opening a GridFS file when you know the id, but not the file name?

BTW:

$ npm ls
speaker-karaoki@0.0.1 /Users/hlship/workspaces/github/speaker-karaoke
├─┬ blueimp-file-upload-node@1.0.2  extraneous
│ ├── formidable@1.0.11 
│ ├── imagemagick@0.1.2 
│ └── node-static@0.6.0 
├── coffee-script@1.3.3 
├─┬ connect-assets@2.1.9 
│ ├── connect-file-cache@0.2.4 
│ ├── mime@1.2.2 
│ ├─┬ snockets@1.3.4 
│ │ ├── coffee-script@1.3.3 
│ │ ├── dep-graph@1.0.1 
│ │ └── uglify-js@1.0.7 
│ └── underscore@1.1.7 
├─┬ express@2.5.9 
│ ├─┬ connect@1.9.1 
│ │ └── formidable@1.0.11 
│ ├── mime@1.2.4 
│ ├── mkdirp@0.3.0 
│ └── qs@0.4.2 
├─┬ jade@0.26.0 
│ ├── commander@0.5.2 
│ └── mkdirp@0.3.0 
├─┬ mongodb@1.0.2 
│ └── bson@0.0.6 
├─┬ mongoose@2.7.0 
│ ├── hooks@0.2.1 
│ └─┬ mongodb@1.0.2 
│   └── bson@0.0.6 
├─┬ passport@0.1.10 
│ └── pkginfo@0.2.3 
├─┬ passport-twitter@0.1.3 
│ ├─┬ passport-oauth@0.1.9 
│ │ ├── oauth@0.9.7 
│ │ └── passport@0.1.11 
│ └── pkginfo@0.2.3 
├── sass@0.5.0 
└── underscore@1.3.3 

And here's the function where it is failing:

var Chunk = exports.Chunk = function(file, mongoObject) {
  if(!(this instanceof Chunk)) return new Chunk(file, mongoObject);

  this.file = file;
  var self = this;
  var mongoObjectFinal = mongoObject == null ? {} : mongoObject;

  this.objectId = mongoObjectFinal._id == null ? new ObjectID() : mongoObjectFinal._id;
  this.chunkNumber = mongoObjectFinal.n == null ? 0 : mongoObjectFinal.n;
  this.data = new Binary();

  if(mongoObjectFinal.data == null) {
  } else if(typeof mongoObjectFinal.data == "string") {
    var buffer = new Buffer(mongoObjectFinal.data.length);
    buffer.write(mongoObjectFinal.data, 'binary', 0);
    this.data = new Binary(buffer);
  } else if(Array.isArray(mongoObjectFinal.data)) {
    var buffer = new Buffer(mongoObjectFinal.data.length);
    buffer.write(mongoObjectFinal.data.join(''), 'binary', 0);
    this.data = new Binary(buffer);
  } else if(mongoObjectFinal.data instanceof Binary || Object.prototype.toString.call(mongoObjectFinal.data) == "[object Binary]") {    
    this.data = mongoObjectFinal.data;
  } else if(Buffer.isBuffer(mongoObjectFinal.data)) {
  } else {
    throw Error("Illegal chunk format");
  }
  // Update position
  this.internalPosition = 0;
};

Solution

Updating the solution here as it does not render correctly in the comments below.

The problem was the duplication; having two copies, even with the same version, of mongodb and bson.

Fortunately, mongoose exports the mongodb it requires as property mongo, so I was able to remove the explicit mongodb from my package.json and changed:

mongo = require "mongodb"
mongoose = require "mongoose"

to:

mongoose = require "mongoose"
mongo = mongoose.mongo

Things are now looking nice; I still think the module system needs a sanctioned way to access a dependencies dependencies (for the case where a dep is not thoughtful enough to expose its deps).

Howard M. Lewis Ship
  • 2,247
  • 15
  • 23
  • Are you writing the GridFS file with node/mongo, or some other way? – MrKurt Jul 17 '12 at 19:31
  • I'm writing to GridFS using node/mongo; I can show that code if it helps, but the fact that the mongofiles command line tool can read the file contents correctly makes me think I'm doing it ok. – Howard M. Lewis Ship Jul 17 '12 at 19:33
  • Yeah, just double checking. There's no real standard for GridFS so I was wondering if it were a driver inconsistency. Since you're writing with the Node driver, though, I don't think that's it. – MrKurt Jul 17 '12 at 20:18
  • Changed my code to generate a file name based on the ObjectID. The file name was used when the GridStore is created for both write and read. However, this didn't change the behavior, so the file name (or lack) is not the really issue. – Howard M. Lewis Ship Jul 17 '12 at 21:04
  • Hm. Seems to be working now. I just went through some aggressive npm work; deleting node_modules and re-running npm install. I know that I had two competing copies of the mongodb module and that may have caused an issue. I want to research this further. – Howard M. Lewis Ship Jul 17 '12 at 21:59
  • As noted below, it is back. Using the debugger, I can see that mongoObjectFinal.data is type Binary, but fails the "instanceof Binary" check, and I don't know why. I suspect there are two different version of Binary around. – Howard M. Lewis Ship Sep 20 '12 at 20:50
  • thanks, i run into this issue after updating my app today, your solution is working for me. – supernova Apr 19 '13 at 14:12
  • Thanks for posting your solution. I encountered this problem tonight, and your resolution cleared it right up for me. – Tharsan Sep 11 '13 at 03:04

1 Answers1

2

Pretty sure it was duplicate copies (same version) of mongodb module; removing node_modules and nmp install seems to have fixed it.

Howard M. Lewis Ship
  • 2,247
  • 15
  • 23