This question already has answers here:
Ajax request returns 200 OK, but an error event is fired instead of success
(14 answers)
Closed 7 years ago.
My c#/WebApi server code looks like:
[HttpPost]
public HttpResponseMessage Logout()
{
// do some stuff here ...
return Request.CreateResponse(HttpStatusCode.OK);
}
In the client I do an ajax call using jquery 2.0.3
ADDED: Jquery code (typescript)...
var ajaxSettings: JQueryAjaxSettings = {};
ajaxSettings.type = 'POST';
ajaxSettings.data = null;
ajaxSettings.contentType = "application/json; charset=utf-8";
ajaxSettings.dataType = "json";
ajaxSettings.processData = false;
ajaxSettings.success = (data: any, textStatus: string, jqXHR: JQueryXHR) => {
console.log("Success: textStatus:" + textStatus + ", status= " + jqXHR.status);
};
ajaxSettings.error = (jqXHR: JQueryXHR, textStatus: string, errorThrow: string) => {
console.log("Error: textStatus:" + textStatus + ", errorThrow = " + errorThrow);
};
$.ajax("http://apidev.someurl.com/v1/users/logout", ajaxSettings);
ADDED 2: Request headers resulting from the above code:
POST http://apidev.someurl.com/v1/users/logout HTTP/1.1
Host: apidev.someurl.com
Connection: keep-alive
Content-Length: 0
Cache-Control: no-cache
Pragma: no-cache
Origin: http://apidev.someurl.com
Authorization: SimpleToken 74D06A21-540A-4F31-A9D4-8F2387313998
X-Requested-With: XMLHttpRequest
Content-Type: application/json; charset=utf-8
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
Referer: http://apidev.someurl.com/test/runner/apitest/index.html?
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
The response is 200, yet the error handler from the ajax call is fired instead of the success handler. Reason: parseerror, unexpected end of input.
One solution is to change serverside code to:
return Request.CreateResponse<String>(HttpStatusCode.OK, "Logout ok");
I understand that an empty response is not valid JSON, but the response message is intentionally empty. The 200 says it all.
The response headers look like:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/7.5
Access-Control-Allow-Origin: http://apidev.someurl.com
Access-Control-Allow-Credentials: true
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 05 Jan 2014 01:20:30 GMT
Content-Length: 0
Is this a bug in jquery? Or should the Request.CreateResponse(OK) never be used that way? Should I fix this with some workaround in the client? AFAIK the server is not doing it wrong here ... any thoughts?
EDIT:
Thanks to feedback of kevin, nick and John this issue has become clear. The solution I choose is to return a NoContent
return Request.CreateResponse(HttpStatusCode.NoContent);
Serverside this seems for this case the correct code. Clientside this is handled perfectly by JQuery (the succes handler is called). Thanx all for clearing this up!
(I don't know who to give credit for the answer ... since nick and kevin gave their valuable feedback in comments, johns feedback also added a better understanding) ... if no other suggestions are given ... i'll later mark the only 'answer' as the Answer)
Thanks all!
From MSDN, HttpStatusCode.OK is defined as follows (emphasis mine):
Equivalent to HTTP status 200. OK indicates that the request succeeded and that the requested information is in the response. This is the most common status code to receive.
Because a 200 implies some content is sent with the response (even an empty JSON object literal would be fine), you can run into problems if jQuery's Ajax implementation assumes a non-zero length response but does not receive it, especially if it tries parsing JSON (and possibly XML) from it. This is why John S makes the suggestion of changing the dataType to text; doing so would allow you to take specific action when receving an empty response.
On the other hand, HttpStatusCode.NoContent is defined as (emphasis mine):
Equivalent to HTTP status 204. NoContent indicates that the request has been successfully processed and that the response is intentionally blank.
In your particular situation, it may make more sense to set the status code to HttpStatusCode.NoContent to ensure that jQuery Ajax understands that it does not need to attempt any parsing/processing of the response.
This is how jQuery works.
JQuery will assume the response is JSON if either:
You specify dataType: 'json' in the ajax call, or
You do not include the dataType setting but jQuery detects the response is JSON because of a response header.
And when jQuery assumes the response is JSON, it automatically tries to parse the response into objects before it calls the success handler. If the parse fails, the error handler is called instead.
From the jQuery documentation:
As of jQuery 1.9, an empty response is also rejected; the server
should return a response of null or {} instead.
I don't know why jQuery couldn't make an exception for an empty response and treat it the same as null, or even as undefined, but if you cannot (or won't) change the response, a work-around would be to specify dataType: 'text' and then parse the response yourself.
$.ajax(url, {
type: 'post',
dataType: 'text',
data: params,
success: function(text) {
if (text) {
var data = jQuery.parseJSON(text);
// ...
}
}
});
Related
UPDATE
As #Alexandru Clonțea suggested, I checked the fiddler log and found:
In both success or fail cases, there are actually 2 requests being sent. The first request are mostly the same for both cases, it's something like:
GET http://myservice.com/handler?param1=something¶m2=somethingelse HTTP/1.1
Authorization: Basic xxxxxx
Accept: application/json, application/xml, text/json, text/x-json,
text/javascript, text/xml
User-Agent: RestSharp/100.0.0.0
Host: myservice.com
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
The response for them are the same, which is:
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=utf-8
Location: /handler/?param1=something¶m2=somethingelse
Date: Sat, 08 Sep 2018 01:50:16 GMT
Content-Length: 115
Moved Permanently.
I have noticed that it always try to redirect the call to /handler/?param1=something¶m2=somethingelse, and that's because of the setup of the server code. it's actually working as expected. The difference is in the second request. The second request of the failure case (which is the c# code) doesn't have the authorization header and that's why it failed. Now, my question will be, why does the second request miss the authorization header? How can I fix it? Below is an example of the failed request:
GET http://myservice.com/handler/?param1=something¶m2=somethingelse HTTP/1.1
Accept: application/json, application/xml, text/json, text/x-json,
text/javascript, text/xml
User-Agent: RestSharp/100.0.0.0
Accept-Encoding: gzip, deflate
Host: myservice.com
Backgroud:
I have a service written in GO deployed on a server. It requires a basic authentication. For example, I can call it successfully with the following request:
GET /handler/?
param1=something¶m2=somethingelse HTTP/1.1
> Host: myservice.com
> Authorization: Basic xxxxxx
> User-Agent: RestClient/5.16.6
> Accept: */*
The request above is made by a rest api client tool (like postman), and it's working fine. It's also working fine if I call it from a browser.
Problem:
Now, I try to make the same call to the same service using c# code, and I have it as:
// pass cert validation
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
HttpClient client = new HttpClient();
var byteArray = Encoding.ASCII.GetBytes(username + ":" + password);
var auth = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = auth;
var response = client.SendAsync(request).Result; // don't need async
But in this case, I am getting Unauthorized (401) back. I have checked into the actually request that was sent by the code, it had exactly the same authorization header as the one shows above (Authorization: Basic xxxxxx, and the xxxxxx is the same as above) and same uri as well. Actually, everything it sent looks the same as when I used the rest api client tool, but it was just failed in code.
when I check the log on the server side, I see the log below when it returns 401:
[GIN-debug] redirecting request 301: /handler --> /hanlder/?param1=something¶m2=somethingelse
but I don't see this log when the call is from the rest api client tool (or browser)
As you may know from the log, the server-side code is using the go gin framework. But since it works fine in other cases, I don't think it's a problem with the server-side code.
Back to the C# code, I have tried to use the HttpWebRequest with NetworkCredential instead of the HttpClient, and I also try to use client.DefaultRequestHeaders.Authorization = auth, but I was still getting the same error.
I am wondering if someone has seen this before or could help? It will be really appreciated.
As a workaround, I can modify the request to be http://myservice.com/handler/?param1=something¶m2=somethingelse so that no redirection is needed. Thus, it will be correctly authorized.
But still, haven't figure out how to make the second request to be sent with the authorize header yet.
As the topic says, I've faced with a problem while trying to call an endpoint (POST) with a Request Body via Fiddler.
There is a http api method:
[HttpPost]
[Route("books")]
public IHttpActionResult GetBooks([FromBody]JToken filterRequest)
{...}
In Fiddler I make a POST call with request header:
User-Agent: Fiddler
Host: localhost
Content-Length: 21
Content-Type: application/json
And the Request Body looks like: { "title" : "Harry Potter"}. While debugging it, I get null filterRequest parameter.
But when I use Content-Type: application/x-www-form-urlencoded, parameter filterRequest parameter is not null but unfortunately contains incorrect syntax like:
{{"{ \"title\" : \"Harry Potter\"}": ""}}
And it's wrong serialized by:
var columnsFilter = new JavaScriptSerializer().Deserialize<Dictionary<string, string>>(filter);
I have no idea what I am doing wrong here. Maybe do you have met with similar problem and could help?
It turned out that the whole problem was based on wrong quotes. I had to use just ' and the posted request body was correct with Content-Type: application/json.
I have a very basic JavaScript AJAX request using jQuery:
$.ajax({
type: "POST",
url: "TabbedSummaryPage.aspx/RunReport",
data: "{'itemId': '', 'lType': '', 'reportId': '', 'requestXml': ''}",
contentType: "application/json",
dataType: "json",
success: function (data, textStatus, jqXHR) {
},
error: function (jqXHR, textStatus, errorThrown) {
},
complete: function (jqXHR, textStatus) {
}
});
Which is calling and executing the following C# code:
[WebMethod]
public static RunReportResponse RunReport(string itemId, string lType, string reportId, string requestXml)
{
var result = new RunReportResponse();
return result;
}
public struct RunReportResponse
{
public string reportTitle;
public string reportError;
public string reportHtml;
public string reportStyles;
public bool showWordMenu;
}
public class Global : System.Web.HttpApplication
{
}
I've stripped everything down so nothing is really being executed.
If I make multiple concurrent AJAX requests something strange starts to happen.
The average time to get the first byte from the response is around 50ms if I make 11 requests but 5 of them always take between 2 and 3 seconds to respond with the first byte.
When I added some logging to find out what was going on it seems it is always the last set of requests, whether I send 6 requests and get 3 slow requests back, or I send 11 requests and get 5 slow requests back, it is always the last requests to be executed by ASP.NET which IIS provides a response to the client with an added 1000+ milliseconds.
We have a high performance app which relies on responding to these requests without a delay so it is imperative we can figure out what is causing this delay.
The same test results are found in Internet Explorer 11, Chrome and Firefox.
Requests which don't use the WebMethod attribute don't suffer this issue.
The following is the headers sent during a request:
POST /pharmadotnet/Pharma/TabbedSummaryPage.aspx/RunReport HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-GB,en;q=0.7,en-US;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/json; charset=UTF-8
X-Requested-With: XMLHttpReques
Referer: http://localhost/pharmadotnet/Pharma/tabbedsummarypage.aspx?lType=coInfo&itemId=1293&compId=co&reportingCurrency=&sceName=&showTabs=
Content-Length: 223
Cookie: sTab=tabs-1; ASP.NET_SessionId=r01d2eqjxu0vdnexbl5mmd3p; curSessionId=r01d2eqjxu0vdnexbl5mmd3p; NewSession=true; __AntiXsrfToken=ec03abb28ee14f50a18e34216aa59d85; .ASPXUSERDEMO=DF6CD4223C47DB289B82E0240DAB40AAF253BACE9A753863E77BA07F9CEE61D00235255A2BAA58F555ECF3166D8470E77654DB8C3E2594E54B5BAF38A5ACDCCA5FDED79ECD0B89DC3583B4F7E56911C15EE894365CA1444CF0A8D2AB8FEF19AA915CE3989F07DCFE6F4941DA69FBB38593BC51A9
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
The following is the response headers from one of the requests:
HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
Date: Tue, 08 Sep 2015 12:50:58 GMT
Content-Length: 174
The same issue is exhibited regardless of whether we are debugging or not.
IIS is not configured to cache the request.
The WebForms page is not configured to cache the request.
Is there any particular reason as to why this might be happening?
It turns out that the problem was session locking.
By creating a separate WebForm, setting EnableSessionState to false, and moving the web method to the web form that doesn't make use of sessions I was able to work around the issue.
I'm new to asp.net mvc. I'm having some trouble getting the values of parameters in my Action methods.
I have the following code:
[HttpPost]
public ActionResult ToggleRecommend(string mode)
{
byte[] requestContent = new byte[Request.ContentLength];
Request.InputStream.Read(requestContent, 0, Request.ContentLength);
string content = Encoding.ASCII.GetString(requestContent);
return EmptyResult();
}
I access this method using an Ajax request. The request has these headers:
Accept application/json
Accept-Encoding gzip, deflate
Accept-Language en-gb,en;q=0.5
Connection keep-alive
Content-Length 8
Content-Type text/plain; charset=UTF-8
Host localhost:62718
Referer http://localhost:62718/microsite
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1
X-Request JSON
X-Requested-With XMLHttpRequest
and this body:
mode=off
My problem is that the mode parameter of ToggleRecommend is not being populated with the value from the request - instead, it is null.
The request is reaching my server correctly: the variable content in the method has the value mode=off and Request.ContentLength is 8 as expected. Also, Request.RequestType is "POST", as is intended. However, Request.Form is empty: it has no keys or values. (I don't know if that is relevant).
What is going wrong here? How do I get the value of my mode parameter to go into my action method?
The problem must be to do with post: If I remove the HttpPost attribute and do a request to the Url localhost:62718/microsite/toggleRecommend/?mode=off the mode parameter gets the value off as is intended.
Edit:
The request is being made with javascript using the Mootools library. I've included that part of the code below:
var req = new Request.JSON({ method: "post",
url: "/microsite/toggleRecommend/" ,
autoCancel: true, urlEncoded: false, secure: false,
onFailure: recommendFail, onException: recommendFail,
onSuccess: recommendSuccess
});
req.send({ data: { mode: 'on'} });
Using firebug, I can see the exact format of the request (and that looks fine) so hopefully the specifics of the javascript code doesn't matter.
Your ajax call content/type is text/html, you need to specify to your controller that you are sending application/json information, otherwise he receives the info, but doesn't know how to distribute it to it's params
I'm working on a small C#/WPF application that interfaces with a web service implemented in Ruby on Rails, using handcrafted HttpWebRequest calls and JSON serialization. Without caching, everything works as it's supposed to, and I've got HTTP authentication and compression working as well.
Once I enable caching, by setting request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.CacheIfAvailable);, things go awry - in the production environment. When connecting to a simple WEBrick instance, things work fine, I get HTTP/1.1 304 Not Modified as expected and HttpWebRequest delivers the cached content.
When I try the same against the production server, running nginx/0.8.53 + Phusion Passenger 3.0.0, the application breaks. First request (uncached) is served properly, but on the second request which results in the 304 response, I get a WebException stating that "The request was aborted: The request was canceled." as soon as I invoke request.GetResponse().
I've run the connections through fiddler, which hasn't helped a whole lot; both WEBrick and nginx return an empty entity body, albeit different response headers. Intercepting the request and changing the response headers for nginx to match those of WEBrick didn't change anything, leading me to think that it could be a keep-alive issue; setting request.KeepAlive = false; changes nothing, though - it doesn't break stuff when connecting to WEBrick, and it doesn't fix stuff when connecting to nginx.
For what it's worth, the WebException.InnerException is a NullReferenceException with the following StackTrace:
at System.Net.HttpWebRequest.CheckCacheUpdateOnResponse()
at System.Net.HttpWebRequest.CheckResubmitForCache(Exception& e)
at System.Net.HttpWebRequest.DoSubmitRequestProcessing(Exception& exception)
at System.Net.HttpWebRequest.ProcessResponse()
at System.Net.HttpWebRequest.SetResponse(CoreResponseData coreResponseData)
Headers for the (working) WEBrick connection:
########## request
GET /users/current.json HTTP/1.1
Authorization: Basic *REDACTED*
Content-Type: application/json
Accept: application/json
Accept-Charset: utf-8
Host: testbox.local:3030
If-None-Match: "84a49062768e4ca619b1c081736da20f"
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
########## response
HTTP/1.1 304 Not Modified
X-Ua-Compatible: IE=Edge
Etag: "84a49062768e4ca619b1c081736da20f"
Date: Wed, 01 Dec 2010 18:18:59 GMT
Server: WEBrick/1.3.1 (Ruby/1.8.7/2010-08-16)
X-Runtime: 0.177545
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: *REDACTED*
Headers for the (exception-throwing) nginx connection:
########## request
GET /users/current.json HTTP/1.1
Authorization: Basic *REDACTED*
Content-Type: application/json
Accept: application/json
Accept-Charset: utf-8
Host: testsystem.local:8080
If-None-Match: "a64560553465e0270cc0a23cc4c33f9f"
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
########## response
HTTP/1.1 304 Not Modified
Connection: keep-alive
Status: 304
X-Powered-By: Phusion Passenger (mod_rails/mod_rack) 3.0.0
ETag: "a64560553465e0270cc0a23cc4c33f9f"
X-UA-Compatible: IE=Edge,chrome=1
X-Runtime: 0.240160
Set-Cookie: *REDACTED*
Cache-Control: max-age=0, private, must-revalidate
Server: nginx/0.8.53 + Phusion Passenger 3.0.0 (mod_rails/mod_rack)
UPDATE:
I tried doing a quick-and-dirty manual ETag cache, but turns out that's a no-go: I get a WebException when invoking request.GetResponce(), telling me that "The remote server returned an error: (304) Not Modified." - yeah, .NET, I kinda knew that, and I'd like to (attempt to) handle it myself, grr.
UPDATE 2:
Getting closer to the root of the problem. The showstopper seems to be a difference in the response headers for the initial request. WEBrick includes a Date: Wed, 01 Dec 2010 21:30:01 GMT header, which isn't present in the nginx reply. There's other differences as well, but intercepting the initial nginx reply with fiddler and adding a Date header, the subsequent HttpWebRequests are able to process the (unmodified) nginx 304 replies.
Going to try to look for a workaround, as well as getting nginx to add the Date header.
UPDATE 3:
It seems that the serverside issue is with Phusion Passenger, they have an open issue about lack of the Date header. I'd still say that HttpWebRequest's behavior is... suboptimal.
UPDATE 4:
Added a Microsoft Connect ticket for the bug.
I think the designers find it reasonable to throw an exception when the "expected behavior"---i.e., getting a response body---cannot be completed. You can handle this somewhat intelligently as follows:
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.ProtocolError)
{
var statusCode = ((HttpWebResponse)ex.Response).StatusCode;
// Test against HttpStatusCode enumeration.
}
else
{
// Do something else, e.g. throw;
}
}
So, it turns out to be Phusion Passenger (or nginx, depending on how you look at it - and Thin as well) that doesn't add a Date HTTP response header, combined with what I see as a bug in .NET HttpWebRequest (in my situation there's no If-Modified-Since, thus Date shouldn't be necessary) leading to the problem.
The workaround for this particular case was to edit our Rails ApplicationController:
class ApplicationController < ActionController::Base
# ...other stuff here
before_filter :add_date_header
# bugfix for .NET HttpWebRequst 304-handling bug and various
# webservers' lazyness in not adding the Date: response header.
def add_date_header
response.headers['Date'] = Time.now.to_s
end
end
UPDATE:
Turns out it's a bit more complex than "just" setting HttpRequestCachePolicy - to repro, I also need to have manually constructed HTTP Basic Auth. So the involved components are the following:
HTTP server that doesn't include a HTTP "Date:" response header.
manual construction of HTTP Authorization request header.
use of HttpRequestCachePolicy.
Smallest repro I've been able to come up with:
namespace Repro
{
using System;
using System.IO;
using System.Net;
using System.Net.Cache;
using System.Text;
class ReproProg
{
const string requestUrl = "http://drivelog.miracle.local:3030/users/current.json";
// Manual construction of HTTP basic auth so we don't get an unnecessary server
// roundtrip telling us to auth, which is what we get if we simply use
// HttpWebRequest.Credentials.
private static void SetAuthorization(HttpWebRequest request, string _username, string _password)
{
string userAndPass = string.Format("{0}:{1}", _username, _password);
byte[] authBytes = Encoding.UTF8.GetBytes(userAndPass.ToCharArray());
request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(authBytes);
}
static public void DoRequest()
{
var request = (HttpWebRequest) WebRequest.Create(requestUrl);
request.Method = "GET";
request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.CacheIfAvailable);
SetAuthorization(request, "user#domain.com", "12345678");
using(var response = request.GetResponse())
using(var stream = response.GetResponseStream())
using(var reader = new StreamReader(stream))
{
string reply = reader.ReadToEnd();
Console.WriteLine("########## Server reply: {0}", reply);
}
}
static public void Main(string[] args)
{
DoRequest(); // works
DoRequest(); // explodes
}
}
}