Atmosphere / atmosphere

Event Driven WebSockets Framework with Cross-Browser Fallbacks
http://async-io.org/
3.69k stars 749 forks source link

Atmosphere&Jersey: Wrong restfull api method is called when do push message from client side #241

Closed ekochnev closed 12 years ago

ekochnev commented 12 years ago

Introduction:

The main goal is to design RESTfull api for TicTacToe game. I have decided to move my app to Jersey since there is not yet "native" support for COMET (WebSocket, pooling and etc.) in any Web frameworks (I mean MVC bidirectional support). So I decided to use Jersey as simple controller on server side instead of using the AtmosphereHandler directly (since a lot of boilerplate code is needed).

API is very simple and should looks like:

/tictactoe/restapi/game/start - starting game (subscribing - create or retrieve a broadcaster, set and publish initial state of game)

/tictactoe/restapi/game/turn/{cell} - player move (send chosen cell) /tictactoe/restapi/game/turn/{cell}

Problem:

When I push chosen cell by user back it doesn't work properly :( It calls the TicTacToeGame.startPost() if I do push by POST:

connectedEndpoint.push(turnUrl, null, $.atmosphere.request = {data: 'cell=' + 1, method: 'POST', url: turnUrl});

and TicTacToeGame.startGet if I do push by GET:

connectedEndpoint.push(turnUrl, null, $.atmosphere.request = {data: 'cell=' + 0, method: 'GET', url: turnUrl});

So, is it bug or restriction or do I something wrong?

So I decided as workaround do ajax call separately - please see README.md in the tictactoe-jersey-2 example

Please see https://github.com/ekochnev/atmosphere-experiments/tree/master/tictactoe-jersey-1

I use: Atmosphere 0.8.6 jetty-distribution-8.1.2.v20120308 Firefox 11.0. No java script issues is found.

jfarcand commented 12 years ago

Your client is only doing/sending GET. Please install wireshark or ngrep.sourceforge.net to see the traffic between your client/server. Closing as not a bug.

jfarcand commented 12 years ago

Also, you can't pass data when doing GET (as you example above). You must use POST with data.

jfarcand commented 12 years ago

With WebSocket, the message sent will be considered by the server as POST. If you want the server to consider them as GET, add the following init-param in your web.xml

 org.atmosphere.websocket.messageMethod
 GET

In that case your @GET annotated method will be invoked instead of the POST.

ekochnev commented 12 years ago

The issue is not that it call GET or POST.

For me issue that when user click cell - frameworks always call subscribe method but not turn. I suppose that the Atmosphere JQuery Plugin doen't take into account passed url.

So I expect that turnPost or turnGet will be called but it calls startGet or startPost depends on what is used POST or GET

jfarcand commented 12 years ago

Just do

 connectedEndpoint.push($.atmosphere.request = {data: 'cell=' + 0, method: 'GET', url: turnUrl});
ekochnev commented 12 years ago

Actually I did it already. Please see script for first cell. I have retested it - I have the same issue. Also I tested it for Atmosphere 9.0.RC2 - I have the same issue. Additional I have add log for which method is called.

Please see https://github.com/ekochnev/atmosphere-experiments/tree/master/tictactoe-jersey-1-9.x

This is log for Atmosphere 9.0.RC2:


12:32:30.646 [qtp984103443-21] DEBUG o.a.container.JettyWebSocketUtil - WebSocket-checkOrigin request /tictactoe/restapi/game/start with origin http://localhost:8080
12:32:30.646 [qtp984103443-21] DEBUG o.a.container.JettyWebSocketUtil - WebSocket-connect request /tictactoe/restapi/game/start with protocol null
12:32:30.667 [qtp984103443-21] DEBUG o.a.websocket.WebSocketProcessor - Atmosphere detected WebSocket: org.atmosphere.container.version.Jetty8WebSocket
12:32:30.692 [qtp984103443-21] INFO  o.a.tictactoe19x.TicTacToeGame - Constructor of TicTacToeGame is called.
12:32:33.211 [qtp984103443-21] INFO  o.a.tictactoe19x.TicTacToeGame - TicTacToeGame.startGet() method is called.
12:32:33.929 [Atmosphere-BroadcasterConfig-0] DEBUG o.atmosphere.cpr.DefaultBroadcaster - Broadcaster game doesn't have any associated resource
12:32:33.939 [qtp984103443-21] DEBUG o.a.container.JettyWebSocketUtil - Suspending response: AtmosphereResponse{cookies=[], headers={Access-Control-Allow-Origin=http://localhost:8080, Expires=-1, Content-Type=text/html; charset=ISO-8859-1, Access-Control-Allow-Credentials=true, Pragma=no-cache, Cache-Control=no-store, no-cache, must-revalidate}, asyncIOWriter=WSFrameConnection@192277ed l(127.0.0.1:8080)<->r(127.0.0.1:53665), status=200, statusMessage='OK', charSet='UTF-8', contentLength=-1, contentType='text/html', isCommited=false, locale=null, asyncProtocol=org.atmosphere.websocket.protocol.SimpleHttpProtocol@25ef757f, headerHandled=false, atmosphereRequest=AtmosphereRequest{bis=null, br=null, pathInfo='/game/start', session=org.atmosphere.util.FakeHttpSession@c0af84e, methodType='GET',
b=org.atmosphere.cpr.AtmosphereRequest$Builder@2433cf0f}, writeStatusAndHeader=false, delegateToNativeResponse=false, destroyable=false, response=org.atmosphere.cpr.AtmosphereResponse$DummyHttpServletResponse@455ef45a}
12:32:37.860 [qtp984103443-21] INFO  o.a.tictactoe19x.TicTacToeGame - TicTacToeGame.StartPost() method is called.
12:32:42.713 [Atmosphere-BroadcasterConfig-0] DEBUG o.atmosphere.cpr.DefaultBroadcaster - Broadcaster game doesn't have any associated resource
12:32:42.715 [qtp984103443-21] DEBUG o.a.container.JettyWebSocketUtil - Suspending response: AtmosphereResponse{cookies=[], headers={Access-Control-Allow-Origin=http://localhost:8080, Expires=-1, Content-Type=text/html; charset=ISO-8859-1, Access-Control-Allow-Credentials=true, Pragma=no-cache, Cache-Control=no-store, no-cache, must-revalidate}, asyncIOWriter=WSFrameConnection@192277ed l(127.0.0.1:8080)<->r(127.0.0.1:53665), status=200, statusMessage='OK', charSet='UTF-8', contentLength=-1, contentType='text/html', isCommited=false, locale=null, asyncProtocol=org.atmosphere.websocket.protocol.SimpleHttpProtocol@25ef757f, headerHandled=false, atmosphereRequest=AtmosphereRequest{bis=org.atmosphere.cpr.AtmosphereRequest$ByteInputStream@1970b890, br=java.io.BufferedReader@376502fc, pathInfo='/
game/start', session=org.atmosphere.util.FakeHttpSession@c0af84e, methodType='POST', b=org.atmosphere.cpr.AtmosphereRequest$Builder@48a70acd}, writeStatusAndHeader=false, delegateToNativeResponse=false, destroyable=false, response=org.atmosphere.cpr.AtmosphereResponse$DummyHttpServletResponse@455ef45a}
12:33:36.160 [qtp984103443-20] INFO  o.a.tictactoe19x.TicTacToeGame - TicTacToeGame.StartPost() method is called.
12:33:42.626 [Atmosphere-BroadcasterConfig-0] DEBUG o.atmosphere.cpr.DefaultBroadcaster - Broadcaster game doesn't have any associated resource
12:33:42.628 [qtp984103443-20] DEBUG o.a.container.JettyWebSocketUtil - Suspending response: AtmosphereResponse{cookies=[], headers={Access-Control-Allow-Origin=http://localhost:8080, Expires=-1, Content-Type=text/html; charset=ISO-8859-1, Access-Control-Allow-Credentials=true, Pragma=no-cache, Cache-Control=no-store, no-cache, must-revalidate}, asyncIOWriter=WSFrameConnection@192277ed l(127.0.0.1:8080)<->r(127.0.0.1:53665), status=200, statusMessage='OK', charSet='UTF-8', contentLength=-1, contentType='text/html', isCommited=false, locale=null, asyncProtocol=org.atmosphere.websocket.protocol.SimpleHttpProtocol@25ef757f, headerHandled=false, atmosphereRequest=AtmosphereRequest{bis=org.atmosphere.cpr.AtmosphereRequest$ByteInputStream@561e5a36, br=java.io.BufferedReader@62b4bb65, pathInfo='/
game/start', session=org.atmosphere.util.FakeHttpSession@c0af84e, methodType='POST', b=org.atmosphere.cpr.AtmosphereRequest$Builder@477b1683}, writeStatusAndHeader=false, delegateToNativeResponse=false, destroyable=false, response=org.atmosphere.cpr.AtmosphereResponse$DummyHttpServletResponse@455ef45a}
o-l-o commented 12 years ago

I encountered exactly the same problem with Atmosphere 1.0.0.beta3.

I establish WebSocket connection by performing GET of URL-1. The call is correctly passed to appropriate Jersey resource, and appropriate method is called. But any subsequent call is passed to the exactly same resource (which has been associated with the URL-1), regardless of the URI specified in the request.

I performed my tests using SimpleHttpProtocol. I could see that correct pathInfo was retrieved from my call, and used to construct the new AtmosphereRequest. But later, inside some Jersey code, the original pathInfo (from the URL-1) is used instead.

The error could be observed for example in this method: com.sun.jersey.spi.container.ContainerRequest.getEncodedPath()

I'm not sure if this is Jersey error or error caused by improper cooperation between the Atmosphere an the Jersey. But since REST without WebSocket seems to work correctly it may suggest rather the latter.

o-l-o commented 12 years ago

The error seems to be introduced by SimpleHttpProtocol.onMessage() implementation. It builds the new AtmosphereRequest instance, and sets pathInfo to a value retrieved from the new request. But both: requestURI and requestURL remain unmodified (as taken from the original request).

Later on, in Jersey code (com.sun.jersey.spi.container.servlet.service(HttpServletRequest request, HttpServletResponse response)), the requestUri and baseUri are calculated, and eventually used to construct the com.sun.jersey.spi.container.ContainerRequest. Then the ContainerRequest calculates path, based on incorrect requestUri and baseUri. In the end - pathInfo used by the Jersey is the original one, not the one calculated by the SimpleHttpProtocol.

jfarcand commented 12 years ago

Ok I've documented how to execute what you are looking for here. You need to write your own WebSocket sub-protocol or write your own WebSocketProtocol to support what you are looking at.