Gửi tin nhắn cho người dùng cụ thể trên Spring Websocket


84

Làm cách nào để gửi tin nhắn websocket từ máy chủ đến người dùng cụ thể?

Ứng dụng web của tôi có thiết lập bảo mật mùa xuân và sử dụng websocket. Tôi đang gặp phải sự cố khó khăn khi chỉ gửi tin nhắn từ máy chủ tới người dùng cụ thể .

Sự hiểu biết của tôi từ việc đọc sách hướng dẫn là từ máy chủ, chúng tôi có thể làm

simpMessagingTemplate.convertAndSend("/user/{username}/reply", reply);

Và về phía khách hàng:

stompClient.subscribe('/user/reply', handler);

Nhưng tôi không bao giờ có thể gọi lại đăng ký. Tôi đã thử nhiều con đường khác nhau nhưng không may mắn.

Nếu tôi gửi nó tới / topic / reply thì nó vẫn hoạt động nhưng tất cả những người dùng được kết nối khác cũng sẽ nhận được nó.

Để minh họa vấn đề, tôi đã tạo dự án nhỏ này trên github: https://github.com/gerrytan/wsproblem

Các bước tái tạo:

1) Sao chép và xây dựng dự án (đảm bảo bạn đang sử dụng jdk 1.7 và maven 3.1)

$ git clone https://github.com/gerrytan/wsproblem.git
$ cd wsproblem
$ mvn jetty:run

2) Điều hướng đến http://localhost:8080, đăng nhập bằng bob / test hoặc jim / test

3) Nhấp vào "Yêu cầu tin nhắn cụ thể của người dùng". Dự kiến: một thông báo "xin chào {tên người dùng}" được hiển thị bên cạnh "Chỉ nhận được tin nhắn cho tôi" chỉ dành cho người dùng này, Thực tế: không nhận được gì


Bạn đã từng xem convertAndSendToUser (Chuỗi người dùng, Chuỗi đích, Thông báo T) chưa? docs.spring.io/spring/docs/4.0.0.M3/javadoc-api/org/…
Viktor K.

Có cố gắng điều đó quá nhưng không có may mắn
gerrytan

Tôi đang làm việc với dự án tư nhân và đây là phương pháp chúng tôi sử dụng và nó đang làm việc cho chúng tôi. Tôi nghĩ rằng một vấn đề tiềm ẩn có thể là bạn đăng ký "/ user / reply" và bạn đang gửi tin nhắn đến "/ user / {username} / reply". Tôi nghĩ rằng bạn nên xóa phần {tên người dùng} và sử dụng convertAndSendToUser (Người dùng chuỗi, Đích chuỗi, thông báo T).
Viktor K.

Cảm ơn, nhưng tôi đã cố gắng simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);và khi tin nhắn được gửi từ máy chủ nó ném ngoại lệ nàyjava.lang.IllegalArgumentException: Expected destination pattern "/principal/{userId}/**"
gerrytan

@ViktorK. là đúng, và bạn đã khá gần với giải pháp đúng. Đăng ký của bạn về phía khách hàng là chính xác, bạn chỉ đơn giản là đã phải cố gắng:convertAndSendToUser(principal.getName(), "/reply", reply);
Tip-Sỹ

Câu trả lời:


81

Oh, client side no need to known about current user máy chủ sẽ làm điều đó cho bạn.

Ở phía máy chủ, sử dụng cách sau để gửi tin nhắn cho người dùng:

simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);

Lưu ý: Đang sử dụng queue, không phải topic, Spring luôn sử dụng queuevớisendToUser

Về phía khách hàng

stompClient.subscribe("/user/queue/reply", handler);

Giải thích

Khi bất kỳ kết nối websocket nào được mở, Spring sẽ gán nó session id(không phải HttpSession, gán cho mỗi kết nối). Và khi khách hàng của bạn đăng ký một kênh, hãy bắt đầu bằng /user/:/user/queue/reply :, phiên bản máy chủ của bạn sẽ đăng ký một hàng đợi có tênqueue/reply-user[session id]

Khi sử dụng gửi tin nhắn cho người dùng, ví dụ: tên người dùng là admin Bạn sẽ viếtsimpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);

Spring sẽ xác định session idánh xạ tới người dùng admin. Ví dụ: Nó tìm thấy hai phiên wsxedc123thnujm456, Spring sẽ dịch nó đến 2 điểm đến queue/reply-userwsxedc123queue/reply-userthnujm456và nó gửi thông điệp của bạn với 2 điểm đến cho người môi giới tin nhắn của bạn.

Nhà môi giới thông báo nhận các thông báo và cung cấp lại cho phiên bản máy chủ của bạn để giữ phiên tương ứng với mỗi phiên (các phiên WebSocket có thể được giữ bởi một hoặc nhiều máy chủ). Spring sẽ dịch thông báo thành destination(ví dụ user/queue/reply:) và session id(ví dụ wsxedc123:). Sau đó, nó gửi tin nhắn đếnWebsocket session


7
Bạn có thể vui lòng giải thích làm thế nào bạn biết tên người dùng? trong ví dụ của bạn, bạn nói tên người dùng là 'quản trị viên', bạn nhận được tên người dùng từ người dùng?
Jorj

1
Tên người dùng được lấy từ HttpSessionkhi bắt đầu kết nối websocket
Thanh Nguyen Van

1
vui lòng bạn có thể cung cấp thêm thông tin về cách đặt tên người dùng ?. vẫn còn đó tôi có thể gửi tên người dùng trên tin nhắn tạm ngừng?
Andres

1
@Andres: Bạn có thể mở rộng DefaultHandshakeHandlervà ghi đè phương thứcdetermineUser
Thanh Nguyen Van

1
Lời giải thích của bạn hoạt động tuyệt vời. Tuy nhiên đâu là queue/reply-user[session id]phần trong tài liệu chính thức?
hbrls

35

Ah, tôi đã tìm thấy vấn đề của mình. Đầu tiên, tôi không đăng ký /usertiền tố trên nhà môi giới đơn giản

<websocket:simple-broker prefix="/topic,/user" />

Sau đó, tôi không cần thêm /usertiền tố khi gửi:

convertAndSendToUser(principal.getName(), "/reply", reply);

Spring sẽ tự động thêm trước vào "/user/" + principal.getName()đích, do đó nó phân giải thành "/ user / bob / reply".

Điều này cũng có nghĩa là trong javascript, tôi phải đăng ký địa chỉ khác nhau cho mỗi người dùng

stompClient.subscribe('/user/' + userName + '/reply,...) 

3
Mmm ... Có cách nào để tránh thiết lập phía máy khách userName không? Bằng cách thay đổi giá trị đó (ví dụ: bằng cách sử dụng tên người dùng khác), bạn sẽ có thể xem tin nhắn của những người khác.
vdenotaris

2
Xem giải pháp của tôi: stackoverflow.com/questions/25646671/…
vdenotaris

cách đăng ký để trả lời người dùng từ các phương thức được chú thích @RequestMappingvà cũng có thể @MessageMappingxem tại đây Cách đăng ký bằng cách sử dụng tích hợp Sping Websocket trên userName cụ thể (userId) + nhận thông báo từ phương thức được chú thích bằng @RequestMapping?
Shantaram Tupe

Này bạn của tôi, bạn có thể cho tôi biết điều này không? "Người dùng" này là gì? Tôi đang sử dụng Spring Security và người dùng (tên người dùng) của tôi là địa chỉ email. Khi mỗi người dùng đăng nhập, họ sẽ nhận được mã thông báo JWT của mình trên Spring Security.
Francisco Souza

Tôi phải đối mặt với những vấn đề tương tự, đã tìm kiếm hàng giờ trước khi đi đến câu trả lời của bạn. CHỈ giải thích của bạn đã giúp tôi. Cảm ơn bạn rất nhiều!!
Navaneeth

3

Tôi cũng đã tạo một dự án websocket mẫu bằng STOMP. Điều tôi đang nhận thấy là

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic", "/queue");// including /user also works
    config.setApplicationDestinationPrefixes("/app");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/getfeeds").withSockJS();
}

}

nó hoạt động cho dù "/ user" có được bao gồm trong config.enableSimpleBroker (...


2

Giải pháp của tôi về điều đó dựa trên lời giải thích tốt nhất của Thanh Nguyen Van, nhưng ngoài ra tôi đã cấu hình MessageBrokerRegistry:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/queue/", "/topic/");
        ...
    }
    ...
}

2

Chính xác là tôi đã làm như vậy và nó đang hoạt động mà không cần sử dụng người dùng

@Configuration
@EnableWebSocketMessageBroker  
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
       registry.addEndpoint("/gs-guide-websocket").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic" , "/queue");
        config.setApplicationDestinationPrefixes("/app");
    }
}

Này, bạn đã từng sử dụng cái này ở /appđâu? Trong trường hợp?
Francisco Souza
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.