Indy doesn't get response text returned with error 404 - httpresponse

My HTTP server returns custom 404 error text when REST route is not found:
{"sessionIdent":"051F-dUen7-tetW-kNf82-WxT","Details":[{"messageCode":60,"messageCategory":"","messageText":"No matching route for \"POST \/Warehouse\/A1\/Orders\/execute\""}]}
Following JavaScript code displays this response text in browser just fine:
function httpReq(method, url, headers, jsonStr, userName, password) {
try
{
var xmlhttp = new XMLHttpRequest();
xmlhttp.open(method, url, true);
xmlhttp.onreadystatechange = function () {
console.log("onreadystatechange");
if (xmlhttp.readyState == 4) {
console.log("ready");
console.log(xmlhttp.status);
console.log(xmlhttp.responseText);
}
}
// Send the request
xmlhttp.setRequestHeader('Cache-Control', 'no-cache, max-age=0');
xmlhttp.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
xmlhttp.setRequestHeader('Session-Ident', '051F-dUen7-tetW-kNf82-WxT');
xmlhttp.setRequestHeader('Accept', 'application/json');
if (headers) {
var headerKeys = Object.keys(headers);
Object.keys(headers).forEach(key => {
xmlhttp.setRequestHeader(key, headers[key]);
});
}
if ((userName !== "") && (password !== ""))
{
xmlhttp.setRequestHeader("Authorization", "Basic " + btoa(userName + ":" + password));
}
console.log("before send");
xmlhttp.send(jsonStr);
console.log("after send");
}
catch (ex)
{
console.log(ex);
}
}
Indy's TIdHTTP raises an EIdHTTPProtocolException exception with message HTTP/1.1 404 Not Found instead of my response text inside.
When I use the hoNoProtocolErrorException option:
_client.HTTPOptions := _client.HTTPOptions + [hoNoProtocolErrorException];
exception is not raised any more, but response text is empty.
procedure TFormRestTest._httpSend(AMethod, APath, AHeaders, ABody: string);
var
queryData, replyData: TStream;
resultText: string;
begin
queryData := TStringStream.Create(ABody, TEncoding.UTF8);
try
replyData := TMemoryStream.Create;
try
_client.Request.ContentType := 'application/json';
_client.Request.CharSet := 'UTF-8';
_client.Request.BasicAuthentication := True;
_client.Request.Username := 'Username';
_client.Request.Password := 'Password';
_client.Request.CustomHeaders.Clear;
_client.Request.CustomHeaders.Text := AHeaders;
_client.DoRequest(AMethod, APath, queryData, replyData, []);
replyData.Position := 0;
resultText = ReadStringAsCharset(replyData, _client.Response.CharSet)]);
_log(resultText); //resultText is empty
finally
replyData.Free();
end;
finally
queryData.Free();
end;
end;
How can I retrieve my response body?

When I use the hoNoProtocolErrorException option ... exception is not raised any more, but response text is empty.
That is by design. When disabling the exception, you need to also enable the hoWantProtocolErrorContent option to actually receive the response's body data into your replyData stream, eg:
_client.HTTPOptions := _client.HTTPOptions + [hoNoProtocolErrorException, hoWantProtocolErrorContent];
This is explained in more detail on the following article on Indy's Changelog Blog:
New TIdHTTP flags and OnChunkReceived event
Three new flag have been added to the TIdHTTP.HTTPOptions property:
...
hoWantProtocolErrorContent: when an HTTP error response is received, TIdHTTP normally reads and discards the response’s message body and then raises EIdHTTPProtocolException (the message body is available in the EIdHTTPProtocolException.ErrorMessage property). If the hoNoProtocolErrorException flag is enabled, or the ResponseCode number is specified in the request’s AIgnoreReplies parameter, then no EIdHTTPProtocolException is raised, as the caller would like to process the ResponseCode manually. Normally TIdHTTP would still discard the message body, though. If this new flag is enabled, the message body will no longer be discarded, it will be saved in the caller’s target TStream like a successful response would be. This flag is disabled by default to preserve existing behavior to discard error message bodies.
...
Based on your earlier comment to another question, you seem to not have the hoWantProtocolErrorContent option available in your version of Indy. In which case, you are using a very outdated version of Indy and should upgrade to the latest version from Indy's GitHub repository.
UPDATE: If that is not an option for you, for whatever reason, then you have no choice but to catch the EIdHTTPProtocolException and read the body content from its ErrorMessage property, eg:
try
_client.DoRequest(AMethod, APath, queryData, replyData, []);
replyData.Position := 0;
resultText := ReadStringAsCharset(replyData, _client.Response.CharSet)]);
except
on E: EIdHTTPProtocolException do
resultText := E.ErrorMessage;
end;

Related

Outlook Add-in - AttachmentId is malformed

We've been having issues intermittently where we get an error when downloading email item content from EWS "AttachmentId is malformed". This is for ItemAttachment (Especially .eml files)
We could not figure why or how this is happening and noticed that the ones that were failing had + and / in the id's. Searching across the web landed me on this article. Although this article is from 2015, wondering if this is still happening.
This article blew my mind and made sense (kind of) and implementing the conversion of + -> _ and / -> - worked fine, for a while.
We are now receiving the same error 'AttachmentId is malformed' and again could not find why, I removed the custom sanitizer function that replaces these characters and it started working again.
I have no idea what and why is this happening and how to reliably get attachment content. Currently, I've moved the sanitizer into the catch handler, so if for some reason the AttachmentId fails, we'll retry it by sanitizing it. Will have to keep an eye on how many fail.
Any light on this issue will be really appreciated.
Update 1.0 - Sample Code
Front-end
//At this point we've got the email and got the files
//We call EWS only if file.type == Office.MailboxEnums.AttachmentType.Item
//For all other files we call REST endpoint ~ Office.context.mailbox.restUrl + '/v2.0/'......
//Sample code below if only for EWS call
let files = this.email.attachments || [];
files.map(file => {
this._getEmailContent(file)
.then(res => {
return res;
});
})
//Get content from EWS
_getEmailContent(file, _failed){
//attachmentId
//Most of the times this will be fine, but at times when Id has a `+` or `/` if fails, Was expecting the convertToEwsId to handle any sanitization required.
let attachmentId = Office.context.mailbox.convertToEwsId(file.id, Office.MailboxEnums.RestVersion.v2_0);
return this.getToken(EWS)
.then(token => {
return this.http.post(`${endpoint}/downloadAttachment`,{
token: token,
url: Office.context.mailbox.ewsUrl,
id: attachmentId
},{
responseType: 'arraybuffer',
}).then(res => res.data);
}).catch(err => {
attachmentId = attachmentId.replace(/\+/g, "_");
this._getEmailContent(attachmentId, true);
})
}
Back-end
[HttpPost]
public DownloadAttachment(Request model){
var data = service.DownloadAttachment(model);
if(data == null)
{
return BadRequest("Error downloading content...");
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
return data;
}
}
//Inside service
public byte[] DownloadAttachment(Request request){
var ser = new ExchangeService
{
Credentials: request.token,
Url = request.url
}
//Here it fails intermittently, returning AttachmentId is malformed.
var attachment = ser.GetAttachments(new [] {request.attachmentId}, null, null).First();
if (attachment is FileAttachment)
{
FileAttachment fileAttachment = attachment as FileAttachment;
fileAttachment.Load();
return fileAttachment.Content;
}
}

Sending appropriate error responses on web actions

I have some web-enabled actions that are exposed through API Connect in IBM Cloud Serverless Functions.
Some of my actions use request-promises to call external REST services and I need to be able to catch an error and respond with an appropriate status-code to the caller.
Since the actions are web-enabled, the documentation indicates that I can use an annotated JSON to set the headers, status-code and body of the response. But it seems that, seems the API expects to always get a Content-Type=application/json, the response processor is failing to understand my annotations in the case of an error.
I tried the following without success:
let rp = require('request-promise');
function main(params){
//setup options
return rp(options).then(
res => {
return res;
}
).catch(
err => {
return { error: { statusCode:err.statusCode } }
}
);
}
Another variation:
let rp = require('request-promise');
function main(params){
//setup options
return rp(options).then(
res => {
return res;
}
).catch(
err => {
return { statusCode:err.statusCode }
}
);
}
The problem is that the status-code I always get is 200... I also tried to change the runtime to node8.0 without success.
Thanks!
I found the answer myself :)
In order to get the status-code and headers, one must set the field Response Content Type to `Use "Content-Type" header from action", while setting up the mapping between the API call and the action....

How to send call to JSP from AJAX?

This servlet code,here 1st i want to send return message(if message!=null) to the Ajax for alert and 2nd one if message==null i want to call another jsp with pass the list to this jsp.
if(message!=null){
response.setContentType("text/plain");
response.getWriter().write(message);
}else{
JSONObject jobj = new JSONObject();
String urlToRedirect = "SearchEmployee.jsp";
jobj.put("url",urlToRedirect );
response.getWriter().write(jobj.toString());
}
Here i cant understand how to call this jsp url in else part
$.ajax({
type:"POST",
url:'../SearchEmployeeServlet',
data:$('#DisplayEmployeeListForm').serialize(),//$('form').serialize();
success:function(msg){
if(msg!=""){
alert(msg);
window.location.reload(true);
}else{
....here i want to call 2nd jsp
}
}
});
You could just forward the request server-side, to avoid needing the client to make a second request:
request.getRequestDispatcher("SearchEmployee.jsp").forward(request, response);
Or alternatively, you could send an HTTP redirect response, so the client should handle the redirection to the second request automatically:
response.sendRedirect(response.encodeRedirectUrl("SearchEmployee.jsp"));
Note that how you handle this in the JavaScript depends on the datatype you expect to receive from SearchEmployee.jsp. For example, if it is XML and you have set the response content-type to text/xml jQuery will parse it and pass you an XML DOM object to your success function:
success:function(msg) {
if (msg instanceof XMLDocument) {
// handle list
} else {
alert(msg);
window.location.reload(true);
}
}
If you're expecting HTML, this may be returned as a string, so you could test to see if your returned string starts with some HTML. For example, if your HTML will always start with a <ul> tag:
success:function(msg) {
if (msg.startsWith("<ul>")) { // note you may need to do msg = $.trim(msg) first
// handle list
} else {
alert(msg);
window.location.reload(true);
}
}
If you don't want to auto-forward as suggested above and you'd rather stick with your current approach, there are a few things you'll need to change.
Using if(msg!="") in your success function is bad as that will return true as long as the server does not return an empty response (so in both cases you'll get an alert).
The first thing to do is to add a content-type header to your servlet code to indicate you're returning JSON in the second case:
response.setContentType("application/json");
response.getWriter().write(jobj.toString());
Now when jQuery handles the response, it will attempt to parse it as JSON before calling the success function. As such, you can now test in your success function whether the argument jQuery has given you is an object or a string:
success:function(msg){
if (typeof msg === "object") {
// you got the JSON response
$.ajax({
url: msg.url,
// etc...
});
} else {
// you got the plain text response
alert(msg);
window.location.reload(true);
}
}
Just make the ajax request in else part as you did above.
$.ajax({url: "jsp url", success: function(result){
}});
Find more information here

Gorilla sessions not working for CORS from client

I have set up a Go rest api. And on login I do this:
session, _ := store.New(r, sessionId)
session.Options.MaxAge = 12 * 3600
err := session.Save(r, w)
//treat error
and for checking the session i have smth like this:
session, err := store.Get(r, sessionId)
//treat error
if session.IsNew {
http.Error(w, "Unauthorized session.", http.StatusUnauthorized)
return
}
If I do the requests from postman it works fine, but when I do them from my client I get 401. Has any of you experienced something like this? The store is a CookieStore.
I already checked the id's, I replaced sessionId variable with a static string. Gorilla session uses gorilla context to register a new request and when I do the request from postman context.data[r] is not null, but from the client it is always null -> always a new session.
https://github.com/gorilla/context/blob/master/context.go - line 33
it is called in
https://github.com/gorilla/sessions/blob/master/sessions.go - line 122
wich is used in the CookieStore.Get function in
https://github.com/gorilla/sessions/blob/master/store.go - line 77
EDIT 1:
For the client I use polymer and I tried xmlhttp too.
Polymer:
<iron-ajax
id="ajaxRequest"
auto
url="{{requestUrl}}"
headers="{{requestHeaders}}"
handle-as="json"
on-response="onResponse"
on-error="onError"
content-type="application/json"
>
</iron-ajax>
and the handlers
onResponse: function(response){
console.log(response.detail.response);
this.items = response.detail.response
},
onError: function(error){
console.log(error.detail)
},
ready: function(){
this.requestUrl = "http://localhost:8080/api/fingerprint/company/" + getCookie("companyId");
this.requestHeaders = {"Set-cookie": getCookie("api_token")}
}
and the cookie successfully reaches the backend.
And xmlhttp:
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE ) {
if(xmlhttp.status == 200){
//do stuff
}else if(xmlhttp.status == 401){
page.redirect("/unauthorized")
}else{
page.redirect("/error")
}
}
}
xmlhttp.open("GET","http://localhost:8080/api/fingerprint/company/" + getCookie("companyId"),true);
xmlhttp.setRequestHeader("Set-cookie", getCookie("api_token"));
xmlhttp.send();
EDIT 2:
So I tried debugging with fiddler(thanks for the suggestion) and i found out that the request from postman has an bold entry Cookies / Login and the request from the client does not. Any idea how to get/set that value? It is somehow automatically set in Postman. In the authentication request I get a set-cookie header that has all the data that I need but I can't get it on the client. I get Refused to get unsafe header set-cookie.
The problem is that in the client the requests need to have withCredentials = true and after that the browser deals with everything. It gets the cookie from the set-cookie header and it sends the cookies via the cookie header. So, after all, it was not a gorilla sessions problem.
If anyone else is having the same problem I was having and you want to whitelist all domains/wildcards (or have a list of domains in an array you can scan through), you can do something like this.
domain_raw := r.Host
domain_host_parts := strings.Split(domain_raw, ".")
domain := domain_host_parts[1] + "." + domain_host_parts[2]
domains := getDomains() // stores a slice of all your allowable domains
has_domain := false
for _, d := range domains {
if d == domain {
has_domain = true
break
}
}
if has_domain == false {
return
} else {
w.Header().Add("Access-Control-Allow-Origin", "https://"+domain_raw)
w.Header().Add("Access-Control-Allow-Credentials", "true")
}
I love go

AJAX Request gets cancelled with AngularJS and Spring Security

We're running an external Grails server-application with the Spring Security plugin.
The front-end is running locally on AngularJS.
Whenever I try to login, the request is immediately canceled.. Remarkably AngularJS sends a GET request first with the OPTIONS method; this returns a 200 OK response just fine.
The actual POST request does never reach the server though... what could possibly cancel my request?
The following code:
$scope.login = function() {
$http.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
$scope.loggingIn = true;
// Setup Config
var data = {
j_username: $scope.user.email,
j_password: $scope.user.password
}
var config = {method: 'POST', url: serverUri+'/j_spring_security_check/', data: data};
// Dispatch HTTP Request
$http(config)
.success(function(data, status, headers, config) {
if (data.status) {
// successful login
User.isLogged = true;
User.username = data.username;
}
else {
User.isLogged = false;
User.username = '';
}
$scope.loggingIn = false;
console.log("NOICE!");
})
.error(function(data, status, headers, config) {
$scope.loggingIn = false;
User.isLogged = false;
User.username = '';
if (status == 0) {
// Request got cancelled
console.log("Request got cancelled.");
return;
}
});
}
This is what the canceled request looks like: http://i.stack.imgur.com/kiWnb.png
This is what the OPTIONS request looks like: http://i.stack.imgur.com/FAj96.png
Apparently Chrome does not handle 302 Moved temporarily status codes efficiently when queried by AngularJS in my situation. Firefox properly shows there is a response where Chrome just shows the request as canceled with no response information whatsoever.
This question is solved, but there is still a mystery as to WHY AngularJS does not work. See my question here:
AngularJS $http ajax does not follow Location header

Resources