I set up a simple frontend service in JavaScript using Axios. This service takes a list of files and sends to my server code using a post request. My JavaScript looks like this
const testPost = async (files) => {
let formData = new FormData();
files.forEach((file) => {
formData.append("file", file);
});
const res = await axios({
method: "POST",
url: baseUrl,
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
return res;
};
And my Go code looks like this:
func (h *SendHandler) TestPost(w http.ResponseWriter, r *http.Request) {
//Allow CORS here By * or specific origin
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
formdata := r.MultipartForm
fmt.Println(formdata)
}
I think my problem lies in the body of my TestPost function in Go because I click send in my frontend code, I receive a response. In my Go function, when I print my formdata, it outputs <nil>. I want to be able to access each file in the list but I can't even access the formdata. I'm still new to both web dev and Go so there is definitely something I am misunderstanding here. Any pointers?
The documentation for MultipartForm says:
MultipartForm is the parsed multipart form, including file uploads. This field is only available after ParseMultipartForm is called.
Fix by calling ParseMultipartForm before using r.MultipartForm.
err := r.ParseMultipartForm(maxMemory)
if err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
// r.MultipartForm is now set.
fmt.Println(r.MultipartForm) // does not print nil
Related
I am trying to upload an image to an S3 bucket using a presigned URL generated using boto3 on Python. I have been using the example python code that was provided in the documentation and was successful (the image got correctly uploaded with the correct Content-Type). However, when trying to do this in Javascript for the purposes of our frontend application, I am really struggling to get it to work.
Here's the example dictionary returned by the backend:
{
"fields": {
"AWSAccessKeyId": "AKIAYS3VM3EBIFL7FKE5",
"key": "posts/623255a762fd9bdfbd13f91a",
"policy": "<very long string>",
"signature": "Qvc/sGBHk0uzirzIfR1YmE2kFlo="
},
"url": "https://hotspot-storage.s3.amazonaws.com/"
}
Here is the functioning Python code:
response = <json response object>
object_name = 'playground/example_profile_group.png'
response['fields']['Content-Type'] = "image/png"
# Demonstrate how another Python program can use the presigned URL to upload a file
with open(object_name, 'rb') as f:
files = {'file': (object_name, f)}
http_response = requests.post(response['url'], data=response['fields'], files=files)
# If successful, returns HTTP status code 204
print(http_response)
print(http_response.text)
Here is the non-functioning Javascript code:
const data = response.data;
let payload = data.fields;
payload['Content-Type'] = 'image/jpeg';
const file = {
uri: previewPath,
name: previewPath,
type: 'image/jpeg',
};
payload.file = file;
const url = data.url;
console.log(payload, "MY PAYLOAD")
axios({
method: 'post',
headers: {'Content-Type': 'multipart/form-data'},
url: url,
data: payload,
})
.then(function (response) {
console.log(response.data, 'uploaded');
const data = response.data;
})
.catch(function (error) {
console.log(
'error uploading image',
error.response.data,
);
});
})
.catch(function (error) {
console.log(
'error getting media link',
error.response.data,
);
});
This is the error that keeps getting returned:
error uploading image <?xml version="1.0" encoding="UTF-8"?>
<Error><Code>MalformedPOSTRequest</Code><Message>The body of your POST request is not well-formed multipart/form-data.</Message><RequestId>Q0ES6P4QP75YVVED</RequestId><HostId>eowLxSJQD1xP1EfHPnzGSJzXVGpPjurIMhkdwAD22JMvi9zRoFGg6Bq+mnUt/Lu7DNPY80iBDMc=</HostId></Error>
I have been stuck on this for an absurd amount of time, and cannot tell what I am doing wrong. Any help would be very much appreciated.
In order to send a multipart/form-data request body, you'll need to use a FormData instance instead of a JavaScript object.
For example
const { url, fields } = response.data;
const payload = new FormData();
payload.append("file", file); // this is the file blob, eg from <input type="file">
payload.append("Content-Type", "image/jpeg");
// add all the other fields
Object.entries(fields).forEach(([ key, val ]) => {
payload.append(key, val);
});
// No need to manually set content-type header, your browser knows what to do
const { data: result } = await axios.post(url, payload);
console.log("uploaded", result);
I am changing my form submission to make it more fluid via using fetch.
In order to process the value of my input image:
<input name="perfil" type='file' id="imageUpload />
And then, in order to upload it to Amazon S3,
I do this in my views.py:
if request.method == "POST"
image = request.FILES['perfil']
im = Image.open(image)
output = BytesIO()
rgb_im = im.convert('RGB')
rgb_im.save(output, format='JPEG', quality=90)
output.seek(0)
s3.Bucket('bucketname').upload_fileobj(output, request.user.email + '.profileImage')
But now (because i'm trying to implement fetch), I am getting the image file like this:
fetch(`url`, {
method: 'POST',
body: JSON.stringify({
image: document.querySelector('#imageUpload').files[0],
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
"X-CSRFToken": getCookie('csrftoken')
}
})
}
The problem is that when I do request.body['image`] in the server (views.py), all I'm getting is this: "image":{}
And I don't know how to process this file in JS before I send it to the server (that will end up uploading it to amazon s3)
With this Example you can upload Images on a Server!
// Select your input type file and store it in a variable
const input = document.getElementById('fileinput');
// This will upload the file after having read it
const upload = (file) => {
fetch('http://www.example.net', { // Your POST endpoint
method: 'POST',
headers: {
// Content-Type may need to be completely **omitted**
// or you may need something
"Content-Type": "You will perhaps need to define a content-type here"
},
body: file // This is your file object
}).then(
response => response.json() // if the response is a JSON object
).then(
success => console.log(success) // Handle the success response object
).catch(
error => console.log(error) // Handle the error response object
);
};
// Event handler executed when a file is selected
const onSelectFile = () => upload(input.files[0]);
// Add a listener on your input
// It will be triggered when a file will be selected
input.addEventListener('change', onSelectFile, false);
Recently I started working with Go to make servers, in my current experiment I am trying to upload an image to my Go/mux webserver via fetch on a React.js front-end. Whenever I upload an image through a form the server fails to receive the image and returns a "no such file" error. I am using JS's FormData API to store and send the image.
Here is the code for the client
handleInput = (e) => {
let formData = new FormData();
formData.append("myImage", e.target.files)
fetch("http://localhost:8080/api", {
method: 'POST',
body: formData,
})
.then((res) => {
console.log(res)
})
.then(() => {
console.log("Success")
})
.catch((error) => console.error(error))
}
Here is the code for the server
func api(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
fmt.Println("Connected -> api route")
fmt.Fprintf(w, "connected to api")
// ERROR HERE: no such file / failed to retrieve image
r.ParseMultipartForm(32 << 20)
file, _, err := r.FormFile("myImage")
if err != nil {
log.Print(err)
fmt.Println("failed to retrieve image")
w.WriteHeader(http.StatusBadRequest)
return
} else if err == nil {
fmt.Println("Success")
}
defer file.Close()
}
I have tried adding and removing the multipart/form-data headers, neither have worked.
I'm new to Go and still trying to figure things out, any help is appreciated!
Checking the code, this is a pretty standard upload file method, is your Go api working with other services?, I can not find anything wrong, so maybe is more about how the api is configured in general.
Other thing you can do is handling the error at
if err := r.ParseMultipartForm(32 << 20); err != nil {
fmt.Println(err)
}
maybe you can have a better idea about the error
Also make sure your file is really there before appending it
console.log(e.target.files)
I don't know why I receive on server [Error: Multipart: Boundary not found]
and bundle.js:37628 POST http://localhost:8800/exporttocsv 500 (Internal Server Error)
When I make post through
<form action="/exporttocsv" method="POST" encType="multipart/form-data">
post works correctly, but through axios doesn't work.
Please help me fix the mistake
this my code
/--client
import axios from 'axios'
var formData = new FormData()
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
export const ipmortToCSV = (file) => dispatch => {
formData.append('file',file)
console.log(formData.getAll('data'))
axios.post('/exporttocsv', {
"UploadCommand": formData
},config)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
//--server
const router = require('express').Router()
var csv = require('csv-express')
const controllers = require('../../controllers/exporttocsv')
var multer = require('multer')
var upload = multer({dest : 'exporttocsv/'})
router.get('/', (req, res) => {
controllers.exportToCsv(req,res)
})
router.post('/',upload.single('file'),(req,res) => {
//controllers.importToCsv(req,res)
})
module.exports = router
You can do this ...
Instantiate a new FormData instance.
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
let fd = new FormData();
fd.append('file',files[0])
return axios.post("http://localhost:5000/upload", fd, config)
Usingconcat and concat-stream
const concat = require("concat-stream")
const fd = new FormData()
fd.append("hello", "world")
fd.append("file", fs.createReadStream(file))
fd.pipe(concat(data => {
axios.post("/hello", data, {
headers: fd.getHeaders()
})
}))
Using promise
const promise = new Promise((resolve) => {
const fd = new FormData();
fd.append("hello", "world");
fd.append("file", fs.createReadStream(binaryFile));
fd.pipe(concat({ encoding: 'buffer' }, data => resolve({ data, headers: fd.getHeaders() })));
});
promise.then(({ data, headers }) => axios.post('/hello', data, { headers }));
I hope I've been useful! :)
References:
github.com - Can't get a .post with Content-Type...
github.com - Better solution using axios, form-data, fs
https://stackoverflow.com/a/47630754/3332734
I was struggling with this issue of multipart boundary not found with fetch api calling to a nestjs server. What I tried was to remove the
'Content-Type': 'multipart/form-data',
headers so that Fetch api automatically set the headers and it worked. Try it out
By default axios do not attach boundary to content type header. You have to do it manually:
axios.post(`${this.baseUrl}/${path}`, formData, {
headers: {
'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,
},
})
It is especially important if you talking to spring server.
In other case you will see exception:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
I was getting this problem with Axios via JavaScript because the content-type header was multipart-form-data but the boundary was missing.
Based on my research, a good way to handle it is to allow Axios to auto-detect the content type and set the headers correctly itself.
Here is an idea for how to accomplish this:
const formDataWithFiles = hasFiles ? new FormData() : undefined;
if (formDataWithFiles) {
// axios will automatically set the content-type to multipart/form-data if the
// data param is a FormData object
// otherwise, it will use application/json
// (study the Dev Tools > Network tab > XHR tab headers)
Object.keys(modifiedFields)
.forEach(field => formDataWithFiles.append(field, modifiedFields[field]));
}
const { data } = await axios({
method,
url: actionUrl,
data: hasFiles ? formDataWithFiles : modifiedFields,
headers: {
...axios.defaults.headers,
...headers,
},
});
return data;
The above code is in a generic handleSubmit function that can be called from anywhere in the client-side.
Here is the function signature:
const { data } = await this.submitForm({
actionUrl: this.actionUrl,
method: this.method,
modifiedFields: {
...this.modifiedUser,
},
hasFiles: true,
});
In the above code, there are two use cases. The first is the default case, where a normal payload is sent via a flat object. The second is the case when the form has files and you want multipart/form-data. In this case, we use the FormData Object as a vessel to instruct Axios to auto-detect the necessary headers and set the correct boundary.
If you do not specify the headers correctly, it is possible to receive an empty $request->all() Array in Laravel, or perhaps any server such as node.js.
The short answer to my answer is to use the FormData Object because it contains more information than a plain-old-JavaScript-object. With it, you can also access:
const formData = new FormData();
console.log('boundary:', formData._boundary);
As my annotation above hints towards, use the Dev Tools > Network tab > XHR tab to examine your request headers and make sure you have content-type application/json or application/x-www-form-urlencoded for regular form submits and multipart/form-data' if you are uploading a file.
For me the main reason was what the OP did; sending the data argument of axios.post as an object ({ key: formDataObj}) instead of just formDataObj directly as the arg.
For me add the following code to fixes it.
axios.post('/xxx/Upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
transformRequest: (data) => {
return data
},
})
Okay, I would like to share my solution as I struggled with this problem for almost a day. The issue was caused by an incorrect package version. Around 27.0.0, some changes were made to the form data sending, which resulted in issues with the boundaries. I'm not sure what version you folks are using, but it might be worth checking if this is the cause of your problem.
https://github.com/axios/axios/issues/4631
I'm trying to upload files from browser to s3 amazon, I've modified the CORS policy rules to allow the post for the bucket, but I'm getting the error
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>InvalidArgument</Code><Message>Bucket POST must contain a field named 'key'. If it is specified, please check the order of the fields.</Message>
<ArgumentValue></ArgumentValue><ArgumentName>key</ArgumentName><RequestId>1E0A8DC78C0CEA9A</RequestId><HostId>XN38Qje9hUrGqHNIhtT8CtowX9tXlpyfEoaXb1UNxlsyLOWreh2mKqKVXg1zjLVl</HostId></Error>
Here is my request and response, I'm passing key parameter in the right order by still getting this error
Can anyone tell me whats wrong with it, I'm submitting request using FormData
any help would be greatly appreciated.
Thanks
Edit: here is the code pls check
var form_data = new FormData();
form_data.append('file',hdlr.file);
//form_data.append('crop_type',settings.get_cropped_type());
//form_data.append('attributes',JSON.stringify(file_attr));
$('input:hidden',$form).each(function(){
form_data.append(this.name,this.value);
});
//finally post the file through AJAX
var xhr = new XMLHttpRequest();
xhr.open("POST", $form[0].action, true);
xhr.send(form_data);
It kind of looks like your file form field is appearing first in the request. I can't tell for sure since you have not included the entire request payload in your answer, but it looks like this is appearing just above the "key" field. AWS ignores all fields in the request after the file field, so all other fields must appear before the file.
Thank you Ray Nicholus
It works for me.
{
"formAttributes": {
"action": "https://**.s3.ap-southeast-1.amazonaws.com",
"method": "POST",
"enctype": "multipart/form-data"
},
"formInputs": {
"acl": "public-read",
"key": "users/2/images/drops-of-water-578897_640.jpg",
"X-Amz-Credential": "**",
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
"X-Amz-Date": "**",
"Policy": "**",
"X-Amz-Signature": "**"
}
}
function uploadFile(event) {
event.preventDefault();
getSignedPost().then(() => {
const fileEl = document.getElementById('id-file');
const file = fileEl.files[0];
const formData = new FormData();
Object.keys(uploadCredentials.formInputs).forEach((key) => {
formData.append(key, uploadCredentials.formInputs[key]);
});
// update key to file name
const key = `users/2/images/${file.name}`;
formData.set('key', key);
uploadCredentials.formInputs.key = key;
// update show data on page
const el = document.getElementById('id-upload-info');
el.innerText = JSON.stringify(uploadCredentials, null, 2);
// IMPORTANCE: https://stackoverflow.com/a/15235866
// AWS ignores all fields in the request after the file field, so all other fields must appear before the file.
formData.append('file', file);
fetch(uploadCredentials.formAttributes.action, {
method: uploadCredentials.formAttributes.method,
// headers: {
// 'Content-Type': 'multipart/form-data',
// },
body: formData,
})
.then((res) => {
if (res.status === 204) {
console.log('Successfully uploaded file');
console.log('-- 204 - no content');
return `Successfully uploaded file: ${key}`;
}
if (res.ok) {
return res.json();
} else {
return res.text();
}
})
.then((res) => {
alert(JSON.stringify(res, null, 2));
})
.catch((err) => {
alert(err.message || err);
});
});
}