I have problem when I try to send message from client to server on Spring websocket.
I have configuration Websocket on server and create #Message on controller.
I send data from client via javascript.
It just work sometimes, but sometimes it fail and throw message on server: MissingSessionUserException: No "user" header in message
Here're my WebsocketConfig:
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/connectsocket").withSockJS();
}
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/");
}
}
Here're my MessageController:
#RestController
public class MessageController {
#Autowired
private SimpMessagingTemplate template;
#MessageMapping("/websocket/message")
public synchronized void message(Message<Object> messageObj,
WebMessage message, Principal principal) throws Exception {
if (principal != null) {
String name = principal.getName();
template.convertAndSendToUser(name, "/topic/dynamic", new MessagePojo("stage", "value", "message"));
}
}
}
Here're my Javascript-backbonejs code:
app.Models.WebsocketModel = Backbone.Model.extend({
fetchData : function() {
console.log("WebsocketModel: fetchData");
var socket = new SockJS(url + "/connectsocket");
var client = Stomp.over(socket);
var onConnect = function(frame) {
console.log('Connected: ' + frame);
client.subscribe("/user/topic/dynamic", function(data) {
var jsonBody = JSON.parse(data.body);
console.log(jsonBody);
});
};
client.connect({}, onConnect);
setInterval(function() {
client.send("/websocket/message", {}, JSON.stringify({
"message" : "Hello world!!!",
"toUser" : "Someone"
}));
}, 10000);
}
});
Here're my server error log:
[2016 Apr 14 - 02:13:19] ERROR:
[org.springframework.web.socket.messaging.WebSocketAnnotationMethodMessageHandler]
- Unhandled exception org.springframework.messaging.simp.annotation.support.MissingSessionUserException:
No "user" header in message at
org.springframework.messaging.simp.annotation.support.PrincipalMethodArgumentResolver.resolveArgument(PrincipalMethodArgumentResolver.java:42)
at
org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77)
at
org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:139)
at
org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:108)
at
org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMatch(AbstractMethodMessageHandler.java:490)
at
org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler.handleMatch(SimpAnnotationMethodMessageHandler.java:497)
at
org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler.handleMatch(SimpAnnotationMethodMessageHandler.java:87)
at
org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessageInternal(AbstractMethodMessageHandler.java:451)
at
org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessage(AbstractMethodMessageHandler.java:389)
at
org.springframework.messaging.support.ExecutorSubscribableChannel$SendTask.run(ExecutorSubscribableChannel.java:135)
at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
You are trying to subscribe to a user destination so the user must be authenticated.
If that is an anonymous user who want to subscribe to the topic, answer to this question will help.
You'll have to assign an anonymous identify to the user and there are two options:
Configure a sub-class of DefaultHandshakeHandler that overrides determineUser and assigns some kind of identity to every WebSocketSession.
The WebSocket session will fall back on the value returned from HttpServletRequest.getUserPrincipal on the handshake HTTP request. You could have a servlet Filter wrap the HttpServletRequest and decide what to return from that method. Or if you're using Spring Security which has the AnonymousAuthenticationFilter, override its createAuthentication method.
Related
I can establish a websocket connection with my springboot server but I can't access the endpoint from #MessageMapping when I'm trying to send a message. Here are my configurations:
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/simulator")
.setAllowedOrigins("http://myiphere:3000")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/endpoint");
registry.setApplicationDestinationPrefixes("/app");
}
And a simple controller :
#RestController
#RequestMapping("/api")
public class MyController {
#MessageMapping("/hello/")
#SendTo("/endpoint/greeting")
public Greeting getCurrentLocation() {
System.out.println("hello here");
return GenericBuilder.of(Greeting::new)
.with(Greeting::setContent, "hello from server")
.build();
}
}
I'm using the socketjs-client library in ReactJS by following this tutorial :
import SockJS from "sockjs-client";
import Stomp from "stompjs";
let stompClient;
const connect = () => {
const socket = new SockJS("http://myiphere:8081/simulator");
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log("Connected " + frame);
stompClient.subscribe("http://myiphere:8081/endpoint/greeting", function (greeting) {
console.log("hi" + JSON.parse(greeting.body).content);
});
});
};
const sendSomething = () => {
stompClient.send("http://myiphere:8081/app/hello/", {});
};
And some buttons with onClick events bound to the methods above. The connection is working, I'm getting "connected" messages in browser console but when I'm trying to click the button with sendSomething() I'm not getting anything in the browser's console nor server's console.
Solved.
The problem was the absolute url path in the send() method.
P.S.: And I've been looking for an answer for this problem on many sites and found out that there is no need to use absolute path for subscribe() url.
P.P.S.: In case that someone else have these problems, look for extra / too. You have to be careful when you're setting the url. The pattern from JS should match the one from SpringBoot.
I want to use pusher for realtime chat and it works properly with public channel but when I use private channel I got this error :
pusher.js:1333 Cross-Origin Read Blocking (CORB) blocked cross-origin response http://20.30.0.236:8000/login with MIME type text/html
this is laravel code :
Event :
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public $user;
public $message;
public function __construct(User $user, Message $message)
{
$this->user = $user;
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('chat');
}
channels.php :
Broadcast::channel('private-chat', function ($user) {
return true;
});
BroadcastServiceProvider :
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes(['middleware' => ['auth:api']]);
require base_path('routes/channels.php');
}
}
and this is react js code :
export const onChatRcv = () => {
try {
Pusher.logToConsole = true;
var pusher = new Pusher('83*********63c912f5', {
cluster: 'ap2',
forceTLS: true,
authTransport: 'jsonp',
authEndpoint: `${baseUrl}broadcasting/auth`,
headers: {
'Authorization' : `Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjRhZTA1YjM2ZGNhN2I5NWI4NTJiZjFhOWRiZTQ5ZWE1NzFmNTNkMTE4NWQyOWU0Mjk0ZDI5NmJmZThhZTE0OGQzNzcwODM1MjEzYTg2NzA1In0.eyJhdWQiOiIxIiwianRpIjoiNGFlMDViMzZkY2E3Yjk1Yjg1MmJmMWE5ZGJlNDllYTU3MWY1M2QxMTg1ZDI5ZTQyOTRkMjk2YmZlOGFlMTQ4ZDM3NzA4MzUyMTNhODY3MDUiLCJpYXQiOjE1NTExMDQ3NTYsIm5iZiI6MTU1MTEwNDc1NiwiZXhwIjoxNTgyNjQwNzU2LCJzdWIiOiI1Iiwic2NvcGVzIjpbXX0.HOnNyhQQ48Hj4AZdP5vS5Zd5AfUr5XNP4zgrgR_f2-aAgFw4eWrNeHQSfdJt071_ChRINmv5W7O1LExxGIvCoSjiYFYPmw_8WjdFI_81WHoqM69ve-bgriK6eO1Yf0N3v3fc1DvPk2ZFYXXDmQbMLLXUyUqfjoYGty8AMgxCDulZ1tRMZ2rOVQZJ0ePbTw1eHQdMzBWG36fXWEbczLR99-_Dn8ta8P6iq0XWDr0cimlFzdHsG66iMeI0xWCJ1DRbxzr2LuX0j5zKe0j0_WNZJNbAFfeY87m7FDHjbHTNB1IB9Meh8kITV1mPQLc2n812j2QgW19KKWgpgZcy4tlfIBfT0x-aQAMkIUtmcHW0aEJ8RkHWKZYhyQ8yV61RIL3IxLpepHUVds8CZnxDGQ2NQ4bmb8UE7xQkV-KpmF5fZ0NCCxMuMpYdVkd0t9gc_Jra07_Sq7HbEJHEZbPCfhbDscAZQr2U9ddVaKwiGuFjSGXvOKS_lUAB91lBWada3k15FG2XoBfAv94mai2aWo41sep0nmlBKXPCVbWiczbeNL6ZXm_aE-tkLNS-Pc0veXogxZIaKVhFnRsW5qHTXI8v6sU6Nd9pzrIe173FqXQtzpA_tqrmdWU-lU-u484hWkPn2OcQcSckANpx-7_EVhrAPSfV7-WWamMRp2EC-3uFpmQ`,
},
});
var privateChannel = pusher.subscribe('private-chat' );
privateChannel.bind('App\\Events\\MessageSent', function(data) {
console.log(data);
});
} catch (error) {
console.error(error);
}
}
what is the problem?
it works when we use public channel but in private channel, we got this warning
Cross-Origin Read Blocking (CORB) blocked cross-origin response http://20.30.0.236:8000/login with MIME type text/html
The default route broadcasting/auth can't retrieve a suitable response so I added the custom authEndPoint.
web.php:
Route::get('pusher/auth', 'PusherController#pusherAuth');
and added PusherController:
class PusherController extends Controller
{
/**
* Authenticates logged-in user in the Pusher JS app
* For presence channels
*/
public function pusherAuth()
{
$user = auth()->user();
if ($user) {
$pusher = new Pusher('auth-key', 'secret', 'app_id');
$auth= $pusher->socket_auth(Input::get('channel_name'), Input::get('socket_id'));
$callback = str_replace('\\', '', $_GET['callback']);
header('Content-Type: application/javascript');
echo($callback . '(' . $auth . ');');
return;
}else {
header('', true, 403);
echo "Forbidden";
return;
}
}
}
This works and subscribes the channel.
You can think of accessing a private channel as if you are making a private auth request to the server .
You Cant Directly Access A private channel from react Because of Security Reasons.
As Mentioned In CodeAcademy ....
Servers are used to host web pages, applications, images, fonts, and much more. When you use a web browser, you are likely attempting to access a distinct website (hosted on a server). Websites often request these hosted resources from different locations (servers) on the Internet. Security policies on servers mitigate the risks associated with requesting assets hosted on different server
You Need a policy in your laravel app to add CORS (CROSS ORIGIN REQUEST SHARING )
Initially It was a bit complicated But You can use this library.
Now You can make any kind of private requests to your laravel app.
PS
Dont Forget to add checks in your broadcasts routes in channels.php as you are just simply returning true without any checks.
For the last few days I have been trying to implement SignalR into my AngularJS/WebAPI application.
I have been able to successfully send/receive messages from client to client, however when I push messages purely from the Server, none of the clients receive any messages.
I have seen many people having the same problem, the answer always seems to be using GlobalHost.ConnectionManager.GetHubContext which I have been implementing, without error, however the clients still don't receive any of the messages.
I thought that perhaps it's because WebAPI calls are asynchronous and and therefore takes place on a different thread, but I can't be sure. Please could someone have a look and tell me what I'm doing wrong.
This is my Hub Class:
public class ChatHub : Hub
{
public void Send(string name, string message)
{
// Call the broadcastMessage
Clients.All.broadcastMessage(name, message);
}
public void RunMe()
{
System.Diagnostics.Debug.WriteLine("Client Started");
}
public static void Notify(string name, string message)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
hubContext.Clients.All.broadcastMessage(name, message);
}
}
This is the Angular Controller in Javascript:
$scope.chat = $.connection.chatHub;
$scope.chat.client.broadcastMessage = function (name, message) {
$scope.$apply(function () {
var scope = angular.element($('#discussion')).scope();
scope.chatMessage = message;
alert(message);
});
};
$.connection.hub.start()
.done(function ()
{
console.log('Now connected, connection ID=' + $.connection.hub.id);
$scope.chat.server.runMe();
})
.fail(function(){ console.log('Could not Connect!'); });
$('#sendmessage').click(function () {
$scope.chat.server.send("SERVER", $('#inputMessage').val());
});
This is the Controller that is trying to notify the clients from the server
public class UserController : ApiController
{
#region METHODS
[ActionName("Create")]
[HttpPost]
public HttpResponseMessage Create(JObject parameters)
{
//DYNAMIC DATA
dynamic data = parameters;
//CHECK IF CALL FAILED
if (data == null)
return Request.CreateResponse(HttpStatusCode.InternalServerError, "Request is null");
//PERFORM REQUEST
using (var svc = new UserService())
{
//SET Parameters
String Username = data.Username;
String Password = data.Password;
//NOTIFY USERS
ChatHub.Notify("SERVER", "SERVER MESSAGE");
//CREATE Response
var response = svc.Create(Username, Password);
//RESPOND
return Request.CreateResponse(HttpStatusCode.OK, response);
}
}
}
So just to reiterate, when the "sendmessage" button is clicked on my UI, it sends a message to the server which is then received again by the clients, this works 100%,
However when I call the static Notify method from the Controller None of the clients receive any messages.
Calling the function does not return any errors.
Please could someone help me!
<!--Reference the SignalR library. -->
<script src="Scripts/jquery.signalR-2.2.0.min.js"></script>
Check your jQuery.signalR version.
If you are using dependency injection, the example at ASP.NET is wrong, you have to set your GlobalHost.DependendcyResolver in the the Global.asax file. not in the startup class.
I have built an ASP.NET WebAPI which is hosted on a console application. There are some web apis I had created and worked well. Then I tried to implement web socket service on it. The server side code was like below
[RoutePrefix("api/notification")]
public class NotificationController : ApiController
{
[HttpGet]
[Route("")]
public HttpResponseMessage Get()
{
if (HttpContext.Current.IsWebSocketRequest)
{
HttpContext.Current.AcceptWebSocketRequest(new NotificationWebSocketHandler());
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
public class NotificationWebSocketHandler : WebSocketHandler
{
private static WebSocketCollection _clients;
static NotificationWebSocketHandler()
{
_clients = new WebSocketCollection();
Task.Factory.StartNew(() =>
{
while (true)
{
Task.Delay(TimeSpan.FromSeconds(5));
if (_clients.Count > 0)
{
_clients.Broadcast(Guid.NewGuid().ToString());
}
}
});
}
public override void OnOpen()
{
_clients.Add(this);
Console.WriteLine("Web socket client opened, client count = {0}", _clients.Count);
}
public override void OnClose()
{
_clients.Remove(this);
Console.WriteLine("Web socket client closed, client count = {0}", _clients.Count);
}
}
}
But when I opened the client page (which was built on AngularJS) I got the error message said WebSocket connection to 'ws://10.222.115.220:8080/api/notification/' failed: Error during WebSocket handshake: Unexpected response code: 500
My client side code was
app.shared.controllers.controller('DashboardCtrl', function ($q, $scope) {
var ws = new WebSocket("ws://10.222.115.220:8080/api/notification/");
ws.onopen = function () {
console.log('web socket opened');
}
ws.onmessage = function (message) {
$scope.seed = message;
};
$scope.seed = '(empty)';
});
When I attached debug and added a breakpoint at the entry of my Get function, and I found the error 500 was raised without this breakpoint hit. I think this means the error was generated by WebAPI internally.
I'm not sure if anything wrong in my code, or WebSocket feature doesn't support console application host.
you are using the Web API in "Self Host"-mode? (hosted in the console)
There you have the problem that the object HttpContext.Current is "null".
This is a problem of self-hosting. HttpContext.Current is set while using the IIS (web-hosting). This property is not available during self-hosting. I think this could be your problem. (A null-ref-exception is thrown in your web-api-application and returns 500 - internal server error).
I have this problem using cxf dispatching behavior.
I have developed an Interceptor that implements the org.apache.cxf.jaxrs.ext.RequestHandler interface.
In its "public Response handleRequest(Message m, ClassResourceInfo resourceClass)" method I throw an exception (e.g. a WebServiceException) or a Fault. I have not apparent problems but, on the client side, the client receives a different exception (a ServerWebApplicationException) with the error message empty.
Here the code:
Server side:
public class RestInterceptor implements RequestHandler {
......
#Override
public Response handleRequest(Message m, ClassResourceInfo resourceClass){
.....
throw new WebServiceException("Failure in the dispatching ws invocation!");
.....
}
}
ServerWebApplicationException received on client side:
Status : 500
Headers :
Content-Length : 0
Connection : close
Server : Jetty(7.x.y-SNAPSHOT)
cause=null
detailMessage=null
errorMessage=""
.....
I received the same exception also if I throw a Fault.
What is the problem? I have to use another exception? Why?
Thanks a lot,
Andrea
OK, I've found the solution.I've registered an ExceptionMapper on the dispatcher and use it to encapsulate the exception inside the Response sent to the client.
To do this the interceptor is registered as provider at the web service publication and it implements the "ExceptionMapper" interface. In its "toResponse" method it encapsulates the exception in the Response.
Look at code:
public static<T extends Throwable> Response convertFaultToResponse(T ex, Message inMessage) {
ExceptionMapper<T> mapper = ProviderFactory.getInstance(inMessage).createExceptionMapper(ex.getClass(), inMessage);
if (mapper != null) {
try {
return mapper.toResponse(ex);
} catch (Exception mapperEx) {
mapperEx.printStackTrace();
return Response.serverError().build();
}
}
return null;
}
#Override
public Response toResponse(Exception arg0) {
ResponseBuilder builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR).type("application/xml");
String message = arg0.toString();
return builder.entity(message).build();
}
Andrea
Annotate your exception with #WebFault
example :
#WebFault(name = "oauthFault")
public class OAuthException extends RuntimeException {
...
}