通过带有 multipart/form-data 的表单发送的文件作为空对象发送
Posted
技术标签:
【中文标题】通过带有 multipart/form-data 的表单发送的文件作为空对象发送【英文标题】:Files sent through form with multipart/form-data are sent as empty object 【发布时间】:2018-10-19 01:08:32 【问题描述】:我正在创建一个 VueJS 组件,它是一个询问用户信息并允许上传多个图像的表单。
表单有 enctype="multipart/form-data" 并且看起来像这样:
<template>
<div>
<form enctype="multipart/form-data" novalidate>
<label>Name</label>
<input type="text" v-model="name">
<label>Surname</label>
<input type="text" v-model="surname">
<label> Description </label>
<input type="text" v-model="description">
<input
type="file"
id="resources"
ref="images"
multiple
@change="handleFileUpload()"
accept="image/*">
<button @click="clear">Clear Form</button>
<button @click="submit"> Submit </button>
</div>
</form>
</template>
我的函数 handleFileUpload() 是这样做的:
this.images = this.$refs.images.files
我的提交函数如下所示:
submit ()
// Saves image to formData
let formData = new FormData()
for( var i = 0; i < this.images.length; i++ )
let img = this.images[i];
formData.append('image[' + i + ']', img);
let newUser =
name : this.name,
surname:this.surname,
description : this.description,
images : formData
axios.post('http://localhost:3000/api/users', newUser)
.then(()=>console.log('ok'))
.catch((err)=>console.log(err))
我的问题是来自 newUser 的 images 字段作为空对象发送。我不确定我是否应该发送这样的 formData 或者我为此构建的 express API 存在一些问题:
var express = require('express');
var cors = require('cors');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded(
extended: true
));
app.use(cors());
let users = [],
app.post('/api/users',(req,res) =>
const newUser = req.body
users.push(newUser)
res.json(users)
API 中 newUser 的 console.log 会返回:
name: 'test',
surname: 'test',
description: 'test',
images:
【问题讨论】:
【参考方案1】:我也在为我的 api 服务器项目中的 empty request body 苦苦挣扎。 我想将由字符串/文本数据和图像组成的多部分形式数据发布到后端 RESTful api 服务器。我认为仅通过 Axios 使用 formData 对象传递数据就足够了。然而,我最终几乎感到沮丧,因为在后端的 restful api 项目中,我一直得到空的请求正文。谢天谢地,通过搜索一些文章、*** 和 youtube。我设法组装了一个完整的 node.js 程序。不再有空的请求正文数据!!! . 在这里我想分享一下我的工作代码。
为了将 multipart/form-data 从客户端传递到服务器,我发现我们使用了几个中间件和 node/java 对象:
在服务器端 api 项目中:我使用 multer, body-parser 中间件 在客户端应用项目中:我使用 formData 和 axios这是我的工作代码:(在 node.js 中编码,使用 Visual Studio 代码)
// in my (server backend api) node project : /src/routes/products/index.js
const express = require("express")
const router = express.Router()
const multer = require('multer')
const ProductsController = require('../controllers/products')
const storage = multer.diskStorage(
destination: function(req, file, cb)
cb(null, './uploads/')
,
filename: function(req, file, cb)
// use this on Windows dir
cb(null, new Date().toISOString().replace(/:/g, '-') + file.originalname)
// otherwise (in Linux or IOS use this
// cb(null, new Date().toISOString() + file.originalname)
)
const fileFilter = (req, file, cb) =>
// reject a file if not allowed
var allowedMimes = ['image/jpeg','image/png' , 'image/gif']
if (allowedMimes.indexOf(file.mimetype) > -1)
cb(null, true)
else
cb(null, false)
const upload = multer(
storage: storage,
limits: fileSize: 1024 * 1024 * 10,
fileFilter: fileFilter
)
router.post("/", upload.single('productImage'),ProductsController.products_create_product)
module.exports = router
// end of /src/routes/products/index.js
现在在(服务器后端 api)/src/app.js 中:
const express = require("express")
const morgan = require("morgan")
const bodyParser = require("body-parser")
const mongoose = require("mongoose")
const productRoutes = require("./routes/products")
const app = express()
mongoose.connect('mongodb://localhost/shopcart',
useMongoClient: true
)
var db = mongoose.connection
db.on('error', console.error.bind(console, 'connection error:'))
db.once('open', function()
// we're connected!
console.log("Connected to mongoDB..."+ db.db.databaseName+ ', url: ' +'http://localhost:5000')
)
mongoose.Promise = global.Promise
app.use(morgan("dev"))
app.use('/uploads', express.static('uploads'))
app.use(bodyParser.urlencoded( extended: false ))
app.use(bodyParser.json())
app.use((req, res, next) =>
res.header("Access-Control-Allow-Origin", "*")
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
)
if (req.method === "OPTIONS")
res.header("Access-Control-Allow-Methods", "PUT, POST, PATCH, DELETE, GET")
return res.status(200).json()
next()
)
// Routes which should handle requests
app.use("/products", productRoutes)
app.use((req, res, next) =>
const error = new Error("api Not found...")
error.status = 404
next(error)
)
app.use((error, req, res, next) =>
res.status(error.status || 500)
res.json(
error:
message: error.message
)
)
module.exports = app
//end of : /src/app.js
现在在(服务器后端 api)/src/index.js 中:
const http = require('http')
const app = require('./app.js')
const ip_addr = 'localhost'
const port = process.env.PORT || 3000
const server = http.createServer(app)
server.listen(port , ip_addr)
console.log('Server started on %s port: %s',ip_addr , port )
// end of : /src/index.js
(后端 api)中 ProductController 的代码:/src/controller/Products.js: (请注意,req.file 是由 'multer' 中间件自动添加的)
exports.products_create_product = (req, res, next) =>
// note : productImage: req.file.path
// (file) is added by 'multer' middleware
const product = new Product(
_id: new mongoose.Types.ObjectId(),
name: req.body.name,
price: req.body.price,
specification: req.body.specification,
productImage: req.file.path
)
product
.save()
.then(result =>
res.status(201).json(
message: "Product created successfully",
createdProduct:
name: result.name,
price: result.price,
specification: result.specification,
_id: result._id,
productImage: result.productImage,
request:
type: "POST",
url: "http://localhost:5000/products/"
)
)
.catch(err =>
res.status(500).json(
error: err
)
)
最后在我的客户端应用程序项目中,我使用 Axios 和 formData 将 post 请求发送到后端 api 服务器。
这是前端客户端应用程序的代码摘录:
function createProduct (payload, apiUrl, myAuthToken)
const headers =
'Content-Type': 'multipart/form-data',
// 'Authorization': 'Bearer ' + myAuthToken // if using JWT
var formData = new FormData()
formData.append( 'productImage', payload.productImage)
formData.append( 'name', payload.name)
formData.append( 'price', payload.price)
formData.append( 'specification', payload.specification)
Axios.post(apiUrl + '/products',
formData,
'headers' : headers
)
.then((response) =>
var imgUrl = response.data.createdProduct.productImage
payload.productImage = apiUrl+'/'+ imgUrl.replace(/\\/g, "/")
payload.id= response.data.createdProduct._id
payload.specification = response.data.createdProduct.specification
payload.price =response.data.createdProduct.price
commit('createProduct', payload)
)
.catch((error) =>
alert(error)
)
以下函数可用于从输入的 html 事件中提取图像数据:
function onFilePicked (event)
const files = event.target.files
if (!files)
return
let filename = files[0].name
if (filename.lastIndexOf('.') <= 0)
return alert('Please add a valid file!')
const fileReader = new FileReader()
fileReader.addEventListener('load', () =>
this.productImage = fileReader.result
)
fileReader.readAsDataURL(files[0])
return files[0]
我希望这些代码能帮助任何遇到空请求正文问题的人。
【讨论】:
【参考方案2】:body-parser 模块不支持 multipart/form-data,正如其文档中所说的那样(如果您是 nodejs 新手,这可能很难弄清楚)。
您需要找到一些可以为您处理此问题的中间件。 The body-parser documentation recommends a few:
这不处理多部分实体,因为它们复杂且 通常大的性质。对于多部分实体,您可能对 以下模块:
busboy 和 connect-busboy multiparty 和 connect-multiparty formidable multer
每个库的工作方式略有不同,因此您应该使用它们的文档在服务器端构建您的 images
对象。
【讨论】:
以上是关于通过带有 multipart/form-data 的表单发送的文件作为空对象发送的主要内容,如果未能解决你的问题,请参考以下文章
带有 multipart/form-data 的请求返回 415 错误
为啥在使用 multipart/form-data 时不能正确发送带有 Unicode 的 POST 名称?
为啥在使用 multipart/form-data 时不能正确发送带有 Unicode 的 POST 名称?
从 multipart/form-data 接收带有 servicestack 的文件
NativeScript Vue 发送带有表单数据的请求(multipart/form-data)
带有 enctype="multipart/form-data" 的 Spring Security CSRF [重复]