2017-08-29 97 views
6

我試圖從移動應用程序上載圖像文件(這是寫在原生態,現在在iOS上運行)。NodeJS/Restify:如何在API中接收文件上傳?

該文件發送到我的REST API,如下所示。我有兩個問題,這:

  1. 我不明白req.body,因爲它始終是一個空的對象,雖然頭部被正確提交。
  2. 我想通過gridfs-stream將接收到的文件寫入我的數據庫(GridFS),但我不明白在哪裏放置該代碼。

API

const restify = require('restify') 
const winston = require('winston') 
const bunyanWinston = require('bunyan-winston-adapter') 
const mongoose = require('mongoose') 
const Grid = require('gridfs-stream') 
const config = require('../config') 

// Configure mongoose to work with javascript promises 
mongoose.Promise = global.Promise 

// Setting up server 
const server = restify.createServer({ 
    name: config.name, 
    version: config.version, 
    log: bunyanWinston.createAdapter(log) 
}) 

server.use(restify.plugins.multipartBodyParser()) 

server.listen(config.port,() => { 
    mongoose.connection.on('open', (err) => { 
    server.post('/upload', (req, res, next) => { 
     console.log(req.headers) // <- returns headers as expected 

     /* Problem 1 */ 
     console.log(req.body) // <- is empty object (unexpected) 
     res.send(200, { message: 'successful upload' }) 
     res.end() 
    }) 
    }) 

    global.db = mongoose.connect(config.db.uri, { useMongoClient: true }) 

    /* Problem 2: The recieved file should be stored to DB via `gridfs-stream` */ 
    // I think this is the wrong place for this line... 
    var gfs = Grid(global.db, mongoose.mongo) 
}) 

我試圖找到的錯誤,但我沒有找到它,所以這裏的數據,這是我得到我的API:

{ 
    host: 'localhost:3000', 
    'content-type': 'multipart/form-data; boundary=pUqK6oKvY65OfhaQ3h01xWg0j4ajlanAA_e3MXVSna4F8kbg-zT0V3-PeJQm1QZ2ymcmUM', 
    'user-agent': 'User/1 CFNetwork/808.2.16 Darwin/15.6.0', 
    connection: 'keep-alive', 
    accept: '*/*', 
    'accept-language': 'en-us', 
    'accept-encoding': 'gzip, deflate', 
    'content-length': '315196' 
} 

身體

{ } 

爲什麼body爲空?


陣營本地文件上傳

這是我如何將文件發送到API。我還告訴你一些變量的內容:

async function upload (photo) { 
    console.log('photo', photo); // OUTPUT SHOWN BELOW 
    if (photo.uri) { 
    // Create the form data object 
    var data = new FormData() 
    data.append('picture', { uri: photo.uri, name: 'selfie.jpg', type: 'image/jpg' }) 

    // Create the config object for the POST 
    const config = { 
     method: 'POST', 
     headers: { 
     'Accept': 'application/json' 
     }, 
     body: data 
    } 
    console.log('config', config); // OUTPUT SHOWN BELOW 

    fetchProgress('http://localhost:3000/upload', { 
     method: 'post', 
     body: data 
    }, (progressEvent) => { 
     const progress = progressEvent.loaded/progressEvent.total 
     console.log(progress) 
    }).then((res) => console.log(res), (err) => console.log(err)) 
    } 
} 

const fetchProgress = (url, opts = {}, onProgress) => { 
    console.log(url, opts) 
    return new Promise((resolve, reject) => { 
    var xhr = new XMLHttpRequest() 
    xhr.open(opts.method || 'get', url) 
    for (var k in opts.headers || {}) { 
     xhr.setRequestHeader(k, opts.headers[k]) 
    } 
    xhr.onload = e => resolve(e.target) 
    xhr.onerror = reject 
    if (xhr.upload && onProgress) { 
     xhr.upload.onprogress = onProgress // event.loaded/event.total * 100 ; //event.lengthComputable 
    } 
    xhr.send(opts.body) 
    }) 
} 

照片

{ 
    fileSize: 314945, 
    origURL: 'assets-library://asset/asset.JPG?id=106E99A1-4F6A-45A2-B320-B0AD4A8E8473&ext=JPG', 
    longitude: -122.80317833333334, 
    fileName: 'IMG_0001.JPG', 
    height: 2848, 
    width: 4288, 
    latitude: 38.0374445, 
    timestamp: '2011-03-13T00:17:25Z', 
    isVertical: false, 
    uri: 'file:///Users/User/Library/Developer/CoreSimulator/Devices/D3FEFFA8-7446-42AB-BC7E-B6EB88DDA840/data/Containers/Data/Application/17CE8C0A-B781-4E56-9347-857E74055119/Documents/images/69C2F27F-9EEE-4611-853E-FC7FF6E5C373.jpg' 
} 

配置

'http://localhost:3000/upload', 
{ 
    method: 'post', 
    body: 
    { 
     _parts: 
     [ 
      [ 'picture', 
      { uri: 'file:///Users/User/Library/Developer/CoreSimulator/Devices/D3FEFFA8-7446-42AB-BC7E-B6EB88DDA840/data/Containers/Data/Application/17CE8C0A-B781-4E56-9347-857E74055119/Documents/images/69C2F27F-9EEE-4611-853E-FC7FF6E5C373.jpg', 
       name: 'selfie.jpg', 
       type: 'image/jpg' } 
      ] 
     ] 
    } 
} 

我覺得data(應該在config被髮送的體)格式不正確。爲什麼數組中有一個數組?

+0

不是一個直接的答案,但我認爲純JavaScript會少一些高性能的,所以我建議使用反應原生取二進制大對象,其中有上傳進度反饋也。 – eden

+0

@Eden:也許你可以發佈一些代碼,上面的代碼應該如何與react-native-fetch-blob一起完成......? – user3142695

+0

當然,給我幾分鐘。 – eden

回答

-3

你需要的base64編碼所述圖像,然後發送該體作爲JSON(與頭的Content-Type設定爲application/JSON)

+0

你能告訴我一個代碼示例嗎? – user3142695

+0

我認爲這個鏈接有很多可能對你有用的例子 - https://github.com/react-community/react-native-image-picker/issues/61 –

+1

你不必使用base64encode:https: //snowball.digital/Blog/Uploading-Images-in-React-Native – user3142695

1

下面的示例使用react-native-fetch-blob在陣營原生部分,和使用Express的NodeJS和Formidable解析在服務器端的形式。

讓我們首先將文件上傳確定用戶是否已上傳的照片或視頻之後:

RNFetchBlob.fetch(
    'POST', 
    Constants.UPLOAD_URL + '/upload', 
    { 
    'Content-Type': 'multipart/form-data' 
    }, 
    [ 
    { 
     name: this.state.photoURL ? 'image' : 'video', 
     filename: 'avatar-foo.png', 
     type: 'image/foo', 
     data: RNFetchBlob.wrap(dataPath) 
    }, 
    // elements without property `filename` will be sent as plain text 
    { name: 'email', data: this.props.email }, 
    { name: 'title', data: this.state.text } 
    ] 
) 
    // listen to upload progress event 
    .uploadProgress((written, total) => { 
    console.log('uploaded', written/total); 
    this.setState({ uploadProgress: written/total }); 
    }) 
    // listen to download progress event 
    .progress((received, total) => { 
    console.log('progress', received/total); 
    }) 
    .then(res => { 
    console.log(res.data); // we have the response of the server 
    this.props.navigation.goBack(); 
    }) 
    .catch(err => { 
    console.log(err); 
    }); 
}; 

類似地,接收文件,並相應地加載數據:

exports.upload = (req, res) => { 
    var form = new formidable.IncomingForm(); 
    let data = { 
    email: '', 
    title: '', 
    photoURL: '', 
    videoURL: '', 
    }; 

    // specify that we want to allow the user to upload multiple files in a single request 
    form.multiples = true; 
    // store all uploads in the /uploads directory 
    form.uploadDir = path.join(__dirname, '../../uploads'); 

    form.on('file', (field, file) => { 
    let suffix = field === 'image' ? '.png' : '.mp4'; 
    let timestamp = new Date().getTime().toString(); 

    fs.rename(file.path, path.join(form.uploadDir, timestamp + suffix)); //save file with timestamp. 

    data[field === 'image' ? 'photoURL' : 'videoURL'] = timestamp + suffix; 
    }); 
    form.on('field', (name, value) => { 
    data[name] = value; 
    }); 
    form.on('error', err => { 
    console.log('An error has occured: \n ' + err); 
    }); 
    form.on('end',() => { 
    // now we have a data object with fields updated. 
    }); 
    form.parse(req); 
}; 

並使用控制器功能:

let route = express.Router(); 
// other controller functions... 
route.post('/upload', uploadController.upload); 
app.use(route); 

請確保您閱讀了代碼中包含的註釋。 數據路徑是使用react-native-image-picker後創建的媒體路徑(不是base64字符串)。您可以使用react-native-progress來顯示上傳進度。

退房反應天然取入團塊用於進一步參考的multipartform數據部分:https://github.com/wkh237/react-native-fetch-blob#multipartform-data-example-post-form-data-with-file-and-data

+0

我曾經想過要收到這個文件,並直接將它通過gridfs-stream存儲到數據庫中,而沒有將文件存儲在任何目錄中......是那個可能。? – user3142695

+0

爲什麼不。這是關於後端設計。對於原生反應,取blob是上傳數據的正確方法。 – eden

+0

接收文件中的「路徑」是什麼? – user3142695

0

可以使用co-busboy節點模塊寫一箇中間件,這是koa一個例子:

首先,您需要通過npmyarn安裝co-busboy

npm i co-busboy -Syarn add co-busboy

// upload.js 
 
var parse = require('co-busboy') 
 
var fs = require('fs') 
 
var path = require('path') 
 
var upload = function * (next) { 
 
    var parts = parse(this, { 
 
    autoFields: true 
 
    }) 
 
    while(var part = yield parts) { 
 
    part.pipe(fs.createReadStream(path.join('uploadPath', part.filename))) 
 
    } 
 
    yield next 
 
} 
 

 
module.exports =() => upload 
 

 

 
// app.js 
 

 
var upload = require('upload') 
 
app.use(upload())

參考:

co-busboy