PHP doesn't populate $_POST and $_FILES when uploading from Angular 2 - php

I'm having some troubles to upload files and data to my server using Angular 2 and PHP.
I've followed this File Upload In Angular 2? to upload the data and file from Angular 2 and all seems to be OK (I can see the data received by printing php://input). My Angular 2 code is something like this:
upload(data: data, file: File): Observable<any> {
let header = new Headers();
header.append('Content-Type', 'multipart/form-data');
header.append('Accept', 'application/json');
let options = new RequestOptions({ headers: header });
let formData:FormData = new FormData();
formData.append('file', file, file.name)
formData.append('data', JSON.stringify({data}));
return this.http
.post('myurl', formData, options)
.map(response => {
return response;
})
.catch(error => Observable.throw(error.message || error));
}
The problem is that the superglobals $_POST and $_FILES aren't populated, so I can't access them (and the data and file uploaded so). I have the same problem when uploading only data and I solved it manually-populating the $_POST superglobal by doing
$_POST = file_get_contents("php://input");
but here I can't do this because of the $_FILES....
I see in this post PHP - empty $_POST and $_FILES - when uploading larger files that the problem could be that post_max_size or upload_max_filesize are small, but I set them to 100M (enought for my purposes) and still the same. Even I toured memory_limit higiher but nothing.
I think that my problem have to be in my server side, could be the headers I set or some configuration I missed. I discarded any CodeIgniter issue (I'm using last version of CodeIgniter) because I tryed to post to a simple php file and I have the same problem. The simplest server side I'm used is:
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: Content-Type');
exit(var_dump($_POST, $_FILES));
?>
But I get empty arrays...
Does anybody have any idea of what can I do?

I finally have found the solution to this problem.
In non-technical speaking (I'm not an expert), an htpp post multipart/form-data request use a boundary random string to identify the different parameters sent by the client in order to give the server a reference to parse the input. A valid request header looks like this:
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryYnNrniY34QSJ48LC
In my implementatation, when setting the request header as:
header.append('Content-Type', 'multipart/form-data');
no boundary is set so PHP can't parse the input to populete $_POST and $_FILES. I tried to define my own boundary by appending at the end of the header but it didn't work. But... The solution is even easier: there is no need to specify the Content-Type header, Angular 2 will make it by only passing the data as FormData and performing a post request, creating a valid boundary that PHP can use.
So, a valid code to do this is:
upload(data: data, file: File): Observable<any> {
let formData:FormData = new FormData();
formData.append('file', file, file.name)
formData.append('data', JSON.stringify({data}));
return this.http
.post('myurl', formData)
.map(response => {
return response;
})
.catch(error => Observable.throw(error.message || error));
}
When performing this post request, Angular 2 will add a valid boundary in the multipart/form-data header.
That's all.
If someone can add more technical details about this, it would be great.
Hope this could help someone else.

Related

Sending multipart/form-data with PUT request doesn't work in Laravel

I am trying to send an HTTP PUT request with "Content-Type": "multipart/form-data" to a Laravel application. When I change the method to POST it works.
$a = $request->all(); // With PUT this is empty but with POST it works fine.
The client-side executes the following code:
axios({
method: "post", // when I try method:"PUT" and change the content type
url: "/api/offer",
data: fd,
headers: {"Content-Type": "multipart/form-data"} // here change to "x-www-form-urlencoded" it the $a array on backend is empty!
}).then(response => {
console.log("/offer/" + response.data)
if (response.data)
window.location.replace("/offer/" + this.offer.id);
else {
console.log("show a message that something went wrong! ")
}
}).catch(function (error) {
})
I could not find anywhere in the docs that PUT can't send "multipart/form-data"
So, can PUT send "multipart/form-data" or only POST can do that in general or it is only a PHP / Laravel Issue?
Edit:
Also, what difference does it make to use PUT instead of POST other than to comply with HTTP protocol and CRUD operation properly?
Laravel (HTML Forms) do not work great with Put requests so you'll need to spoof a POST request as if it was a PUT or PATCH request. On Axios you use the .post verb but within your form data you append
_method: "put"
Information from the official documentation:
https://laravel.com/docs/8.x/routing#form-method-spoofing
Excerpt from the documentation:
HTML forms do not support PUT, PATCH, or DELETE actions. So, when defining PUT, PATCH, or DELETE routes that are called from an HTML form, you will need to add a hidden _method field to the form. The value sent with the _method field will be used as the HTTP request method
I ran into this issue a few weeks ago myself with a Symfony 5.3 project. It only worked with POST Requests, not with PUT. Here's an issue from the Symfony GitHub that explains it in more detail.
To my understanding the issues lies within the PHP implementation of those requests. The HTTP standard "PUT" supports it, but PHP does not. Here's also a link to the bug from the PHP bugtracker.

axios doesnt post data to server

I am trying to make a login validation form in React and i am using axios to make database calls.
The thing is, i make contact with the server - i get 200 response but the data i post is not there.
This is what i did the first time:
const user = {username: this.state.username, password: this.state.password}
axios.post("http://localhost:80/thinksmart/post_requests/login.php", user)
.then(res => console.log(res))
.catch(err => console.log(err))
Then i tried another approach - where i set up base url :
const api = axios.create({baseURL: 'http://localhost:80'})
api.post("/thinksmart/post_requests/login.php", user)
.then(response => {
console.log(response)
})
.catch(error =>
console.log(error)
)
and neither of these worked.
In PHP i only do this:
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
echo json_encode($_POST);
and i get an empty array even though i have data sent (the user data)
You can solve this issue in two ways:
1- PHP code changes: to get the JSON data in the backend you need to execute the following code instead of the $_POST:
$json = file_get_contents('php://input');
2- Javascript code changes: To be able to receive the data in the backend using $_POST you need to send it using FormData more details can be found in the following link:
axios post requests using form-data

How to send an http post in ionic2 (typescript) with parameters as a multipart/form-data request?

I have a webservice (Actually generated with the drupal 7 plugin Service) accepting requests on 'http://mywebsite:8080/api_name/user' and checking possible connections against an 'api-key' variable in the requests, containing sort of a password.
Trying to connect to the service with my ionic2/cordova app result in a fail for the payload of the request results empty on the server side ($_REQUEST is an empty array and does not contain the api-key required, so that the request fails).
Debugging the request with https://requestb.in/ shows that the variable is in the body of the request, as expected.
The webservice is working well, on its side, for a post request sent by Google Postman go on successfully (thought I noticed a difference sending the postman request to requestb.in: 'api-key' parameter is not in the body, but in a part called "Form/Post Parameters" and the Content type is 'multipart/form-data' instead of application/json)
I am a bit confused about how my code (that seems correct) is not working...
Here is the code of the function trying to connect to the webservice:
register(username,password,email){
var headers = new Headers();
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
let postParams = {
"api-key" : "someSecret",
"name" : "something",
"mail" : "somethingelse"
}
this.http.post("http://mywebsite:8080/api_name/user", postParams, options).subscribe(data => {
console.log("ok");
}, error => {
console.log("error:"+error);// Error getting the data
});
}
EDIT: i'm reading that "$_REQUEST is used to collect data after submitting an HTML form" so the problem lays actually in sending the request as a 'multipart/form-data' but how I can do that? Changing the 'Content-type' in the header seems not to be enough. I tried adding this (with no fortune)
headers.append('Content-type', 'multipart/form-data');
After hours of attempts, here it is the correct code:
register(username,password,email){
var headers = new Headers();
headers.append('enctype', 'multipart/form-data');
let options = new RequestOptions({ headers: headers });
let postParams:FormData = new FormData();
postParams.append("api-key", "something secret");
this.http.post("http://mywebsite:8080/api_name/user", postParams, options).subscribe(data => {
console.log("ok");
}, error => {
console.log("error:"+error); // Error getting the data
});
}

Confusion in Angular 2 Http Post Request

First of all. Please bear with my questions.
What I am doing is just performing an ajax request which will return a response data of string.
Here's my php
<?php
header('Access-Control-Allow-Origin: *');
echo 'Peenoise Crazy';
?>
Http request
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
let options = new RequestOptions({ headers: headers });
return this.http.post('./app/parsers/peenoise.php', '', options)
.then(val => console.log(val))
This doesn't work. But if I change the url into this http://localhost:8912/app/parsers/peenoise.php. It works.
Questions
Why ./app/parsers/peenoise.php doesn't work?
If I change the url to http://localhost:8912/app/parsers/peenoise.php. Why do I need to put header('Access-Control-Allow-Origin: *')? It is on the same folder of my app.
How to use http.post without passing an url of http://localhost:8912/route/to/phpfolder.php?
Any help would be appreciated. Please guide me to this. Thanks
Post requires a full or a relative URL to work. You can use:
this.http.post('/parsers/peenoise.php', '', options)
Actually Angular looks for a backend server to post the data, so Imagine in your post request is like below
this.http.post('./app/parsers/peenoise.php', '', options).then(val => console.log(val))
It will try to make the post request at the location
imagine you are serving angular on localhost:4200
http://localhost:4200/app/parsers/peenoise.php
Now to answer your Questions :
1)./app/parsers/peenoise.php doesn't work because it's actually trying to find it from your backend service
2)You need to put header('Access-Control-Allow-Origin: *') whenever you are making an http request to a different server than from the one which your site is currently using, if you don't use that you'll end up with Connection_refused_Error.
3)If you want to use just angular then there are mockAckends, in-memory-web-api
An example using mockBackEnd is
here

Outputting file contents as binary as AJAX response

OK the title doesn't give much a way so let me explain my very strange set-up.
Two servers are involved:
website: remote
localhost: local machine
The workflow is as follows:
The site calls localhost via cross-domain AJAX
In response localhost dynamically creates a ZIP file via PHP's ZipArchive lib
localhost conveys the raw data that comprises the archive as the AJAX response
The request is made and the archive is made - all good. The archive is openable, all good. What I'm stuck on now is how to convey that archive as the AJAX response, such that it can be "put together again" (à la Humpty Dumpty). When I do this currently (via file_put_contents()) it errors on opening, saying it's invalid. Currently I'm just outputting the archive's raw data:
echo file_get_contents('path/to/archive.zip');
This is fine, but sends garbled characters in the response. I don't know much about encoding and headers, so apologise if this seems obvious.
For the response, should I be looking to convert it to binary, or sending certain headers etc? I tried sending the multipart/form-data header, but no dice. Headers aren't my strong point.
Please note cURL is not an option in this scenario, else I'd be laughing.
You have to read the zip file as a binary data with Blob javascript class.
This is a code snippet from Mozilla documentation
var oReq = new XMLHttpRequest();
oReq.open("GET", "/myfile.png", true);
oReq.responseType = "arraybuffer";
oReq.onload = function(oEvent) {
var blob = new Blob([oReq.response], {type: "application/octet-stream"}); //
// you have nothing to do with the blob...
// ...
};
oReq.send();
Then send this file (blob) with POST method to your destination
var oReq = new XMLHttpRequest();
oReq.open("POST", url, true);
oReq.onload = function (oEvent) {
// Uploaded.
};
oReq.send(blob); //the blob that you loaded
you can read more in the documentation by Mozilla :https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data

Categories