Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Taming client-server communication

Taming client-server communication

In the early days of the web, a browser could only interact with a server on page load. The XMLHttpRequest enabled the web 2.0 revolution popularizing RESTful web services in the process. Now, with HTML5, we have WebSockets. WebSockets enable a plethora of messaging based communication patterns. This talk will dive into two cujoJS libraries that specialize in client-server communication: rest.js (https://github.com/cujojs/rest) for consuming RESTful HTTP services and msgs.js (https://github.com/cujojs/msgs) for message oriented programing between clients and servers utilizing WebSockets. We’ll talk about what access patterns each of these mediums enable and how to extend these libraries to fit your specific needs.

Scott Andrews

May 31, 2013
Tweet

Other Decks in Technology

Transcript

  1. About me • engineer @ SpringSource / Pivotal • contributor

    to cujoJS • drifter • application 㲗 framework • open source 㲗 commercial • client 㲗 server • front-end 㲗 back-end
  2. A brief word from my sponsor • leader in Enterprise

    Java • now part of Pivotal • Spring • Cloud Foundry • Redis • RabbitMQ & SockJS • Groovy & Grails • Pivotal Labs
  3. We use jQuery, duh $.get('/usersList.php').then( function (response) { ... }

    ); ...not to start a holy war, but I like promises
  4. What's wrong here? $.ajax({ url: '/usersList.php', data: { page: 1,

    count: 50 } }).then( function (response) { ... }, function (error) { ... } ); ...lot of little nits, but it's not too bad
  5. What's wrong here? $.ajax({ url: '/usersList.php', data: { page: 3,

    count: 50 } }).then( function (response) { ... }, function (error) { ... } ); $.ajax({ url: '/usersList.php', data: { page: 5, count: 50 } }).then( function (response) { ... }, function (error) { ... } ); $.ajax({ url: '/usersList.php', data: { page: 4, count: 50 } }).then( function (response) { ... }, function (error) { ... } ); $.ajax({ url: '/usersList.php', data: { page: 1, count: 50 } }).then( function (response) { ... }, function (error) { ... } ); $.ajax({ url: '/usersList.php', data: { page: 2, count: 50 } }).then( function (response) { ... }, function (error) { ... } );
  6. Abstract the common bits function loadUsers(pageNum) { return $.ajax({ url:

    '/usersList.php', data: { page: pageNum, count: 50 } }); } loadUsers(1).then( function (response) { ... }, function (error) { ... } );
  7. Poor abstractions • abstracts HTTP rather than embracing it •

    it's not just jQuery • every 'Ajax' lib suffers from this issue • should abstract interacting with resources rather than mechanics of making requests
  8. rest.js • very thin wrapper around a raw request/response •

    XHR, Node, JSONP, IE XDomainRequest • AMD and CommonJS/Node • MIT licensed, part of cujoJS
  9. How do I use rest.js? var rest = require('rest'); --

    or -- define(['rest'], function (rest) { ... });
  10. rest.js • abstraction to work with resources, rather than just

    an abstraction to make requests • full access to all aspects of the request and response, including response headers • advanced behavior applied with interceptors • configure behavior once, share
  11. What makes rest.js different? • Interceptors! • apply advanced behavior

    to the request/response • encapsulate configuration • use what you need
  12. What's an interceptor? var client = rest.chain(basicAuthInterceptor, { username: 'marisa',

    password: 'koala' }); client('/usersList.php').then( function (response) { ... authenticated response... } );
  13. What can interceptors do? • almost anything • hook into

    the request/response lifecycle • augment or replace a request/response • provide an alternate client • detect and/or recover from errors • abort the request
  14. Provided interceptors • Common: Default Request, Entity, Path Prefix, Location

    • RESTful: Content negotiation, Hypermedia APIs • Authentication: Basic, OAuth • Error Detection and Recover: Error Code, Retry, Timeout • Fallbacks: JSONP, XDomainRequest (IE), ActiveX XHR (IE)
  15. Custom interceptors return interceptor({ init: function (config) { return config;

    }, request: function (request, config) { return request; }, response: function (response, config, client) { return response; }, success: function (response, config, client) { return response; }, error: function (response, config, client) { return response; } });
  16. A real interceptor: basic auth return interceptor({ request: function handleRequest(request,

    config) { var headers, username, password; headers = request.headers || (request.headers = {}); username = request.username || config.username; password = request.password || config.password || ''; if (username) { headers.Authorization = 'Basic ' + base64.encode(username + ':' + password); } return request; } });
  17. Chaining interceptors • extend a client applying behavior to requests/response

    resulting in a new client • client can be safely shared within an application • clients are immutable
  18. Chaining interceptors var client = rest.chain(basicAuthInterceptor, { username: 'marisa', password:

    'koala' }) .chain(errorCodeInterceptor, { code: 500 }) .chain(mimeInterceptor, { mime: 'application/json' }) .chain(hateoasInterceptor);
  19. Interceptors in practice var client = rest.chain(basicAuthInterceptor, { username: 'marisa',

    password: 'koala' }) .chain(errorCodeInterceptor, { code: 500 }) .chain(mimeInterceptor, { mime: 'application/json' }) .chain(hateoasInterceptor); ...I heard about this OAuth thing, can you use it instead of whatever we currently do?
  20. Interceptors in practice var client = rest.chain(oAuthInterceptor, { clientId: 'myapp',

    scope: 'read,write', authorizationUrl: 'http://example.com/oauth', redirectUrl: 'http://myapp.com/oauth' }) .chain(errorCodeInterceptor, { code: 500 }) .chain(mimeInterceptor, { mime: 'application/json' }) .chain(hateoasInterceptor); • replaced basic auth with OAuth • request is authenticated under the hood • other interceptors, consumers don't care
  21. What about events? • no problem • let’s use WebSockets

    • bidirectional communication • server → client • client → server
  22. The problem with WebSocket libraries • abstracts sending and receiving

    of messages • little support for interacting with messages
  23. Enterprise Integration Patterns • simple patterns for message oriented programming

    • don't let the enterprise word scare you • widely deployed server side
  24. msgs.js • Enterprise Integration Patterns in action • provides core

    messaging patterns • adapters to other messaging APIs • supports browsers (AMD) and Node.js • MIT licensed, part of cujoJS
  25. Hello World var bus = require('msgs').bus(); bus.channel('lowercase'); bus.channel('uppercase'); bus.transform( function

    (message) { return message.toUpperCase(); }, { input: 'lowercase', output: 'uppercase' } ); bus.outboundAdapter( function (str) { console.log(str); }, { input: 'uppercase' } ); bus.send('lowercase', 'hello world'); // 'HELLO WORLD'
  26. • transformer • filter • router • splitter • aggregator

    • inboundAdapter • outboundAdapter • inboundGateway • outboundGateway Messaging primitives • bus • channel • pubsubChannel • exchangeChannel • queueChannel • subscribe • unsubscribe • tap • untap
  27. var bus = require('msgs').bus(), webSocketServer = ...; bus.pubsubChannel('fromClient'); bus.pubsubChannel('toClient'); webSocketServer.on('connection',

    function (connection) { bus.nodeStreamGateway(connection, { output: 'fromClient', input: 'toClient' }); }); bus.forward('fromClient', 'toClient'); Broadcast server
  28. Scalable broadcast server var bus = require('msgs').bus(), redis = require('redis'),

    webSocketServer = ...; bus.pubsubChannel('fromClient'); bus.pubsubChannel('toClient'); webSocketServer.on('connection', function (connection) { bus.nodeStreamGateway(connection, { output: 'fromClient', input: 'toClient' }); }); bus.redisGateway(redis.createClient, 'circles', { output: 'toClient', input: 'fromClient' });
  29. Let's not forget about the client var bus = require('msgs').bus(),

    SockJS = require('sockjs'); bus.channel('toServer'); bus.channel('fromServer'); bus.webSocketGateway( new SockJS('/msgs'), { output: 'fromServer', input: 'toServer' } ); ... Full source: https://github.com/know-cujojs/circles See it running: http://cujojs-circles.cloudfoundry.com/