How to pass 'filename' parameter to FormData.append() method for string data - multipartform-data

In my FormData.append method I have some job settings that I send as part of multipart/form-data POST. The problem is: my case is the third one, as specified in the link above, that is - I pass my data as a string (and not as file or blob), and therefore, I cannot specify the third, optional parameter filename.
Is there a workaround of how I could specify that additional parameter for the string data.

There is no method to modify FormData to make it add a filename header field unless you use File. There isn't even a method to read data from FormData, so it's quite a restricted object. However, you can construct a File object with the string you need and pass that to FormData.append. Like such:
var fileStringArray = [ "Foo bar" ];
var fileName = "...";
var blobAttrs = { type: "text/plain", ... };
var file = new File(fileStringArray, fileName, blobAttrs);
var fd = new FormData();
fd.append("file1", file, file.name);
Check out the File and Blob object pages on MDN.

I completely forgot about posting this one :)
In the end of the day I went for the solution that used Blob:
var jsonFD = JSON.stringify($scope.jsonResult.analysis);
var jsonFile = new Blob([jsonFD], {type: 'text/plain'} );
fd.append('upload', jsonFile, 'jobSettings.json');
xhr.open("POST", $scope.hostPdf);
Which is essentially what you suggest :)
Thank you.

Related

Send .mat file through Django Rest Framework

I have an issue to send the contents of a .mat file to my frontend. My end goal is to allow clients to download the content of this .mat file at the click of a button so that they end up with the same file in their possession. I use Next.js + Django Rest Framework.
My first try was as follow:
class Download(APIView):
def get(self, request):
with open('file_path.mat', 'rb') as FID:
fileInstance = FID.read()
return Response(
fileInstance,
status=200,
content_type="application/octet-stream",
)
If I print out the fileInstance element I get some binary results:
z\xe1\xfe\xc6\xc6\xd2\x1e_\xda~\xda|\xbf\xb6\x10_\x84\xb5~\xfe\x98\x1e\xdc\x0f\x1a\xee\xe7Y\x9e\xb5\xf5\x83\x9cS\xb3\xb5\xd4\xb7~XK\xaa\xe3\x9c\xed\x07v\xf59Kbn(\x91\x0e\xdb\xbb\xe8\xf5\xc3\xaa\x94Q\x9euQ\x1fx\x08\xf7\x15\x17\xac\xf4\x82\x19\x8e\xc9...
But I can't send it back to my frontend because of a
"UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9c in position 137: invalid start byte"
This error is always the same regardless of which .mat file I try to send in my response.
Next I tried to use the scipy.io.loadmat() method. In this case, fileInstance gives me a much more readable dictionary object, but I still can't get it to transfer to the frontend because of the presence of NaN in my dict:
ValueError: Out of range float values are not JSON compliant
Finally, some suggested to use h5py to send back the data as such:
with h5py.File('file_path.mat', 'r') as fileInstance:
print(fileInstance)
But in that case the error I get is
Unable to open file (file signature not found)
I know my files are not corrupted because I can open them in Matlab with no problem.
With all this trouble I'm wondering if I'm using the right approach to this problem. I could technically send the dictionary obtained through 'scipy.io.loadmat()' as a str element instead of binary, but I'll have to figure out a way to convert this text back to binary inside a Javascript function. Would anybody have some ideas as to how I should proceed?
The problem was in my frontend after all. Still, here's the correct way to go about it:
class Download(APIView):
parser_classes = [FormParser, MultiPartParser]
def get(self, request):
try:
file_path = "xyz.mat"
response = FileResponse(file_path.open("rb"), content_type="application/octet-stream")
response["Content-Disposition"] = f"attachment; filename=file_name"
return response
except Exception as e:
return Response(status=500)
This should send to the frontend the right file in the right format. No need to worry about encoding and such.
Meanwhile, on the frontend you should receive the file as follows:
onClick={() => {
const url = '/url_to_your_api/';
axios({ method: 'get', url: url, responseType: 'blob' })
.then((response) => {
const { data } = response;
const fileName = 'file_name';
const blob = new Blob([data], { type: 'application/octet-stream' });
const href = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = href;
link.download = fileName + '.mat';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(href);
})
.catch((response) => {
console.error(response);
});
}}
Long story short, the part I was missing was to specify to receive the data as blob inside the 'onClick()' function. By default, responseType from Axios is set to Json/String. For that reason, my file was modified at reception and would not be usable in matlab afterwards. If you face a similar problem in the future, try to use the 'shasum' BASH function to observe the hashed value of the file. It is with the help of that function that I could deduce that my API function would return the correct value and that therefore the problem was happenign on the frontend.

How to send a JSON and an image file to a server?

I am trying to send a JSON file and an image file together to a server, but am really struggling.
1) If I send just the quilt item, so skipping the formData and changing the $http part below to $http.post('quilts/create/', quilt), then set the server end point to expect (#RequestBody QuiltRequest quiltRequest) without the bits about transformRequest and headers, it processes the data therein quite happily but I don't have an image to add to the records.
2) If I don't add the quilt item to the formData, and tell the server to expect (#RequestParam("image") MultipartFile image), I can save the image file on my server and generate a url string for it, but have no other quilt information to make the corresponding database entry.
How can I send both the quilt and the image in one request, and have the server receive and process both?
Many thanks!
Client-side service:
this.create = function (quilt, image) {
quilt.size = JSON.parse(quilt.size);
quilt.maker = JSON.parse(quilt.maker);
const formData = new FormData();
formData.append('quiltRequest', quilt);
formData.append('image', image);
$http.post('quilts/create/', formData, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).then(function (response) {
return window.location = '#!/quilts/created/' + response.data;
})
};
Server-side end point:
#PostMapping(path = "/create")
public BigInteger create(#RequestPart QuiltRequest quiltRequest, #RequestPart MultipartFile image) throws IOException {
// do stuff based on parameters received
}
Apart of it, i think you can try to encode the image to base64 string. Send it to server and at the server, You decode it
My required solution have given by a real-world hero, and is posted here in case anyone else with a similar problem stumbles upon this thread :) (But thank you to user3562932 for taking some time to read and make a suggestion).
On the client side, we have moved the five lines of data preparation into a separate method, such that the original create() now takes a bunch of parameters and jumps straight to $http.post(url, data which has been magically transformed into something appropriate to send {rules on how to send the data}).
$http.post('quilts/create/', formData(quilt, image), {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).then(function (response) {
return window.location = '#!/quilts/created/' + response.data;
})
The magical transformation happens in new function formData(), which takes as its parameters the data we want to send and makes the necessary changes:
1) make a formData container for the data to be POSTed.
2) stringify information from the html form (e.g. text, numbers) into a JSON and append to formData.
2a) in this particular case, my quilt structure contains size and maker details which arrived from the backend as JSONs, and were selected in the webpage from drop-down lists of various sizes and makers, hence the parsing rows to get these items ready to be included in the formData.
3) convert files into BLOBs, and then likewise append.
4) return formData, with all required information neatly wrapped up and ready to go!
Note: in the services.js file, this formData() method actually appears above the create() method, but it feels more logical to talk about them this way around.
function formData(quilt, image) {
let formData = new FormData();
quilt.size = JSON.parse(quilt.size);
quilt.maker = JSON.parse(quilt.maker);
formData.append('quiltRequest', JSON.stringify(quilt));
formData.append('image', new Blob([image]));
return formData;
}
On the server side, we can now happily receive this through:
#PostMapping(path = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public BigInteger create(#RequestParam(value = "quiltRequest") String quiltRequest,
#RequestParam(value = "image") MultipartFile image) throws IOException {
QuiltRequest quilt = new ObjectMapper().readValue(quiltRequest, QuiltRequest.class);
QuiltResponse quiltResponse = quiltService.create(quilt, image);
return quiltResponse.getQuilt().getId();
}
In order to enable the end point to consume our exciting multimedia input, we have to add the following import at the top of the class:
import org.springframework.http.MediaType;
We use another import to enable the use of the MultipartFile class that we have designated for the incoming image file:
import org.springframework.web.multipart.MultipartFile;
The JSON object from the webpage has come through as a String, but that needs to be parsed into its underlying components to actually be of use. This is where the ObjectMapper comes into play. Call on its readValue() method, and pass in the string argument plus a template of what the information should look like when unwrapped (here, a QuiltRequest class with defined properties corresponding to the information we fed into the JSON back in the client-end server). Remember to include the necessary import to access the ObjectMapper:
import com.fasterxml.jackson.databind.ObjectMapper;
Hopefully this breakdown of the changes makes sense, with enough explanation to help other developers build end-to-end POST requests to suit their own projects.

Dart and Client Side File Handling (with authorization)

A server side application requires authorization on file download links. This means a normal <a ng-href="{{createLinkToFile()}}"> is no longer sufficient to get enough parameters passed to the server.
When trying to use a programmatic call to the file download, I get the response data back to Dart client application. Using a simple http GET:
var url = "http://example.com/file";
headers.putIfAbsent("Authorization", () => "bearer " + token;
_http.get(url: url, headers : headers);
The future returned by the GET will hold the data, but how do I instruct the browser to download it as a file, instead of just trying to keep it in memory?
Or is there a way to just do it in a normal link?
After downloading the data from the server like shown in Using Dart to Download a PNG File (Binary File) and displaying it not working you can create a download link like shown at http://blog.butlermatt.me/2014/03/dynamically-generating-download-files/
import 'dart:html';
void main() {
List body = [ 'Some test data ...\n'];
// Create a new blob from the data.
Blob blob = new Blob(body, 'text/plain', 'native');
// Create a data:url which points to that data.
String url = Url.createObjectUrlFromBlob(blob);
// Create a link to navigate to that data and download it.
AnchorElement link = new AnchorElement()
..href = url
..download = 'random_file.txt'
..text = 'Download Now!';
// Insert the link into the DOM.
var p = querySelector('#text');
p.append(link);
}
The code of Seth solves indeed part of the problem. To make it a bit more complete, I'm now using the following:
void doPdfFileRequest(String url) {
var request = new HttpRequest();
request.open('GET', url);
request.responseType = "blob";
request.withCredentials = false;
request.setRequestHeader("Accept", _httpAcceptHeader);
request.setRequestHeader("Authorization", "bearer " + token);
request.onReadyStateChange
.listen((r) => onData(request, "filename.pdf"));
request.send();
}
void onData(HttpRequest request, String filename) {
if (request.readyState == HttpRequest.DONE && request.status == 200) {
if (!isIE()) {
var contentType = request.getResponseHeader("content-type");
AnchorElement downloadLink = new AnchorElement(
href: Url.createObjectUrlFromBlob(request.response));
downloadLink.rel = contentType;
downloadLink.download = filename;
var event = new MouseEvent("click", view: window, cancelable: false);
downloadLink.dispatchEvent(event);
} else {
var href = Url.createObjectUrlFromBlob(request.response);
window.open(href, "_self");
}
}
}
A few things to notice. Instead of using the downloadLink.click(), a mouse event is constructed to ensure that it works on Firefox as well as on Safari and Chrome. Firefox seems not to handle the click() otherwise. Binding it to the DOM as is done in the code of Seth isn't necessary.
Internet Explorer doesn't understand the download attribute, so nothing will happen, therefore a window.open is used to at least have it work (though not ideal) on IE, it's redirecting to self to avoid being hit by the pop up blocker.
There are solutions that convert the result download result to Base64 first and put it in a data:mimetype href, using the blob this isn't necessary.
A nice way to set the filename on the file to download would be through the content disposition header, but this header is marked as unsafe, so cannot be used. The filename is now set in the code.
Another note, notice that a HttpRequest is used instead http.get(), The HttpRequest allows you to set the responseType, in this case blob, which can be transformed into a object url.

How do you change a filename once uploaded in Loopback?

I am trying to assign a profile picture to a user, I am currently trying to do this by uploading a file using the angular file-upload and then renaming it to the id of the user.
Is this
the best way to do this
if not how what is a better way
I just started looking at making a relation of a file belonging to one user but im not sure where the resources are to look for this
Any help appreciated!
Create a newfile datasource.development.js and set NODE_ENV=development
module.exports = {
'storageDs': {
'name': 'storageDs',
'connector': 'loopback-component-storage',
'provider': 'filesystem',
'root': './test/storageTesting',
'maxFileSize': 20971520,
'getFilename': function(fileInfo, req, res) {
var origFilename = fileInfo.name;
// optimisticly get the extension
var parts = origFilename.split('.'),
extension = parts[parts.length-1];
// Using a local timestamp + user id in the filename (you might want to change this)
var newFilename = (new Date()).getTime()+'.'+extension;
return newFilename;
}
}
};

Upload data to Yesod server using input forms

What is a correct way to uploading files via custom input forms in Yesod?
I developed a frontend in AngularJS, and I pass data to and from the server via JSON objects. However, this does not work when uploading files. For this reason I resorted to forms.
Gathering different resources I ended up with the following code. On the client side I use the following function:
this.submitNewMeas = function() {
var selected_file = document.getElementById('measurements').files[0];
var fd = new FormData();
fd.append("measurementsFile", selected_file);
var response = $http({
method: 'POST',
url: '/measurements/'+this.currMedia.id,
headers: {
'Content-Type': 'multipart/form-data'
},
data: {
fd
},
transformRequest: formDataObject
});
...
};
And on the server side I receive data as follows:
postMeasurementsR :: MediaId -> Handler Value
postMeasurementsR mediaId = do
result <- runInputPost $ iopt fileField "measurementsFile"
case result of
Just fileInfo -> do
saveMeas fileInfo "measDir"
saveMeas :: FileInfo -> FilePath -> HandlerT App IO (FilePath)
saveMeas file dest = do
let filename = unpack $ fileName file
dest = dest </> filename
liftIO $ fileMove file dest
return filename
However this causes a server error
20/Nov/2014:13:40:15 +0100 [Error#yesod-core] <<loop>> #(yesod-core-1.4.3:Yesod.Core.Class.Yesod ./Yesod/Core/Class/Yesod.hs:502:5)
Status: 500 Internal Server Error 54.70131s
If I don't try to save the data, then the file information is received, and I am able to see the file name on the server.
I've successfully managed to upload files using Yesod forms, but when it comes to custom forms all my attempts so far have failed.
Any help is greatly appreciated.
Thanks.
dest = dest </> filename
That's the source of your infinite loop: you're saying "dest is defined as itself plus filename." What's happening is that you're shadowing the original declaration of dest. A simple solution is to call the second declaration something like dest' (or better yet, something even more informative).

Resources