Push-notiser i Grails & React.js baserade webbprojekt
Det här inlägget riktar sig kanske mer mot dig som utvecklare än dig som beställare. Som oftast med system så krävs det en kommunikation mellan servern och klienten. Det är viktigt eftersom olika händelser kan uppstå på servern och en anmälan ska då skickas till klienten. Ett enkelt user cace är en chatt, där användarna kan utbyta meddelanden mellan varandra. Vi kan också använda annan teknik, till exempel Message Queue system. Beroende på behoven så kan man välja den man har mest användning för.
Beskrivning
I det här inlägget kommer vi att diskutera push-meddelanden med hjälp av WebSocket. Jag vill beskriva pedagogiken bakom arbetet, när det kommer till leverans och produktion så har vi självklart enklare rutiner på företaget för att bemöta varje beställning.
Mål
Att kunna genomföra push-notiser i webbapplikationer
Krav
Grails 3.* (Dependencies: Grails Spring WebSocket, Grails Spring Security Rest)
React.js (Dependencies: Sock.js client, Stomp.js)
1 – Server konfiguration
Vi måste konfigurera WebSocket message agent för att låta vårt system skicka och ta emot meddelanden via säkra kanaler.
Följande kod (configureClientInboundChannel metod) visar hur man autentiserar via WebSocket. Vi måste vara säkra på att endast en auktoriserad användare kan ansluta, prenumerera och ta emot meddelanden från vår server. Vårt system använder Spring Security Rest och följaktligen är användarna autentiserade via access token i varje förfrågan.
Vi konfigurerar också den endpoint där vår message agent lyssnar på subscription requests och meddelanden (registerStompEndpoints method)
import grails.plugin.springsecurity.rest.token.storage.TokenStorageService import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.messaging.simp.SimpMessageType import org.springframework.messaging.simp.config.ChannelRegistration import org.springframework.messaging.simp.stomp.StompHeaderAccessor import org.springframework.messaging.support.ChannelInterceptorAdapter import org.springframework.messaging.support.MessageBuilder import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker import org.springframework.web.socket.config.annotation.StompEndpointRegistry import org.springframework.messaging.Message import org.springframework.messaging.MessageChannel @Configuration @EnableWebSocketMessageBroker class DefaultWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { public static final String AUTHORIZATION = 'Authorization' public static final String BEARER = 'Bearer' @Autowired TokenStorageService tokenStorageService @Override void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) { stompEndpointRegistry.addEndpoint("/ws") .setAllowedOrigins("*") .withSockJS() .setSessionCookieNeeded(false) } void configureClientInboundChannel(ChannelRegistration registration) { registration.setInterceptors(new ChannelInterceptorAdapter() { Message<Object> preSend(Message<Object> message, MessageChannel channel) { String token = null Boolean access = false StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message) List tokens = accessor.getNativeHeader(AUTHORIZATION) accessor.removeNativeHeader(AUTHORIZATION) if(tokens?.size > 0) { token = tokens[0] } if (token?.startsWith(BEARER)) { token = token.substring(BEARER.length()).trim() } switch (accessor.messageType){ case SimpMessageType.CONNECT: access = (tokenStorageService.loadUserByToken(token))?true:false break case SimpMessageType.SUBSCRIBE: access = (tokenStorageService.loadUserByToken(token))?true:false break } accessor.setLeaveMutable(true) return (access)?MessageBuilder.createMessage(message.payload, accessor.messageHeaders):null } }) }
2 – Skicka meddelanden
Vi använder Grails Spring WebSocket vilket ger oss den funktionaliteten som behövs för att kunna använda konfigurationen.
I vårt exempel kommer meddelandena att ha en relaterad åtgärd, för att radera, uppdatera eller skapa meddelanden i klienten. Detta är också möjligt om vi skapar olika ämnen för det ändamålet, men i det här fallet är det inte relevant. BrokerService ansvarar för att skicka meddelanden till en destination med hjälp av brokerMesssagingService.
class BrokerService { def brokerMessagingTemplate /** * Send message to destination * @param destination * @param message */ void send(String destination, Map message) { brokerMessagingTemplate.convertAndSend destination, (message as JSON) as String } }
3 – Web client configuration
I webbklienten använder vi Sock.js och Stomp.js för att prenumerera på vår meddelandekanal. För att prenumerera på en kanal i connection request så skickar vi header configuration med användarens token som behörighet.
Vi ställer som en parameter en callback-funktion som kommer att utföras efter att ha mottagit ett meddelande från servern. Med unsubscribe method kan du ta bort en prenumeration, användaren kommer då inte längre att få push-meddelanden när det är gjort.
import {SERVER_URL} from "../config"; import headers from "../security/headers"; import SockJS from 'sockjs-client'; import Stomp from 'stompjs'; /** * WebSockets support */ const WebSocket = (endpoint) => { const socket = SockJS(`${SERVER_URL}/${endpoint}`); const client = Stomp.over(socket); function subscribe(destination, callback) { client.connect(headers(), function(frame) { client.subscribe(destination, function(message){ callback(JSON.parse(message.body)) }, headers()); }); return client; } function unsubscribe(client){ for (const sub in client.subscriptions) { if (client.subscriptions.hasOwnProperty(sub)) { client.unsubscribe(sub); } } } return { subscribe, unsubscribe } }; export default WebSocket;
4 – Prenumeration och mottagande av anmälningar
Vi lägger subscription call på componentDidMount med en klass baserad på React Component.
Vi måste också implementera funktionaliteten som ska utföras när vi får notiserna.
import React from 'react'; import WebSocket from "../../api/WebSocket"; import {ACTION_STATUS} from '../../constant'; import MessageList from "../../components/MessageList"; import { notifyAddedMessage, notifyDeletedMessage, notifyUpdatedMessage} from "../../actions/actions"; /** * Real-time messages */ class MessagePanel extends React.Component { componentDidMount() { this.subscribe(); } componentWillUnmount() { if(this.state.client !== undefined) WebSocket('ws').unsubscribe(this.state.client); } subscribe() { const callback = (message) => { switch (message.action){ case ACTION_STATUS.DELETED: this.props.notifyDeletedMessage(message.id); break; case ACTION_STATUS.UPDATED: this.props.notifyUpdatedMessage(message); break; default: this.props.notifyAddedMessage(message); } }; callback.bind(this); let client; if (user instanceof Object) { client = WebSocket('ws').subscribe(‘/topic/messages’, callback); this.setState({client}); } } render() { return ( <MessageList/> ) } }
Slutsats
Push-meddelanden gör systemet attraktivt och mer användbart, det minskar också serverns latency och förbättrar användarupplevelsen. I många fall kan vi, på ett enkelt sätt och med ett bra resultat, lägga till det med hjälp av WebSockets .
Referens
/Carlos Senior Developer på Visionmate