Не так давно я написал в Твиттере то, что я почувствовал, как маленький триумф в моем последнем проекте — потоковую передачу файлов из MongoDB GridFS для загрузки (вместо того, чтобы извлекать весь файл в память и затем подавать его). Я обещал написать в блоге об этом, но, к сожалению, мое конкретное использование было немного связано с доменом в моем проекте, поэтому я не мог просто показать его как есть. Поэтому я собрал пример приложения node.js + GridFS и поделился им с GitHub, и буду использовать этот пост, чтобы объяснить, как я это сделал.
Модуль GridFS
Прежде всего, специальный реквизит идет к tjholowaychuk, который ответил в канале # node.js irc, когда я спросил, повезло ли кому-нибудь использовать GridFS из mongoose . Большая часть моего результирующего кода получена из сущности, которой он поделился со мной. Во всяком случае, к коду. Я опишу, как я использую gridfs, и после установки основы проиллюстрирую, как просто передавать файлы из GridFS.
Я создал модуль gridfs, который в основном обращается к GridStore через mongoose (который я использую во всем приложении), который также может использовать соединение db, созданное при подключении mongoose к серверу mongodb.
mongoose = require "mongoose" request = require "request" GridStore = mongoose.mongo.GridStore Grid = mongoose.mongo.Grid ObjectID = mongoose.mongo.BSONPure.ObjectID
Мы не можем получить файлы из mongodb, если не можем что-то в них поместить, поэтому давайте создадим операцию putFile.
exports.putFile = (path, name, options..., fn) -> db = mongoose.connection.db options = parse(options) options.metadata.filename = name new GridStore(db, name, "w", options).open (err, file) -> return fn(err) if err file.writeFile path, fn parse = (options) -> opts = {} if options.length > 0 opts = options[0] if !opts.metadata opts.metadata = {} opts
Это действительно просто делегирует операции putFile, которая существует в GridStore как часть модуля mongodb. У меня также есть небольшая логика для анализа параметров, предоставляя значения по умолчанию, если они не были предоставлены. Одна интересная особенность, которую следует отметить, заключается в том, что я сохраняю имя файла в метаданных, потому что в то время я столкнулся с забавной проблемой, когда файлы, извлеченные из gridFS, имели идентификатор в качестве имени файла (хотя взгляд на монго показывает, что имя файла на самом деле база данных).
Теперь операция get. Первоначальная реализация этого просто передавала содержимое в виде буфера предоставленному обратному вызову, вызывая store.readBuffer (), но теперь это изменяется, чтобы передать полученный объект хранилища обратному вызову. Значение в том, что вызывающая сторона может использовать объект хранилища для доступа к метаданным, contentType и другим деталям. Пользователь также может определить, как он хочет прочитать файл (либо в память, либо с помощью ReadableStream).
exports.get = (id, fn) -> db = mongoose.connection.db id = new ObjectID(id) store = new GridStore(db, id, "r", root: "fs" ) store.open (err, store) -> return fn(err) if err # band-aid if "#{store.filename}" == "#{store.fileId}" and store.metadata and store.metadata.filename store.filename = store.metadata.filename fn null, store
У этого кода есть небольшая ошибка в том, что он проверяет, совпадают ли имя файла и fileId. Если это так, то он проверяет, установлено ли metadata.filename, и устанавливает store.filename в найденное там значение. Я поставил эту проблему на обсуждение позже.
Модель
В моем конкретном случае я хотел прикрепить файлы к модели. В этом примере давайте представим, что у нас есть Приложение для чего-либо (работа, заявка на кредит и т. Д.), К которому мы можем прикрепить любое количество файлов. Подумайте о налоговых поступлениях, заполненном заявлении, других отсканированных документах.
ApplicationSchema = new mongoose.Schema( name: String files: [ mongoose.Schema.Mixed ] ) ApplicationSchema.methods.addFile = (file, options, fn) -> gridfs.putFile file.path, file.filename, options, (err, result) => @files.push result @save fn
Здесь я определяю файлы как массив типов объектов Mixed (то есть они могут быть чем угодно) и метод addFile, который в основном принимает объект, который по крайней мере содержит атрибут пути и имени файла. Он использует это для сохранения файла в gridfs и сохраняет полученный объект файла gridstore в массиве файлов (он содержит такие вещи, как id, uploadDate, contentType, name, size и т. Д.).
Обработка запросов
Это все подключается к обработчику запросов для обработки отправки формы в / new. Все это влечет за собой создание экземпляра модели приложения, добавление загруженного файла из запроса (в данном случае мы назвали поле файла «file», отсюда и req.files.file) и сохранение его.
app.post "/new", (req, res) -> application = new Application() application.name = req.body.name opts = content_type: req.files.file.type application.addFile req.files.file, opts, (err, result) -> res.redirect "/"
Теперь сумма всей этой работы позволяет нам пожинать плоды, упрощая загрузку требуемого файла из gridFS.
app.get "/file/:id", (req, res) -> gridfs.get req.params.id, (err, file) -> res.header "Content-Type", file.type res.header "Content-Disposition", "attachment; filename=#{file.filename}" file.stream(true).pipe(res)
Здесь мы просто ищем файл по идентификатору и используем полученный объект файла для установки полей Content-Type и Content-Disposition и, наконец, используем ReadableStream :: pipe для записи файла в объект ответа (который является экземпляром WritableStream ). Это волшебство, которое передает данные из MongoDB на клиентскую сторону.
идеи
Это просто скромное начало. Другие идеи включают в себя полностью инкапсулирование gridfs внутри модели. Продвигаясь дальше, мы можем даже превратить модель gridfs в плагин mongoose, чтобы полностью использовать gridfs в черном ящике.
Не стесняйтесь проверить проект и дайте мне знать, если у вас есть идеи, чтобы пойти еще дальше. Вилка прочь!
Источник: http://blog.james-carr.org/2012/01/09/streaming-files-from-mongodb-gridfs/