Last night, I read a not so old post from Brandon Savage about why "every developer should write their own framework". Very interesting reading, especially since I love writing my own frameworks. But I could not imagine how right he could be.
Yesterday, I decided to build a small library to write forms. Basically, I wanted the library to handle basic form stuff, like managing input types, select boxes, with a nice client and server-side validation. The server-side validation led me to think about one question: when the <form> is submitted, what should I use: HTTP GET or POST? And where should I submit the form?
In this article, I'll try to explain my thoughts, and the best practices that I deduced from coding my framework.
But let's start by the basics first.
You all know that GET requests are visible in the browser while POST requests are "hidden" from the browser's address bar.
There are many times where you would like to have a "clean" URL bar with a simple address, but this is not a reason to use POST. This will not prevent anyone to modify the request, and this is gives a false feeling of security.
Cache systems and search engines are relying on these properties, so you should enforce them in your application. For instance, a cache proxy might try to cache the results of a GET request (if HTTP cache headers are set correctly), while it will not cache a POST request.
The GET/POST choice has a direct impact on the web experience of your users. For instance, if you try to refresh a page that was loaded with a POST, your browser will warn you that you are sending data again (with a somewhat hard to understand message). Furthermore, if the user tries to put a bookmark on a page that was loaded with a POST, he is likely to face problems.
For an optimal user experience we should aim for this behaviour:
Here is a proposition to make this happen:
The idea is quite simple, if the application performs a redirect after the POST, the new page will be loaded using a GET request. This way, if the user clicks the "refresh" button, the GET page will be refreshed and there is no risk for the user to resubmit the page. Furthermore, the user can bookmark the page easily. It is true however that the redirect will incur an additional roundtrip between the browser and the server. In my experience, it is worth the benefit, so unless you are having extremely high latency issues, you should consider using this advice.
Let's imagine a simple subscription page on a website. A typical PHP application would certainly implement it this way:
The subscription page would be served by "subscribe.php". Then, the form "action" would redirect to "subscribe_action.php" that would only take POST requests. If the form is validated we would then go to "success.php" through a redirect header. If validation failed, we would be redirected to "subscribe.php".
Redirecting to "subscribe.php" is costly enough (in term of development). Indeed, while redirecting, you must pass ALL parameters so that the form is displayed back with correct information.
Here is an alternative solution:
In this proposed solution, the POST request is performed on "subscribe.php". The subscribe page is having a different behaviour if we request it through a GET or a POST request. The great advantage is that if form validation is failed, we don't need to redirect! Indeed, the URL is already the right one. If the user tries to bookmark the page, it will succeed. If he presses the refresh button, we will be asked for confirmation, but that is only happening on a failed validation, so there is no risk of submitting twice the same form!
So now that I tried to develop my own framework, I understand best the decisions that lead developers to submit a POST on the same page that has served the GET request. This design pattern seems to be common enough. In particular, ASP.NET forms and events management is entirely based on this design-pattern, so my conclusions might be right:
Does it make sense? What do you think? Personnaly, I'll rely on this behaviour on my next framework -- Raydee --, but I'll blog about this one later!