In a plain Play application I have the following scenario.
A route file which looks like this:
GET /accounts/add controllers.Accounts.add()
POST /accounts controllers.Accounts.create()
The first route results in a view where I can add a new account. The form to submit the new account looks something like this:
#helper.form(action = routes.Accounts.create()) {...}
Now the controller binds the input to the form and checks for any validation errors:
public static Result create() {
Form<Account> form = Form.form(Account.class).bindFromRequest();
if (form.hasErrors()) {
return badRequest(views.html.account.add.render(form));
}
...
}
Now the thing is, the client will see the same view with some additional error messages. However, meanwhile the URL has changed from http://example.com/accounts/add to http://example.com/accounts.
If the client now reloads the browser this calls GET http://example.com/accounts (which isn't even mapped in this scenario - thus getting a 404 - Not Found).
Maybe it's just me but I find this kind of annoying and browsing some GitHub projects I couldn't find a good solution for this case.
Of cause things would be much simpler if the second route is rewritten to:
POST /accounts/add controllers.Accounts.create()
... in which case everything works fine. But from a REST point of view this doesn't feel good either. The same applies to update scenarios (having GET /accounts/:id/update vs. PUT /accounts/:id).
Is there a guide on how to handle this? Am I getting something wrong or is this no problem at all (from a pragmatic point of view)?
It's not possible to leave the previous URL because a request for a new address has already been made. A controller only provides response for a requested resource. To go to the previous URL you could only make a redirect in case of validation failure but you would lost errors that way so this is not a solution.
I suggest mapping both actions with the same URL. This way you would solve problem with the browser reload.
If you create a REST service for http clients that aren't browsers you will probably want to serve different response than a simple http page. The separation of actions for particular clients could be a good solution for keeping REST API clean and a browser user happy.
Related
I'm using FlowRouter. If I start on the homepage everything works well. I can work through the routes (change the pages) without problem. However, if I hit refresh in the browser, I get a series of errors. My url looks like this:
/story/586d536e34821281735b53a4
The ID is being returned in console under the following method:
Tracker.nonreactive(function(){
I think the subscription is being completed, so I'm a little confused as to why reloading a url is different than loading from the home page.
What am I not understanding here?
Reloading a url will make a HTTP request to server to get all the application source. Whereas navigating to a route from another one does not make any HTTP requests to get the application source because they are already available (they were loaded from the previous route), in this case the router will just get the appropriate content and render on the page. This is normal behaviour for Meteor apps and all other single-page apps
The error you encounter is because your data is not yet available on client, to fix it you could simple use a placeholder if the value is undefined.
I've been playing around with building some realtime functionality using Sails.js version 0.10-rc5 (currently the #beta release).
To accomplish anything, i've been following the sweet SailsCast tutorial on this subject (sailsCast link)
It talks about subscribing to a model via a 'subscribe' action within the model's controller. Then listening to it at the client side, waiting for the server to emit messages. Quite straightforward, although I do not seem to receive any messages.
I'm trying to do this to get real-time updates on anything that changes in my User models, or if new ones get created.. So I can display login status etc. in real time. Pretty much exactly the stuff that's explained in the sailsCast.
In my terminal i'll get two things worth noticing, of which the first is the following:
debug: Deprecated: `Model.subscribe(socket, null, ...)`
debug: See http://links.sailsjs.org/docs/config/pubsub
debug: (⌘ + double-click to open link from terminal)
debug: Please use instance rooms instead (or raw sails.sockets.*() methods.)
It seems like the 'subscribe' method has been deprecated. Could anybody tell me if that's correct, and tell me how to fix this? I've been checking out the reference to the documentation in the debug message, although it just points me to the global documentation page. I've been searching for an answer elsewhere, but haven't found anything useful.
The second message I'm getting is:
warn: You are trying to render a view (_session/new), but Sails doesn't support rendering views over Socket.io... yet!
You might consider serving your HTML view normally, then fetching data with sockets in your client-side JavaScript.
If you didn't intend to serve a view here, you might look into content-negotiation
to handle AJAX/socket requests explictly, instead of `res.redirect()`/`res.view()`.
Now, i'm quite sure this is because I have an 'isAuthenticated' policy added to all of my controllers and actions. When a user is not authenticated, it'll redirect to a session/new page. Somebody must log in to be able to use the application. When I remove the 'isAuthenticated' policy from the 'subscribed' action, the warnings disappear. Although that means anyone will get updates via sockets (when I get it to work), even when they're logged out. - I don't really feel like people just sitting at the login screen, fishing out the real time messages which are intended only for users who are logged in.
Can anyone help me getting the real time updates to work? I'd really appreciate!
As far as the socket messages not being received, the issue is that you're following a tutorial for v0.9.x, but you're using a beta version of Sails in which PubSub has gone through some changes. That's covered in this answer about the "create" events not being received.
Your second issue isn't about sockets at all; you'll just need to reconsider your architecture a bit. If you want to to use socket requests to sign users in, then you'll have to be more careful about redirecting them because, as the message states, you can't render a view over a socket. Technically you could send a bunch of HTML back to the client over a socket, and replace your current page with it, but that's not very good practice. What you can do instead is, in your isAuthenticated policy, check whether the request is happening via sockets (using req.isSocket) and if so, send back a message that the front end can interpret to mean, "you should redirect to the login page now". Something like:
module.exports = function (req, res, next) {
if ([your auth logic here]) {
return next();
}
else {
if (req.isSocket) {
return res.json({status: 403, redirectTo: "/session/new"});
} else {
return res.redirect("/session/new");
}
}
}
Several web application authentication protocols (like WS-Federation and the SAML protocol, i.e., so-called 'passive' protocols, and apparently also ASP.NET Forms authentication, see this StackOverflow question, and AppEngine, see this GWT bug comment) lose the original 'URL fragment', i.e. the part after the #-sign.
What happens is roughly the following: in a clean browser (so no cached info/cookies/login information) I open URL (1) http://example.com/myapp/somepage?some=parameter#somewhere. This makes the browser request (2) http://example.com/myapp/somepage?some=parameter, the server redirects me to my identity provider (including URL (2) in the authentication request), and ultimately I'm redirected back to where I came from, which is URL (2): that is the only URL that the server knows about. But I wanted to go to URL (1), and the URL fragment ('anchor') has been lost along the way, actually in the first step already.
This seems to be a fundamental limitation of these protocols, since the server never sees the URL fragment at all.
I know that it according to specifications that the browser requests (2) from the server, when I navigate to (1), leading to this fragment-losing limitation on the SAML protocol, WS-Federation, etc. My question is: how do I work around this limitation?
The obvious workaround is to avoid URL fragments, as suggested in this answer. However, for our specific web application that is not nice, since we use bookmarkable URL fragments in our single-page GWT application, to make sure that a navigation within our application does not cause the page to reload.
My question: What other workarounds or standard patterns are there for this situation?
(I'm specifically interested in a GWT + SAML protocol solution.)
You basically have two options:
avoid using location.hash (use HTML5's pushState instead, at least on browsers that support it; and/or propose a way to generate permalinks within your app – Google Groups does that)
do the redirection using JavaScript. I.e. instead of sending a redirect from the server, send an empty HTML page with some script that takes the full URL (with the hash) and does the redirection using location.assign() or location.replace(). With a bit of luck (depending on the servers), you'll be redirected to that full URL after authentication.
You can of course do both: if the link is a deep-link into the app, then do the redirect (i.e. assume there's no hash), otherwise send a page with JS to make sure you don't lose any state present in the hash.
And finally the obvious third solution, far from ideal: don't do anything, and try to educate users that when they needed to (re)authenticate then they should re-paste the URL or re-click the link or re-click the bookmark.
According to RFC 1738 anchor tags are not sent by the client to the server, when requesting for a resource.
Anchor tags are used to identify a location within a resource and not a different resource on the server. In order to identify the location in the resource, the client needs to fetch the complete resource from the server, and this process need not involve transfer of information about the fragment (as it does not mean anything to the server).
If you do wish to send the fragment character (#) to the server, then you'll need to encode it in the query string, or the client(browser) will simply ignore that section of the URL when it sends the request to the server.
EDIT:
I don't know any real solution but to work around this issue you need to save your full return URL (with anchor tags) somewhere on the client side, because server don't know anything about anchors. For that you could use SessionStorage (http://www.w3schools.com/html/html5_webstorage.asp) to temporary store ReturnUrl until login process is completed. Please note that it won't be supported on older browsers (like <= IE7).
In that case workaround would look something like this:
<script>
if(typeof(sessionStorage) == 'undefined')
{
sessionStorage = {
getItem: function(){},
setItem: function(){}
};
}
window.onload = function ()
{
var key = 'ReturnUrl';
//try to get last returnUrl with anchors
var returnUrl = sessionStorage.getItem(key);
//if we got something, do the navigation
if(returnUrl !== undefined && returnUrl !== document.URL)
{
//clean it up
sessionStorage.setItem(key, null);
//navigate to last URL
window.location = returnUrl;
}
else
{
//store url
sessionStorage.setItem(key, document.URL);
}
}
</script>
PS. Bear with me if there are some syntax errors because I wrote it down from top of my head and didn't try it.
I have two questions:
Q: 1
I'm currently developing a GWT app. The entry point for the app is: ImageViewer.java. I could well access it by http://127.0.0.1:8888/ImageViewer.html?gwt.codesvr=127.0.0.1:9997. I have a service called "Search" which has corresponding "Async" and "Impl"'s defined. Now, I call the service from client side, using RPC. I could call the service, obtain return value. Everything works fine.
However, I expect the application to show a behavioral change on URL. i.e. when a service is being accessed, I thought it would be reflected on the browser's URL something like: http://127.0.0.1:8888/search?gwt.codesvr=127.0.0.1:9997 as I've modified web.xml. However, this behavior is not realized. Any particular reason why this is not reflected??
Q:2
This one is a reverse of the previous ques. i.e. I have an application running. Let's say it has an entrypoint class(Imageviewer.java) and another composite class (searchClass.java) which would be loaded on the Imageviewer based on an event. This searchClass invokes the "search" service mentioned in the previous question.
I could load the "searchClass" in "Imageviewer", invoke the service, and the service also returns the value needed. Everything works fine... But,
I need something like this: by just typing this query string:
http://127.0.0.1:8888/search?value=John
I want the "searchClass" to be loaded on the "ImageViewer", call the service using the value(which is "john" in this case) and display the result. Is this possible at all?
what I've tried: I have tried to create a httpServletClass on the server and mapped it with the URL and could do the search. The search returns appropriate results. However, I want the results from the server to be displayed on the client. Remember, I'm directly using a servlet to read the URL and so there is no value being passed from client to server.
Thanks in advance.
A: 1. To change URL, the hash part, you need to set new history token in the History class. More about history management in this article.
A: 2. For the second part you could achieve it by changing the history token, for instance "http://127.0.0.1/search#value=John". The history service will trigger an event if the # part changes. You could also use the part with "?", as in your example, if you use Window.Location , but it will cause reload of the application, which would put the whole idea of using GWT in question.
RPC (AJAX) calls are done Via XHR and do not change the browser URL.
You can't (with the URL you presented). GWT apps normally run in one web page, i.e. the URL does not change (see how gmail changes browser url bar). What you can do is enable GWT history support. Then your url would be http://host/#search?value=queryu
I have implemented custom errors in my asp.net mvc application by following this article. What I have noticed is that if I go to http://www.mysite.com/some-non-existent-controller-and-action I get my 404 error page as expected. However, looking at what happens with firebug, I see that I get a 302 Found response for the non-existent page, which then redirects to my custom error page which then returns with a 404 (and displays the custom error page). Is this right? I don't think the 302 that is first returned is very good especially from an SEO perspective, and that maybe I need to think again about how I have implemented this.
The best guide(i think) for handling 404s can be found in this answer. Basically there are multiple ways in which 404s can happen:
No route exists - matched by the catch all rule.
Matched route but not found a controller - for rules with dynamic controller names - {controller}/{action}/{parameter} rule.
Found route, but didn't find action - handled through HandleUnknownAction override.
Found route and action but couldn't convert parameters - matched by the catch all rule.
The linked answer basically sets up a controller that can be executed from any point in the code without rewriting the URL - which is what you want.
In addition, you should also think about handling unhandled exceptions and bad URLs (like the ones containing unsafe characters like angle brackets). I that particular case you have to rewrite the URL, otherwise you can't render the response at all. These particular requests are kind of tricky, i blogged about that here.
Did you follow the advice down towards the bottom of the page adding a "catch-all" route that maps to your "NotFound" action:
routes.MapRoute("Catch All", "{*path}",
new { controller = "Error", action = "NotFound" });
If you make this the very last route you add, any "unknown" URLs will map directly to your "NotFound" action on the ErrorController and you can just return the "not found" view directly from there, no redirects required.