从 REST API 返回的图像总是显示损坏
Posted
技术标签:
【中文标题】从 REST API 返回的图像总是显示损坏【英文标题】:Image returned from REST API always displays broken 【发布时间】:2019-02-18 18:14:11 【问题描述】:我正在使用 React 为艺术作品集应用程序构建内容管理系统。客户端将 POST 到使用 Mongoose 插入 MongoDB 的 API。然后 API 会在数据库中查询新插入的图像,并将其返回给客户端。
这是我使用 Mongoose 连接到 MongoDB 的代码:
mongoose.connect('mongodb://localhost/test').then(() =>
console.log('connected to db')).catch(err => console.log(err))
mongoose.Promise = global.Promise
const db = mongoose.connection
db.on('error', console.error.bind(console, 'MongoDB connection error:'))
const Schema = mongoose.Schema;
const ImgSchema = new Schema(
img: data: Buffer, contentType: String
)
const Img = mongoose.model('Img', ImgSchema)
我正在使用 multer 和 fs 来处理图像文件。我的 POST 端点如下所示:
router.post('/', upload.single('image'), (req, res) =>
if (!req.file)
res.send('no file')
else
const imgItem = new Img()
imgItem.img.data = fs.readFileSync(req.file.path)
imgItem.contentType = 'image/png'
imgItem
.save()
.then(data =>
Img.findById(data, (err, findImg) =>
console.log(findImg.img)
fs.writeFileSync('api/uploads/image.png', findImg.img.data)
res.sendFile(__dirname + '/uploads/image.png')
))
)
我可以在文件结构中看到 writeFileSync 正在将图像写入磁盘。 res.sendFile 抓取它并将其发送给客户端。
客户端代码如下所示:
handleSubmit = e =>
e.preventDefault()
const img = new FormData()
img.append('image', this.state.file, this.state.file.name)
axios
.post('http://localhost:8000/api/gallery', img,
onUploadProgress: progressEvent =>
console.log(progressEvent.loaded / progressEvent.total)
)
.then(res =>
console.log('responsed')
console.log(res)
const returnedFile = new File([res.data], 'image.png', type: 'image/png' )
const reader = new FileReader()
reader.onloadend = () =>
this.setState( returnedFile, returned: reader.result )
reader.readAsDataURL(returnedFile)
)
.catch(err => console.log(err))
这确实成功地将返回的文件和 img 数据 url 置于状态。但是,在我的应用程序中,图像总是显示损坏。
以下是一些截图:
如何解决这个问题?
【问题讨论】:
【参考方案1】:避免发回 base64 编码图像(多个图像 + 大文件 + 大编码字符串 = 性能非常慢)。我强烈建议创建一个只处理图像上传和任何其他与图像相关的 get/post/put/delete 请求的微服务。将其与您的主应用程序分开。
例如:
我使用 multer 创建图像缓冲区 然后使用 sharp 或 fs 保存图像(取决于文件类型) 然后我将文件路径发送到我的控制器以保存到我的数据库中 然后,前端在尝试访问时会发出 GET 请求:http://localhost:4000/uploads/timestamp-randomstring-originalname.fileext
简单来说,我的微服务就像一个专门用于图像的 CDN。
例如,用户向http://localhost:4000/api/avatar/create
发送带有一些FormData 的post 请求:
它首先通过一些 Express 中间件:
libs/middlewares.js
...
app.use(cors(credentials: true, origin: "http://localhost:3000" )) // allows receiving of cookies from front-end
app.use(morgan(`tiny`)); // logging framework
app.use(multer(
limits:
fileSize: 10240000,
files: 1,
fields: 1
,
fileFilter: (req, file, next) =>
if (!/\.(jpe?g|png|gif|bmp)$/i.test(file.originalname))
req.err = `That file extension is not accepted!`
next(null, false)
next(null, true);
).single(`file`))
app.use(bodyParser.json()); // parses header requests (req.body)
app.use(bodyParser.urlencoded( limit: `10mb`, extended: true )); // allows objects and arrays to be URL-encoded
...etc
然后,点击avatars
路由:
routes/avatars.js
app.post(`/api/avatar/create`, requireAuth, saveImage, create);
然后通过一些用户认证,然后通过我的saveImage
中间件:
services/saveImage.js
const createRandomString = require('../shared/helpers');
const fs = require("fs");
const sharp = require("sharp");
const randomString = createRandomString();
if (req.err || !req.file)
return res.status(500).json( err: req.err || `Unable to locate the requested file to be saved` )
next();
const filename = `$Date.now()-$randomString-$req.file.originalname`;
const filepath = `uploads/$filename`;
const setFilePath = () => req.file.path = filepath; return next();
(/\.(gif|bmp)$/i.test(req.file.originalname))
? fs.writeFile(filepath, req.file.buffer, (err) =>
if (err)
return res.status(500).json( err: `There was a problem saving the image.`);
next();
setFilePath();
)
: sharp(req.file.buffer).resize(256, 256).max().withoutEnlargement().toFile(filepath).then(() => setFilePath())
如果文件被保存,它会发送一个req.file.path
到我的create
控制器。这将作为文件路径和图像路径保存到我的数据库(avatarFilePath
或 /uploads/imagefile.ext
被保存用于删除目的,avatarURL
或 [http://localhost:4000]/uploads/imagefile.ext
被保存并用于前端 GET 请求) :
controllers/avatars.js(我使用的是 Postgres,但你可以替代 Mongo)
create: async (req, res, done) =>
try
const avatarurl = `$apiURL/$req.file.path`;
await db.result("INSERT INTO avatars(userid, avatarURL, avatarFilePath) VALUES ($1, $2, $3)", [req.session.id, avatarurl, req.file.path]);
res.status(201).json( avatarurl );
catch (err) return res.status(500).json( err: err.toString() ); done();
然后,当前端尝试通过<img src=avatarURL alt="image" />
或<img src="[http://localhost:4000]/uploads/imagefile.ext" alt="image" />
访问uploads
文件夹时,它会被微服务提供服务:
libs/server.js
const express = require("express");
const path = app.get("path");
const PORT = 4000;
//============================================================//
// EXPRESS SERVE AVATAR IMAGES
//============================================================//
app.use(`/uploads`, express.static(`uploads`));
//============================================================//
/* CREATE EXPRESS SERVER */
//============================================================//
app.listen(PORT);
记录请求时的样子:
19:17:54 INSERT INTO avatars(userid, avatarURL, avatarFilePath) VALUES ('08861626-b6d0-11e8-9047-672b670fe126', 'http://localhost:4000/uploads/1536891474536-k9c7OdimjEWYXbjTIs9J4S3lh2ldrzV8-android.png', 'uploads/1536891474536-k9c7OdimjEWYXbjTIs9J4S3lh2ldrzV8-android.png')
POST /api/avatar/create 201 109 - 61.614 ms
GET /uploads/1536891474536-k9c7OdimjEWYXbjTIs9J4S3lh2ldrzV8-android.png 200 3027 - 3.877 ms
用户在成功的 GET 请求后看到的内容:
【讨论】:
以上是关于从 REST API 返回的图像总是显示损坏的主要内容,如果未能解决你的问题,请参考以下文章
Onedrive Upload API 上传损坏的文件或图像
即使 url 有效,使用 imgur api 获取损坏的图像
使用 REST API 上传到 Azure Blob 存储时,Zip 档案损坏
通过 REST API 上传多部分文件会损坏文件并增加文件大小