I am displaying profile picture of a user on a vue/nuxt app. This picture is uploaded to S3 through a file API.
While receiving the image from file API and displaying the profile pic, I am not able to convert it to the right format.
My problems:
s3 says the file content type is application/octet-stream. I was expecting a specific type like image/jpeg or image/png. There's an npm that came to rescue here.
Wrapping the returned file in a blob using createObjectURL creates a dummy URL / link. We can set the mime type to blob while creating like so const blob = new Blob([response.data],{type=response.type}
Using responseType - Is it blob, arrayBuffer or stream? I went ahead with experimenting on blob
Step 1: File API - Reading from S3 using AWS client v3 (File: FileService.js)
let goc = new GetObjectCommand({
...
});
s3client.send(goc)
.then(response => {
if(response){
const body = response.Body;
const tempFileName = path.join(config.FILE_DOWNLOAD_PATH, file_name);
const tempFile = fs.createWriteStream(tempFileName);
body.pipe(tempFile);
resolve(Service.createSuccessResponse(
{
file_name_local: tempFileName,
file_name: file_name,
content_length: response.ContentLength,
}
,"SUCCESS")); // This is **responsePayload** in the next snippet.
...
Step 2: Send the response using expressjs (File: Controller.js)
if(responsePayload.file_name_local){
response.set('Content-Length',responsePayload.content_length);
response.write(fs.readFileSync(responsePayload.file_name_local));
response.end();
response.connection.end();
}
Step 3: Define image (File: view-profile.vue)
<template>
...
<v-img src="dpURL"/>
...
</template>
Step 4 - EAGER DOWNLOAD: Receive image as blob (File: view-profile.vue)
<script>
...
mounted(){
...
this.getDP(this.profileInfo.dp_url).then(r => {
this.dpURL = window.URL.createObjectURL(new Blob([r.data])); // Do we need explicit MIME here?
}).catch(e => {
console.error("DP not retrieved: "+JSON.stringify(e));
})
...
},
methods: {
...
getDP(file_name){
return new Promise(
async(resolve, reject) => {
callGenericAPI({
url: this.$config.API_HOST_URL+'/file',
configObj: {
method: 'get',
params: {
file_name: file_name,
file_category: 'user-dp'
},
headers: {
'api_token': idToken,
'Cache-Control':'no-cache'
},
responseType: 'blob'
},
$axios: this.$axios //Just some crazy way of calling axios. Don't judge me here.
})
.then(r => {
console.log("DP received.");
resolve(r.data);
})
.catch(e => {
console.error("DP not received." + JSON.stringify(e));
reject(e);
})
}
)
},
...
}
Step 5 - LAZY DOWNLOAD: Receive image as blob (File: view-profile.vue > custom-link.vue - child) after clicking the link
File: view-profile.vue
<template>
...
<CustomLink file_name="fileName" file_category="USER_DP" label="User Profile Picture"/>
...
</template>
File: custom-link.vue
<template>
<a v-text="label"
#click.prevent="downloadItem()">
</template>
<script>
...
methods:{
downloadItem(){
this.$axios.get(...)
.then(response => {
const blob = new Blob([response.data],{ type: response.data.type }) // Here I tried Uint8Array.from(response.data) as well
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob,{ type: response.data.type });
link.download = this.file_name
link.click()
URL.revokeObjectURL(link.href)
}
...
}
...
}
...
</script>
The problem here is that image is being downloaded. I can see it from devtools network tab. But I just can't link/display it reactively (shown in Step 3).
After Step 5, the file downloads but has a bloated size compared to original. When I convert response.data to Uint8Array, the file shrinks compared to original.
It looks like a very simple get and display image problem but haven't got the combination of mimetypes and utilities right. Express supports sendFile and download options for the files. But both the calls don't seem to work for some reason!
Do you have any pointers?
Can't we just download the image somewhere on the webserver directory and reactive link it? Even that should be fine with me. Can avoid some API calls.
The following code is taken from https://aws.plainenglish.io/using-node-js-to-display-images-in-a-private-aws-s3-bucket-4c043ed5c5d0 :
function encode(data){
let buf = Buffer.from(data);
let base64 = buf.toString('base64');
return base64
}
getImage()
.then((img)=>{
let image="<img src='data:image/jpeg;base64," + encode(img.Body) + "'" + "/>";
let startHTML="<html><body></body>";
let endHTML="</body></html>";
let html=startHTML + image + endHTML;
res.send(html)
}).catch((e)=>{
res.send(e)
})
#coder.in.me's post leads us to the answer - obtain S3 Object's presignedURL.
Added benefit - you can also expire the URL beyond a time-limit.
Related
I am using react-native-image-to-pdf library to convert images to pdf in my react native app. from https://www.npmjs.com/package/react-native-image-to-pdf
var photoPath = ['https://images.pexels.com/photos/20787/pexels-photo.jpg?auto=compress&cs=tinysrgb&h=350','https://images.pexels.com/photos/20787/pexels-photo.jpg?auto=compress&cs=tinysrgb&h=350'];
const myAsyncPDFFunction = async () => {
try {
const options = {
imagePaths: photoPath,
name: 'PDFName',
};
const pdf = await RNImageToPdf.createPDFbyImages(options);
console.log(pdf.filePath);
} catch(e) {
console.log(e);
}
}
but this is giving error Error: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference
I have also tried giving path as ['./assets/a.png', './assets/b.png']
but still getting same error
Based on the usage example, your photoPath needs to be a local file path and not a remote path.
My recommendation is to first use rn-fetch-blob to download the remote image to the device, and then pass your new local image path to react-native-image-to-pdf. Something like:
RNFetchBlob
.config({
// add this option that makes response data to be stored as a file,
// this is much more performant.
fileCache : true,
})
.fetch('GET', 'http://www.example.com/file/example.png', {
//some headers ..
})
.then(async (res) => {
// the temp file path
console.log('The file saved to ', res.path())
const options = {
imagePaths: [res.path()],
name: 'PDFName',
};
const pdf = await RNImageToPdf.createPDFbyImages(options);
})
from file path remove the text 'file://; with empty string('').
const options = {
imagePaths: [uri.replace('file://', '')],
name: 'FileName',
quality: .9, // optional compression paramter
};
replace('file://', '') it's work for me
I'm building a Vue app with a Rails backend. I'm following some articles online that suggested a workflow in which I:
Let my Rails API generate a pre-signed S3 url
Which I get through an API request in my Vue app
I use the data from that request to POST the actual image directly to S3
The first two steps are working fine, but I'm struggling to understand how to include the filedata in the request that is of type: 'multipart/form-data'.
My Code is as follows, I use vuetify as a component library:
<template>
<v-form>
<v-file-input v-model="file" label="File input"></v-file-input>
<v-btn class="mt-2" block bottom color="primary" #click="submit">Opslaan</v-btn>
</v-form>
</template>
<script>
import { axios_request } from '../_helpers';
export default {
name:'AccountImageForm',
data: () => ({
file: {}
}),
methods: {
filesChange(event) {
this.file = event.target.files[0]
},
submit() {
axios_request('/api/v1/images/upload_url')
.then(
response => {
// this response contains the pre-signed info
var data = {
...response.url_fields,
file: this.file
}
var headers = { 'Content-Type': 'multipart/form-data' }
axios_request(response.url, 'POST', data, headers)
.then(
response => {
console.log(response)
}
)
}
)
},
}
}
</script>
This requests fails with the following error
<Error>
<Code>MalformedPOSTRequest</Code>
<Message>The body of your POST request is not well-formed multipart/form-data.</Message>
<RequestId>x</RequestId>
<HostId>xx</HostId>
</Error>
When I look at my original formData, it seems that the filedata is empty. Also the size of the request is not big enough, so my assumption is that the file is missing. Is there some additional work to serialize the file for this request?
Thanks
The issue is that you're trying to post multipart/form-data but are sending a JS object literal which Axios is probably just stringifying.
What you need to do instead is create a FormData instance.
For example
response => {
// convert your url_fields into a FormData instance
const data = Object.entries(response.url_fields).reduce((fd, [ key, val ]) =>
(fd.append(key, val), fd), new FormData())
// now add the file
data.append('file', this.file)
axios_request(response.url, 'POST', data)
.then(...)
}
I'm creating a web application using React / Aws Amplify as the front end and AWS API-Gateway and S3 in the back end and Cognito as our user authentication. I have a page where the user needs to submit a form and a file. I was able to set this up for text files but once I started to work on binary files bad things happened.
I build the API in AWS and tested it using Postman as well as Curl and I'm able to post binary files. When I make the call through Amplify it stops working. I can make the call through Axios but then I need to turn off the Authentication, hence why I'm trying to do this through amplify. I also do not want to use amplify storage as it does not meet my needs. What typically happens is the file size is larger then the file sent and when I download it out of S3 it does not work any longer.
import React, { Component } from "react";
import "./Dashboard.css";
import { API } from "aws-amplify";
import { saveAs } from 'file-saver';
import axios from 'axios';
export default class Home extends Component {
uploadLambda = async (event) => {
//This one work if I turn off User Authentication
let file = event.target.files[0];
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = async () => {
try
{
return axios({
method: 'POST',
url: 'https://XXXXXXXXXX.execute-api.us-east-1.amazonaws.com/dev/upload',
headers: {
'Content-Type': 'application/pdf'
},
data: reader.result
});
}
catch(e)
{
console.log(e);
}
}
}
uploadImageLambda = async(event) => {
//This is the one I'm trying to get to work with binary files
var file_name = event.target.files[0].name;
console.log('Saving File Via Lambda: ' + file_name);
var reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
//reader.readAsBinaryString(event.target.files[0]);
//reader.readAsArrayBuffer(event.target.files[0]);
reader.onload = async () =>
{
try
{
/**
Someone suggested this but it does not fix the problem
let encoded = reader.result.toString().replace(/^data:(.*,)?/, '');
if ((encoded.length % 4) > 0) {
encoded += '='.repeat(4 - (encoded.length % 4));
}
console.log(encoded);
//"isBase64Encoded": "true",
**/
return await API.post("lambdadocshell", 'upload', { 'headers': { 'Content-Type': 'application/pdf', }, 'body': reader.result });
}
catch (e)
{
console.log(e);
}
}
}
render() {
return (
<div className="FileTest">
<h1>Upload A File</h1>
<form onSubmit={this.handleSubmit} enctype="multipart/form-data">
Select File: <input type="file" onChange={this.uploadLambda} />
</form>
</div>
);
}
}
In the code above you can see 2 upload functions, both hit the same API-Gateway. uploadLambda works but only if authentication on the API-Gateway is turned off. uploadImageLambda does not work regardless of authentication. We do use the Amplify in a number of other pages to move JSON back and forth to the API without issues. You can also see commented code as we tried a number of different ways to get amplify to work.
After talking with AWS support, they said that amplify apparently does a JSON.stringify to the data which then increases the length of the file. Currently there does not seem to be a workaround for this issue. As such they suggested that I use Axios to make the request to API Gateway. Hopefully this will be resolved in the future.
I need to upload a selection of images that user picked from CameraRoll to the LoopBack Component Storage. The thing is that the component storage is working fine, because I can upload and download the files through Postman. But, when I try to upload from react native to loopback, it always returns "No file content upload" with http status 400.
I read a lot of people talking about it and tried everything and none worked for me.
First, I am taking the images from the CameraRoll and my images array looks like this:
[
{
exists: 1,
file: "assets-library://asset/asset.JPG?id=3FF3C864-3A1A-4E55-9455-B56896DDBF1F&ext=JPG",
isDirectory: 0,
md5: "428c2e462a606131428ed4b45c695030",
modificationTime: 1535592967.3309255,
size: 153652,
uri: null
}
]
In the example above I just selected one image.
I transformed to Blob, then I got:
[
{
_data: {
blobId: "3FF3C864-3A1A-4E55-9455-B56896DDBF1F",
name: "asset.JPG",
offset: 0,
size: 153652,
type: "image/jpeg"
}
}
]
So I tried a lot of things after this, tried to send the blob itself as the request body, tried to append to a form data and send the form data, but it doesn't matter the way I try, I always get the "No file content upload" response.
I also tried the example from Facebook, didn't work: https://github.com/facebook/react-native/blob/master/Libraries/Network/FormData.js#L28
The way I am trying now:
In my view:
finalizarCadastro = async () => {
let formData = new FormData();
let blobs = [];
for(let i=0;i<this.state.fotos.length;i++){
let response = await fetch(this.state.fotos[i]);
let blob = await response.blob();
blobs.push(blob);
}
formData.append("files", blobs);
this.props.servico.criar(formData);
}
And the function that send to my server:
criar: (servico) => {
this.setState({carregando: true});
axios.post(`${REQUEST_URL}/arquivos/seila/upload`, servico, {headers: {'content-type': 'multipart/form-data'}}).then(() => {
this.setState({carregando: false});
this.props.alertWithType("success", "Sucesso", "Arquivo salvo com sucesso");
}).catch(error => {
this.setState({carregando: false});
console.log(error.response);
this.props.alertWithType("error", "Erro", error.response.data.error.message);
})
}
I found the solution. So the problem was actually not the code itself, the problem was sending multiple files at the same time. To fix everything, I did this:
this.state.fotos.forEach((foto, i) => {
formData.append(`foto${i}`, {
uri: foto,
type: "image/jpg",
name: "foto.jpg"
});
})
this.props.servico.criar(formData);
And my function that sends the request to the server:
criar: (servico) => {
this.setState({carregando: true});
axios.post(`${REQUEST_URL}/arquivos/seila/upload`, servico).then((response) => {
this.setState({carregando: false});
this.props.alertWithType("success", "Sucesso", "Arquivo salvo com sucesso");
}).catch(error => {
this.setState({carregando: false});
this.props.alertWithType("error", "Erro", error.response.data.error.message);
})
},
So you don't need to set the Content-Type header to multipart/form-data and don't need to transform the images to blob, actually you just need the uri of each one, and I think the type and name attributes are opcional.
I am developing a app where i need to upload an image to the server. Based on the image i get a response which i need to render?.
Can you please help me how to upload an image using react-native?.
There is file uploading built into React Native.
Example from React Native code:
var photo = {
uri: uriFromCameraRoll,
type: 'image/jpeg',
name: 'photo.jpg',
};
var body = new FormData();
body.append('authToken', 'secret');
body.append('photo', photo);
body.append('title', 'A beautiful photo!');
var xhr = new XMLHttpRequest();
xhr.open('POST', serverURL);
xhr.send(body);
My solution is using fetch API and FormData.
Tested on Android.
const file = {
uri, // e.g. 'file:///path/to/file/image123.jpg'
name, // e.g. 'image123.jpg',
type // e.g. 'image/jpg'
}
const body = new FormData()
body.append('file', file)
fetch(url, {
method: 'POST',
body
})
I wrote something like that. Check out https://github.com/kamilkp/react-native-file-transfer
I have been struggling to upload images recently on react-native. I didn't seem to get the images uploaded. This is actually because i was using the react-native-debugger and network inspect on while sending the requests. Immediately i switch off network inspect, the request were successful and the files uploaded.
I am using the example from this answer above it works for me.
This article on github about the limitations of network inspect feature may clear things for you.
Just to build on the answer by Dev1, this is a good way to upload files from react native if you also want to show upload progress. It's pure JS, so this would actually work on any Javascript file.
(Note that in step #4 you have to replace the variables inside the strings with the type and file endings. That said, you could just take those fields out.)
Here's a gist I made on Github: https://gist.github.com/nandorojo/c641c176a053a9ab43462c6da1553a1b
1. for uploading one file:
// 1. initialize request
const xhr = new XMLHttpRequest();
// 2. open request
xhr.open('POST', uploadUrl);
// 3. set up callback for request
xhr.onload = () => {
const response = JSON.parse(xhr.response);
console.log(response);
// ... do something with the successful response
};
// 4. catch for request error
xhr.onerror = e => {
console.log(e, 'upload failed');
};
// 4. catch for request timeout
xhr.ontimeout = e => {
console.log(e, 'cloudinary timeout');
};
// 4. create formData to upload
const formData = new FormData();
formData.append('file', {
uri: 'some-file-path', // this is the path to your file. see Expo ImagePicker or React Native ImagePicker
type: `${type}/${fileEnding}`, // example: image/jpg
name: `upload.${fileEnding}` // example: upload.jpg
});
// 6. upload the request
xhr.send(formData);
// 7. track upload progress
if (xhr.upload) {
// track the upload progress
xhr.upload.onprogress = ({ total, loaded }) => {
const uploadProgress = (loaded / total);
console.log(uploadProgress);
};
}
2. uploading multiple files
Assuming you have an array of files you want to upload, you'd just change #4 from the code above to look like this:
// 4. create formData to upload
const arrayOfFilesToUpload = [
// ...
];
const formData = new FormData();
arrayOfFilesToUpload.forEach(file => {
formData.append('file', {
uri: file.uri, // this is the path to your file. see Expo ImagePicker or React Native ImagePicker
type: `${type}/${fileEnding}`, // example: image/jpg
name: `upload.${fileEnding}` // example: upload.jpg
});
})
In my opinion, the best way to send the file to the server is to use react-native-fs package, so install the package
with the following command
npm install react-native-fs
then create a file called file.service.js and modify it as follow:
import { uploadFiles } from "react-native-fs";
export async function sendFileToServer(files) {
return uploadFiles({
toUrl: `http://xxx/YOUR_URL`,
files: files,
method: "POST",
headers: { Accept: "application/json" },
begin: () => {
// console.log('File Uploading Started...')
},
progress: ({ totalBytesSent, totalBytesExpectedToSend }) => {
// console.log({ totalBytesSent, totalBytesExpectedToSend })
},
})
.promise.then(({ body }) => {
// Response Here...
// const data = JSON.parse(body); => You can access to body here....
})
.catch(_ => {
// console.log('Error')
})
}
NOTE: do not forget to change the URL.
NOTE: You can use this service to send any file to the server.
then call that service like the following:
var files = [{ name: "xx", filename:"xx", filepath: "xx", filetype: "xx" }];
await sendFileToServer(files)
NOTE: each object must have name,filename,filepath,filetype
A couple of potential alternatives are available. Firstly, you could use the XHR polyfill:
http://facebook.github.io/react-native/docs/network.html
Secondly, just ask the question: how would I upload a file in Obj-C? Answer that and then you could just implement a native module to call it from JavaScript.
There's some further discussion on all of this on this Github issue.
Tom's answer didn't work for me. So I implemented a native FilePickerModule which helps me choose the file and then use the remobile's react-native-file-transfer package to upload it. FilePickerModule returns the path of the selected file (FileURL) which is used by react-native-file-transfer to upload it.
Here's the code:
var FileTransfer = require('#remobile/react-native-file-transfer');
var FilePickerModule = NativeModules.FilePickerModule;
var that = this;
var fileTransfer = new FileTransfer();
FilePickerModule.chooseFile()
.then(function(fileURL){
var options = {};
options.fileKey = 'file';
options.fileName = fileURL.substr(fileURL.lastIndexOf('/')+1);
options.mimeType = 'text/plain';
var headers = {
'X-XSRF-TOKEN':that.state.token
};
options.headers = headers;
var url = "Set the URL here" ;
fileTransfer.upload(fileURL, encodeURI(url),(result)=>
{
console.log(result);
}, (error)=>{
console.log(error);
}, options);
})
Upload Files : using expo-image-picker npm module. Here we can upload any files or images etc. The files in a device can be accessed using the launchImageLibrary method. Then access the media on that device.
import * as ImagePicker from "expo-image-picker";
const loadFile = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
aspect: [4, 3],
});
return <Button title="Pick an image from camera roll" onPress={loadFile} />
}
The above code used to access the files on a device.
Also, use the camera to capture the image/video to upload by using
launchCameraAsync with mediaTypeOptions to videos or photos.