I'm trying to send an image from the server to the client. After some googling, the best solution seems to send the data as ArrayBuffer and then convert it to Blob on FE. However, I'm not able to get the image to display on FE. Am I doing some conversions wrong that might cause the issue?
For the server code:
exports.getImage = async (req, res) => {
try {
const file = fs.readFileSync(`${__dirname}/images/image.png`);
let ab = file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);
return res.status(200).send(ab);
} catch (err) {
console.log(err);
return res.status(400).send({
code: err.errCode,
description: 'General error'
});
}
}
And on the receiving side in angular:
service.ts
getCustomMap(groupId: string, mapId: string): Observable<ArrayBuffer> {
return this._http
.get(`${this._URL}/${groupId}/customMap/${mapId}`, {
responseType: 'arraybuffer'
});
}
image component:
getCustomMap() {
this._groupManagerService.getCustomMap()
.subscribe((imgFile: ArrayBuffer) => {
map.file = new Blob([imgFile], { type: 'image/png' });
map.url = this.sanitizer.bypassSecurityTrustUrl(window.URL.createObjectURL(map.file));
});
}
Thank you
Simply follow the steps below:
1. Server / Node.js:
app.get('/', (req, res) => {
const imageName = "image.png"
const imagePath = path.join(__dirname, "images", imageName);
fs.exists(imagePath, exists => {
if (exists) {
const { size } = fs.statSync(imagePath);
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': size,
'Content-Disposition': `attachment; filename='${imageName}`
});
fs.createReadStream(imagePath).pipe(res);
}
else res.status(400).send('Error: Image does not exists');
});
})
Optionally: using sendFile as below:
app.get('/', (req, res) => {
const imageName = "image.jpg"
const imagePath = path.join(__dirname, "images", imageName);
fs.exists(imagePath, exists => {
if (exists) res.sendFile(imagePath);
else res.status(400).send('Error: Image does not exists');
});
});
2. Client / Angular - Component:
public url: SafeResourceUrl;
constructor(private http: HttpClient, private sanitizer: DomSanitizer) {
this.getImage('URL').subscribe(x => this.url = x)
}
public getImage(url: string): Observable<SafeResourceUrl> {
return this.http
.get(url, { responseType: 'blob' })
.pipe(
map(x => {
const urlToBlob = window.URL.createObjectURL(x) // get a URL for the blob
return this.sanitizer.bypassSecurityTrustResourceUrl(urlToBlob); // tell Anuglar to trust this value
}),
);
}
3. Client / Angular - Template:
<img [src]="url">
Related
I am trying to download an image from Amazon s3 using the #aws-sdk/client-s3 package. The image will download but I can't open it. I get an error and says it is an unrecognizable format.
React Component Download Function
const downloadImg = (e) => {
const href = e.target.getAttribute('img-data');
var img = href.split('/').pop();
const options = {
method: 'GET',
headers: { "Authorization" : `Bearer ${token}` },
};
fetch(`/download-img/${img}`, options)
.then(response => response.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = img;
document.body.appendChild(a);
a.click();
a.remove();
});
}
Node/Express Route
// #desc Download Screenshot
// #route get /download-img
// #access Private
app.get('/download-img/:id', authenticateToken, async(req, res) => {
const imgUrl = req.params.id;
try {
const getObjectParams = {
Bucket: awsBucketName,
Key: imgUrl,
}
const command = new GetObjectCommand(getObjectParams);
const file = await getSignedUrl(s3, command, {expiresIn: 60});
const img = axios.get(file)
.then(function (response) {
res.send(response.data)
})
.catch(function (error) {
// handle error
console.log(error);
})
} catch (err) {
console.log(err)
}
});
Response.data Output
I'm building a webApp in MEVN stack (Mongo, Express, Vue, Node).
In my backend, I have a controller (./backend/controllers/screenshots.controller.js) downloading an image from an external REST API. The image (PNG) is downloaded in a directory called 'images' placed in the controllers directory.
screenshots.controller.js:
const path = require('path');
const axios = require('axios');
const fs = require('fs');
const downloadScreenshot = async(screenshotPath) => {
let isDownloaded = false;
const fileUrl = `https://myexternalapi.com/screenshot/${screenshotPath}`;
const fileName = screenshotPath.split('/')[1]
const downloadFolder = './images'
if(!fs.existsSync(downloadFolder)){
fs.mkdirSync(downloadFolder);
console.log('Images directory created successfully.');
}
const localFilePath = path.resolve(__dirname, downloadFolder, fileName);
try {
const response = await axios({
method: 'GET',
url: fileUrl,
responseType: 'stream',
});
if(response.status === 200){
isDownloaded = true;
}
await response.data.pipe(fs.createWriteStream(localFilePath));
} catch (error) {
console.log('Error occured while downloading screenshot... : ', error);
}
return { isDownloaded, fileName };
}
const readScreenshot = async(req, res) => {
try {
const {isDownloaded, fileName} = await downloadScreenshot(req.body.temp);
if(isDownloaded){
console.log('__dirname + /images/ + fileName : ', __dirname + '/images/' + fileName )
res
.status(200)
.sendFile(fileName, {root : __dirname + '/images/'} );
} else {
res
.status(500)
.send({
message: 'No screenshot for this offer...'
})
}
} catch (error) {
console.log('Error occured while retrieving screenshot...', error)
res
.status(500)
.send({ message: error });
}
}
module.exports = {
readScreenshot: readScreenshot,
}
I would like to display the required image in my Vue app. Thus, I created the following view: ReadScreenshot.vue
<template>
<div>
<img :src="img">
</div>
</template>
<script>
import Screenshots from '../../services/screenshots.service'
export default {
props: ['id'],
data(){
return {
img: '',
}
},
async mounted(){
console.log(this.id)
const temp = await Screenshots.readScreenshot({ temp: this.id });
console.log(temp)
this.img = temp.data
}
}
</script>
Here is my screenshots.service.js script:
import api from '../../http-common';
export default new class ScreenshotsService {
//Read Screenshot
readScreenshot(screenshotName){
return api.post('read/screenshot', screenshotName)
}
}
Console.log(temp) is returning empty data.
In the screenshots.controller.js file, if I'm forcing the fileName with an existing one in the sendFile function, e.g. '2882LsgIXHOiXiOQ5MSv3R6v1hDijAdG5i756CdG5o7v527i5sS1XZgiXR6i1sSGj.png', I'm receiving a non empty data in my ReadScreenshot.vue .
Even if I'm receiving the data, the image is still not displayed...
How should I proceed, to get this right?
thks for your help
I am using expo-image-picker in react-native to pick image/video files and multer in nodejs as middleware to download files in directory /public/upload. When i am uploading file along with other parameters from react-native, multer is unable to detect a file present in req.body and hence not downloading any file.
Here is my react-native code using axios
pickImage = async () => {
try {
let options = {
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 1,
// base64:true
}
let result = await ImagePicker.launchImageLibraryAsync(options)
if (!result.cancelled) {
this.setState({ content: result })
}
} catch (E) {
console.log("error in picking image:", E)
}
}
createFormData = (response) => {
const photo = {
uri: response.uri,
type: response.type,
name: "my-img.jpg",
};
const form = new FormData();
form.append('acivityFile',photo);
return form;
};
handleSubmit = async () => {
if (this.state.content) {
const formData = this.createFormData(this.state.content)
console.log("form data:", formData)
try {
const res = await axios.post('http://393ad751391b.ngrok.io/activities',
{
title: "This is the title",
description: "This is a description",
eventType: "LOST & FOUND",
file: formData
},
{
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data'
},
},
)
console.log("res:", res.data);
} catch (err) {
console.log("err in post axios:",err)
}
}
}
Here is my route file handling http requests in server-side
const express = require('express');
const Upload = require('./../utils/multerSetup');
const activityController = require('../Controllers/activityController');
const router = express.Router();
router
.route('/')
.get(activityController.getAllActivities)
.post(
Upload.single('activityFile'),
activityController.addActivity
);
Here is my multerSetup.js file in server-side
const multer = require('multer');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'public/uploads/');
},
filename: function (req, file, cb) {
const ext = file.mimetype.split('/')[1];
cb(null, file.fieldname + '-' + Date.now() + '.' + ext);
},
});
const upload = multer({ storage });
module.exports = upload;
Here is my activityController.js file in server-side
const Activity = require('./../modals/activityModel');
const User = require('./../modals/user');
exports.getActivity = async (req, res, next) => {
console.log('here');
const activity = await Activity.findById(req.params.id);
res.status(200).json({
status: 'success',
data: {
activity,
},
});
};
exports.addActivity = async (req, res, next) => {
if (req.file) {
let file = {
Ftype: req.file.mimetype.split('/')[0],
name: req.file.filename,
};
req.body.file = file;
}
if (!req.body.location) {
req.body.location = {
coordinates: ['77.206612', '28.524578'],
};
}
if (req.body.votes) {
req.body.votes.diff = req.body.votes.up - req.body.votes.down;
}
req.body.creator = "12345" || "req.userId";
const activity = await Activity.create(req.body);
res.status(201).json({
status: 'success',
data: {
activity,
},
});
};
Also when Content-type:'multipart/form-data, then server console throws Error: Multipart:Boundary not found. When i use Content-type:'application/json', then multer does not work.
I just want to know what is the correct way of uploading files with additional parameters from react-native to nodejs multer. Any suggestions would be a great help!
I'm trying to upload an image with expo picker image to a nodejs server.
The problem is I never receive the image. I tried so many things I'm desesperate :(
Here is my code :
React-Native
postImage = async (image) => {
const photo = {
uri: image.uri,
type: "image/jpg",
name: "photo.jpg",
};
const form = new FormData();
form.append("test", photo);
axios.post(
url,
{
body: form,
headers: {
'Content-Type': 'image/jpeg',
}
}
)
.then((responseData) => {
console.log("Succes "+ responseData)
})
.catch((error) => {
console.log("ERROR " + error)
});
}
pickImage = async () => {
const result = await ImagePicker.launchImageLibraryAsync({
// mediaTypes: ImagePicker.MediaTypeOptions.All,
// allowsEditing: true,
// aspect: [4, 3],
quality: 1
});
if (!result.cancelled) {
try {
await this.postImage(result);
} catch (e) {
console.log(e);
}
this.setState({ image: result.uri });
}
};
It always works.
And here the nodejs code
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const multer = require('multer');
const fs = require("fs");
const app = express();
const upload = multer({
dest: "upload/",
});
// app.use(upload.single("test"));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cors());
app.listen(8080, () => {
console.log("running ...")
})
app.post("/upload", upload.single("photo.jpg"), async (req, res) => {
console.log("body =>", req.body);
console.log('files => ', req.files);
console.log("file =>", req.file);
// const oldpath = req.body.;
// const newpath = '/Users/mperrin/test/test-native/test-upload-photo/server/lol.jpg';
// fs.rename(oldpath, newpath, (err) => {
// if (err) {
// throw err;
// }
// res.write('File uploaded and moved!');
// res.sendStatus(200);
// });
res.sendStatus(200);
});
I always see this in the console and I don't know what to do with that ...
body => {
body: { _parts: [ [Array] ] },
headers: { 'Content-Type': 'image/jpeg' }
}
At the moment only the folder "upload" is created.
I don't know where I can get the files, I guess I'm really missing something but I don't know what.
Thanks for help guys !
I've never used multer before but after a quick review of the docs it looks like you need to have the same name in the headers as you're expecting in the node side post
Right now, in your header you have
name: 'photo.jpg'
and the following in your node post
upload.single("test")
Your post is looking for something with the name 'test' not 'photo.jpg' and you're sending 'photo.jpg'
Try it out and let me know how it goes.
Edit: My mistake, you may have add
name: "test"
to the headers here instead of in the photo object:
axios.post(
url,
{
body: form,
headers: {
'Content-Type': 'image/jpeg',
}
})
i wondering what is the best way to upload photo from my phone(using react native) to my node server.
currently I encode my picture in base64 and store it in a LONGTEXT
but is there a more efficient way to do it ?
i'm using
'Content-Type': 'application/x-www-form-urlencoded'
to reach my API
thanks
Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files.
It is very simple just copy paste the code in documentation.
I would recommend using formdata instead of base64.
For speed and effeciency, maybe consider resizing your image first before transit and perharps creating a thumbnail image for client's side browsing.
This example Im using Axios, 'react-native-image-picker', 'react-native-image-resizer' and Redux
Api.js
export const api = axios.create({
baseURL: server,
headers: {
'Cache-Control': 'no-cache'
},
timeout: 5000
})
PhotoUpload.js
uploadPicture = (photo) => {
api.post('/image/'+this.state.position, photo)
.then(() => {
this.props.getThumbList()
.then((response) => {
this.props.setThumbSource(response.payload.data)
this.setState({thumbUri: {uri: this.props.thumbSource[this.state.position]}})
})
.catch((error) => {
console.log(this.props.errorText)
})
})
.catch((error) => {
console.log(this.props.errorText)
})
}
openImagePicker = () => {
// get image from image picker
ImagePicker.showImagePicker(this.options, async response => {
this.setState({
originUri: response.uri
})
if (response.didCancel) {
console.log('User cancelled image picker')
return
} else if (response.error) {
console.log('ImagePicker Error: ', response.error)
return
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton)
return
}
//Post the photo with position which it was selected
const photo = new FormData();
// data.append('authToken', 'secret');
photo.append('photo', {
uri: this.state.originUri,
type: 'image/jpeg',
name: 'p-'+this.state.position+'.jpg'
});
let { height, width, quality, format, originUri } = this.state
// Resize and post the thumb
const resizedImageUri = await ImageResizer.createResizedImage(
originUri,
height,
width,
format,
quality
).then(({uri}) => {
photo.append('thumb', {
uri: uri,
type: 'image/jpeg',
name: 't-'+this.state.position+'.jpg'
});
this.uploadPicture(photo);
})
})
}
Redux.js
export const GET_THUMB_LIST = 'GET_THUMB_LIST';
export const GET_THUMB_LIST_SUCCESS = 'GET_THUMB_LIST_SUCCESS';
export const GET_THUMB_LIST_FAIL = 'GET_THUMB_LIST_FAIL';
export const SET_THUMB_SOURCE = 'SET_THUMB_SOURCE';
export const SET_THUMB_SOURCE_FAIL = 'SET_THUMB_SOURCE_FAIL';
export function getThumbList() {
return {
type: GET_THUMB_LIST,
payload: {
request: {
method: 'GET',
url:'/thumbs'
}
}
};
}
export function setThumbSource(list) {
return {
type: SET_THUMB_SOURCE,
payload: list
};
}
export default function reducer(state = {}, action) {
switch (action.type) {
case GET_THUMB_LIST_SUCCESS:
// console.log(action.payload.data)
return {
...state,
thumbList: action.payload.data
}
case GET_THUMB_LIST_FAIL:
return {
...state,
errorText: "Cannot get image list"
}
case SET_THUMB_SOURCE:
return {
...state,
thumbSource: action.payload
}
case SET_THUMB_SOURCE_FAIL:
return {
...state,
errorText: "Set thumb uri failed"
}
default:
return state
}
}