Изображение, возвращаемое из REST API, всегда отображается поврежденным - PullRequest
0 голосов
/ 14 сентября 2018

Я создаю систему управления контентом для приложения из портфолио с React.Клиент отправит POST API, который использует Mongoose для вставки в MongoDB.Затем API запрашивает в БД вновь вставленное изображение и возвращает его клиенту.

Вот мой код для подключения к MongoDB с использованием Mongoose:

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))
  }

Это успешно помещает как возвращенный файл, так и URL-адрес данных img в состояние,Тем не менее, в моем приложении изображение всегда отображается неработающим.

Вот несколько снимков экрана:

correct file is written to disk on server

file is returned from API and placed on application state

returned image is always broken, no matter what image I upload

Как это исправить?

1 Ответ

0 голосов
/ 14 сентября 2018

Избегайте отправки обратно изображений в кодировке base64 (несколько изображений + большие файлы + большие закодированные строки = очень низкая производительность). Я настоятельно рекомендую создать микросервис, который обрабатывает только загрузку изображений и любые другие связанные с изображениями запросы get / post / put / delete. Отделите его от основного приложения.

Например:

  • Я использую Multer для создания буфера изображения
  • Затем используйте Sharp или Fs для сохранения изображения (в зависимости от типа файла)
  • Затем я отправляю путь к файлу на мой контроллер для сохранения в моей БД
  • Затем внешний интерфейс выполняет запрос GET при попытке доступа: http://localhost:4000/uploads/timestamp-randomstring-originalname.fileext

Проще говоря, мой микросервис действует как CDN исключительно для изображений.


Например, пользователь отправляет почтовый запрос на http://localhost:4000/api/avatar/create с некоторыми данными формы:

Сначала он проходит через некоторые промежуточные программы Express:

ЛИЭС / 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:

маршруты / avatars.js

app.post(`/api/avatar/create`, requireAuth, saveImage, create);

Затем он проходит некоторую аутентификацию пользователя, а затем проходит через мое saveImage промежуточное ПО:

услуги / 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(); 
        }

Затем, когда клиент пытается получить доступ к папке uploads через <img src={avatarURL} alt="image" /> или <img src="[http://localhost:4000]/uploads/imagefile.ext" alt="image" />, он обслуживается микросервисом:

ЛИЭС / 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:

enter image description here

...