Eider

Eider is an object-oriented, asynchronous, late-binding, web-first, polyglot RPC protocol. Eider is also the name of the reference implementations in Python and JavaScript.

Out of the box, Eider uses WebSockets for transport and JSON or MessagePack for serialization. The core protocol and concepts are transport- and format-neutral.

With Eider, developing applications that combine server-side, client-side, and in-browser code is duck soup!

Installation

Python

The eider package currently targets Python 3.7+ (for Python 3.4-3.6, eider 1.x is largely compatible). Requires either aiohttp or websockets. Includes elective support for MessagePack using msgpack-python, if available.

pip install eider

# either:
pip install aiohttp  # recommended
pip install websockets  # slower

# optional:
pip install msgpack

You can also check out the source code on GitHub.

JavaScript

The eider-rpc package targets Node.js 6+, modern browsers, and any other environment that supports ES6.

For clients, no external libraries are strictly required. Node.js servers need the ws package. Other optional dependencies are msgpack-lite for MessagePack encoding and weak for implicit remote garbage collection.

npm install eider-rpc

# for servers:
npm install ws

# optional:
npm install msgpack-lite
npm install weak

For the browser: eider-rpc.min.js.

You can also check out the source code on GitHub.

C++

The eider-pybind11 project provides a simple header file that can enable classes implemented in C++ to be served over Eider connections using pybind11.

Getting Started

Python

Here is a simple server (simple_server.py):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import eider

class DuckTester(eider.LocalRoot):

    def is_it_a_duck(self, obj):
        return (
            obj['looks'] == 'like a duck' and
            obj['swims'] == 'like a duck' and
            obj['quacks'] == 'like a duck')

eider.serve(12345, root=DuckTester)

And here is a corresponding client (simple_client.py):

1
2
3
4
5
6
7
8
9
import eider

how = 'like a duck'
obj = {'looks': how, 'swims': how, 'quacks': how}

with eider.BlockingConnection('ws://localhost:12345') as conn:
    with conn.create_session() as duck_tester:
        is_duck = duck_tester.is_it_a_duck(obj)
        print("It's probably " + ("" if is_duck else "NOT ") + "a duck.")

And here is an equivalent client using non-blocking APIs: (simple_client_async.py):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import asyncio, eider

how = 'like a duck'
obj = {'looks': how, 'swims': how, 'quacks': how}

async def ducktest():
    async with eider.Connection('ws://localhost:12345') as conn:
        async with conn.create_session() as duck_tester:
            is_duck = await duck_tester.is_it_a_duck(obj)
            print("It's probably " + ("" if is_duck else "NOT ") + "a duck.")

asyncio.run(ducktest())

JavaScript

Here is an equivalent server in JavaScript (simple_server.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const Eider = require('eider-rpc');

class DuckTester extends Eider.LocalRoot {
    is_it_a_duck(obj) {
        return obj.looks === 'like a duck' &&
            obj.swims === 'like a duck' &&
            obj.quacks === 'like a duck';
    }
}

Eider.serve(12345, {root: DuckTester});

And here is an equivalent client (simple_client.html):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<script src="eider-rpc.min.js"></script>
<script type="text/javascript">
const how = 'like a duck';
const obj = {looks: how, swims: how, quacks: how};
Eider.using(Eider.connect('ws://localhost:12345'), conn =>
    Eider.using(conn.createSession(), duckTester =>
        duckTester.is_it_a_duck(obj).then(isDuck => {
            alert("It's probably " + (isDuck ? '' : 'NOT ') + 'a duck.');
        })
    )
);
</script>

And here is an equivalent client using async/await syntax (simple_client_async.html):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<script src="eider-rpc.min.js"></script>
<script type="text/javascript">
const how = 'like a duck';
const obj = {looks: how, swims: how, quacks: how};
(async () =>
    await Eider.using(Eider.connect('ws://localhost:12345'), async conn =>
        await Eider.using(conn.createSession(), async duckTester => {
            const isDuck = await duckTester.is_it_a_duck(obj);
            alert("It's probably " + (isDuck ? '' : 'NOT ') + 'a duck.');
        })
    )
)();
</script>

C++

Here is how the core of the Python server could be written in C++ (simple_server.cpp):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <eider_pybind11.hpp>

using namespace eider_pybind11;

struct DuckTester : LocalRoot {
    using LocalRoot::LocalRoot;

    bool is_it_a_duck(py::object obj) {
        return std::string(py::str(obj["looks"])) == "like a duck" &&
            std::string(py::str(obj["swims"])) == "like a duck" &&
            std::string(py::str(obj["quacks"])) == "like a duck";
    }
};

PYBIND11_MODULE(ducktest, m) {
    bind(m);

    py::class_<DuckTester, LocalRoot>(m, "DuckTester")
        .def(py::init<LocalSession>())
        .def("is_it_a_duck", &DuckTester::is_it_a_duck);
}

Blame

Eider began as an internal software library at ON Semiconductor and was open-sourced under the Apache License 2.0 in April 2017.

Bart Robinson is the original author and current maintainer.

King Eider photo by Ron Knight (CC BY 2.0).