I'm trying to send a multipart request via http.StreamedRequest. I am aware of http.MultipartRequest, however, it does not fit my needs (the API I'm trying to send the request to encodes arrays by sending the same attribute multiple times, which is not possible using the fields parameter of http.MultipartRequest since it is a Map<String, String>, which does not allow the same key to be inserted multiple times).
The file (with form-data name document) I'm trying to send is provided in form of a Uint8List (a .pdf file), in addition to a filename (in the following "some_filename.pdf", a pdf title (in the following "some_document_title") and an array of numbers (which I'm trying to add to the multipart request) with the attribute tags.
For instance, I want the array tags=[1, 2, 3], title="some_document_title" and file to be encoded as follows:
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="title"
Content-type: text/plain
some_document_title
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="tags"
Content-type: text/plain
1
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="tags"
Content-type: text/plain
2
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="tags"
Content-type: text/plain
3
--dart-http-boundary-rVI-TO32QXZlS.VAGKWpuhbt99woLKVVvjToGoT-MVOU1YGSpnQ
Content-Disposition: form-data; name="document"; filename="some_filename.pdf"
Content-type: application/octet-stream
After this part, I want to stream the file data.
The rough implementation looks like this:
Future<void> createDocument(Uint8List file, String filename, String title, List<int> tags) {
final StreamedRequest request = StreamedRequest('POST', Uri.parse('some_url'));
String boundary = "..."; // Generate random boundary string
String contentType = "application/octet-stream";
String body = "";
Map<String, String> fields = {
"title": title,
// Some more fields left out for readability
};
for (final fieldKey in fields.keys) {
body += _buildMultipartField(fieldKey, fields[fieldKey], boundary);
}
for (final tag in tags) {
body += _buildMultipartField('tags', tag.toString(), boundary);
}
// Prepare file part
body += "--$boundary" +
'\r\nContent-Disposition: form-data; name="document"; filename="$filename"' +
"\r\nContent-type: $contentType" +
"\r\n\r\n";
final closing = "\r\n--" + boundary + "--\r\n";
request.headers.addAll({
"Content-Type": "multipart/form-data; boundary=$boundary",
"Content-Length": "${body.length + closing.length + file.lengthInBytes}"
});
// Add current body
request.sink.add(utf8.encode(body));
Stream.fromIterable(file).listen((chunk) => request.sink.add([chunk]))
.onDone(() {
// Add closing to multipart request
request.sink.add(utf8.encode(closing));
request.sink.close();
});
final StreamdResponse response = await IOClient().send(request);
if (response.statusCode != 200) {
throw Error();
}
}
String _buildMultipartField(String fieldName, String value, String boundary) {
return '--$boundary' +
'\r\nContent-Disposition: form-data; name="$fieldName"' +
'\r\nContent-type: text/plain' +
'\r\n\r\n' +
value +
'\r\n';
}
Now, my current implementation produces the expected body, which is good, however, the streamed request does not seem to finish, and according to the DevTools Network page, the request is never sent.
Is the content-length relevant for the request to finish? I.e. is it possible that the request is not finishing because the server is still expecting data because the content-length is calculated incorrectly?
Also, I'm getting a segfault when hot restarting the application when the request is still pending (it never returns).
Any help is appreciated, thanks!
For my extbase-based TYPO3 CMS extension I created an ApiController with JsonView as view object. Returning values works like a charm, the correct header Content-type: application/json is set.
To return other responses like missing authorization messages or validation errors, I currently use:
$data = ["errors" => [
"status" => 401,
"message" => "Missing access token"
]];
$this->throwStatus($status, null, json_encode($data));
When I use $this->throwStatus() the header Content-type: text/html is set. Even if I manually set header("Content-type: application/json"); before using $this->throwStatus().
How can I create responses with the correct content type header?
Before you throw the status, try to set the headers in the response object:
$this->response->setHeader('Content-Type', 'application/json', true);
$this->response->sendHeaders();
If you are accessing your data through a dedicated pageType, you can set the header for this pageType in TypoScript:
myPageType.config.additionalHeaders {
10 {
header = Content-Type: application/json
replace = 1
}
}
I will add this to my post about the topic: https://usetypo3.com/json-view.html
How do you add headers to your http request in Angular2 RC6?
I got following code:
login(login: String, password: String): Observable<boolean> {
console.log(login);
console.log(password);
this.cookieService.removeAll();
let headers = new Headers();
headers.append("Authorization","Basic YW5ndWxhci13YXJlaG91c2Utc2VydmljZXM6MTIzNDU2");
this.http.post(AUTHENTICATION_ENDPOINT + "?grant_type=password&scope=trust&username=" + login + "&password=" + password, null, {headers: headers}).subscribe(response => {
console.log(response);
});
//some return
}
The problem is, that angular doesn't add Authorization header. Instead of that, in request I can see following additional headers:
Access-Control-Request-Headers:authorization
Access-Control-Request-Method:POST
and sdch added in Accept-Encoding:
Accept-Encoding:gzip, deflate, sdch
Unfornately there is no Authorization header. How should I add it correctly?
Whole request sent by my code looks as follow:
OPTIONS /oauth/token?grant_type=password&scope=trust&username=asdf&password=asdf HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost:3002
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
Access-Control-Request-Headers: authorization
Accept: */*
Referer: http://localhost:3002/login
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,pl;q=0.6
Ok. I found problem.
It was not on the Angular side. To be honest, there were no problem at all.
Reason why I was unable to perform my request succesfuly was that my server app was not properly handling OPTIONS request.
Why OPTIONS, not POST? My server app is on different host, then frontend. Because of CORS my browser was converting POST to OPTION:
http://restlet.com/blog/2015/12/15/understanding-and-using-cors/
With help of this answer:
Standalone Spring OAuth2 JWT Authorization Server + CORS
I implemented proper filter on my server-side app.
Thanks to #Supamiu - the person which fingered me that I am not sending POST at all.
you need RequestOptions
let headers = new Headers({'Content-Type': 'application/json'});
headers.append('Authorization','Bearer ')
let options = new RequestOptions({headers: headers});
return this.http.post(APIname,body,options)
.map(this.extractData)
.catch(this.handleError);
for more check this link
I believe you need to map the result before you subscribe to it. You configure it like this:
updateProfileInformation(user: User) {
var headers = new Headers();
headers.append('Content-Type', this.constants.jsonContentType);
var t = localStorage.getItem("accessToken");
headers.append("Authorization", "Bearer " + t;
var body = JSON.stringify(user);
return this.http.post(this.constants.userUrl + "UpdateUser", body, { headers: headers })
.map((response: Response) => {
var result = response.json();
return result;
})
.catch(this.handleError)
.subscribe(
status => this.statusMessage = status,
error => this.errorMessage = error,
() => this.completeUpdateUser()
);
}
If you are like me, and starring at your angular/ionic typescript, which looks like..
getPdf(endpoint: string): Observable<Blob> {
let url = this.url + '/' + endpoint;
let token = this.msal.accessToken;
console.log(token);
return this.http.post<Blob>(url, {
headers: new HttpHeaders(
{
'Access-Control-Allow-Origin': 'https://localhost:5100',
'Access-Control-Allow-Methods': 'POST',
'Content-Type': 'application/pdf',
'Authorization': 'Bearer ' + token,
'Accept': '*/*',
}),
//responseType: ResponseContentType.Blob,
});
}
And while you are setting options but can't seem to figure why they aren't anywhere..
Well.. if you were like me and started this post from a copy/paste of a get, then...
Change to:
getPdf(endpoint: string): Observable<Blob> {
let url = this.url + '/' + endpoint;
let token = this.msal.accessToken;
console.log(token);
return this.http.post<Blob>(url, null, { // <----- notice the null *****
headers: new HttpHeaders(
{
'Authorization': 'Bearer ' + token,
'Accept': '*/*',
}),
//responseType: ResponseContentType.Blob,
});
}
I had the same issue. This is my solution using angular documentation and firebase Token:
getService() {
const accessToken=this.afAuth.auth.currentUser.getToken().then(res=>{
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': res
})
};
return this.http.get('Url',httpOptions)
.subscribe(res => console.log(res));
}); }}
Here is the detailed answer to the question:
Pass data into the HTTP header from the Angular side (Please note I am
using Angular4.0+ in the application).
There is more than one way we can pass data into the headers.
The syntax is different but all means the same.
// Option 1
const httpOptions = {
headers: new HttpHeaders({
'Authorization': 'my-auth-token',
'ID': emp.UserID,
})
};
// Option 2
let httpHeaders = new HttpHeaders();
httpHeaders = httpHeaders.append('Authorization', 'my-auth-token');
httpHeaders = httpHeaders.append('ID', '001');
httpHeaders.set('Content-Type', 'application/json');
let options = {headers:httpHeaders};
// Option 1
return this.http.post(this.url + 'testMethod', body,httpOptions)
// Option 2
return this.http.post(this.url + 'testMethod', body,options)
In the call you can find the field passed as a header as shown in the image below :
Still, if you are facing the issues like.. (You may need to change the backend/WebAPI side)
Response to preflight request doesn't pass access control check: No
''Access-Control-Allow-Origin'' header is present on the requested resource. Origin ''http://localhost:4200'' is therefore not allowed
access
Response for preflight does not have HTTP ok status.
Find my detailed answer at https://stackoverflow.com/a/52620468/3454221
if you are a ruby on rails developer and you facing a similar issue, this is because of the config of your backend: especially in api mode
so with
gem 'rack-cors' installed
goto app/config/cors.rb
Be sure to restart your server when you modify this file.
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'domain_name:port or just use *'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true
end
end
the *credentials:true line does the trick
then in your SessionController
after a user is valid for login
insert a line(this assumes you are using gem 'jwt')
token = user.generate_jwt
response.headers['Authorization'] = token
generate_jwt is a method called in model User , it is
JWT.encode(id, key, alogrithm)
If you use django, that is already taken care for you
you just have to use
installed app: restframework_simplejwt
I got a C# client that performs POST call like so:
public void Add(ModelA newObj)
{
HttpWebRequest httpRequest = (HttpWebRequest)HttpWebRequest.Create("http://www.x.com/api/ModelA");
string json = JsonConvert.SerializeObject(newObj, Formatting.None,
new JsonSerializerSettings {
PreserveReferencesHandling = PreserveReferencesHandling.All
});
var messagebyte = Encoding.ASCII.GetBytes(json);
httpRequest.Method = "POST";
httpRequest.ContentType = "application/json";
httpRequest.ContentLength = messagebyte.Length;
// Send request
using (var stream = httpRequest.GetRequestStream())
{
stream.Write(messagebyte, 0, messagebyte.Length);
stream.Close();
}
// Get response
using (var response = httpRequest.GetResponse() as HttpWebResponse)
{
response.Close();
}
}
And the controller looks like that:
[ResponseType(typeof(ModelA))]
public IHttpActionResult PostModelA(ModelA newModelA)
{
return NotFound();
}
And surprisingly, the client response is 200 OK. Moreover, the property of "response.Method" is "GET" instead of "POST". What am I missing here ?
Ok, so apparently my web host changed some configuration and the API calls should have been made to http://x.com instead of http://www.x.com.
I found it out since I tried to perform POST call using the service of https://www.hurl.it/, and the call returned something like that:
HEADERS
Content-Length: 163
Content-Type: text/html; charset=UTF-8
Date: Mon, 29 Dec 2014 04:40:55 GMT
Location: http://x.co/api/ModelA
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
X-Powered-By-Plesk: PleskWin
BODY view raw
<head>
<title>Document Moved</title>
</head>
<body>
<h1>Object Moved</h1>This document may be found
here
</body>
So I have a simple Grails UI which takes a few fields .. firstName, lastName etc. in a form. The controller calls a service method which then uses the Rest Client Builder plugin to call a REST service.
The rest service is not recognizing the parameters however.
Here is the simple rest call.
def resp = rest.post(baseUrl, params)
{
header 'Accept', 'application/json'
contentType "application/x-www-form-urlencoded"
}
Using version 2.0.1 of the plugin.
params looks like
[firstName:Kas, action:index, format:null, controller:myController, max:10]
Rest Service Method looks like ...
#POST
#Path("/employees")
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Consumes({MediaType.APPLICATION_FORM_URLENCODED})
public IdmResult createNewEmployee(#FormParam("firstName") String firstName) {
try {
if(firstName == null) return constructFailedIdmResult("First Name is a required field");
// Do some other stuff
}
}
Service responds with "First Name is a required field"
When I submit the Post from Postman it works fine. Successful request from Postman looks like
POST /idm/employees HTTP/1.1
Host: <ip>:<url>
Accept: application/json
firstName: Kas
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
Would like to figure how I can see the request that the plugin is constructing so I can compare differences, but ultimately I just need to know how to properly send the request from the plugin so that the Rest Service recognizes the form parameters.
Rest client should be using request body to POST:
def resp = rest.post(baseUrl) {
header 'Accept', 'application/json'
contentType "application/x-www-form-urlencoded"
json {
firstName = "Kas"
}
}
or simply,
def resp = rest.post(baseUrl) {
header 'Accept', 'application/json'
contentType "application/x-www-form-urlencoded"
json firstName: "Kas"
}
Refer docs for detail.
UPDATE:
Since producer is expecting request params as big query string instead of JSON, you might end up doing this instead:
def queryString = params.collect { k, v -> "$k=$v" }.join(/&/)
def resp = rest.post("$baseUrl?$queryString") {
header 'Accept', 'application/json'
contentType "application/x-www-form-urlencoded"
}
or just def resp = rest.post("$baseUrl?$queryString")
To pass the values cleanly in the request body, use a MultiValueMap and the (undocumented, from what I see) 'body()' method as per this answer. https://stackoverflow.com/a/21744515/17123