With a complete reorganisation of the directory structure and most of the content converted to pandoc-flavoured markdown. Some TODO's left before this can go live: - Main page - Atom feeds - Bug tracker
632 lines
35 KiB
Markdown
632 lines
35 KiB
Markdown
% The Sorry State of Convenient IPC
|
|
|
|
(Published on **2014-07-29**)
|
|
|
|
# The Problem
|
|
|
|
How do you implement communication between two or more processes? This is a
|
|
question that has been haunting me for at least 6 years now. Of course, this
|
|
question is very broad and has many possible answers, depending on your
|
|
scenario. So let me get more specific by describing the problem I want to
|
|
solve.
|
|
|
|
What I want is to write a daemon process that runs in the background and can be
|
|
controlled from other programs or libraries. The intention is that people can
|
|
easily write custom interfaces or quick scripts to control the daemon. The
|
|
service that the daemon offers over this communication channel can be thought
|
|
of as its primary API, in this way you can think of the daemon as a persistent
|
|
programming library. This concept is similar to existing programs such as
|
|
[btpd](https://github.com/btpd/btpd), [MPD](http://www.musicpd.org/),
|
|
[Transmission](https://www.transmissionbt.com/) and
|
|
[Telepathy](http://telepathy.freedesktop.org/wiki/) - I'll get back to these
|
|
later.
|
|
|
|
More specifically, the most recent project I've been working on that follows
|
|
this pattern is [Globster](/globster), a remotely controllable Direct Connect
|
|
client (if you're not familiar with Direct Connect, think of it as IRC with
|
|
some additional file sharing capabilities built in). While the problem I
|
|
describe is not specific to Globster, it still serves as an important use case.
|
|
I see many other projects with similar IPC requirements.
|
|
|
|
The IPC mechanism should support two messaging patterns: Request/response and
|
|
asynchronous notifications. The request/response pattern is what you typically
|
|
get in RPC systems - the client requests something of the daemon and the daemon
|
|
then replies with a response. Asynchronous notifications are useful in allowing
|
|
the daemon to send asynchronous status updates to the client, such as incoming
|
|
chat messages or file transfer status. Lack of support for such notifications
|
|
would mean that a client needs to continuously poll for updates, which is
|
|
inefficient.
|
|
|
|
So what I'm looking for is a high-level IPC mechanism that handles this
|
|
communication. Solutions are evaluated by the following criteria, in no
|
|
particular order.
|
|
|
|
**Easy**
|
|
: And with _easy_ I refer to _ease of use_. As mentioned above, other people
|
|
should be able to write applications and scripts to control the daemon. Not
|
|
many people are willing to invest days of work just to figure out how to
|
|
communicate with the daemon.
|
|
|
|
**Simple**
|
|
: Simplicity refers to the actual protocol and the complexity of the code
|
|
necessary to implement it. Complex protocols require complex code, and complex
|
|
code is hard to maintain and will inevitably contain bugs. Note that _simple_
|
|
and _easy_ are very different things and often even conflict with each other.
|
|
|
|
**Small**
|
|
: The IPC implementation shouldn't be too large, and shouldn't depend on huge
|
|
libraries. If you need several megabytes worth of libraries just to send a few
|
|
messages over a socket, you're doing it wrong.
|
|
|
|
**Language independent**
|
|
: Control the daemon with whatever programming language you're familiar with.
|
|
|
|
**Networked**
|
|
: A good solution should be accessible from both the local system (daemon running
|
|
on the same machine as the client) and from the network (daemon and client
|
|
running different machines).
|
|
|
|
**Secure**
|
|
: There's three parts in having a secure IPC mechanism. One part is to realize
|
|
that IPC operates at a _trust boundary_; The daemon can't blindly trust
|
|
everything the client says and vice versa, so message validation and other
|
|
mechanisms to prevent DoS or information disclosure on either part are
|
|
necessary.
|
|
|
|
Then there the matter of _confidentiality_. On a local system, UNIX sockets
|
|
will provide all the confidentiality you can get, so that's trivial. Networked
|
|
access, on the other hand, requires some form of transport layer security.
|
|
|
|
And finally, we need some form of _authentication_. There should be some
|
|
mechanism to prevent just about anyone to connect to the daemon. A
|
|
coarse-grained solution such as file permissions on a local UNIX socket or a
|
|
password-based approach for networked access will do just fine for most
|
|
purposes. Really, just keep it simple.
|
|
|
|
**Fast**
|
|
: Although performance isn't really a primary goal, the communication between the
|
|
daemon and the clients shouldn't be too slow or heavyweight. For my purposes,
|
|
anything that supports about a hundred messages a second on average hardware
|
|
will do perfectly fine. And that shouldn't be particularly hard to achieve.
|
|
|
|
**Proxy support**
|
|
: This isn't really a hard requirement either, but it would be nice to allow
|
|
other processes (say, plugins of the daemon, or clients connecting to the
|
|
daemon) to export services over the same IPC channel as the main daemon. This
|
|
is especially useful in implementing a cross-language plugin architecture. But
|
|
again, not a hard requirement, because even if the IPC mechanism doesn't
|
|
directly support proxying, it's always possible for the daemon to implement
|
|
some custom APIs to achieve the same effect. This, however, requires extra work
|
|
and may not be as elegant as a built-in solution.
|
|
|
|
Now let's discuss some existing solutions...
|
|
|
|
# Custom Protocol
|
|
|
|
Why use an existing IPC mechanism in the first place when all you need is
|
|
UNIX/TCP sockets? This is the approach taken by
|
|
[btpd](https://github.com/btpd/btpd), [MPD](http://www.musicpd.org/)
|
|
([protocol spec](http://www.musicpd.org/doc/protocol/index.html)) and older
|
|
versions of Transmission (see their [1.2x
|
|
spec](https://trac.transmissionbt.com/browser/branches/1.2x/doc/ipcproto.txt)).
|
|
Brpd hasn't taken the time to documented the protocol format, suggesting it's
|
|
not really intended to be used as a convenient API (other than through their
|
|
btcli), and Transmission has since changed to a different protocol. I'll mainly
|
|
focus on MPD here.
|
|
|
|
MPD uses a text-based request/response mechanism, where each request is a
|
|
simple one-line command and a response consists of one or more lines, ending
|
|
with an `OK` or `ACK` line. There's no support for asynchronous
|
|
notifications, although that could obviously have been implemented, too. Let's
|
|
grade this protocol...
|
|
|
|
**Easy?** Not really.
|
|
: Although MPD has conventions for how messages are formatted, each individual
|
|
message still requires custom parsing and validation. This can be automated by
|
|
designing an
|
|
[IDL](https://en.wikipedia.org/wiki/Interface_description_language) and
|
|
accompanying code generator, but writing one specific for a single project
|
|
doesn't seem like a particularly fun task.
|
|
|
|
The protocol, despite its apparent simplicity, is apparently painful enough to
|
|
use that there is a special _libmpdclient_ library to abstract away the
|
|
communication with MPD, and interfaces to this library are available in many
|
|
programming languages. If you have access to such an application-specific
|
|
library for your language of choice, then sure, using the IPC mechanism is easy
|
|
enough. But that applies to literally any IPC mechanism.
|
|
|
|
Ideally, such a library needs to be written only once for the IPC mechanism in
|
|
use, and after that no additional code is needed to communicate with
|
|
services/daemons using that particular IPC mechanism. Code re-use among
|
|
different projects is great, yo. It also doesn't scale very well when extending
|
|
the services offered by daemon, any addition to the API will require
|
|
modifications to all implementations.
|
|
|
|
**Simple?** Definitely.
|
|
: I only needed a quick glance at the MPD protocol reference and I was able to
|
|
play a bit with telnet and control my MPD. Writing an implementation doesn't
|
|
seem like a complex task. Of course, this doesn't necessarily apply to all
|
|
custom protocols, but you can make it as simple or complex as you want it to
|
|
be.
|
|
|
|
**Small?** Sure.
|
|
: This obviously depends on how elaborate you design your protocol. If you have a
|
|
large or complex API, the size of a generic message parser and validator can
|
|
easily compensate for the custom parser and validator needed for each custom
|
|
message. But for a simple APIs, it's hard to beat a custom protocol in terms of
|
|
size.
|
|
|
|
**Language independent?** Depends.
|
|
: Of course, a socket library is available to most programming languages, and in
|
|
that sense any IPC mechanism built on sockets is language independent. This is,
|
|
as such, more of an argument as to how convenient it is to communicate with the
|
|
protocol directly rather than with a library that abstracts the protocol away.
|
|
In the case of MPD, the text-based protocol seems easy enough to use directly
|
|
from most languages, yet for some reason most people prefer language-specific
|
|
libraries for MPD.
|
|
|
|
If you design a binary protocol or anything more complex than simple
|
|
request/response message types, using your protocol directly is going to be a
|
|
pain in certain languages, and people will definitely want a library specific
|
|
to your daemon for their favourite programming language. Something you'll want
|
|
to avoid, I suppose.
|
|
|
|
**Networked?** Sure enough.
|
|
: Just a switch between UNIX sockets and TCP sockets. Whether a simple solution
|
|
like that is a good idea, however, depends on the next point...
|
|
|
|
**Secure?** Ugh.
|
|
: Security is hard to get right, so having an existing infrastructure that takes
|
|
care of most security sensitive features will help a lot. Implementing your own
|
|
protocol means that you also have to implement your own security, to some
|
|
extent at least.
|
|
|
|
Writing code to parse and validate custom messages is error-prone, and a bug in
|
|
this code could make both the daemon and the client vulnerable to crashes and
|
|
buffer overflows. A statically-typed abstraction that handles parsing and
|
|
validation would help a lot.
|
|
|
|
For networked communication, you'll need some form of confidentiality. MPD does
|
|
not seem to support this, so any networked access to an MPD server is
|
|
vulnerable to passive observers and MITM attacks. This may be fine for a local
|
|
network (presumably what it is intended to be used for), but certainly doesn't
|
|
work for exposing your MPD control interface to the wider internet. Existing
|
|
protocols such as TLS or SSH can be used to create a secure channel, but these
|
|
libraries tend to be large and hard to use securely. This is especially true
|
|
for TLS, but at least there's [stunnel](https://www.stunnel.org/) to simplify
|
|
the implementation - at the cost of less convenient deployment.
|
|
|
|
In terms of authentication, you again need to implement this yourself. MPD
|
|
supports authentication using a plain-text password. This is fine for a trusted
|
|
network, but on an untrusted network you certainly want confidentiality to
|
|
prevent a random observer from reading your password.
|
|
|
|
**Fast?** Sure.
|
|
: Existing protocols may have put more effort into profiling and implementing
|
|
various optimizations than one would typically do with a custom and
|
|
quickly-hacked-together protocol, but still, it probably takes effort to design
|
|
a protocol that isn't fast enough.
|
|
|
|
**Proxy support?** Depends...
|
|
: Really depends on how elaborate you want to be. It can be very simple if all
|
|
you want is to route some messages, it can get very complex if you want to
|
|
ensure that these messages follow some format or if you want to reserve certain
|
|
interfaces or namespaces to certain clients. What surprised me about the MPD
|
|
protocol is that it actually has [some support for
|
|
proxying](http://www.musicpd.org/doc/protocol/ch03s11.html). But considering the
|
|
ad-hoc nature of the MPD protocol, the primitiveness and simplicity of this
|
|
proxy support wasn't too surprising. Gets the job done, I suppose.
|
|
|
|
Overall, and as a rather obvious conclusion, a custom protocol really is what
|
|
you make of it. In general, though, it's a lot of work, not always easy to use,
|
|
and a challenge to get the security part right.
|
|
|
|
# D-Bus
|
|
|
|
D-Bus is being used in [Transmission](https://www.transmissionbt.com/) and is
|
|
what I used for [Globster](/globster).
|
|
|
|
On a quick glance, D-Bus looks _perfect_. It is high-level, has the messaging
|
|
patterns I described, the [protocol
|
|
specification](http://dbus.freedesktop.org/doc/dbus-specification.html) does
|
|
not seem _overly_ complex (though certainly could be simplified), it has
|
|
implementations for a number of programming languages, has support for
|
|
networking, proxying is part of normal operation, and it seems fast enough for
|
|
most purposes. When you actually give it a closer look, however, reality isn't
|
|
as rose-colored.
|
|
|
|
D-Bus is designed for two very specific use-cases. One is to allow local
|
|
applications to securely interact with system-level daemons such as
|
|
[HAL](https://en.wikipedia.org/wiki/HAL_\(software\)) (now long dead) and
|
|
[systemd](http://freedesktop.org/wiki/Software/systemd/), and the other
|
|
use-case is to allow communication between different applications inside one
|
|
login session. As such, on a typical Linux system there are two D-Bus daemons
|
|
where applications can export interfaces and where messages can be routed
|
|
through. These are called the _system bus_ and the _session bus_.
|
|
|
|
**Easy?** Almost.
|
|
: The basic ideas behind D-Bus seem easy enough to use. The fact that is has
|
|
type-safe messages, interface descriptions and introspection really help in
|
|
making D-Bus a convenient IPC mechanism.
|
|
|
|
The main reasons why I think D-Bus isn't all that easy to use in practice is
|
|
due to the lack of good introductionary documentation and the crappy state of
|
|
the various D-Bus implementations. There is a [fairly good
|
|
article](https://pythonhosted.org/txdbus/dbus_overview.html) providing a
|
|
high-level overview to D-Bus, but there isn't a lot of material that covers how
|
|
to actually use D-Bus to interact with applications or to implement a service.
|
|
|
|
On the implementations, I have had rather bad experiences with the actual
|
|
libraries. I've personally used the official libdbus-1, which markets itself a
|
|
"low-level" library designed to facilitate writing bindings for other
|
|
languages. In practice, the functionality that it offers appears to be too
|
|
high-level for writing bindings ([GDBus](https://developer.gnome.org/glib/)
|
|
doesn't use it for this reason), and it is indeed missing a lot of
|
|
functionality to make it convenient to use directly. I've also played around
|
|
with Perl's [Net::DBus](http://search.cpan.org/perldoc?Net%3A%3ADBus) and was
|
|
highly disappointed. Not only is the documentation rather incomplete, the
|
|
actual implementation has more bugs than features. And instead of building on
|
|
top of one of the many good event loops for Perl (such as
|
|
[AnyEvent](http://search.cpan.org/perldoc?AnyEvent)), it chooses to implement
|
|
[its own event
|
|
loop](http://search.cpan.org/perldoc?Net%3A%3ADBus%3A%3AReactor). The existence
|
|
of several different libraries for Python doesn't incite much confidence,
|
|
either.
|
|
|
|
I was also disappointed in terms of the available tooling to help in the
|
|
development, testing and debugging of services. The [gdbus(1)](http://man.he.net/man1/gdbus) tool is useful
|
|
for monitoring messages and scripting some things, but is not all that
|
|
convenient because D-Bus has too many namespaces and the terrible Java-like
|
|
naming conventions make typing everything out a rather painful experience.
|
|
[D-Feet](http://live.gnome.org/DFeet/) offers a great way to explore services,
|
|
but lacks functionality for quick debugging sessions. I [made an
|
|
attempt](http://g.blicky.net/dbush.git/) to write a convenient command-line
|
|
shell, but lost interest halfway. :-(
|
|
|
|
D-Bus has the potential to be an easy and convenient IPC mechanism, but the
|
|
lack of any centralized organization to offer good implementations,
|
|
documentation and tooling makes using D-Bus a pain to use.
|
|
|
|
**Simple?** Not quite.
|
|
|
|
: D-Bus is conceptually easy and the message protocol is alright, too. Some
|
|
aspects of D-Bus, however, are rather more complex than they need to be.
|
|
|
|
I have once made an attempt to fully understand how D-Bus discovers and
|
|
connects to the session bus, but I gave up halfway because there are too
|
|
many special cases. To quickly summarize what I found, there's the
|
|
`DBUS_SESSION_BUS_ADDRESS` environment variable which could point to the
|
|
(filesystem or abstract) path of a UNIX socket or a TCP address. If that
|
|
variable isn't set, D-Bus will try to connect to your X server and get the
|
|
address from that. In order to avoid linking everything against X
|
|
libraries, a separate [dbus-launch](https://metacpan.org/pod/dbus-launch)
|
|
utility is spawned instead. Then the bus address could also be obtained
|
|
from a file in your `$HOME/.dbus/` directory, with added complexity to
|
|
still support a different session bus for each X session. I've no idea how
|
|
exactly connection initiation to the system bus works, but my impression is
|
|
that a bunch of special cases exist there, too, depending on which init
|
|
system your OS happens to use.
|
|
|
|
As if all the options in connection initiation aren't annoying enough,
|
|
there's also work on [kdbus](https://lwn.net/Articles/580194/), a Linux
|
|
kernel implementation to get better performance. Not only will kdbus use a
|
|
different underlying communication mechanism, it will also switch to a
|
|
completely different serialization format. If/when this becomes widespread
|
|
you will have to implement and support two completely different protocols
|
|
and pray that your application works with both.
|
|
|
|
On the design aspect there is, in my opinion, needless complexity with
|
|
regards to naming and namespaces. First there is a global namespace for
|
|
_bus names_, which are probably better called _application names_, because
|
|
that's usually what they represent. Then, there is a separate _object_
|
|
namespace local to each bus name. Each object has methods and properties,
|
|
and these are associated with an _interface name_, in a namespace specific
|
|
to the particular object. Despite these different namespaces, the
|
|
convention is to use a full and globally unique path for everything that
|
|
has a name. For example, to list the IM protocols that Telepathy supports,
|
|
you call the `ListProtocols` method in the
|
|
`org.freedesktop.Telepathy.ConnectionManager` interface on the
|
|
`/org/freedesktop/Telepathy/ConnectionManager` object at the
|
|
`org.freedesktop.Telepathy` bus. Fun times indeed. I can understand the
|
|
reasoning behind most of these choices, but in my opinion they found the
|
|
wrong trade-off.
|
|
|
|
Another point of complexity that annoys me is the fact that an XML format
|
|
is used to describe interfaces. Supporting XML as an IDL format is alright,
|
|
but requiring a separate format for an introspection interface gives me the
|
|
impression that the message format wasn't powerful enough for such a simple
|
|
purpose. The direct effect of this is that any application wishing to use
|
|
introspection data will have to link against an XML parser, and almost all
|
|
conforming XML parser implementations are as large as the D-Bus
|
|
implementation itself.
|
|
|
|
**Small?** Kind of.
|
|
: `libdbus-1.so.3.8.6` on my system is about 240 KiB. It doesn't cover parsing
|
|
interface descriptions or implementing a D-Bus daemon, but still covers most of
|
|
what is needed to interact with services and to offer services over D-Bus.
|
|
It's not _that_ small, but then again, libdbus-1 was not really written with
|
|
small size in mind. There's room for optimization.
|
|
|
|
**Language independent?** Sure.
|
|
: D-Bus libraries exist for a number of programming languages.
|
|
|
|
**Networked?** Half-assed.
|
|
: D-Bus _officially_ supports networked connections to a D-Bus daemon. Actually
|
|
using this, however, is painful. Convincing
|
|
[dbus-daemon(1)](http://man.he.net/man1/dbus-daemon) to accept connections
|
|
on a TCP socket involves disabling all authentication (it expects UNIX
|
|
credential passing, normally) and requires adding an undocumented
|
|
`<allow_anonymous/>` tag in the configuration (I only figured this out from
|
|
reading the source code).
|
|
|
|
Even when you've gotten that to work, there is the problem that D-Bus isn't
|
|
totally agnostic to the underlying socket protocol. D-Bus has support for
|
|
passing UNIX file descriptors over the connection, and this of course doesn't
|
|
work over TCP. While this feature is optional and easily avoided, some services
|
|
(I can't find one now) use UNIX fds in order to keep track of processes that
|
|
listen to a certain event. Obviously, those services can't be accessed over the
|
|
network.
|
|
|
|
**Secure?** Only locally.
|
|
: D-Bus has statically typed messages that can be validated automatically, so
|
|
that's a plus.
|
|
|
|
For local authentication, there is support for standard UNIX permissions and
|
|
credential passing for more fine-grained authorization. For remote
|
|
authentication, I think there is support for a shared secret cookie, but I
|
|
haven't tried to use this yet.
|
|
|
|
There is, as with MPD, no support at all for confidentiality, so using
|
|
networked D-Bus over an untrusted network would be a very bad idea anyway.
|
|
|
|
**Fast?** Mostly.
|
|
: The messaging protocol is fairly lightweight, so no problems there. I do have
|
|
to mention two potential performance issues, however.
|
|
|
|
The first issue is that the normal mode of operation in D-Bus is to proxy all
|
|
messages through an intermediate D-Bus daemon. This involves extra context
|
|
switches and message parsing passes in order to get one message from
|
|
application A to application B. I believe it is _officially_ supported to
|
|
bypass this daemon and to communicate directly between two processes, but after
|
|
my experience with networking I am wary of trying anything that isn't part of
|
|
how D-Bus is _intended_ to be used. This particular performance issue is what
|
|
kdbus addresses, so I suppose it won't apply to future Linux systems.
|
|
|
|
The other issue is that a daemon that provides a service over D-Bus does not
|
|
know whether there exists an application that is interested receiving its
|
|
notifications. This means that the daemon always has to spend resources to send
|
|
out notification messages, even if no application is actually interested in
|
|
receiving them. In practice this means that the notification mechanism is
|
|
avoided for events that may occur fairly often, and an equally inefficient
|
|
polling approach has to be used instead. It is possible for a service provider
|
|
to keep track of interested applications, but this is not part of the D-Bus
|
|
protocol and not something you would want to implement for each possible event.
|
|
I've no idea if kdbus addresses this issue, but it would be stupid not to.
|
|
|
|
**Proxy support?** Yup.
|
|
: It's part of normal operation, even.
|
|
|
|
D-Bus has many faults, some of them are by design, but many are fixable. I
|
|
would have contributed to improving the situation, but I get the feeling that
|
|
the goals of the D-Bus maintainers are not at all aligned with mine. My
|
|
impression is that the D-Bus maintainers are far too focussed on their own
|
|
specific needs and care little about projects with slightly different needs.
|
|
Especially with the introduction of kdbus, I consider D-Bus too complex now to
|
|
consider it worth the effort to improve. Starting from scratch seems less work.
|
|
|
|
# JSON/XML RPC
|
|
|
|
While I haven't extensively used JSON-RPC or XML-RPC myself, it's still an
|
|
interesting alternative to study.
|
|
[Transmission](https://www.transmissionbt.com/) uses JSON-RPC
|
|
([spec](https://trac.transmissionbt.com/browser/trunk/extras/rpc-spec.txt)) as
|
|
its primary IPC mechanism, and [RTorrent](http://rakshasa.github.io/rtorrent/)
|
|
has support for an optional XML-RPC interface. (Why do I keep referencing
|
|
torrent clients? Surely there are other interesting applications? Oh well.)
|
|
|
|
The main selling point of HTTP-based IPC is that it is accessible from
|
|
browser-based applications, assuming everything has been setup correctly. This
|
|
is a nice advantage, but lack of this support is not really a deal-breaker for
|
|
me. Browser-based applications can still use any other IPC mechanism, as long
|
|
as there are browser plugins or some form of proxy server that converts the
|
|
messages of the IPC mechanism to something that is usable over HTTP. For
|
|
example, both solutions exist for D-Bus, in the form of the [Browser DBus
|
|
Bridge](http://sandbox.movial.com/wiki/index.php/Browser_DBus_Bridge) and
|
|
[cloudeebus](https://github.com/01org/cloudeebus). Of course, such solutions
|
|
typically aren't as convenient as native HTTP support.
|
|
|
|
Since HTTP is, by design, purely request-response, JSON-RPC and XML-RPC don't
|
|
generally support asynchronous notifications. It's possible to still get
|
|
asynchronous notifications by using
|
|
[WebSockets](https://en.wikipedia.org/wiki/WebSocket) (Ugh, opaque stream
|
|
sockets, time to go back to our [custom protocol](#custom-protocol)) or by
|
|
having the client implement a HTTP server itself and send its URL to the
|
|
service provider (This is known as a
|
|
[callback](https://duckduckgo.com/?q=web%20service%20callback) in the
|
|
[SOAP](https://en.wikipedia.org/wiki/SOAP) world. I have a lot of respect for
|
|
developers who can put up with that crap). As I already hinted, neither
|
|
solution is simple or easy.
|
|
|
|
Let's move on to the usual grading...
|
|
|
|
**Easy?** Sure.
|
|
: The ubiquity of HTTP, JSON and XML on the internet means that most developers
|
|
are already familiar with using it. And even if you aren't, there are so many
|
|
easy-to-use and well-documented libraries available that you're ready to go in
|
|
a matter of minutes.
|
|
|
|
Although interface description languages/formats exist for XML-RPC (and
|
|
possibly for JSON-RPC, too), I get the impression these are not often used
|
|
outside of the SOAP world. As a result, interacting with such a service tends
|
|
be weakly/stringly typed, which, I imagine, is not as convenient in strongly
|
|
typed programming languages.
|
|
|
|
**Simple?** Not really.
|
|
: Many people have the impression that HTTP is somehow a simple protocol. Sure,
|
|
it may look simple on the wire, but in reality it is a hugely bloated and
|
|
complex protocol. I strongly encourage everyone to read through [RFC
|
|
2616](https://tools.ietf.org/html/rfc2616) at least once to get an idea of its
|
|
size and complexity. To make things worse, there's a lot of recent activity to
|
|
standardize on a next generation HTTP
|
|
([SPDY](https://en.wikipedia.org/wiki/SPDY) and [HTTP
|
|
2.0](https://en.wikipedia.org/wiki/HTTP_2.0)), but I suppose we can ignore these
|
|
developments for the foreseeable future for the use case of IPC.
|
|
|
|
Of course, a lot of the functionality specified for HTTP is optional and can be
|
|
ignored for the purpose of IPC, but that doesn't mean that these options don't
|
|
exist. When implementing a client, it would be useful to know exactly which
|
|
HTTP options the server supports. It would be wasteful to implement compression
|
|
support if the server doesn't support it, or keep-alive, or content
|
|
negotiation, or ranged requests, or authentication, or correct handling for all
|
|
response codes when the server will only ever send 'OK'. What also commonly
|
|
happens is that server implementors want to support as much as possible, to the
|
|
point that you can have JSON or XML output, depending on what the client
|
|
requested.
|
|
|
|
XML faces a similar problem. The format looks simple, but the specification has
|
|
a bunch of features that hardly anyone uses. In contrast to HTTP, however, a
|
|
correct XML parser can't just decide to not parse `<!DOCTYPE ..>` stuff,
|
|
so it _has_ to implement some of this complexity.
|
|
|
|
On the upside, JSON is a really simple serialization format, and if you're
|
|
careful enough to only implement the functionality necessary for basic HTTP, a
|
|
JSON-RPC implementation _can_ be somewhat simple.
|
|
|
|
**Small?** Not really.
|
|
: What typically happens is that implementors take an existing HTTP library and
|
|
build on top of that. A generic HTTP library likely implements a lot more than
|
|
necessary for IPC, so that's not going to be very small. RTorrent, for example,
|
|
makes use of the not-very-small [xmlrpc-c](http://xmlrpc-c.sourceforge.net/),
|
|
which in turn uses [libcurl](http://curl.haxx.se/) (400 KiB, excluding TLS
|
|
library) and either the bloated [libxml2](http://xmlsoft.org/) (1.5 MiB) or
|
|
[libexpat](http://www.libexpat.org/) (170 KiB). In any case, expect your
|
|
programs to grow by a megabyte or more if you go this route.
|
|
|
|
Transmission seems rather less bloated. It uses the HTTP library that is built
|
|
into [libevent](http://libevent.org/) (totalling ~500 KiB, but libevent is also
|
|
used for other networking parts), and a simple JSON parser can't be that large
|
|
either. I'm sure that if you reimplement everything from scratch for the
|
|
purpose of building an API, you could get something much smaller. Then again,
|
|
even if you manage to shrink the size of the server that way, you can't expect
|
|
all your users to do the same.
|
|
|
|
If HTTPS is to be supported, add ~500 KiB more. TLS isn't the simplest
|
|
protocol, either.
|
|
|
|
**Language independent?** Yes.
|
|
: Almost every language has libraries for web stuff.
|
|
|
|
**Networked?** Definitely.
|
|
: In fact, I've never seen anyone use XML/JSON RPC over UNIX sockets.
|
|
|
|
**Secure?** Alright.
|
|
: HTTP has built-in support for authentication, but it also isn't uncommon to use
|
|
some other mechanism (based on cookies, I guess?).
|
|
|
|
Confidentiality can be achieved with HTTPS. There is the problem of verifying
|
|
the certificate, since I doubt anyone is going to have certificates of their
|
|
local applications signed by a certificate authority, but there's always the
|
|
option of trust-on-first use. Custom applications can also include a
|
|
fingerprint of the server certificate in the URL for verification, but this
|
|
won't work for web apps.
|
|
|
|
**Fast?** No.
|
|
: JSON/XML RPC messages add significant overhead to the network and requires more
|
|
parsing than a simple custom solution or D-Bus. I wouldn't really call it
|
|
_fast_, but admittedly, it might still be _fast enough_ for most purposes.
|
|
|
|
**Proxy support?** Sure.
|
|
: HTTP has native support for proxying, and it's always possible to proxy some
|
|
URI on the main server to another server, assuming the libraries you use
|
|
support that. It's not necessarily simple to implement, however.
|
|
|
|
The lack of asynchronous notifications and the overhead and complexity of
|
|
JSON/XML RPC make me stay away from it, but it certainly is a solution that
|
|
many client developers will like because of its ease of use.
|
|
|
|
# Other Systems
|
|
|
|
There are a more alternatives out there than I have described so far. Most of
|
|
those were options I dismissed early on because they're either incomplete
|
|
solutions or specific to a single framework or language. I'll still mention a
|
|
few here.
|
|
|
|
## Message Queues
|
|
|
|
In the context of IPC I see that message queues such as
|
|
[RabbitMQ](https://www.rabbitmq.com/) and [ZeroMQ](http://zeromq.org/) are
|
|
quite popular. I can't say I have much experience with any of these, but these
|
|
MQs don't seem to offer a solution to the problem I described in the
|
|
introduction. My impression of MQs is that they offer a higher-level and more
|
|
powerful alternative to TCP and UDP. That is, they route messages from one
|
|
endpoint to another. The contents of the messages are still completely up to
|
|
the application, so you're still on your own in implementing an RPC mechanism
|
|
on top of that. And for the purpose of building a simple RPC mechanism, I'm
|
|
convinced that plain old UNIX sockets or TCP will do just fine.
|
|
|
|
## Cap'n Proto
|
|
|
|
I probably should be spending a full chapter on [Cap'n
|
|
Proto](http://kentonv.github.io/capnproto/) instead of this tiny little section,
|
|
but I'm simply not familiar enough with it to offer any deep insights. I can
|
|
still offer my blatantly uninformed impression of it: It looks very promising,
|
|
but puts, in my opinion, too much emphasis on performance and too little
|
|
emphasis on ease of use. It lacks introspection and requires that clients have
|
|
already obtained the schema of the service in order to interact with it. It
|
|
also uses a capability system to handle authorization, which, despite being
|
|
elegant and powerful, increases complexity and cognitive load (though I
|
|
obviously need more experience to quantify this). It still lacks
|
|
confidentiality for networked access and the number of bindings to other
|
|
programming languages is limited, but these problems can be addressed.
|
|
|
|
Cap'n Proto seems like the ideal IPC mechanism for internal communication
|
|
within a single (distributed) application and offers a bunch of unique features
|
|
not found in other RPC systems. But it doesn't feel quite right as an easy API
|
|
for others to use.
|
|
|
|
## CORBA
|
|
|
|
CORBA has been used by the GNOME project in the past, and was later abandoned
|
|
in favour of D-Bus, primarily (I think) because CORBA was deemed too [complex
|
|
and incomplete](http://dbus.freedesktop.org/doc/dbus-faq.html#corba). A system
|
|
that is deemed more complex than D-Bus is an immediate red flag. The [long and
|
|
painful history of CORBA](http://queue.acm.org/detail.cfm?id=1142044) also makes
|
|
me want to avoid it, if only because that makes it very hard to judge the
|
|
quality and modernness of existing implementations.
|
|
|
|
## Project Tanja
|
|
|
|
A bit over two years ago I was researching the same problem, but from a much
|
|
more generic angle. The result of that was a project that I called Tanja. I
|
|
described its concepts [in an earlier article](/doc/commvis), and wrote an
|
|
incomplete [specification](https://g.blicky.net/tanja.git/) along with
|
|
implementations in [C](https://g.blicky.net/tanja-c.git/),
|
|
[Go](https://g.blicky.net/tanja-go.git/) and
|
|
[Perl](https://g.blicky.net/tanja-perl.git/). I consider project Tanja a
|
|
failure, primarily because of its genericity. It supported too many
|
|
communication models and the lack of a specification as to which model was
|
|
used, and the lack of any guarantee that this model was actually followed, made
|
|
Tanja hard to use in practice. It was a very interesting experiment, but not
|
|
something I would actually use. I learned the hard way that you sometimes have
|
|
to move some complexity down into a lower abstraction layer in order to keep
|
|
the complexity in check at higher layers of abstraction.
|
|
|
|
# Conclusions
|
|
|
|
This must be the longest rant I've written so far.
|
|
|
|
In any case, there isn't really a perfect IPC mechanism for my use case. A
|
|
custom protocol involves reimplementing a lot of stuff, D-Bus is a pain, and
|
|
JSON/XML RPC are bloat.
|
|
|
|
I am still undecided on what to do. I have a lot of ideas as to what a perfect
|
|
IPC solution would look like, both in terms of features and in how to implement
|
|
it, and I feel like I have enough experience by now to actually develop a
|
|
proper solution. Unfortunately, writing a complete IPC system with the required
|
|
utilities and language bindings takes **a lot** of time and effort. It's not
|
|
really worth it if I am the only one using it.
|
|
|
|
So here is my plea to you, dear reader: If you know of any existing solutions
|
|
I've missed, please tell me. If you empathize with me and want a better
|
|
solution to this problem, please get in touch as well! I'd love to hear about
|
|
projects which face similar problems and have similar requirements.
|