Jquery Ajax post sometimes turns to get MVC - asp.net-mvc

I have the following line in my page which is called whenever a change is made in the form (the change in the form is persisted and stored in session):
function persistFormDetails() {
$.post("<%= Url.Action<AvailabilityController>(action => action.PersistForm(null)) %>", $("form#availabilityForm").serialize());
}
The above is called from 3 different events happening on the page: $("select").change, $("#NumberOfNights").change, $("#PromoCode").change.
These are the only 3 calls to 'PersistForm'. This works most of the time, but >5% of the time, 'PersistForm' is called using get instead of post. Extract from our weblogs for a failed request:
2012-08-07 06:17:34 120.151.214.16 - HTTP 10.12.0.151 80 POST /availability/persistform - 302 1151 434 0 HTTP/1.1 Mozilla/5.0+(iPhone;+CPU+iPhone+OS+5_1_1+like+Mac+OS+X)+AppleWebKit/534.46+(KHTML,+like+Gecko)+Version/5.1+Mobile/9B206+Safari/7534.48.3 __utma=212581192.532115380.1343637559.1343637559.1344320319.2;+__utmb=212581192.1.10.1344320319;+__utmc=212581192;+__utmz=212581192.1343637559.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none);+.BREAKFREEBOOKING=H7n50kf8yh2VLsbC2Czo6LALPpef1jqj4RtcC4l34Q-fA3WKG8dD5Dps9CFq2i3j-YEVMEH5qTh_b5f7IDRJ5NYeB28gBV_czMmGeSfnd26FHsw83WbwBpz2K3oAVYCg6dG_MiOKqrpn8ViaBizKMKXD4yw1;+stella_referrer=referrerGuestId=14076864270&additionalInfo=mantra_on_kent_24h_sale30aug12&referrerSite= http://m.mantra.com.au/check-availability
2012-08-07 06:17:34 120.151.214.16 - HTTP 10.12.0.152 80 GET /availability/persistform chkCookies=True 302 950 353 0 HTTP/1.1 Mozilla/5.0+(iPhone;+CPU+iPhone+OS+5_1_1+like+Mac+OS+X)+AppleWebKit/534.46+(KHTML,+like+Gecko)+Version/5.1+Mobile/9B206+Safari/7534.48.3 __utma=212581192.532115380.1343637559.1343637559.1344320319.2;+__utmb=212581192.2.10.1344320319;+__utmc=212581192;+__utmz=212581192.1343637559.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none);+.testCookie=.testCookie;+.BREAKFREEBOOKING=H7n50kf8yh2VLsbC2Czo6LALPpef1jqj4RtcC4l34Q-fA3WKG8dD5Dps9CFq2i3j-YEVMEH5qTh_b5f7IDRJ5NYeB28gBV_czMmGeSfnd26FHsw83WbwBpz2K3oAVYCg6dG_MiOKqrpn8ViaBizKMKXD4yw1;+stella_referrer=referrerGuestId=14076864270&additionalInfo=mantra_on_kent_24h_sale30aug12&referrerSite= http://m.mantra.com.au/check-availability
Note the first call to 'PersistForm' does a post (correct) but then 302's (not sure why its redirecting). Then the very next call from the same user on the same session and time calls 'PersistForm' and this time its with get. Then we get an exception "A public action method 'persistform' was not found on controller 'MG.Mobile.Controllers.AvailabilityController'".
This makes sense as my 'PersistForm' action has a HttpPost attribute.
[HttpPost]
public ActionResult PersistForm(AvailabilityForm form)
{
var model = _availabilityMapper.MapViewToDomain(form);
_availabilitySession.SaveAvailabilityToSession(model);
return new EmptyResult();
}
We can't allow gets on this action as it posts a lot of data. As I said earlier, this only happens about 5% of the time (maybe less).
Any ideas on why I sometimes get a 'get' instead of a 'post' or why the call to 'persistform' 302's (redirects) sometimes?
This is for our mobile site and the problem has only shown up with iPhone (but this could just be coincidence as 75% of mobile visits to our site are with iPhone).

Interesting issue. I put some my assumption here.
What I can see is that you controller is Sessionfull. That means that each request locks the Session object, so multiple requests from same client are handled one by one (request is wait till the Session lock is released).
As soon as you got to much, server might reach some threshold so, redirecting the request.
Options to try:
For such heavy loaded API, it is better to go with Session-Less model.
Try to utilize ASP.NET MVC Async controllers.

Related

Chrome does not follow 302 Location redirect (to other origin) after HTTP POST

tl;dr; How do I get Chrome to follow the 302 'Location' redirect to a different domain after an HTTP POST?
I'm using OpenID Connect, using an external provider to provide authentication services to my ASP.NET MVC (C#, .NET 6) application.
I have a controller action to close an account. In this instance,
[HttpPost("close-account")]
public async Task<IActionResult> CloseAccount()
{
// This does not work as expected in a POST and the browser does not redirect
var properties = new AuthenticationProperties { RedirectUri = "..." }
return SignOut(properties, "Cookies", "OpenIdConnect");
}
There is a standard html <form> which is completing the post action to the controller via a <button>. No Javascript involved.
Looking at the Chrome developer tools, the browser receives the 302 Found with the Location response header correctly set to the URL the browser needs to redirect to in order to complete the sign out with the OpenID Connect provider.
However, the browser does not follow the redirect. I am presuming because it is cross-origin, and starts with "https://oidc.myauthenticationprovider.com/logout?...." which is a different domain.
I have verified this - because if I change the redirect to be another URL in the same site, then the browser follows up with a GET to the URL provided in the Location header. It's just that if the origin is a different domain, it does nothing.
I only see this behaviour with POST. I have a similar GET endpoint to sign out users (without closing their account- just a regular sign-out) - which works perfectly.
[HttpGet("sign-out")]
public async Task<IActionResult> CloseAccount()
{
// This works as expected in a GET and the user is redirected.
var properties = new AuthenticationProperties { RedirectUri = "..." }
return SignOut(properties, "Cookies", "OpenIdConnect");
}
I'm not sure this is strictly a CORS issue because it is the response to an HTTP POST rather than an XHR request, however, I have tried the below in case it is related to CORS:
I have tried the services.AddCors method in the application startup, adding the origin
I have tried manually adding the Access-Control-Allow-Origin header to both the original GET and also the POST response of the close account page
These do not make a difference.
To the user, the behaviour is as if the form has done nothing. They press the button, and it seems like nothing at all happens. The code within the controller executes (in my example, the account does get closed), but the redirection is then ignored.
This was due to us setting the form-action: self directive in CSP. As described on MDN Web Docs,
Warning: Whether form-action should block redirects after a form
submission is debated and browser implementations of this aspect are
inconsistent (e.g. Firefox 57 doesn't block the redirects whereas
Chrome 63 does).
For us this was what was causing the blocked redirect to a different domain after form submission.

Does every successful HTTP request always return status code 200?

In Delphi, I'm using Indy's TIdHTTPWebBrokerBridge coupled with TIdHTTP to send/receive data via HTTP. On the Server, I don't have any fancy handling, I always just respond with a simple content stream. If there's any issues, I only return information about that issue in the response content (such as authentication failed, invalid request, etc.). So, on the client side, can I assume that every successful request I make to this server will always have a response code of 200 (OK)?
I'm wondering because on the client, the requests are wrapped inside functions which return just a boolean for the success of the request.
Inside this function:
IdHTTP.Get(SomeURL, AStream);
Result:= IdHTTP.ResponseCode = 200;
This function handles any and every request which could possibly fetch data. If there were any issues in the request, This function should return False. In my scenario, since I always return some sort of content on the server, would the client always receive a response code of 200 in this function?
I guess the real question is, if I always return some sort of content and handle all exceptions on the server, then will the server always return status code of 200 to each request?
"Does every successful HTTP request always return status code 200?"
See w3.org: HTTP/1.1 Status Code Definitions (RFC 2616)
The answer is No. All 2xx are considered successful.
That may depend on the HTTP method used.
Should your web-server application always return 200 upon success? That may as well depend on the request method and the signal it intends for the client . e.g.
for PUT method (emphasis is mine):
If an existing resource is modified, either the 200 (OK) or 204 (No
Content) response codes SHOULD be sent to indicate successful
completion of the request.
for POST method:
The action performed by the POST method might not result in a resource
that can be identified by a URI. In this case, either 200 (OK) or 204
(No Content) is the appropriate response status, depending on whether
or not the response includes an entity that describes the result.
If a resource has been created on the origin server, the response
SHOULD be 201 (Created) and contain an entity which describes the
status of the request and refers to the new resource, and a Location
header (see section 14.30). Responses to this method are not
cacheable, unless the response includes appropriate Cache-Control or
Expires header fields. However, the 303 (See Other) response can be
used to direct the user agent to retrieve a cacheable resource.
As you can learn from the RCF, every method SHOULD have it's own success status codes, depending on the implementation.
Your other question:
"can I assume that every successful request I make to this server will always have a response code of 200 (OK)?"
You can always expect Status code 200, if your web server always responds with Status 200. Your web server application controls what response it returns to the client.
That said, Status code 200 is the Standard response for successful HTTP requests (The actual response will depend on the request method used), and in the real world of web servers, SHOULD be set as default upon successful request, unless told otherwise (As explained in Remy's answer).
To answer your specific question:
can I assume that every successful request I make to this server will always have a response code of 200 (OK)?
The answer is Yes, because TIdHTTPWebBrokerBridge wraps TIdHTTPServer, which always sets the default response code to 200 for every request, unless you overwrite it with a different value yourself, or have your server do something that implicitly replies with a different response code (like Redirect() which uses 302, or SmartServeFile() which uses 304), or encounter an error that causes TIdHTTPServer to assign a 4xx or 5xx error response code.
However, in general, what others have told you is true. On the client side, you should handle any possible HTTP success response code, not just 200 by itself. Don't make any assumptions about the server implementation.
In fact, TIdHTTP already handles that for you. If TIdHTTP encounters a response code that it considers to be an error code, it will raise an EIdHTTPProtocolException exception into your code. So if you don't get an exception, assume the response is successful. You don't need to check the response code manually.
If there is a particular response code that normally raises an exception but you do not want it to, you can specify that value in the optional AIgnoreReplies parameter of TIdHTTP.Get() or TIdHTTP.DoRequest(). Or, if you are are using an up-to-date Indy 10 SVN revision, a new hoNoProtocolErrorException flag was recently added to the TIdHTTP.HTTPOptions property so the EIdHTTPProtocolException exception is not raised for any response code.
Successful resposes are 2xx List_of_HTTP_status_codes
i did the following. Process straight all 200`s and LOG exceptions. worked, not a single non 200 - except unauthorized and timeouts (password or sometimes unavaliable server). but many/all responses will be considered for a wide range of mainstream apps.
while (iRedo < 3) do begin
s := Self.HTTPComponent.Get( sUrl );
if self.HTTPComponent.ResponseCode = 200 then begin
break;
end;
// IDEIA - log what happend if not 200
logWhatHappend( s, HTTPComponent ); // then log content, headers, etc
inc( iRedo ); sleep( 5 );
end;

Unknown GET Request after POST Request

I have an a.shtml page and a form on it. When i submit the form with POST i call a.cgi and redirect the page b.shtml from the cgi with META. But i saw on access.log that a.cgi executes two times. It causes some problems. Why it is called twice and the second one is with GET and how can i avoid this? It is only occurs on Chrome. IE,Firefox is OK
my form:
<form method="post" action="cgi-bin/a.cgi"> ....</form>
meta inside the cgi:
printf("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"3;url='/b.shtml'\ "/>");
access log:
..POST /cgi-bin/a.cgi HTTP/1.1|Host: xxx.xxx.xxx.xxx|Connection: keep-alive|Content-Length: 42|Cache-Control: max-age=0|Origin: .....
..GET /cgi-bin/a.cgi HTTP/1.1|Host: xxx.xxx.xxx.xxx|Connection: keep-alive|User-Agent: Mozilla/5.0 (Windows NT
It sounds like you want to trigger a resubmission of an HTTP POST using a html meta refresh element.
I don't know if this is possible or reliable. Hopefully these terms help you with googling at least.
You may wish to look into using sessions instead.
Edit2:
I found this SO question:
"POST-requesting a location sending Refresh header makes Firefox create GET request but still hold POST data"

WebKit image reload on Post/Redirect/Get

We just redesigned a web application and as a result we've noticed a bug in Chrome (but it supposedly affects all WebKit browsers) that causes a full image/js/css reload after a Post/Redirect/Get. Our app is built using ASP.NET and uses a lot of Response.Redirect's which means users will run into this issue a lot. There's a bug report for the issue with test case: https://bugs.webkit.org/show_bug.cgi?id=38690
We've tried the following to resolve the issue:
Change all Response.Redirects to be JavaScript redirects. This wasn't ideal because instead of images reloading, there would be a "white flash" during page transitions.
We wrote our own HTTP handler for images, CSS and JS files. We set it up to where the handler sends a max-age of 1 hour. When the client requests the file again, the handler checks the If-Modified-Since header sent by the browser to see if the file had been updated since the last time it was downloaded. If the dates match, the handler returns an HTTP 302 (Not Modified) with 0 for the Content-Length. We ran a test where if the image was downloaded for the first time (HTTP 200), there was a delay of 10 seconds. So the first time the page loaded, it was very slow. If the handler returned 302 (Not Modified), there was no delay. What we noticed was that Chrome would still "reload" images even when the server returned a 302 (Not Modified). It's not pulling the file from the server (if it were, it would cause a 10 seconds delay), but yet it's flashing/reloading the images. So Chrome seems to be ignoring the 302 and still reloading images from it's cache causing the "reload".
We've checked big sites to see if they've fixed it somehow, but sites like NewEgg and Amazon are also affected.
Has anyone found a solution to this? Or a way to minimize the effect?
Thanks.
This is a bug. The only "workaround" I've seen untill now is to use a Refresh header instead of a Location header to do the redirecting. This is far from ideal.
Bug 38690 - Submitting a POST that leads to a server redirect causes all cached items to redownload
Also, this question is a duplicate of "Full page reload on Post/Redirect/Get ignoring cache control".
I ran into this problem myself with an ASP.NET web forms site that uses
Response.Redirect(url, false) following a post on many of its pages.
From reading the HTTP/1.1 specification it sounds like a 303 response code would be correct for implementing the Request: POST, Response: Redirect behavior. Unfortunately changing the status code does not make browser caching work in Chrome.
I implemented the workaround described in the post above by creating a custom module for non-static content. I'm also deleting the response content from 302's to avoid the appearance of a blink of "object moved to here". This is probably only relevant for the refresh headers. Comments are welcome!
public class WebKitHTTPHeaderFixModule : IHttpModule
{
public void Init(HttpApplication httpApp)
{
// Attach application event handlers.
httpApp.PreSendRequestHeaders += new EventHandler(httpApp_PreSendRequestHeaders);
}
void httpApp_PreSendRequestHeaders(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
if (context.Response.StatusCode == 302)
{
context.Response.ClearContent();
// If Request is POST and Response is 302 and browser is Webkit use a refresh header
if (context.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase) && context.Request.Headers["User-Agent"].ToLower().Contains("webkit"))
{
string location = context.Response.Headers["Location"];
context.Response.StatusCode = 200;
context.Response.AppendHeader("Refresh", "0; url=" + location);
}
}
}
public void Dispose()
{}
}
Note: I don't think this will work with the non-overloaded version of Response.Redirect since it calls Response.End().

Supporting the "Expect: 100-continue" header with ASP.NET MVC

I'm implementing a REST API using ASP.NET MVC, and a little stumbling block has come up in the form of the Expect: 100-continue request header for requests with a post body.
RFC 2616 states that:
Upon receiving a request which
includes an Expect request-header
field with the "100-continue" expectation, an origin server MUST
either respond with 100 (Continue) status and continue to read
from the input stream, or respond with a final status code. The
origin server MUST NOT wait for the request body before sending
the 100 (Continue) response. If it responds with a final status
code, it MAY close the transport connection or it MAY continue
to read and discard the rest of the request. It MUST NOT
perform the requested method if it returns a final status code.
This sounds to me like I need to make two responses to the request, i.e. it needs to immediately send a HTTP 100 Continue response, and then continue reading from the original request stream (i.e. HttpContext.Request.InputStream) without ending the request, and then finally sending the resultant status code (for the sake of argument, lets say it's a 204 No Content result).
So, questions are:
Am I reading the specification right, that I need to make two responses to a request?
How can this be done in ASP.NET MVC?
w.r.t. (2) I have tried using the following code before proceeding to read the input stream...
HttpContext.Response.StatusCode = 100;
HttpContext.Response.Flush();
HttpContext.Response.Clear();
...but when I try to set the final 204 status code I get the error:
System.Web.HttpException: Server cannot set status after HTTP headers have been sent.
The .NET framework by default always sends the expect: 100-continue header for every HTTP 1.1 post. This behavior can be programmatically controlled per request via the System.Net.ServicePoint.Expect100Continue property like so:
HttpWebRequest httpReq = GetHttpWebRequestForPost();
httpReq.ServicePoint.Expect100Continue = false;
It can also be globally controlled programmatically:
System.Net.ServicePointManager.Expect100Continue = false;
...or globally through configuration:
<system.net>
<settings>
<servicePointManager expect100Continue="false"/>
</settings>
</system.net>
Thank you Lance Olson and Phil Haack for this info.
100-continue should be handled by IIS. Is there a reason why you want to do this explicitly?
IIS handles the 100.
That said, no it's not two responses. In HTTP, when the Expect: 100-continue comes in as part of the message headers, the client should be waiting until it receives the response before sending the content.
Because of the way asp.net is architected, you have little control over the output stream. Any data that gets written to the stream is automatically put in a 200 response with chunked encoding whenever you flush, be it that you're in buffered mode or not.
Sadly all this stuff is hidden away in internal methods all over the place, and the result is that if you rely on asp.net, as does MVC, you're pretty much unable to bypass it.
Wait till you try and access the input stream in a non-buffered way. A whole load of pain.
Seb

Resources