Showing posts with label HTTP. Show all posts
Showing posts with label HTTP. Show all posts

Thursday, April 10, 2014

Waiting screen - doing bad things right - is it even possible?

Sometimes in large web applications, there is a necessity to make a client wait, before the server is able to provide any content. There may be some heavy calculations to be performed, caches refreshed etc. In most cases it probably can - and should - be avoided using background workers not being a part of the actual web request or some kind of asynchronous AJAX calls. Those approaches give the possibility to have either completely undisturbed user experience or at least to reduce the fuss and eliminate the need to have a blocking wait.

But there's a chance the task we are to accomplish cannot be offloaded onto the background thread at the server nor performed asynchronously with AJAX during normal site operation. I had that kind of situation in a project where authorization was handled by an external, awfully enterprisey component, available through the web service which was slow as a snail and completely out of our control. It was not possible to show anything more than the waiting screen on the first log on, as everything of any value had to be authorized first.

In that hopeless situation, we've decided we need a waiting screen, so that users at least see something that lets them know the service is being prepared for them. You know, if it's slow, it's a clear indication there's a lot of value inside and it's worth paying big money for it. In case it loaded quickly, there seems to have no real value (in case my English is not so fluent to express my thoughts accurately - yes, this was meant to be sarcastic).

Anyway, I was looking for a solution for the waiting screen, that ideally holds all of the features listed here in my subjective order of importance.

  1. it needs to be available not just as a JS throbber to be put somewhere on the site - it needs to be a response for the initial request to the website;
  2. it needs to draw something on screen while waiting (obvious to say, but not so obvious to implement);
  3. it should be HTTP-compliant in terms of status codes etc. so that it doesn't confuse any browsers or web crawlers;
  4. it should not break "back" button;
  5. while waiting, it should indicate "waiting" status in a browser's status bar, to convince the user something is really going on.

Serving a simple wait screen with 302 Redirect to the target request that will do the actual work is not an option, as it will fail on requirement no. 2 - the browser will issue a redirected request without rendering our wait screen. But in order to have point 3 and 4 fulfilled, we need 302 Redirect - serving 200 OK without the actual content will harm the protocol and browsers' history badly - it's tightly coupled. So there's a big contradiction here. We can try going the unwanted HTML Redirect route or use JavaScript redirection - it satisfies point 2, but it's no better about protocol compliance nor browser history obedience.

Well, I got stuck here. I've asked for help on StackOverflow, but no interest at all. Let me know if I'm missing something.

By now, I've sacrificed protocol compliance and proper "back" button behavior in order to have anything shown on screen - points 1 and 2 are the crucial ones for the general user experience. It's still quite weak experience, but without those, there is no experience at all. I've chosen the JavaScript redirect route called on window.load jQuery event (note that document.ready event is raised when the DOM is ready, but possibly not yet rendered - a bit too early for us to be sure something is already drawn).

I can't see a way to have the waiting screen done right. Well, maybe there is no right way to do bad things?

Tuesday, March 20, 2012

HTTP protocol breaking in ASP.NET MVC

HTTP clients (such as browsers) are designed to handle different error codes differently and there are a lot of reasons why server-side errors have different status codes than those triggered by users. Depending on status code, responses are cached differently, web crawlers are indexing differently and so on.

Recently, durring error handling review in our project, I've learned how ASP.NET MVC obeys HTTP protocol rules in terms of status codes. And unfortunately, there are some pretty easy cases where it doesn't. See this simple controller:

public class TestController : Controller
{
public ActionResult Index(int test, string html)
{
return Content("OK");
}
}

MVC handles missing controllers/actions properly, as 404 Not Found:

Let's now try to call the Index action without parameters:

MVC couldn't bind parameter values to an action and throws an exception, which yields 500 Internal Server Error status code. According to the HTTP protocol, it means that something unexpected happened on the server, but it is server's own problem, not that the request was wrong ("hey, I have some problems at the moment, can't help you, come back later"). But that's not true, I wouldn't say that missing parameter is an unexpected situation, and definitely it's the request what is wrong. The protocol has better solutions for that kind of situations - like 400 Bad Request ("hey, I've tried to help you but you're doing something wrong and I can't understand you").

Another example:

MVC has some validation rules that protects the server from potentially malicious requests, like cross-site scripting. But again, those cases are handled with 500 Internal Server Error, despite that it's obviously the client's fault - again 400 Bad Request will work here better. Purely from the HTTP protocol point of view, 500 Internal Server Error here is like admitting that the malicious request actually broke something on the server.

How can we fix these two? For example by modifying the response generated by MVC on error. We can add this code to our Global.asax.cs:

protected void Application_Error()
{
var lastError = Server.GetLastError();
if (lastError is ArgumentException || lastError is HttpRequestValidationException)
{
Server.ClearError();
Response.StatusCode = (int) HttpStatusCode.BadRequest;
}
}

It checks for type of exception thrown and changes the status code to more appropriate 400 Bad Request in these two cases mentioned.