ส่งข้อความถึงผู้ใช้เฉพาะบน Spring Websocket


87

จะส่งข้อความ websocket จากเซิร์ฟเวอร์ไปยังผู้ใช้เฉพาะได้อย่างไร?

เว็บแอปของฉันมีการตั้งค่าความปลอดภัยแบบสปริงและใช้ websocket ฉันพบปัญหายุ่งยากพยายามส่งข้อความจากเซิร์ฟเวอร์ไปยังผู้ใช้ที่ระบุเท่านั้น

ความเข้าใจของฉันจากการอ่านคู่มือมาจากเซิร์ฟเวอร์ที่เราสามารถทำได้

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

และในฝั่งไคลเอ็นต์:

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

แต่ฉันไม่สามารถเรียกใช้การเรียกกลับการสมัครรับข้อมูลได้ ฉันได้ลองเส้นทางต่างๆมากมาย แต่ไม่มีโชค

ถ้าฉันส่งไปที่/ topic / replyมันใช้งานได้ แต่ผู้ใช้ที่เชื่อมต่ออื่น ๆ ทั้งหมดจะได้รับมันด้วย

เพื่อแสดงปัญหาฉันได้สร้างโปรเจ็กต์เล็ก ๆ นี้บน github: https://github.com/gerrytan/wsproblem

ขั้นตอนในการผลิตซ้ำ:

1) โคลนและสร้างโครงการ (ตรวจสอบให้แน่ใจว่าคุณใช้ jdk 1.7 และ maven 3.1)

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

2) ไปที่http://localhost:8080เข้าสู่ระบบโดยใช้ bob / test หรือ jim / test

3) คลิก "ขอข้อความเฉพาะผู้ใช้" คาดว่าจะมีข้อความ "hello {username}" ปรากฏถัดจาก "ได้รับข้อความถึงฉันเท่านั้น" สำหรับผู้ใช้รายนี้เท่านั้นตามจริง: ไม่ได้รับอะไร


คุณเคยดูที่ convertAndSendToUser (ผู้ใช้สตริงปลายทางสตริงข้อความ T) หรือไม่ docs.spring.io/spring/docs/4.0.0.M3/javadoc-api/org/…
Viktor K.

ใช่พยายามเช่นกัน แต่ไม่มีโชค
gerrytan

ฉันทำงานในโครงการส่วนตัวและนี่เป็นวิธีการที่เราใช้และได้ผลสำหรับเรา ฉันคิดว่าปัญหาหนึ่งที่อาจเกิดขึ้นคือคุณสมัครรับข้อมูล "/ user / reply" และคุณกำลังส่งข้อความถึง "/ user / {username} / reply" ฉันคิดว่าคุณควรลบส่วน {username} และใช้ convertAndSendToUser (ผู้ใช้สตริงปลายทางสตริงข้อความ T)
Viktor K.

ขอบคุณ แต่ฉันพยายามsimpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);แล้วและเมื่อข้อความถูกส่งจากเซิร์ฟเวอร์มันทำให้เกิดข้อยกเว้นนี้java.lang.IllegalArgumentException: Expected destination pattern "/principal/{userId}/**"
gerrytan

@ViktorK. ถูกต้องและคุณเข้าใกล้โซลูชันที่เหมาะสมแล้ว การสมัครของคุณในฝั่งไคลเอ็นต์ถูกต้องคุณต้องลอง:convertAndSendToUser(principal.getName(), "/reply", reply);
Tip-Sy

คำตอบ:


85

โอ้client side no need to known about current userเซิร์ฟเวอร์จะทำเพื่อคุณ

ในฝั่งเซิร์ฟเวอร์โดยใช้วิธีต่อไปนี้เพื่อส่งข้อความถึงผู้ใช้:

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

หมายเหตุ: การใช้queueไม่ใช่topicสปริงใช้queueกับsendToUser

ในฝั่งไคลเอ็นต์

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

อธิบาย

เมื่อการเชื่อมต่อ websocket เปิดอยู่ Spring จะกำหนดsession id(ไม่ใช่HttpSessionกำหนดต่อการเชื่อมต่อ) และเมื่อลูกค้าของคุณสมัครรับข้อมูลช่องเริ่มต้นด้วย/user/เช่น:/user/queue/replyอินสแตนซ์เซิร์ฟเวอร์ของคุณจะสมัครรับคิวที่ชื่อqueue/reply-user[session id]

เมื่อใช้ส่งข้อความถึงผู้ใช้เช่นชื่อผู้ใช้คือadmin คุณจะเขียนsimpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);

ฤดูใบไม้ผลิจะเป็นตัวกำหนดซึ่งแมปให้กับผู้ใช้session id adminเช่น: พบสองเซสชันwsxedc123และthnujm456Spring จะแปลเป็น 2 ปลายทางqueue/reply-userwsxedc123และqueue/reply-userthnujm456ส่งข้อความของคุณพร้อม 2 ปลายทางไปยังนายหน้าข้อความของคุณ

นายหน้าข้อความจะรับข้อความและส่งกลับไปยังอินสแตนซ์เซิร์ฟเวอร์ของคุณที่ถือเซสชันที่สอดคล้องกับแต่ละเซสชัน (เซสชัน WebSocket สามารถเก็บไว้โดยเซิร์ฟเวอร์ตั้งแต่หนึ่งเซิร์ฟเวอร์ขึ้นไป) ฤดูใบไม้ผลิจะแปลข้อความไปdestination(เช่นuser/queue/reply) และsession id(เช่นwsxedc123) จากนั้นจะส่งข้อความไปยังผู้ที่เกี่ยวข้องWebsocket session


7
คุณช่วยอธิบายว่าคุณรู้จักชื่อผู้ใช้งานได้อย่างไร? ในตัวอย่างของคุณคุณบอกว่าชื่อผู้ใช้คือ 'admin' คุณได้รับชื่อผู้ใช้จากผู้ใช้?

1
ได้รับชื่อผู้ใช้HttpSessionเมื่อเริ่มต้นการเชื่อมต่อ websocket
Thanh Nguyen Van

1
กรุณาให้ข้อมูลเพิ่มเติมเกี่ยวกับวิธีการตั้งชื่อผู้ใช้งาน? ฉันสามารถส่งชื่อผู้ใช้ในข้อความระงับได้หรือไม่?
Andres

1
@ แอนเดรส: คุณสามารถขยายDefaultHandshakeHandlerและแทนที่วิธีการdetermineUser
Thanh Nguyen Van

1
คำอธิบายของคุณใช้งานได้ดี อย่างไรก็ตามqueue/reply-user[session id]เอกสารราชการอยู่ที่ไหน?
hbrls

35

ฉันพบว่าปัญหาของฉันคืออะไร ก่อนอื่นฉันไม่ได้ลงทะเบียน/userคำนำหน้าในโบรกเกอร์ธรรมดา

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

จากนั้นฉันไม่ต้องการ/userคำนำหน้าเพิ่มเติมเมื่อส่ง:

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

Spring จะ"/user/" + principal.getName()นำหน้าไปยังปลายทางโดยอัตโนมัติดังนั้นจึงเปลี่ยนเป็น "/ user / bob / reply"

นอกจากนี้ยังหมายความว่าในจาวาสคริปต์ฉันต้องสมัครสมาชิกตามที่อยู่ที่แตกต่างกันต่อผู้ใช้

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

3
อืม ... มีวิธีหลีกเลี่ยงการตั้งค่า userName ฝั่งไคลเอ็นต์หรือไม่? การเปลี่ยนค่านั้น (ตัวอย่างเช่นโดยใช้ชื่อผู้ใช้อื่น) คุณจะสามารถดูข้อความของผู้อื่นได้
vdenotaris

2
ดูวิธีแก้ปัญหาของฉัน: stackoverflow.com/questions/25646671/…
vdenotaris

วิธีสมัครสมาชิกเพื่อตอบกลับผู้ใช้จากวิธีการที่มีคำอธิบายประกอบ@RequestMappingและ@MessageMappingดูที่นี่วิธีสมัครสมาชิกโดยใช้การรวม Sping Websocket กับ userName เฉพาะ (userId) + รับการแจ้งเตือนจาก method anotated ด้วย @RequestMapping
Shantaram Tupe

เฮ้เพื่อนของฉันคุณช่วยบอกฉันได้ไหม "ผู้ใช้" รายนี้คืออะไร? ฉันใช้ Spring Security และผู้ใช้ของฉัน (ชื่อผู้ใช้) คือที่อยู่อีเมล เมื่อผู้ใช้แต่ละคนเข้าสู่ระบบเขาจะได้รับโทเค็น JWT ของเขาใน Spring Security
Francisco Souza

ฉันประสบปัญหาเดียวกันค้นหาเป็นเวลาหลายชั่วโมงก่อนที่จะได้รับคำตอบ มีเพียงการอธิบายของคุณเท่านั้นที่ช่วยฉันได้ ขอบคุณมาก!!
Navaneeth

3

ฉันสร้างโครงการ websocket ตัวอย่างโดยใช้ STOMP ด้วย สิ่งที่ฉันสังเกตเห็นก็คือ

@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();
}

}

มันใช้งานได้หรือไม่ "/ user" รวมอยู่ใน config.enableSimpleBroker (...


2

วิธีแก้ปัญหาของฉันตามคำอธิบายที่ดีที่สุดของ Thanh Nguyen Van แต่นอกจากนี้ฉันได้กำหนดค่า MessageBrokerRegistry:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

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

2

ฉันทำเช่นเดียวกันและใช้งานได้โดยไม่ต้องใช้ผู้ใช้

@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");
    }
}

เฮ้คุณเคยใช้สิ่งนี้/appที่ไหน ในกรณีใด?
Francisco Souza
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.