Angular 8: URL encoded form POST - javascript

I want to post form data to a server that accepts and returns text/html/xml. I am effectively trying to emulate a normal URL encoded form POST. My Angular 8 POST function successfully posts (200 OK), but the server can't understand the data because it is JSON and not URL encoded.
Response and request headers state Content-Type: text/html; Charset=utf-8 and Accept: text/html, application/xhtml+xml, */* and I have added responseType: "text" to the httpClient options. Why is the server still being sent JSON and not URL encoded data?
// obj2 = output from ngForm
// baseUrl2 = server that sends and receives text/html/xml
public postForm(obj2) {
return this.httpClient
.post(this.baseUrl2, obj2, {
headers: new HttpHeaders({
"Content-Type": "application/x-www-form-urlencoded",
Accept: "text/html, application/xhtml+xml, */*"
}),
responseType: "text"
})
.map(data => data);
}
Form data sent:
{"Form data":{"{\"personsNameText\":\"name9\",\"centreEmailAddressText\":\"name9#name.com\",\"centreTelephoneNumberText\":123456789,\"centreNumberText\":\"ab123\",\"centreNameText\":\"ab123\",\"invoiceText\":\"123456789\",\"currencyText\":\"GBP\",\"amountText\":\"100\",\"cardtypeText\":\"Credit card\",\"commentsText\":\"Comments.\",\"declarationText\":true}":""}}
What I want:
personsNameText=name9?centreEmailAddressText=name9#name.com?centreTelephoneNumberText=123456789?centreNumberText=ab123?centreNameText=ab123?invoiceText=123456789?currencyText=GBP?amountText=100?cardtypeText=Credit card?commentsText=Comments.?declarationText=true

I'm not sure of the type of the obj2 object here but I'll assume it's somethings like
interface UserFormData {
['Form data']: { [name: string]: value };
}
You would need to transform this to FormData before posting it. Something along the lines:
const formEncodedObj2 = new FormData();
const obj2Keys = obj2['Form data'];
Object.keys(obj2Keys).forEach(key => formEncodedObj2.append(key, obj2Keys[key]));
And then send the formEncodedObj2 object.

So, this solution solved various problems for me:
Posting x-www-form-urlencoded data using Angular 8's forms and HttpClient
Correcting unwanted encoded characters
My specific problem was that a unique verification string contained ampersands that were being converted to HTML entities, i.e. & to &.
// userdata.service.ts
public postForm(obj) {
return this.httpClient
.post(this.baseUrl2, obj, {
headers: new HttpHeaders({
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Referer": "http://referer.com" // Replace with your own.
}),
responseType: "text"
})
.map(data => data)
.pipe(
retry(1),
catchError(this.handleError)
);
}
// app.component.ts
PostForm(userdata) {
// Stringify and convert HTML entity ampersands back to normal ampersands.
const corrected = JSON.stringify(userdata).replace(/(&)/gm, '&');
// Convert back to JSON object.
const corrected2 = JSON.parse(corrected);
// entries() iterates form key:value pairs, URLSearchParams() is for query strings
const URLparams = new URLSearchParams(Object.entries(corrected2));
// Convert to string to post.
const final = URLparams.toString();
// Post it
this.userdataService.postForm(final).subscribe(reponse2 => {
console.log(reponse2);
});
}
URLSearchParams() was the breakthrough and, as Vlad suggested, being absolutely sure of the type one is dealing with. I should have used Types to avoid confusion. I probably should use Angular Interceptors to deal with the character manipulation.

Related

org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'document' is not present

I am having problems when i try to send a great file. When the file is small there is not problem.
Controller
#SuppressWarnings({ "unchecked", "rawtypes" })
#RequestMapping(value = "newFile", method = RequestMethod.POST)
#ResponseBody
public JSONObject newFile(final HttpSession session, final HttpServletResponse response,
#RequestPart("document") final DocumentAjusteDTO dto, #RequestPart("file") final MultipartFile file)
throws IOException {
js
formDoc.append('file', (file.files[0]));
var documentData = {
tipo: tipo,
periodo: periodo
};
formDoc.append('document', new Blob([JSON.stringify(documentData)], {
type: "application/json"
}));
var ajaxData = {
data: formDoc,
sendMethod: 'POST',
target: 'new_file',
properties: {
contentType: false,
processData: false
},
callbackSuccess: function (e) {
....
}
};
Ajax.call(ajaxData);
}
};
when the file is< 5mb ok.
file > 5mb ko
this is tomcat log
HandlerMethod details:
Controller [com.isb.hpa.ajustes.web.controllers.AjustesController]
Method [public org.jose4j.json.internal.json_simple.JSONObject com.isb.hpa.ajustes.web.controllers.AjustesController.newFile(javax.servlet.http.HttpSession,javax.servlet.http.HttpServletResponse,com.isb.hpa.logic.rest.beans.DocumentAjusteDTO,org.springframework.web.multipart.MultipartFile) throws java.io.IOException]
org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'document' is not present
at ....
Your Exception says that there is no 'document' in your request. You can add required = false to your argument in case your request contains only MultipartFile:
#RequestPart("document", required = false) final DocumentAjusteDTO dto
Add these properties to your application to avoid file size errors:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=12MB
Change 10 and 12 to bigger numbers if your files are bigger in size.
Also, add the following header to your Ajax request as said here.
headers: {
"Content-Type": undefined
},
Content-Type: undefined. This causes the browser to set the Content-Type to multipart/form-data and fill the boundary correctly. Manually setting Content-Type to multipart/form-data will fail to fill in the boundary parameter of the request.

Axios get URL not working after concatenation

I am having a problem using Axios with node js. Here is my code
let callResult = await axios.get(urlData, config)
Where config is
let config = {
headers: {
'X-Token': token
}
};
And the urlData is
let urlData = 'https://api.regulaforensics.com/webapi/Transaction2/GetTransactionResultJson?transactionId=<IDVariable>&resultType=15'
I am trying to add my IDVariable to the URL but it does not work. If I take the variable and put it directly in the URL I get a response.
I have also tried this
let config = {
headers: {
'X-Token': token
},
params: {
transactionId: IDVariable,
resultType: 15
}};
And this
let querys = querystring.stringify({ transactionId: keyId, resultType: 15 })
let path = 'https://api.regulaforensics.com/webapi/Transaction2/GetTransactionResultJson?'
let urlData = path.concat("", querys)
This is the complete URL
https://api.regulaforensics.com/webapi/Transaction2/GetTransactionResultJson?transactionId=05cc6ccc-3ae6-4185-b2c9-1e1aba01d705&resultType=15
When using {params: } or concatenation
When putting the whole URL. As the URL I pasted above
This is my whole function
If you want to pass some parameters as a query string you can use the following syntax for the GET request:
axios.get('/user', { params: { ID: 12345 } });
Tath will be translated in the following request:
/user?ID=12345
As explained in the documentation here: https://github.com/axios/axios#note-commonjs-usage
Note
You don't need to add the ? char at the end of URL and you don't need to have the parameters in the URL, so the part ?transactionId=<IDVariable>&resultType=15 must be removed
I found the solution.
I am calling a service first and the response of that service is the key I need for the second service.
The solution was to just put a sleep() between those two services.
Thanks you for your response, guys!

Best way to send a file and additional params from Angular client and handle them on the server

I have a service with the method which gets three parameters which should be sent to the server.
setMainPhotoFor: function(file, petName, petId) {
...
}
I have the following solution:
Client side
services.js
setMainPhotoFor: function(file, pet) {
var baseServerApiUrl = configuration.ServerApi;
var data = new FormData();
data.append("image", file);
data.append("petName", pet.Name);
data.append("petId", pet.ID);
$http.post(baseServerApiUrl + '/pictures/main-picture/add', data, {
headers: { "Content-Type": undefined }
});
}
Server side
PicturesApiController
[HttpPost]
[Route("main-picture/add")]
public async Task<HttpResponseMessage> SetMainPicture()
{
if (!Request.Content.IsMimeMultipartContent())
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
MemoryStream mainPicture = new MemoryStream(await provider.Contents[0].ReadAsByteArrayAsync());
string petName = await provider.Contents[1].ReadAsStringAsync();
int petId;
if (!int.TryParse(await provider.Contents[2].ReadAsStringAsync(), out petId))
{
//...
}
//...
But in my opinion it doesn't look good. Can anybody suggest a right and more elegant solution for this task?
to send multipart form with angular add th options like this:
$http.post(baseServerApiUrl + '/pictures/main-picture/add', data, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
});
I have answered this before here with server side sample code.
Basically you should stream the content and put your dto in a header. I've tried many ways and this is the best way in my opinion.

How to alter the headers of a Request?

Is it possible to alter the headers of the Request object that is received by the fetch event?
Two attempts:
Modify existing headers:
self.addEventListener('fetch', function (event) {
event.request.headers.set("foo", "bar");
event.respondWith(fetch(event.request));
});
Fails with Failed to execute 'set' on 'Headers': Headers are immutable.
Create new Request object:
self.addEventListener('fetch', function (event) {
var req = new Request(event.request, {
headers: { "foo": "bar" }
});
event.respondWith(fetch(req));
});
Fails with Failed to construct 'Request': Cannot construct a Request with a Request whose mode is 'navigate' and a non-empty RequestInit.
(See also How to alter the headers of a Response?)
Creating a new request object works as long as you set all the options:
// request is event.request sent by browser here
var req = new Request(request.url, {
method: request.method,
headers: request.headers,
mode: 'same-origin', // need to set this properly
credentials: request.credentials,
redirect: 'manual' // let browser handle redirects
});
You cannot use the original mode if it is navigate (that's why you were getting an exception) and you probably want to pass redirection back to browser to let it change its URL instead of letting fetch handle it.
Make sure you don't set body on GET requests - fetch does not like it, but browsers sometimes generate GET requests with the body when responding to redirects from POST requests. fetch does not like it.
You can create a new request based on the original one and override the headers:
new Request(originalRequest, {
headers: {
...originalRequest.headers,
foo: 'bar'
}
})
See also: https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
Have you tried with a solution similar to the one in the question you mention (How to alter the headers of a Response?)?
In the Service Worker Cookbook, we're manually copying Request objects to store them in IndexedDB (https://serviceworke.rs/request-deferrer_service-worker_doc.html). It's for a different reason (we wanted to store them in a Cache, but we can't store POST requests because of https://github.com/slightlyoff/ServiceWorker/issues/693), but it should be applicable for what you want to do as well.
// Serialize is a little bit convolved due to headers is not a simple object.
function serialize(request) {
var headers = {};
// `for(... of ...)` is ES6 notation but current browsers supporting SW, support this
// notation as well and this is the only way of retrieving all the headers.
for (var entry of request.headers.entries()) {
headers[entry[0]] = entry[1];
}
var serialized = {
url: request.url,
headers: headers,
method: request.method,
mode: request.mode,
credentials: request.credentials,
cache: request.cache,
redirect: request.redirect,
referrer: request.referrer
};
// Only if method is not `GET` or `HEAD` is the request allowed to have body.
if (request.method !== 'GET' && request.method !== 'HEAD') {
return request.clone().text().then(function(body) {
serialized.body = body;
return Promise.resolve(serialized);
});
}
return Promise.resolve(serialized);
}
// Compared, deserialize is pretty simple.
function deserialize(data) {
return Promise.resolve(new Request(data.url, data));
}
If future readers have a need to also delete keys in the immutable Request/Response headers and also want high fidelity to the immutable headers, you can effectively clone the Header object:
const mutableHeaders = new Headers();
immutableheaders.forEach((value, key, parent) => mutableHeaders.set(key, value));
mutableHeaders.delete('content-encoding');
mutableHeaders.delete('vary');
mutableHeaders['host'] = 'example.com';
// etc.
You can then create a new Request and pass in your mutableHeaders.
This is preferred to the accepted answer because if you have the need to proxy a Request, you don't want to manually specify every possible header while including the Cloudflare, AWS, Azure, Google, etc. custom CDN headers.
Background Info
The reason why the headers are immutable or read-only in a Request is because:
interface Request extends Body {
readonly cache: RequestCache;
readonly credentials: RequestCredentials;
readonly destination: RequestDestination;
readonly headers: Headers;
readonly integrity: string;
...
The interface for Headers is:
interface Headers {
append(name: string, value: string): void;
delete(name: string): void;
get(name: string): string | null;
has(name: string): boolean;
set(name: string, value: string): void;
forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any): void;
}

Multipart/form-data form sending data with AngularJS

I need to post form data as below to URL which is a webapi and the method is post
var surveyData = {
Name: xyz,
SetReminder: 'false',
NoOfReminder: '0',
StartDate: 'xyz',
EndDate: 'xyz',
Language: 'eng-us',
Duration: '1',
ApplicationName: 'xyz',
SurveyTypeName: 'zxy'
};
i have written the below code which call the post method of wbeapi but the function is able to send the data but the post method is not able to read the data that is send using the angular js.
function(surveyData) {
return $http.post(URL , surveyData,{
headers: { 'Content-Type': 'multipart/form-data' }
});
Use :
var data = HttpContext.Current.Request.Form["surveyData"];
To recieve the json data from your multipart form.
If your multipart form contail files, then to get these files use :
System.Web.HttpFileCollection postedfiles = System.Web.HttpContext.Current.Request.Files;
Also keep in mind that do not use any parameter in your controller action method.
Make sure you import these namespaces:
using System.Net.Http;
using System.Web;
using System.Web.Http;
EDIT :
modify your javascript code.
var data = new FormData();
data.append("surveyData", angular.toJson(surveyData));
If you want to add images/other, you can add those using the code
below.
//data.append("uploadedFile", $scope.files[0]);
So your code will be :
function(surveyData) {
return $http.post(URL , data,{
headers: { 'Content-Type': 'multipart/form-data' }
});
Now you will be able to receive the json data using
var data = HttpContext.Current.Request.Form["surveyData"];
and images/otherfiles(If you have) using
System.Web.HttpFileCollection postedfiles = System.Web.HttpContext.Current.Request.Files;
if web api is Multipart/form-data then you need to create key value pair in the below form so that multi part form data will be created.
var objFormData = new FormData();
for (var key in surveyData)
objFormData.append(key, surveyData[key]);
then you need to send the created multi part form data using the below code:
return $http.post(URL, objFormData, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
});

Categories