Comment combiner websockets et http pour créer une API REST qui garde les données à jour?

je pense à construire une API REST avec websockets et http où j'utilise websockets pour dire au client que de nouvelles données sont disponibles ou pour fournir les nouvelles données directement au client.

voici quelques idées différentes sur la façon dont cela pourrait fonctionner:

ws = websocket

Idée A:

  1. David obtenir tous les utilisateurs avec GET /users
  2. Jacob ajouter un utilisateur avec POST /users
  3. ws message est envoyé à tous les clients avec les informations qu'un nouvel utilisateur existe pas
  4. David recevoir un message par ws et les appels GET /users

Idée B:

  1. David obtenir tous les utilisateurs avec GET /users
  2. David s'inscrire pour obtenir des mises à jour ws quand un changement est fait à /users
  3. Jacob ajouter un utilisateur avec POST /users
  4. le nouvel utilisateur est envoyé à David par ws

Idée C:

  1. David obtenir tous les utilisateurs avec GET /users
  2. David s'inscrire pour obtenir des mises à jour ws quand un changement est fait à /users
  3. Jacob ajouter un utilisateur avec POST /users et il obtient le id 4
  4. David recevoir l'id 4 du nouvel utilisateur par ws
  5. David obtenir le nouvel utilisateur avec GET /users/4

Idée D:

  1. David obtenir tous les utilisateurs avec GET /users
  2. David s'inscrire pour obtenir ws mises à jour lorsque des modifications est fait pour /users .
  3. Jacob ajouter un utilisateur avec POST /users
  4. David recevoir un ws message qui change, c'est fait pour /users
  5. David obtenez seulement le delta en appelant GET /users?lastcall='time of step one'

quelle alternative est la meilleure et quels sont les avantages et les inconvénients?

Est-ce une autre meilleure "idée E"?

- Nous encore besoin de REPOS ou ws assez pour toutes les données?

Modifier

Pour résoudre les problèmes avec les données sortir de la synchronisation nous pourrions fournir l'en-tête

" si-non modifié-depuis"

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Unmodified-Since

ou "e-Tag"

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag

ou les deux avec des requêtes PUT.

34
demandé sur David Berg 2015-10-30 00:08:49

10 réponses

idée B est pour moi la meilleure, parce que le client souscrit spécifiquement aux modifications d'une ressource, et obtient les mises à jour progressives à partir de ce moment.

avons-nous même besoin D'utiliser le repos ou est-ws enought pour toutes les données?

s'il vous Plaît vérifier: WebSocket/REPOS: connexions Client?

9
répondu vtortola 2017-05-23 12:10:48

Je ne sais pas Java, mais J'ai travaillé avec Ruby et C sur ces dessins...

assez drôle, je pense que la solution la plus simple est d'utiliser JSON, où L'API REST ajoute simplement les données method (i.e. method: "POST" ) au JSON et transmet la requête au même Gestionnaire que le Websocket utilise.

la réponse de L'API sous-jacente (la réponse de L'API traitant les requêtes JSON) peut être traduite dans n'importe quel format dont vous avez besoin, comme HTML rendu... bien que j'envisagerais simplement de rendre JSON pour la plupart des cas d'utilisation.

cela aide à encapsuler le code et à le garder au sec.

comme vous pourriez le déduire, cette conception rend les tests plus faciles, puisque l'API sous-jacente qui gère le JSON peut être testé localement sans avoir besoin d'émuler un serveur.

Bonne Chance!

P. S. (Pub/Sub)

en ce qui concerne le Pub/Sub, je trouve préférable d'avoir un" hook " pour tous les appels d'API de mise à jour (un callback) et un Pub/Sub séparé module qui gère ces choses.

je trouve aussi qu'il est plus facile d'écrire l'ensemble des données au service Pub/Sub (option B) plutôt qu'un simple numéro de référence (option C) ou un message" update available " (options A et D).

en général, je crois aussi qu'envoyer le tout la liste des utilisateurs n'est pas efficace pour les grands systèmes. Sauf si vous avez 10-15 utilisateurs, l'appel de la base de données pourrait être un échec. Considérez L'administrateur D'Amazon appelant pour une liste de tous les utilisateurs... Brrr....

à la place, j'envisagerais de diviser cela en pages, disons 10-50 utilisateurs par page. Ces tables peuvent être remplies en utilisant plusieurs requêtes (Websocket / REST, doesn't matter) et facilement mises à jour en utilisant des messages Pub/Sub en direct ou rechargées si une connexion a été perdue et rétablie.

MODIFIER (REPOS contre les Websockets)

comme pour repos vs. Websockets... Je trouve la question de besoin est principalement un sous-ensemble de la question "qui est le client?"...

cependant, une fois la logique séparée de la couche transport, il est très facile de supporter les deux et souvent il est plus logique de supporter les deux.

je dois noter que Websockets ont souvent un léger bord quand il s'agit de authentification (les justificatifs d'identité sont échangés une fois par connexion au lieu d'une fois par demande). Je ne sais pas si c'est une préoccupation.

pour la même raison (ainsi que d'autres), les Websockets ont généralement un bord en ce qui concerne la performance... la taille d'un bord par rapport au repos dépend de la couche de transport REST (HTTP/1.1, HTTP/2, etc').

habituellement, ces choses sont négligeables quand vient le temps d'offrir un point D'accès API public et je crois que la mise en œuvre des deux est probablement la voie à suivre pour l'instant.

5
répondu Myst 2017-05-28 16:41:30

Pour résumer vos idées:

A: envoyer un message à tous les clients lorsqu'un utilisateur édite des données sur le serveur. Tous les utilisateurs demandent alors une mise à jour de toutes les données.

-ce système peut faire beaucoup d'appels inutiles sur le serveur au nom des clients qui n'utilisent pas les données. Je ne recommande pas de produire tout ce trafic supplémentaire car le traitement et l'envoi de ces mises à jour pourraient devenir coûteux.

B: après qu'un utilisateur a retiré des données du serveur, il s'abonne aux mises à jour du serveur qui lui envoie des informations sur ce qui a changé.

-cela économise beaucoup de trafic sur le serveur, mais si vous tombez en panne de synchronisation, vous allez poster des données incorrectes à vos utilisateurs.

C: les utilisateurs qui s'abonnent à des mises à jour de données sont envoyés des informations sur les données qui ont été mis à jour, puis récupérez-le eux-mêmes.

-c'est le pire de A et B en ce que vous aurez des allers-retours supplémentaires entre vos utilisateurs et les serveurs juste pour les aviser qu'ils ont besoin de faire une demande d'information qui peut être hors de synchronisation.

D: les utilisateurs qui s'abonnent à des mises à jour sont avisés lorsque des modifications sont apportées, puis demandent la dernière modification apportée au serveur.

- cela présente tous les problèmes avec C, mais inclut la possibilité que, une fois hors de synchronisation, vous pouvez envoyer des données qui seront absurdes à vos utilisateurs qui pourraient juste planter l'application côté client pour tout ce que nous savons.

je pense que cette option E serait la meilleure:

Chaque fois que les données changent sur le serveur, envoyer le contenu de toutes les données aux clients qui y ont souscrit. Cela limite le trafic entre vos utilisateurs et le serveur tout en leur donnant la moindre chance d'avoir des données hors synchronisation. Ils pourraient obtenir des données périmées si leur connexion tombe, mais au moins vous ne leur enverriez pas quelque chose comme Delete entry 4 quand vous n'êtes pas sûr si oui ou non ils ont reçu le message que l'entrée 5 vient de passer dans la fente 4.

Quelques Considérations:

  • à quelle fréquence les données sont-elles mises à jour?
  • combien d'utilisateurs doivent être mis à jour chaque fois qu'une mise à jour se produit?
  • Quelle est votre transmission? les coûts? Si vous avez des utilisateurs sur des appareils mobiles avec des connexions lentes, cela affectera combien de fois et combien vous pouvez vous permettre de leur envoyer.
  • combien de données est mis à jour dans une mise à jour donnée?
  • que se passe-t-il si un utilisateur voit des données périmées?
  • que se passe-t-il si un utilisateur obtient des données hors de synchronisation?

votre pire scénario serait quelque chose comme ceci: beaucoup d'utilisateurs, avec des connexions lentes qui mettent fréquemment à jour de grandes quantités de données qui ne devraient jamais être périmées et, si elle sort de la synchronisation, devient trompeuse.

3
répondu Glen Pierce 2017-05-15 17:12:13

une autre option consiste à utiliser Firebase Cloud Messaging :

en utilisant FCM, vous pouvez informer un client app que le nouveau courrier électronique ou d'autres données est disponible pour la synchronisation.

comment ça marche?

une mise en œuvre de la FCM comprend deux composantes principales pour l'envoi et la réception:

  • un environnement de confiance tel que le Cloud Fonctions pour Firebase ou un serveur app sur lequel construire, cibler et envoyer des messages.
  • une application client iOS, Android, ou Web (JavaScript) qui reçoit des messages.

le Client enregistre sa clé Firebase sur un serveur. Lorsque des mises à jour sont disponibles, le serveur envoie une notification push à la touche Firebase associée au client. Le Client peut recevoir des données dans une structure de notification ou les synchroniser avec un serveur après avoir reçu un notification.

3
répondu Justas 2017-05-16 07:31:15

généralement, vous pourriez avoir un regard sur les cadres actuels "temps réel" Web comme Météorjs qui s'attaquent exactement à ce problème.

Meteor dans des travaux spécifiques plus ou moins comme votre exemple d avec des abonnements sur certaines données et des deltas étant envoyés après des changements seulement aux clients affectés. Leur protocole utilisé est appelé DDP qui envoie en plus les deltas non pas comme des données HTML mais des données brutes.

si les WebSockets ne sont pas disponibles, vous pouvez utiliser des méthodes de repli telles que long polling ou les évènements envoyés par le serveur .

si vous prévoyez de le mettre en œuvre vous-même, j'espère que ces sources sont une sorte d'inspiration sur la façon dont ce problème a été abordé. Comme déjà indiqué, le cas d'utilisation spécifique est important

3
répondu Maximilian Körner 2017-05-16 14:29:21

La réponse dépend de votre cas d'utilisation. Pour la plupart, bien que j'ai trouvé que vous pouvez mettre en œuvre tout ce dont vous avez besoin avec les sockets. Aussi longtemps que vous essayez seulement d'accéder à votre serveur avec des clients qui peuvent prendre en charge des sockets. Aussi, l'échelle peut être un problème lorsque vous utilisez uniquement des supports. Voici quelques exemples de la façon dont vous pourriez utiliser just sockets.

Côté Serveur:

socket.on('getUsers', () => {
    // Get users from db or data model (save as user_list).
    socket.emit('users', user_list );
})
socket.on('createUser', (user_info) => {
    // Create user in db or data model (save created user as user_data).
    io.sockets.emit('newUser', user_data);
})

côté Client:

socket.on('newUser', () => {
    // Get users from db or data model (save as user_list).
    socket.emit('getUsers');
})
socket.on('users', (users) => {       
    // Do something with users
})

This utilise socket.io pour noeud. Je ne suis pas sûr du scénario exact, mais ça marcherait pour cette affaire. Si vous avez besoin d'inclure des paramètres de repos qui serait très bien aussi.

3
répondu Brian Baker 2017-05-16 19:19:05

j'ai personnellement utilisé L'idée B dans la production et je suis très satisfait des résultats. Nous utilisons http://www.axonframework.org / , de sorte que toute modification ou création d'une entité est publiée en tant qu'événement tout au long de la demande. Ces événements sont ensuite utilisés pour mettre à jour plusieurs modèles de lecture, qui sont essentiellement des tables Mysql simples soutenant une ou plusieurs requêtes. J'ai ajouté quelques intercepteurs aux processeurs d'événements qui mettent à jour ces modèles de lecture afin qu'ils publient les événements ils viennent d'être traités après que les données aient été transférées au DB.

la publication des événements se fait par STOMP sur les sockets web. Il est rendu très simple est vous utilisez le support de prise Web de Spring ( https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html ). Voici comment je l'ai écrit:

@Override
protected void dispatch(Object serializedEvent, String topic, Class eventClass) {
    Map<String, Object> headers = new HashMap<>();
    headers.put("eventType", eventClass.getName());
    messagingTemplate.convertAndSend("/topic" + topic, serializedEvent, headers);
}

j'ai écrit un petit configurateur qui utilise L'API Springs bean factory pour que je puisse annoter mon événement Axon les gestionnaires comme ceci:

@PublishToTopics({
    @PublishToTopic(value = "/salary-table/{agreementId}/{salaryTableId}", eventClass = SalaryTableChanged.class),
    @PublishToTopic(
            value = "/salary-table-replacement/{agreementId}/{activatedTable}/{deactivatedTable}",
            eventClass = ActiveSalaryTableReplaced.class
    )
})

bien sûr, c'est juste une façon de le faire. Se connecter du côté du client peut ressembler à quelque chose comme ceci:

var connectedClient = $.Deferred();

function initialize() {
    var basePath = ApplicationContext.cataDirectBaseUrl().replace(/^https/, 'wss');
    var accessToken = ApplicationContext.accessToken();
    var socket = new WebSocket(basePath + '/wss/query-events?access_token=' + accessToken);
    var stompClient = Stomp.over(socket);

    stompClient.connect({}, function () {
        connectedClient.resolve(stompClient);
    });
}


this.subscribe = function (topic, callBack) {
    connectedClient.then(function (stompClient) {
        stompClient.subscribe('/topic' + topic, function (frame) {
            callBack(frame.headers.eventType, JSON.parse(frame.body));
        });
    });
};

initialize();
1
répondu CrimsonCricket 2017-05-28 17:13:21

avec toutes les grandes informations que toutes les grandes personnes ont ajoutées avant moi.

j'ai découvert que finalement il n'y a pas de bien ou de mal, il s'agit simplement de ce qui convient à vos besoins:

permet de prendre CRUD dans ce scénario:

WS Seule Approche:

Create/Read/Update/Deleted information goes all through the websocket.    
--> e.g If you have critical performance considerations ,that is not 
acceptable that the web client will do successive REST request to fetch 
information,or if you know that you want the whole data to be seen in 
the client no matter what was the event ,  so just send the CRUD events 
AND DATA inside the websocket.

WS TO SEND EVENT INFO + REST TO CONSUME THE DATA ITSELF

Create/Read/Update/Deleted , Event information is sent in the Websocket,
giving the web client information that is necessary to send the proper 
REST request to fetch exactly the thing the CRUD that happend in server.

par exemple WS envoie UsersListChangedEvent {"ListChangedTrigger: "ItemModified" , "IdOfItem":"XXXX#3232" , "UserExtrainformation":" Assez d'info pour laisser le client décider si c'est pertinent pour récupérer les données modifiées"}

j'ai trouvé que l'utilisation de WS [seulement pour l'utilisation des données D'événement] et le repos [Pour consommer les données ]est mieux parce que:

[1] séparation entre le modèle de lecture et d'écriture, Imaginez que vous voulez ajouter un peu de runtime information lorsque vos données sont récupérées lorsque leur lecture à partir du repos, qui est maintenant atteint parce que vous ne mélangez pas écriture & modèles de lecture comme dans 1.

[2] disons autre plate-forme , pas nécessairement le client web consommera ces données. donc vous changez juste le déclencheur D'événement de WS à la nouvelle façon, et utilisez le repos pour consommer les données.

[3] Le Client n'a pas besoin d'écrire deux façons de lire les données nouvelles/modifiées. Habituellement, il ya aussi le code que lit les données lorsque la page se charge, et non

grâce au websocket, ce code peut maintenant être utilisé deux fois, une fois lorsque la page WS a déclenché l'événement spécifique.

[4] peut-être que le client ne veut pas aller chercher le nouvel utilisateur parce qu'il ne montre actuellement qu'une vue de données anciennes [par exemple. utilisateurs], et les nouveaux changements de données n'est pas dans son intérêt de récupérer ?

1
répondu Robocide 2017-09-16 12:39:31

plus je lis sur ce sujet, plus je pense que la bonne chose à faire est d'implémenter une implémentation REST sur une connexion websockets. Une solution de repli où vous http pourraient être une solution.

https://hackernoon.com/rest-over-websockets-instead-of-http-81b0f0632295

0
répondu David Berg 2017-05-30 11:49:46

je préfère le A , il permet au client la flexibilité de mettre à jour ou non les données existantes.

aussi avec cette méthode, la mise en œuvre et le contrôle d'accès devient beaucoup plus facile.

par exemple, vous pouvez simplement diffuser l'événement userUpdated à tous les utilisateurs, ce qui évite d'avoir une liste de clients pour faire des diffusions spécifiques et le Access Controls and Authentications appliqué pour votre Route de repos ne doit pas changer pour appliquer à nouveau parce que le client va encore faire une demande GET .

beaucoup de choses dépendent du type d'application que vous faites.

-1
répondu Mujtaba Kably 2017-05-16 13:11:08