I need to wrap this up but not sure how :-)
Basically, I'm using jQuery forms to upload a file to a WCF REST service.
Client looks like:
<script type="text/javascript">
$(function () {
$('form').ajaxForm(
{
url: '/_vti_bin/VideoUploadService/VideoUploadService.svc/Upload/theFileName',
type: 'post'
})
});
</script>
<form id="fileUploadForm" name="fileUploadForm" method="post" action="/_vti_bin/VideoUploadService/VideoUploadService.svc/Upload" enctype="multipart/form-data">
<input type="text" name="filename" />
<input type="file" id="postedFile" name="postedFile" />
<input type="submit" id="fileUploadSubmit" value="Do Upload" />
</form>
While server relevant snippets are
[WebInvoke(UriTemplate = "Upload/{fileName}", ResponseFormat = WebMessageFormat.Json)]
void Upload(string fileName, Stream fileStream);
public void Upload(string fileName, Stream stream)
{
// just write stream to disk...
}
Problem is that what I write to disk looks like this, not the content of the file (in my case, "0123456789"):
-----------------------------7dc7e131201ea
Content-Disposition: form-data; name="MSOWebPartPage_PostbackSource"
// bunch of same stuff here
-----------------------------7dc7e131201ea
Content-Disposition: form-data; name="filename"
fdfd
-----------------------------7dc7e131201ea
Content-Disposition: form-data; name="postedFile"; filename="C:\Inter\Garbage\dummy.txt"
Content-Type: text/plain
0123456789
-----------------------------7dc7e131201ea--
Question - should I be manually parsing what I get above and extract the last part which corresponds to the uploaded file (not elegant)? Or there is a way to apply attribute, content type, whatever setting, in order to get in the stream JUST the uploaded file's content?
I'm suspect there is, but I'm missing it... any help?
Thanks much!
Take a look at 2 articles about Form file upload using ASP.NET Web API:
http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-1
http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-2
ASP.NET Web API already has classes (like MultipartFormDataStreamProvider) to parse multipart requests and also to save data from them. Why not to use these classes to solve your problem
UPD In case of .NET 3.5 code and hosting:
Think that MultipartFormDataStreamProvider class and it's assembly aren't for .NET 3.5. In these case you should use some other library/class to parse multipart or do it manually.
Try following project (MIT license) and file HttpMultipartParser.cs from this project:
https://bitbucket.org/lorenzopolidori/http-form-parser/src
Related
This is my first exercise in angular8. I am on the attempt to make a form that consumes an API written in springboot. The api written in spring-boot is never executed when trying to consume it from angular8 and here is the endpoint
http://localhost:8080/api/startreg
#PostMapping("/startreg")
public ResponseData<Activity> addReg(
#RequestParam(value="firstDate") String firstDate,#RequestParam(value="secondDate") String secondDate
,#RequestParam(value="username") String username) {
try {
Here is the service.ts script
private baseUrl = 'http://localhost:8080/api/startreg';
createReg(activity: Object): Observable<Object> {
return this.http.post('${this.baseUrl}', activity);
}
the html file of the angular8 is shown
<form (ngSubmit)="onSubmit()">
<div class="col-md-3 col-sm-5 col-xs-12 gutter">
<div class="sales">
<h2>From:</h2>
<div class="btn-group">
<select [(ngModel)]="activity.firstDate" class="form-control" name="firstDate">
when I attempt to submit the form, from the browser console below error
HttpErrorResponse {headers: HttpHeaders, status: 404, statusText: "Not Found", url: "http://localhost:4202/$%7Bthis.baseUrl%7D", ok: false, …}
Please where I am getting it wrong
Because this.http.post('${this.baseUrl}', activity); won't invoke service with the value of this.baseUrl, you can find out why according to the error message.
The URL you passed to post is still a string not a variable.
Please modify service.ts as follows:
...
return this.http.post(this.baseUrl, activity);
}
UPDATE
Another problem is your service consumes request parameters, so you have to pass these URL arguments for HTTP request in service.ts.
The valid URL should look like this:
http://localhost:8080/api/startreg?firstDate=XXX&secondDate=XXX&username=XXX
But I am not familar with TypeScript, so I don't know how to do this. Maybe you can refer to Angular2 - Http POST request parameters.
BTW, I strongly recommend that you should use #RequestBody rather than #RequestParam for your POST service.
I would like to upload multiple image files in one request using multipart. I have reviewed the Karate examples on this, but the multiple file upload does not meet my need (/multiple endpoint here - https://github.com/intuit/karate/blob/master/karate-demo/src/main/java/com/intuit/karate/demo/controller/UploadController.java). My service method (Spring REST) signature expects an array of MultipartFile[] so that I can accept any number of files. Here is my scenario:
Scenario: Upload multiple files
* def json = {}
* set json.files[0] = { read: 'file1.jpg', filename: 'file1.jpg', contentType: 'image/jpeg' }
* set json.files[1] = { read: 'file2.jpg', filename: 'file2.jpg', contentType: 'image/jpeg' }
Given path '/rest'
And multipart files json
When method post
Then status 200
And here is the Spring web service method (just trying to receive the files right now, so the method doesn't do much):
#PostMapping("/rest")
public String handleFileUpload(#RequestParam("file") MultipartFile[] file) {
System.out.println("Len: " + file.length);
for(MultipartFile currentFile : file) {
System.out.println("In here: " + currentFile.getOriginalFilename());
}
return file[0].getOriginalFilename();
}
When I run this I receive a Karate error: 'multipart file value should be json'
If I change the scenario to do this:
Scenario: Upload multiple files
* def json = {}
* set json.files = { read: 'file1.jpg', filename: 'file1.jpg', contentType: 'image/jpeg' }, { read: 'file2.jpg', filename: 'file2.jpg', contentType: 'image/jpeg' }
Given path '/rest'
And multipart files json
When method post
Then status 200
Then the test executes ok, but only one file ends up in the MultipartFile array 'files' (service method argument).
What is the proper way to upload multiple files to the web service method above using Karate?
Edit: Adding client code (below) and updated Spring method above.
Here is a simple HTML form that will submit multiple files to the Spring method above:
<form method="POST" enctype="multipart/form-data" action="/rest">
<table>
<tr><td>File to upload:</td><td><input type="file" name="file" /></td></tr>
<tr><td>File to upload:</td><td><input type="file" name="file" /></td></tr>
<tr><td></td><td><input type="submit" value="Upload" /></td></tr>
</table>
</form>
When submitted I get 2 files in the service method.
Seems that it does work, you just need to send the same parameter twice in the feature file
And multipart file files = { read: 'mergeTest.pdf', filename: 'upload-name.pdf', contentType: 'application/pdf' }
And multipart file files = { read: 'mergeTest.pdf', filename: 'upload-name.pdf', contentType: 'application/pdf' }
And multipart field filesMetadata = .........................................
my service:
public ResponseEntity<StreamingResponseBody> mergePdfs(#RequestPart #Validated
List<FileMetaData> filesMetadata, #RequestPart #Validated List<MultipartFile> files)
Wow, never seen this before and it is likely that Karate does not support it. I'm also wondering if this is legal as per the HTTP spec - as far as I know - each file has to have unique field-name. Do you have the corresponding client-side code for the Apache HTTP Client, that would help.
EDIT: also see answer by Esteban Lopez below: https://stackoverflow.com/a/59833358/143475
Your best bet may be to submit a feature request and also contribute code to expedite. Note that this is the first time in 2 years that anyone has reported this as a problem.
I would need to upload Gziped content to S3 via a signed URL.
Here is how I generate the signed URL with a JS backend:
s3.createPresignedPost({
Bucket: 'name',
Fields: {
key: 'key'
}
})
I have tried passing the Content-Encoding header to the signedURL POST request but that did not work. The headers are not set properly on the s3 object.
I have also tried setting up a post upload lambda to update the metadata. It failed with an error File is identical error
Finally I have tried using cloudfront + a lambda to force a header. This failed too with an error stating that Content-Enconding is a protected error.
--Update Start--
For uploading to S3 via Ajax or JS scripts, I would advise to use s3.getSignedUrl method. s3.createPresignedPost is meant for only direct browser uploads.
Below is example of Ajax jQuery Upload I created using this guide.
s3.getSignedUrl('putObject', {
Bucket: 'bucketName',
Key: 'sample.jpg.gz',
// This must match with your ajax contentType parameter
ContentType: 'binary/octet-stream'
/* then add all the rest of your parameters to AWS puttObect here */
}, function (err, url) {
console.log('The URL is', url);
});
Ajax PUT Script - Take the Url from above function call and use it below.
$.ajax({
type: 'PUT',
url: "https://s3.amazonaws.com/bucketName/sample.jpg.gz?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Content-Type=binary%2Foctet-stream&Expires=1547056786&Signature=KyTXdr8so2C8WUmN0Owk%2FVLw6R0%3D",
//Even thought Content-Encoding header was not specified in signature, it uploads fine.
headers: {
'Content-Encoding': 'gzip'
},
// Content type must much with the parameter you signed your URL with
contentType: 'binary/octet-stream',
// this flag is important, if not set, it will try to send data as a form
processData: false,
// the actual file is sent raw
data: theFormFile
}).success(function () {
alert('File uploaded');
}).error(function () {
alert('File NOT uploaded');
console.log(arguments);
});
In S3 object, you should see Content-Type, Content-Encoding under metadata.
Importent Note
When you try to upload via JS scripts which is running on browsers, typically browsers will tend to send OPTIONS method preflight(or CORS check) first before calling PUT method. You will get 403 Forbidden error for OPTIONS since CORS on S3 bucket doesn't allow that. One way, I resolved is by using following CORS configuration on bucket level. Reference
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>PUT</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
--Update End--
Did you try like this?. I just tested the policy using sample html given in AWS documentation. Reference - https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
s3.createPresignedPost({
Bucket: 'name',
Conditions: [
{ "Content-Encoding": "gzip" }
],
Fields: {
key: 'key'
}
})
Update -
Here is my observation so far.
We really need to check your client which is doing upload operation. If you want Content-Encoding set on MetaData, then your Pre-Signed Url should have Content-Encoding property set. If Signed Url doesn't have it but your request header does then it will give you Extra input fields: content-encoding.
I have signed a url with Content-Encoding and uploaded a zipped file with following sample html.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<form action="http://bucket-name.s3.amazonaws.com" method="post" enctype="multipart/form-data">
Key to upload:
<input type="input" name="key" value="sample.jpg.gz" /><br />
Content-Encoding:
<input type="input" name="Content-Encoding" value="gzip" /><br />
<input type="text" name="X-Amz-Credential" value="AKIAIOSFODNN7EXAMPLE/20190108/us-east-1/s3/aws4_request" />
<input type="text" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
<input type="text" name="X-Amz-Date" value="20190108T220828Z" />
Tags for File:
<input type="hidden" name="Policy" value='bigbase64String' />
<input type="hidden" name="X-Amz-Signature" value="xxxxxxxx" />
File:
<input type="file" name="file" /> <br />
<!-- The elements after this will be ignored -->
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</html>
If I do not send Content-Encoding header it gives the error Policy Condition failed: ["eq", "$Content-Encoding", "gzip"]
Note -
If you are using https while uploading, please make sure you have proper certificate on S3 endpoint otherwise you will get cert errors.
S3 Screenshot.
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js" type="text/javascript"></script>
<script>
$.get("http://example.com/", function(data) {
alert(data);
});
</script>
it does an OPTIONS request to that URL, and then the callback is never called with anything.
When it isn't cross domain, it works fine.
Shouldn't jQuery just make the call with a <script> node and then do the callback when its loaded? I understand that I won't be able to get the result (since it is cross domain), but that's OK; I just want the call to go through. Is this a bug, or am I doing something wrong?
According to MDN,
Preflighted requests
Unlike simple requests (discussed above), "preflighted" requests first
send an HTTP OPTIONS request header to the resource on the other
domain, in order to determine whether the actual request is safe to
send. Cross-site requests are preflighted like this since they may
have implications to user data. In particular, a request is
preflighted if:
It uses methods other than GET or POST. Also, if POST is used to send
request data with a Content-Type other than
application/x-www-form-urlencoded, multipart/form-data, or text/plain,
e.g. if the POST request sends an XML payload to the server using
application/xml or text/xml, then the request is preflighted.
It sets custom headers in the request (e.g. the request uses a header such as
X-PINGOTHER)
The OPTIONS is from http://www.w3.org/TR/cors/ See http://metajack.im/2010/01/19/crossdomain-ajax-for-xmpp-http-binding-made-easy/ for a bit more info
If you're trying to POST
Make sure to JSON.stringify your form data and send as text/plain.
<form id="my-form" onSubmit="return postMyFormData();">
<input type="text" name="name" placeholder="Your Name" required>
<input type="email" name="email" placeholder="Your Email" required>
<input type="submit" value="Submit My Form">
</form>
function postMyFormData() {
var formData = $('#my-form').serializeArray();
formData = formData.reduce(function(obj, item) {
obj[item.name] = item.value;
return obj;
}, {});
formData = JSON.stringify(formData);
$.ajax({
type: "POST",
url: "https://website.com/path",
data: formData,
success: function() { ... },
dataType: "text",
contentType : "text/plain"
});
}
Just change the "application/json" to "text/plain" and do not forget the JSON.stringify(request):
var request = {Company: sapws.dbName, UserName: username, Password: userpass};
console.log(request);
$.ajax({
type: "POST",
url: this.wsUrl + "/Login",
contentType: "text/plain",
data: JSON.stringify(request),
crossDomain: true,
});
I don't believe jQuery will just naturally do a JSONP request when given a URL like that. It will, however, do a JSONP request when you tell it what argument to use for a callback:
$.get("http://metaward.com/import/http://metaward.com/u/ptarjan?jsoncallback=?", function(data) {
alert(data);
});
It's entirely up to the receiving script to make use of that argument (which doesn't have to be called "jsoncallback"), so in this case the function will never be called. But, since you stated you just want the script at metaward.com to execute, that would make it.
In fact, cross-domain AJAX (XMLHttp) requests are not allowed because of security reasons (think about fetching a "restricted" webpage from the client-side and sending it back to the server – this would be a security issue).
The only workaround are callbacks. This is: creating a new script object and pointing the src to the end-side JavaScript, which is a callback with JSON values (myFunction({data}), myFunction is a function which does something with the data (for example, storing it in a variable).
I had the same problem. My fix was to add headers to my PHP script which are present only when in dev environment.
This allows cross-domain requests:
header("Access-Control-Allow-Origin: *");
This tells the preflight request that it is OK for the client to send any headers it wants:
header("Access-Control-Allow-Headers: *");
This way there is no need to modify the request.
If you have sensitive data in your dev database that might potentially be leaked, then you might think twice about this.
In my case, the issue was unrelated to CORS since I was issuing a jQuery POST to the same web server. The data was JSON but I had omitted the dataType: 'json' parameter.
I did not have (nor did I add) a contentType parameter as shown in David Lopes' answer above.
I was able to fix it with the help of following headers
Access-Control-Allow-Origin
Access-Control-Allow-Headers
Access-Control-Allow-Credentials
Access-Control-Allow-Methods
If you are on Nodejs, here is the code you can copy/paste.
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin','*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH');
next();
});
It's looking like Firefox and Opera (tested on mac as well) don't like the cross domainness of this (but Safari is fine with it).
You might have to call a local server side code to curl the remote page.
I've a sails application. So in the backend I've node js server and in the front end I've an angular js application.
And from the frontend I want to upload some file to s3. So for that I'm using s3-skipper in the backend.
So in the frontend I've
<form id="formUpload"
method = "post"
action = "/bin/upload"
enctype= "multipart/form-data" >
<input type="file" id="inFile" name="file" />
<input type="button" value="Upload" onclick="uploadFile();" />
</form>
And then to upload the file in s3 using skipper I'm accessing the file stream in the backend as:
req.file("file");
And this whole process works perfectly.
Now I've another scenario when I'm recording an audio file from the browser and I want to upload the file using same s3-skipper to s3.
But in this case I don't have any html form element and input type file for obvious reason. And for the audio file I've base64 audio dataUrl:
data:audio/ogg;base64,T2dnUwACAAAAAAAAAABx+oohAAAAAH....
And as I can't use form submit I want to send the data using $http. So what I'm doing:-
var blob = $scope.dataURItoBlob(audioDataUrl, 'audio/ogg');
var formData = new FormData();
formData.append('file', blob);
$http({
method: 'POST',
url: '/file/upload/multi',
headers: { 'Content-Type': 'multipart/form-data;' },
data: formData
And after sending the request I can see some data being sent in the browser network.
But in the backend this is what I'm getting as req.file("file"):
{ _fatalErrors: [],
isNoop: true,
_files: [],....
So, can anyone help me here how to upload the file properly here?