2

I am a GWT noob but am working with someone else who is more advanced than I, and we cannot figure out why a cookie being returned by the server as a Set-Cookie HTTP header is not actually being set in the browser.

I wrote a server using Tomcat that has an authentication call. I wrote a dummy website all in HTML that uses web forms to send a request to the server with the authentication information and receives a response that contains a Set-Cookie header. This all works. It then has a second button in a different form on the same page that sends a different request to my server with some form data, and the browser automatically injects the cookie into the header as expected. Therefore, the server, for the second call, can pull the cookie header out of the request and authenticate the request. This all works and is great.

Now, for the test GWT application we have developed, I have used the code that is automatically generated when a new GWT application is developed (no AppEngine) and modified it in the following ways on the client side's EntryPoint class. I removed the TextBox for entering my name and the GWT RPC calls. I modified MyHandler so that it no longer implemented KeyPressedListener or whatever and does implement RequestCallback. I edited the contents of the onClick to create a new RequestBuilder that sends a POST with the authentication information. So far, this all works as I can watch the logs on my server and it receives the request, processes it, and places the authentication cookie in the response. Using Firebug, I can see that the response contains the Set-Cookie header with the necessary cookie information. However, the browser never actually saves this information. Unsurprisingly, a subsequent call to the server doesn't include the cookie.

GWT is just compiled into JavaScript when deployed, correct? And JavaScript can't inject itself between the HTTP response and the browser can it? I have checked the Response object that is a parameter to the onResponseReceived() call from the RequestCallback interface, and it doesn't contain any method to get access to the cookie except through the getHeaders() call. I have dumped the results of this call, though, and it doesn't exist there. Anyway, the browser should at least be getting access to the HTTP header before the code and should be grabbing and setting the cookie values before handing the code to GWT. Not only am I new to GWT, I am new to most HTTP client-side development, but am I really that far off track?

Thank you,

John

Edit:

Here is the code I ended up with. I didn't change anything else in the project.

public void onModuleLoad() {
        final Button loginButton = new Button("Login");
        final Button requestBuilderButton = new Button("Campaign Read");
        final Label errorLabel = new Label();

        // Add the nameField and sendButton to the RootPanel
        // Use RootPanel.get() to get the entire body element
        RootPanel.get("sendButtonContainer").add(loginButton);
        RootPanel.get("sendButtonContainer").add(requestBuilderButton);
        RootPanel.get("errorLabelContainer").add(errorLabel);

        // Create the popup dialog box
        final DialogBox dialogBox = new DialogBox();
        dialogBox.setText("Remote Procedure Call");
        dialogBox.setAnimationEnabled(true);
        final Button closeButton = new Button("Close");
        // We can set the id of a widget by accessing its Element
        closeButton.getElement().setId("closeButton");
        final Label textToServerLabel = new Label();
        final HTML serverResponseLabel = new HTML();
        VerticalPanel dialogVPanel = new VerticalPanel();
        dialogVPanel.addStyleName("dialogVPanel");
        dialogVPanel.add(new HTML("<b>Sending name to the server:</b>"));
        dialogVPanel.add(textToServerLabel);
        dialogVPanel.add(new HTML("<br><b>Server replies:</b>"));
        dialogVPanel.add(serverResponseLabel);
        dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
        dialogVPanel.add(closeButton);
        dialogBox.setWidget(dialogVPanel);

        // Add a handler to close the DialogBox
        closeButton.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                dialogBox.hide();
            }
        });

        // Create a handler for the sendButton and nameField
        class LoginHandler implements ClickHandler, RequestCallback {
            /**
             * Fired when the user clicks on the sendButton.
             */
            public void onClick(ClickEvent event) {
                dialogBox.show();

                serverResponseLabel.setText(Cookies.getCookie("auth_token"));

                final String url = "http://localhost:8080/app/user/auth_token";
                RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, URL.encode(url));
                builder.setHeader("Content-Type", "application/x-www-form-urlencoded");
                StringBuilder parameters = new StringBuilder();
                parameters.append("user=username&password=password&client=gwt");
                try {
                    builder.sendRequest(URL.encode(parameters.toString()), this);
                }
                catch(RequestException e) {
                    serverResponseLabel.setText(e.toString());
                }
            }

            public void onError(Request request, Throwable exception) {
                serverResponseLabel.setText("Failure.");
            }

            public void onResponseReceived(Request request, Response response) {
                textToServerLabel.setText(Integer.toString(response.getStatusCode()));
                serverResponseLabel.setText(serverResponseLabel.getText() + Cookies.getCookie("auth_token"));
            }
        };

        class CampaignReadHandler implements ClickHandler, RequestCallback {
            public void onClick(ClickEvent event) {
                dialogBox.show();

                final String url = "http://localhost:8080/app/campaign/read";
                RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, URL.encode(url));
                builder.setHeader("Content-Type", "application/x-www-form-urlencoded");
                StringBuilder parameters = new StringBuilder();
                parameters.append("output_format=short&client=gwt&campaign_urn_list=urn:andwellness:nih");
                try {
                    builder.sendRequest(URL.encode(parameters.toString()), this);
                }
                catch(RequestException e) {
                    serverResponseLabel.setText(e.toString());
                }
            }

            public void onError(Request request, Throwable exception) {
                serverResponseLabel.setText("Failure.");
            }

            public void onResponseReceived(Request request, Response response) {
                textToServerLabel.setText(Integer.toString(response.getStatusCode()));
                serverResponseLabel.setText(response.getText());
            }
        };

        // Add a handler to send the name to the server
        LoginHandler loginHandler = new LoginHandler();
        loginButton.addClickHandler(loginHandler);

        CampaignReadHandler campaignReadHandler = new CampaignReadHandler();
        requestBuilderButton.addClickHandler(campaignReadHandler);
    }
John
  • 21
  • 1
  • 4

2 Answers2

1

This is the expected behavior of browsers: http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method (GWT's Response#getHeaders simply calls getAllResponseHeaders and parses the string).

If you want to get cookies, you have to use the cookies object (Cookies class in GWT); which obviously filters out httponly cookies.

Thomas Broyer
  • 64,353
  • 7
  • 91
  • 164
  • I understand and agree. I, as a client, shouldn't be responsible for the Set-Cookie header. The browser should catch this, set the cookie, then pass me the rest* of the headers. And it is working as expected when I use pure HTML and when I use JavaScript with just enough HTML to call the JavaScript. When I use GWT to make a POST to my server, everything appears to work exactly the same. GWT doesn't get the Set-Cookie header which it shouldn't have received in the first place. However, it's like the Set-Cookie wasn't in the response. But, I have verified that it was, in fact, returned. – John Jun 24 '11 at 16:50
  • How do you check whether it's set or not? and are you sure the `Set-Cookie` is correct (domain and path particularly) ? – Thomas Broyer Jun 24 '11 at 17:14
  • I check it by going into the browser and checking for cookies. Plus, I have Firebug running and it tells me when a new cookie is set. I know it is being sent because it works as expected with an HTTP-only sample page I created as well as a JavaScript-only (except for the bare minimum HTML to call the JavaScript) page I created. Also, Firebug shows it in the response header and the browser shows it as a stored cookie. Even when I do the GWT version, Firebug will still show it, but the browser won't set it and GWT (as expected) doesn't get it. – John Jun 27 '11 at 16:20
0

If you are using RequestBuilder to contact the RPC servlet that may be the problem. Especially if you are using a different host in your request, than what you have in your browser.

Say navigating to http://localhost/app

But your RequestBuilder builds a request for http://machinename/app/servlet.

If you are just using RPC without RequestBuilder you shouldn't have these problems.

As well if you are using RequestBuilder you may have to manually provide the cookies via setting that particular header

In browser client development cookies are handled on a host name basis.

Terrell Plotzki
  • 2,014
  • 17
  • 17
  • As best I can tell, there is no RPC going on in the GWT sense. I am using a RequestBuilder to make a call to a server that knows nothing about GWT. As far as the GWT server that is built, I believe it is running, but it will never receive any RPC calls. I will post the client code to give a better idea. – John Jun 15 '11 at 00:30
  • Yeah I think you are going to need to handle the cookie header manually in RequestBuilder. – Terrell Plotzki Jun 15 '11 at 01:45
  • You could try [Cookies.setCookie] (http://google-web-toolkit.googlecode.com/svn/javadoc/latest/com/google/gwt/user/client/Cookies.html#setCookie(java.lang.String, java.lang.String)) as well, but since you are using RequestBuilder this may be a long shot. – Terrell Plotzki Jun 15 '11 at 01:47
  • I would be alright, if I could grab the Cookie from the Response and put store it locally. Then, hopefully, the browser would append it to all subsequent messages. The problem is, I cannot get at the Cookie. The Response object that is returned to me doesn't contain any Cookie accessors, only a get headers accessor. But, the headers that are returned from this call are not the same that are returned to the client from the server, including the all-important Set-Cookie header. – John Jun 15 '11 at 18:13
  • I have been able to get a Set-Cookie header from a ResponseCallback: `@Override public void onResponseReceived(final Request request, final Response response) { String headersString = ""; for (final Header header : response.getHeaders()) { if (header != null) { headersString = headersString + header.getName() + ":" + header.getValue() + "/n"; } } GWT.log(headersString, null); } ` – Terrell Plotzki Jun 16 '11 at 00:08
  • Interesting. So, in Chrome 12.0.742.100, Firebug Lite says it sees all of the headers except the Set-Cookie header, and the code correctly dumps all of the headers that Firebug Lite saw. In Firefox 4.0.1, Firebug says it sees all of the headers including the Set-Cookie one, but the code says that there is only one header and its value is null. What browser/version were you using? – John Jun 16 '11 at 16:41
  • IE8 & GWT 1.7. Perhaps try disabling Firebug and any other debugging proxies you may have set up? – Terrell Plotzki Jun 16 '11 at 18:04
  • I compiled the code using Google's GWT Compile functionality, and opened the resulting web page in the same browser with my sample HTML page that worked and the GWT continued to work as it did in hosted mode. Given that it works differently across all browsers, my guess is that the browser is stripping some headers before handing the request over to the JavaScript. However, something GWT is doing on initialization is preventing the browser from accurately handling the Set-Cookie header. – John Jun 17 '11 at 19:54
  • Are you still testing with this in your code `Cookies.getCookie("auth_token")`? And is auth-token what you are looking for in the response? – Terrell Plotzki Jun 18 '11 at 05:27
  • No. The way I check to see if it's being set is via the browsers' controls. After I run the POST, I check what cookies it has saved and there is no such cookie named "auth_token". – John Jun 20 '11 at 17:33
  • When you are doing testing your application what URL are you using? http://localhost/app ? – Terrell Plotzki Jun 20 '11 at 18:06
  • The GWT app is using localhost:8888/authtokentest and my Tomcat server is running on localhost/app. I was trying to do it purely in JavaScript, not that I was convinced it would work, with a function like: private static native void attempt() /*-{ try { javascript:var req = new XMLHttpRequest(); req.open('POST', '/app/user/auth_token', false); req.send('user=username&password=password&client=javascript'); alert(req.responseText); var headers = req.getAllResponseHeaders().toLowerCase(); alert(headers); } catch(er) { alert(er); } }-*/; – John Jun 21 '11 at 23:30
  • But that wouldn't work because of the port number differences and JavaScript doesn't like cross-domain communication. I wanted to build a WAR file and run it in ROOT as that's the final deployment expectation anyway, and there wouldn't be any port number differences or cross domain issues. But, I couldn't figure out how to build the WAR file and have spent too much time on this already. Don't get me wrong, I am still looking for a solution, but I cannot afford to keep banging my head against it. – John Jun 21 '11 at 23:34
  • I don't think ports play a role in cookie management though. It really seems almost like you have cookies disabled in your browser though. – Terrell Plotzki Jun 22 '11 at 12:08
  • They don't play a part in cookie management, but I do believe they play a part in cross-domain communication. Even if you are communicating with the same machine, specifying a new port will cause JavaScript to say no. I tried using 'localhost' and '127.0.0.1', and they both failed. Maybe JavaScript's XMLHttpRequest.open() function doesn't like taking a full resource name instead of just a path, i.e. 'http://localhost:8080/app/user/auth_token' instead of '/app/user/auth_token'. It seems ok with '../app/user/auth_token', but that's where the port issue comes in. I am sure I have cookies enabled. – John Jun 23 '11 at 00:20
  • Thanks! So, we had a "breakthrough" today in that we got everything to work in JavaScript on both Firefox and Chrome; we didn't try IE. Therefore, it's not something JavaScript is doing. This leads me to further believe that there is something going on with GWT startup that is preventing the Set-Cookie from working correctly. When we did the pure-JavaScript solution, we print the header of the response; Set-Cookie is missing because the browser caught it and correctly set the cookie. The next reply will be the full HTML page that it works. – John Jun 23 '11 at 22:42
  • Ok, the whole thing wouldn't work, but this should be enough: function newPopup() { parameters = 'user=username&password=password&client=curl'; javascript:var req = new XMLHttpRequest(); req.open('POST', '/app/user/auth_token', false); //Send the proper header information along with the request req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); req.setRequestHeader("Content-length", parameters.length); req.send(parameters); alert(req.responseText); var headers = req.getAllResponseHeaders().toLowerCase(); alert(headers); } – John Jun 23 '11 at 22:43