I am working with an external API which is written with Symfony (5.4.31)
My own code is written with NestJS, and I am trying to send local files to the Symfony API using Axios.
My problem is, the file is empty on the API.
So here is a classical test:
import * as FormData from 'form-data';
const stream = fs.createReadStream(filePath);
const form = new FormData();
form.append('file', stream, 'file.pdf');
axios({
method: 'post',
url: url,
data: form,
headers: {
"Authorization": "Bearer "+token,
...form.getHeaders()
}
})
.then(function (response) {
console.log(response.data);
})
.catch(function (response) {
console.log(response);
});
I know that the API is checking the existence of the file with:
) : DocumentFileApiDto { $uploadedFile = $request->files->get('file'); if($uploadedFile === null) { throw new BadRequestHttpException('"file" is required'); }
And I am getting the response :
data: {
'@context': '/api/public/contexts/Error',
'@type': 'hydra:Error',
'hydra:title': 'An error occurred',
'hydra:description': '"file" is required',
trace: [Array]
}
I don't see what's wrong with my request.
I tried to send to this API for testing : http://httpbin.org/post and it seems to work fine.
So to figure out what is going on, I have another project with symfony (an older version). So I add a test route on it to check if the file is empty there to :
public function testUploadAction(Request $request) {
$jsonConverter = new JsonConverter();
print_r($request->files->get('file'));
print_r($_FILES);
print_r($_POST);
$response = $jsonConverter->objectToJson([]);
return $response;
}
And here is the response:
Symfony\Component\HttpFoundation\File\UploadedFile Object
(
[test:Symfony\Component\HttpFoundation\File\UploadedFile:private] =>
[originalName:Symfony\Component\HttpFoundation\File\UploadedFile:private] => test.pdf
[mimeType:Symfony\Component\HttpFoundation\File\UploadedFile:private] => application/pdf
[size:Symfony\Component\HttpFoundation\File\UploadedFile:private] => 138806
[error:Symfony\Component\HttpFoundation\File\UploadedFile:private] => 0
[pathName:SplFileInfo:private] => /tmp/php6bG6bm
[fileName:SplFileInfo:private] => php6bG6bm
)
Array
(
[file] => Array
(
[name] => test.pdf
[type] => application/pdf
[tmp_name] => /tmp/php6bG6bm
[error] => 0
[size] => 138806
)
)
So I thought the problem was on the API, then I tried with a rest tool (AdvancedRestClient), and what a surprise, my file is successfully uploaded...
So here it is, I think there is some middleware or something on the API which is removing my file, because there is no content-length header. But I can't add a content-length header because I am sending a stream.
I tried with
fs.statSync(filePath)['size'];
The API detect my file, but then I have the classical:
The uploaded file was only partially uploaded.
I also have this error on my own Symfony test by the way, which seems to be normal.
I finally found a solution to my problem, and I was on the good way.
I don't know why, but the client API where I need to push my files absolutely need the 'Content-Length' header.
I almost found the correct way to use it, but was missing some Bytes.
Previoulsy, I tried like :
await axios({
method: 'post',
url: url,
data: form,
headers: {
"Authorization": "Bearer "+token,
...form.getHeaders(),
"Content-Length": fs.statSync(downloadPath)['size']
}
})
But the length here is not totally the good one. I needed to change the length in the FromData like this :
form.append('file', stream, {
filename: test.pdf,
contentType: 'application/pdf',
knownLength: fs.statSync(downloadPath)['size']
});
Then, I can extract the total formData length to use it :
await axios({
method: 'post',
url: url,
data: form,
headers: {
"Authorization": "Bearer "+token,
...form.getHeaders(),
"Content-Length": form.getLengthSync()
}
})
The difference between the two length is :
fs.statSync(downloadPath)['size'] = 138806 Bytes
form.getLengthSync() = 139043 Bytes
Hope this can help sometime :)