Posting image to laravel with Axios only passing [object file] as string - javascript

I have a react app that is the frontend for a laravel application. I am using a simple image uploader that returns the uploaded file to a post route. In the controller for the post route the request is coming in as a string and not the actual object. see my code.
onUpload = (picture) => {
this.setState({
pictures: this.state.pictures.concat(picture),
});
var curr = this
const formData = new FormData();
formData.append('file',picture)
console.log(picture[0])
axios
.post('/upload-subitem-image', formData, fileHeaders)
.then(function (response) {
const data = response.data
console.log(data)
})
.catch(error => {
console.log(error);
})
}
When i console the picture object I get
File {name: "test.png", lastModified: 1553443959805, lastModifiedDate: Sun Mar 24 2019 12:12:39 GMT-0400 (Eastern Daylight Time), webkitRelativePath: "", size: 11695, …}
But after passing it to laravel to try and get the file it returns a string when i try to do this.
$data = $request->File();
and looks like
{file:{"[object file]"}
Headers look like this:
const fileHeaders = {
'X-CSRF-TOKEN' : document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'content-type': 'multipart/form-data',
'Accept': 'application/json',
}

Judging from the surrounding code, it looks like you're append-ing an array picture.
The API for FormData expects a File or Blob for file uploads so for an array it just turns it into a string. Since your array only has one item, it becomes [object file].
Try doing picture[0] in the append (And make sure to do proper bounds checks etc.)
Ref: https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects#Sending_files_using_a_FormData_object

Ended up adding some stuff to controller in php.
$data = $request->all();
$file = $data['file'];

Related

Laravel 9 and Javascript: how to download a file returned from Storage::download()

DISCLAIMER: Before creating this question, I've checked here, here and here, and also checked Laravel docs.
Context
Laravel 9 full-stack
No JS framework on front-end, which means I'm using vanilla JS
The folders on Storage are setted like this:
storage
app
public
folder1
folder1A
folder1B
folder1C
etc
The files stored in each folder1X are .pdf format and I don't know its names.
No folders are empty, nor with invalid/corrupted files.
The problem
I have a FileController.php to download files that are inside a folder1X/ directory. The method to download it is as follows:
public function downloadFileFromStorage(Request $request): mixed
{
$dirpath = $request->dirpath; // dirpath = public/folder1/folder1X.
$files = Storage::allFiles($dirpath);
return response()->download(storage_path('app\\' . $files[0]));
}
(Note: dirpath is sent in a axios request by client and is also fetched from database on a previous request)
My Javascript CLI needs to enable the download of this file. The download is enabled by clicking on a button. The button calls downloadPDF(dirpath) which works as follows:
function downloadPDF(dirpath) {
axios.post('/download-pdf-file', { dirpath })
.then(
success => {
const url = success.data
const a = document.createElement('a')
a.download = 'file.pdf'
a.href = url
a.click()
},
error => {
console.log(error.response)
}
)
}
But, when I run this function, I get a about:blank#blocked error.
Attempts
Changed the a HTML DOM approach to a window.open(url) on client;
Changed response() to Storage::download($files[0], 'file-name.pdf'), and with this I also tried using Blob on client as follows:
success => {
const blob = new Blob([success.data], { type: 'application/pdf' })
const fileURL = URL.createObjectURL(blob)
window.openURL(fileURL)
},
Also mixed Blob with the a HTML DOM approach;
Changed storage_path argument to /app/public/ before concatenating to $files[0].
UPDATE
Following tips from #BenGooding and #cengsemihsahin, I changed files to the following:
JS
// FileDownload is imported on a require() at the code beginning
function downloadPDF(dirpath) {
axios({
url: '/download-pdf-file',
method: 'GET',
responseType: 'blob',
options: {
body: { dirpath }
}
}).then(
success => {
FileDownload(success.data, 'nota-fiscal.pdf')
}
)
}
PHP:
public function downloadFileFromStorage(Request $request): mixed
{
$dirpath = $request->dirpath; // dirpath = public/folder1/folder1X.
$files = Storage::allFiles($dirpath);
return Storage::download($files[0], 'filename.pdf');
}
and now it downloads a corrupted PDF that can't be opened.
Finally found the issue, and it was here:
axios({
url: '/download-pdf-file',
method: 'GET',
responseType: 'blob',
options: { // here
body: { dirpath } // here
}
})
Laravel's Request arrow operator -> can't fetch a GET body sent through options (At least, not on $request->key fashion; see more about it here) thus making me download a corrupted file - it wasn't fetching any file on Laravel as it didn't get any path at all.
Here is the solution I came with:
As I want to get a file in a route that doesn't change except for the 1X at folder1X, I'm processing the path obtained and sending the 1X as a GET query param:
let folderNumber = dirpath.split('/')
folderNumber = folderNumber[folderNumber.length].replaceAll('/', '')
axios({
url: '/download-pdf-file?folder=',
method: 'GET',
responseType: 'blob'
})
This way I don't pass the whole path to back-end and it's possible to get folderNumber by using $request->query():
public function downloadFileFromStorage(Request $request): mixed
{
$folderNumber = $request->query('folderNumber');
$folderPath = '/public/folder1/folder' . $folderNumber . '/';
$files = Storage::allFiles($folderPath);
return Storage::download($files[0], 'file-name.pdf');
}
In a nutshell:
To download files, use GET requests;
To send arguments within GET requests, use query parameters and fetch them with $request->query('keyname') (or find out another way. Good luck!);

FormData not sending File object through Post request

I am making an axios.post() request in my Vue component to send some formData holding a media File to upload through a PHP script. I know the file object is being appended correctly as I log the data before the request here:
// this.postMedia is a Vue array holding any post media objects to be uploaded
this.postMedia.forEach((mediaObj, index) => {
formData.append('media[' + index + ']', mediaObj.media);
formData.append('mediaType[' + index + ']', mediaObj.mediaType);
});
// Logging formData
for (let p of formData.values()) {
console.log(p);
}
This logs the information of the file object and the corresponding media type as expected.
// This is the media data
File {
lastModified: 1627299857401
lastModifiedDate: Mon Jul 26 2021 07:44:17 GMT-0400 (Eastern Daylight Time) {}
name: "test-image.jpg"
size: 12295
type: "image/jpeg"
webkitRelativePath: ""
}
// This is the mediaType data
1
I am then passing this data through an axios request and logging the response data as follows:
// Vue.js file
axios.post('/api/createPostV2', formData, config)
.then(success => {
console.log(success.data);
})
.catch(function(err) {
console.log(err.response);
});
// PHP file being requested (/api/createPostV2)
public static function createPostV2(Request $request)
{
// Just return request to check data
return $request->all();
}
Here is what gets logged:
media: Array(1)
0: {}
length: 1
[[Prototype]]: Array(0)
mediaType: ['1']
So it is passing the media array, but the actual file is not being passed as you can see by the empty object at index 0. As you can see the mediaType is passed correctly, keep in mind both 'media' and 'mediaType' are arrays to support uploading multiple files. Am I missing something here? Why is the file data being lost when I am passing it through formData?

Angular 8: URL encoded form POST

I want to post form data to a server that accepts and returns text/html/xml. I am effectively trying to emulate a normal URL encoded form POST. My Angular 8 POST function successfully posts (200 OK), but the server can't understand the data because it is JSON and not URL encoded.
Response and request headers state Content-Type: text/html; Charset=utf-8 and Accept: text/html, application/xhtml+xml, */* and I have added responseType: "text" to the httpClient options. Why is the server still being sent JSON and not URL encoded data?
// obj2 = output from ngForm
// baseUrl2 = server that sends and receives text/html/xml
public postForm(obj2) {
return this.httpClient
.post(this.baseUrl2, obj2, {
headers: new HttpHeaders({
"Content-Type": "application/x-www-form-urlencoded",
Accept: "text/html, application/xhtml+xml, */*"
}),
responseType: "text"
})
.map(data => data);
}
Form data sent:
{"Form data":{"{\"personsNameText\":\"name9\",\"centreEmailAddressText\":\"name9#name.com\",\"centreTelephoneNumberText\":123456789,\"centreNumberText\":\"ab123\",\"centreNameText\":\"ab123\",\"invoiceText\":\"123456789\",\"currencyText\":\"GBP\",\"amountText\":\"100\",\"cardtypeText\":\"Credit card\",\"commentsText\":\"Comments.\",\"declarationText\":true}":""}}
What I want:
personsNameText=name9?centreEmailAddressText=name9#name.com?centreTelephoneNumberText=123456789?centreNumberText=ab123?centreNameText=ab123?invoiceText=123456789?currencyText=GBP?amountText=100?cardtypeText=Credit card?commentsText=Comments.?declarationText=true
I'm not sure of the type of the obj2 object here but I'll assume it's somethings like
interface UserFormData {
['Form data']: { [name: string]: value };
}
You would need to transform this to FormData before posting it. Something along the lines:
const formEncodedObj2 = new FormData();
const obj2Keys = obj2['Form data'];
Object.keys(obj2Keys).forEach(key => formEncodedObj2.append(key, obj2Keys[key]));
And then send the formEncodedObj2 object.
So, this solution solved various problems for me:
Posting x-www-form-urlencoded data using Angular 8's forms and HttpClient
Correcting unwanted encoded characters
My specific problem was that a unique verification string contained ampersands that were being converted to HTML entities, i.e. & to &.
// userdata.service.ts
public postForm(obj) {
return this.httpClient
.post(this.baseUrl2, obj, {
headers: new HttpHeaders({
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Referer": "http://referer.com" // Replace with your own.
}),
responseType: "text"
})
.map(data => data)
.pipe(
retry(1),
catchError(this.handleError)
);
}
// app.component.ts
PostForm(userdata) {
// Stringify and convert HTML entity ampersands back to normal ampersands.
const corrected = JSON.stringify(userdata).replace(/(&)/gm, '&');
// Convert back to JSON object.
const corrected2 = JSON.parse(corrected);
// entries() iterates form key:value pairs, URLSearchParams() is for query strings
const URLparams = new URLSearchParams(Object.entries(corrected2));
// Convert to string to post.
const final = URLparams.toString();
// Post it
this.userdataService.postForm(final).subscribe(reponse2 => {
console.log(reponse2);
});
}
URLSearchParams() was the breakthrough and, as Vlad suggested, being absolutely sure of the type one is dealing with. I should have used Types to avoid confusion. I probably should use Angular Interceptors to deal with the character manipulation.

Upload JSON file using Angular 6

I'm trying to load a JSON file from the user using this method:
<input
style="display: none"
type="file" (change)="onFileChanged($event)"
#fileInput>
<button (click)="fileInput.click()">Select File</button>
<button (click)="onUpload()">Upload!</button>
and this is the code in the component ts file:
export class MyFileUploadComponent {
selectedFile: File
onFileChanged(event) {
this.selectedFile = event.target.files[0];
console.log(this.selectedFile);
console.log('content: ' + JSON.stringify(this.selectedFile));
}
onUpload() {
// upload code goes here
}
}
the line console.log(this.selectedFile); does provide me with the file meta data which is:
lastModified: 1551625969247
lastModifiedDate: Sun Mar 03 2019 17:12:49 GMT+0200 (Israel Standard Time) {}
name: "manuscripts.json"
size: 6008
type: "application/json"
webkitRelativePath: ""
__proto__: File
But when I'm trying to print it's content using JSON.stringify I get: {} (empty file).
What's the cause?
Thanks.
But when I'm trying to print it's content using JSON.stringify I get: {} (empty file).
This is not a content of JSON file. It's a File object. To read content of JSON you need to use FileReader
onFileChanged(event) {
this.selectedFile = event.target.files[0];
const fileReader = new FileReader();
fileReader.readAsText(this.selectedFile, "UTF-8");
fileReader.onload = () => {
console.log(JSON.parse(fileReader.result));
}
fileReader.onerror = (error) => {
console.log(error);
}
}
JSON.Stringify does not work for File objects in TS/JS. You should extract data from File and then stringify it.
For examlple, extract file content as a string or array of strings using https://developer.mozilla.org/en-US/docs/Web/API/FileReader

Add dynamic header to request-promise options (node)

I am having trouble adding a header to a request-promise (npm) request using a variable (headersTest). If I hard-code the value in options, I receive the expected API response. If I create the header in a variable and add that to options, the API rejects the request (the header key is not recognised). When I log the headersTest variable it would appear identical to the hard-coded value, but doesn't work.
Here is the relevant code section:
const headersTest = "'x-custom-date':'Fri, 27 Oct 2017 09:45:18 EST'";
console.log(headersTest);
const options = {
method: "GET",
uri: 'http://' + whatEvz,
qs: {
queryString
},
headers: {
//'x-custom-date':'Fri, 27 Oct 2017 09:45:18 EST'
headersTest
},
json: true
};
rp(options)
.then(function(results){
res.send(results);
})
.catch(function (err) {
res.send("Shite! That didn't work! " + err.message);
});
It seems I'm missing something in my understanding as to how variables are interpreted within options. Any guidance would be much appreciated.
I solved this by using object properties, rather than trying to pass on object into options.headers. I actually had an array of headers and used lodash to add the headers like so:
_.each(myHeaders, function (val, key) {
_.each(val, function (val, key) {
options.headers[key] = val;
});
})

Categories