<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Eli Bendersky's website - Plugins</title><link href="https://eli.thegreenplace.net/" rel="alternate"></link><link href="https://eli.thegreenplace.net/feeds/plugins.atom.xml" rel="self"></link><id>https://eli.thegreenplace.net/</id><updated>2026-06-14T03:20:58-07:00</updated><entry><title>Plugins case study: Pluggy</title><link href="https://eli.thegreenplace.net/2026/plugins-case-study-pluggy/" rel="alternate"></link><published>2026-06-13T20:21:00-07:00</published><updated>2026-06-14T03:20:58-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-06-13:/2026/plugins-case-study-pluggy/</id><summary type="html">&lt;p&gt;Recently I came upon &lt;a class="reference external" href="https://pluggy.readthedocs.io/en/latest/"&gt;Pluggy&lt;/a&gt;,
a Python library for developing plugin systems. It was originally developed
as part of the &lt;tt class="docutils literal"&gt;pytest&lt;/tt&gt; project - known for its rich plugin ecosystem - and
later extracted into a standalone library. You're supposed to reach out for
Pluggy if you want to add a plugin system …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently I came upon &lt;a class="reference external" href="https://pluggy.readthedocs.io/en/latest/"&gt;Pluggy&lt;/a&gt;,
a Python library for developing plugin systems. It was originally developed
as part of the &lt;tt class="docutils literal"&gt;pytest&lt;/tt&gt; project - known for its rich plugin ecosystem - and
later extracted into a standalone library. You're supposed to reach out for
Pluggy if you want to add a plugin system to your tool or library and want
to use something proven rather than rolling your own.&lt;/p&gt;
&lt;p&gt;In this post I will share some notes on how Pluggy works, and will
then review how it aligns with the
&lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;fundamental concepts of plugin infrastructures&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="Pluggy plug logo" class="align-center" src="https://eli.thegreenplace.net/images/2026/pluggy-plug.png" /&gt;
&lt;div class="section" id="using-pluggy"&gt;
&lt;h2&gt;Using Pluggy&lt;/h2&gt;
&lt;p&gt;Pluggy is built around the concept of &lt;em&gt;hooks&lt;/em&gt;: functions that host
applications or tools (from here on, just &amp;quot;hosts&amp;quot;) expose and plugins implement.
A host exposes hooks by using
a decorator returned from &lt;tt class="docutils literal"&gt;pluggy.HookspecMarker&lt;/tt&gt; and a plugin implements this
hook using a decorator returned from &lt;tt class="docutils literal"&gt;pluggy.HookimplMarker&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Pluggy's &lt;a class="reference external" href="https://pluggy.readthedocs.io/en/stable/"&gt;documentation&lt;/a&gt; explains
this fairly well; in this post, I'll show how to implement the &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt; tool
with some plugins, introduced in &lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;the original article in my plugin series&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a reminder, &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt; is a toy tool that takes markup notation similar to
reStructuredText, and converts it to to HTML. It supports plugins to handle
custom &amp;quot;roles&amp;quot; like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;some text :role:`customized text` and more text
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As well as plugins that do arbitrary processing on the entire text.&lt;/p&gt;
&lt;div class="section" id="defining-hooks"&gt;
&lt;h3&gt;Defining hooks&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2026/plugin-pluggy/htmlize/htmlize"&gt;Out host&lt;/a&gt; defines two hooks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pluggy&lt;/span&gt;

&lt;span class="n"&gt;hookspec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pluggy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HookspecMarker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;htmlize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@hookspec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstresult&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;htmlize_role_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Return a function accepting role contents.&lt;/span&gt;

&lt;span class="sd"&gt;    The function will be called with a single argument - the role contents, and&lt;/span&gt;
&lt;span class="sd"&gt;    should return what the role gets replaced with.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="nd"&gt;@hookspec&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;htmlize_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Return a function accepting full document contents.&lt;/span&gt;

&lt;span class="sd"&gt;    The function will be called with a single argument - the document contents&lt;/span&gt;
&lt;span class="sd"&gt;    (after paragraph splitting and role processing), and should return the&lt;/span&gt;
&lt;span class="sd"&gt;    transformed contents.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A hook is created by calling &lt;tt class="docutils literal"&gt;HookspecMarker&lt;/tt&gt; with the project's name. This
project name has to match between the host and its plugins. Pluggy is permissive
about what hooks accept as parameters and what they return; for maximal
flexibility and to stay true to the original &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt; example, our hooks
return functions.&lt;/p&gt;
&lt;p&gt;To accompany this &lt;tt class="docutils literal"&gt;HookspecMarker&lt;/tt&gt;, the host also defines a &lt;tt class="docutils literal"&gt;HookimplMarker&lt;/tt&gt; with
the same name:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;hookimpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pluggy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HookimplMarker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;htmlize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is used by plugins to attach to hooks when they're loaded.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="loading-plugins-in-the-host"&gt;
&lt;h3&gt;Loading plugins in the host&lt;/h3&gt;
&lt;p&gt;The host's main function loads plugins at startup as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pluggy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;htmlize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_hookspecs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hookspecs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_setuptools_entrypoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;htmlize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;hookspecs&lt;/tt&gt; is our Python module containing the hooks shown above.
&lt;tt class="docutils literal"&gt;load_setuptools_entrypoints&lt;/tt&gt; is Pluggy's helper for loading plugins that
were &lt;tt class="docutils literal"&gt;pip&lt;/tt&gt;-installed into the same environment and registered as
setuptools &lt;a class="reference external" href="https://setuptools.pypa.io/en/latest/userguide/entry_point.html"&gt;entry points&lt;/a&gt;.
It's a way to signal - in one's &lt;tt class="docutils literal"&gt;setup.py&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt; file - some
metadata that projects can review at runtime. In our project, the plugins
register themselves with this section in the &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[project.entry-points.htmlize]
tt = &amp;quot;tt&amp;quot;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This says &amp;quot;for entry point &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt;, define a new entry named &lt;tt class="docutils literal"&gt;tt&lt;/tt&gt;&amp;quot;.
Pluggy's &lt;tt class="docutils literal"&gt;load_setuptools_entrypoints&lt;/tt&gt; then uses &lt;a class="reference external" href="https://docs.python.org/3/library/importlib.metadata.html"&gt;importlib.metadata&lt;/a&gt;
to access this information.&lt;/p&gt;
&lt;p&gt;Note that Pluggy doesn't require using this mechanism. Hosts can implement any
plugin discovery method they want, and add plugins directly to their
&lt;tt class="docutils literal"&gt;PluginManager&lt;/tt&gt; with the &lt;tt class="docutils literal"&gt;register&lt;/tt&gt; method. But this is the mechanism used
for &lt;tt class="docutils literal"&gt;pytest&lt;/tt&gt; and many other projects; it makes it very easy to
automatically discover and register plugins that are installed with &lt;tt class="docutils literal"&gt;pip&lt;/tt&gt; and
equivalent tools.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="invoking-plugins"&gt;
&lt;h3&gt;Invoking plugins&lt;/h3&gt;
&lt;p&gt;Once &lt;tt class="docutils literal"&gt;PluginManager&lt;/tt&gt; loads the plugins, invoking them is straightforward;
here's how &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt; invokes the contents hooks &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Build full contents back again, and ask plugins to act on&lt;/span&gt;
&lt;span class="c1"&gt;# contents.&lt;/span&gt;
&lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;plugin_manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;htmlize_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Generally, hook invocations return a &lt;em&gt;list&lt;/em&gt; of all the hooks attached to by
different plugins (a single host application can have multiple plugins installed
and attaching to the same hook). When the host invokes the hook as shown above,
the default order is LIFO, but plugins can affect this with
&lt;a class="reference external" href="https://pluggy.readthedocs.io/en/stable/#call-time-order"&gt;hook options&lt;/a&gt;
like &lt;tt class="docutils literal"&gt;tryfirst&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;trylast&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="implementing-hooks-in-plugins"&gt;
&lt;h3&gt;Implementing hooks in plugins&lt;/h3&gt;
&lt;p&gt;Here's our entire &lt;tt class="docutils literal"&gt;narcissist&lt;/tt&gt; plugin that's attaching to the contents hook:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;htmlize&lt;/span&gt;

&lt;span class="nd"&gt;@htmlize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hookimpl&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;htmlize_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;repl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;b&amp;gt;I (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;)&amp;lt;/b&amp;gt;&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\bI\b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Some notes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;It expects &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt; to be installed; as discussed previously, we rely on
Pluggy's default install-based approach where both the host and plugins are
installed into the same Python environment and can thus find each other.
However, Pluggy supports any custom discovery method.&lt;/li&gt;
&lt;li&gt;It uses the &lt;tt class="docutils literal"&gt;hookimpl&lt;/tt&gt; exported value shown earlier.&lt;/li&gt;
&lt;li&gt;It returns a function that acts on contents; this is the &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt;-specific
contract (ABI, if you will) we've discussed before.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="fundamental-plugin-concepts-in-this-case-study"&gt;
&lt;h2&gt;Fundamental plugin concepts in this case study&lt;/h2&gt;
&lt;p&gt;Let's see how this case study of Pluggy measures against the
&lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;Fundamental plugin concepts&lt;/a&gt;
that were covered &lt;a class="reference external" href="https://eli.thegreenplace.net/tag/plugins"&gt;several times on this blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It's important to remember that Pluggy is not a specific host application with
a bespoke plugin system; rather, it's a reusable library for creating such
plugin systems. Therefore, this is more of a &lt;em&gt;meta&lt;/em&gt; case study.&lt;/p&gt;
&lt;div class="section" id="discovery"&gt;
&lt;h3&gt;Discovery&lt;/h3&gt;
&lt;p&gt;Generally, Pluggy leaves discovery logic to the user's discretion. Its
&lt;tt class="docutils literal"&gt;PluginManager&lt;/tt&gt; has a &lt;tt class="docutils literal"&gt;register&lt;/tt&gt; method for adding plugins, and these can
be discovered in any way the application chooses.&lt;/p&gt;
&lt;p&gt;That said, Pluggy comes with one discovery mechanism built in - through the
entry points process of Python packaging, as shown above. This is hugely
convenient for a large number of applications, as long as both the application
and its plugins are installed via standard Python packaging tools (which is
a very reasonable assumption in the Python ecosystem).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="registration"&gt;
&lt;h3&gt;Registration&lt;/h3&gt;
&lt;p&gt;In the entry point process, plugins register themselves by adding a
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;[project.entry-points.&amp;lt;HOST-ID&amp;gt;]&lt;/span&gt;&lt;/tt&gt; section in their &lt;tt class="docutils literal"&gt;pyproject.toml&lt;/tt&gt;
file.&lt;/p&gt;
&lt;p&gt;Otherwise - as in the previous section - users are free to devise their own
registration schemes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="hooks"&gt;
&lt;h3&gt;Hooks&lt;/h3&gt;
&lt;p&gt;This one is easy, since it's called &lt;em&gt;hooks&lt;/em&gt; in Pluggy parlance as well!
Pluggy's implementation of hooks is rather elegant, with function decorators
available for plugins to set. We've seen an example of this above with
&lt;tt class="docutils literal"&gt;&amp;#64;htmlize.hookimpl&lt;/tt&gt; decorating &lt;tt class="docutils literal"&gt;htmlize_contents&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="exposing-an-application-api-to-plugins"&gt;
&lt;h3&gt;Exposing an application API to plugins&lt;/h3&gt;
&lt;p&gt;Since Pluggy is designed for Python hosts and Python plugins, this one is
fairly straightforward. The plugins typically assume the host project is
already installed in the Python environment and its modules can be imported.&lt;/p&gt;
&lt;p&gt;In our example, &lt;tt class="docutils literal"&gt;hookimpl&lt;/tt&gt; is imported from &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt; by the plugin to
accomplish this. It also shows how host data is passed to the plugin - the
&lt;tt class="docutils literal"&gt;post&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;db&lt;/tt&gt; parameters. These are APIs exposed by the host for the
plugins' use.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion-is-pluggy-worth-it"&gt;
&lt;h2&gt;Conclusion - is Pluggy worth it?&lt;/h2&gt;
&lt;p&gt;In footnote 2 of my original &lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;fundamental concepts of plugin infrastructures&lt;/a&gt;
post, I wrote &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
This is probably why there are very few well-established plugin frameworks
in existence (even in low-level languages like C or C++). It's too easy (and
tempting) to roll your own.&lt;/blockquote&gt;
&lt;p&gt;I still believe my statement is true - plugin frameworks are very easy
to create, and the functionality they provide is relatively small compared to
their large surface area. In other words, this is a &lt;em&gt;shallow API&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That said, Pluggy does provide some nice functionality for the more advanced
uses of plugins:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Automatic entry point registration mechanism - if you need it&lt;/li&gt;
&lt;li&gt;Signature validation&lt;/li&gt;
&lt;li&gt;Consistent plugin result collection across multiple hook attachments in a
single plugin and across many plugins&lt;/li&gt;
&lt;li&gt;Plugin ordering with &lt;tt class="docutils literal"&gt;firstresult&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;tryfirst&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;trylast&lt;/tt&gt;, etc.&lt;/li&gt;
&lt;li&gt;Hook &amp;quot;wrappers&amp;quot; for some special use cases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Are these worthwhile for your project? It really depends on the project,
and it's always worth keeping the &lt;a class="reference external" href="https://eli.thegreenplace.net/2017/benefits-of-dependencies-in-software-projects-as-a-function-of-effort/"&gt;tradeoff between dependencies and project
effort&lt;/a&gt;
in mind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="code"&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;The full code repository for this post &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2026/plugin-pluggy"&gt;is available here&lt;/a&gt;.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Here &lt;tt class="docutils literal"&gt;plugin_manager&lt;/tt&gt; is the value previously returned from
&lt;tt class="docutils literal"&gt;pluggy.PluginManager&lt;/tt&gt;; in the previous code snippet it's saved into
&lt;tt class="docutils literal"&gt;pm&lt;/tt&gt; - the different variable name is because a function call is made
and &lt;tt class="docutils literal"&gt;plugin_manager&lt;/tt&gt; is the parameter name.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;To be fair, that post predates the creation of Pluggy!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Plugins"></category><category term="Python"></category></entry><entry><title>Plugins case study: mdBook preprocessors</title><link href="https://eli.thegreenplace.net/2025/plugins-case-study-mdbook-preprocessors/" rel="alternate"></link><published>2025-12-17T18:11:00-08:00</published><updated>2026-06-07T00:38:58-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2025-12-17:/2025/plugins-case-study-mdbook-preprocessors/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://rust-lang.github.io/mdBook/index.html"&gt;mdBook&lt;/a&gt; is a tool for easily
creating books out of Markdown files. It's very popular in the Rust ecosystem,
where it's used (among other things) to publish &lt;a class="reference external" href="https://doc.rust-lang.org/book/"&gt;the official Rust book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;mdBook has a simple yet effective plugin mechanism that can be used to modify
the book output in arbitrary …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://rust-lang.github.io/mdBook/index.html"&gt;mdBook&lt;/a&gt; is a tool for easily
creating books out of Markdown files. It's very popular in the Rust ecosystem,
where it's used (among other things) to publish &lt;a class="reference external" href="https://doc.rust-lang.org/book/"&gt;the official Rust book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;mdBook has a simple yet effective plugin mechanism that can be used to modify
the book output in arbitrary ways, using any programming language or tool. This
post describes the mechanism and how it aligns with the
&lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;fundamental concepts of plugin infrastructures&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="mdbook-preprocessors"&gt;
&lt;h2&gt;mdBook preprocessors&lt;/h2&gt;
&lt;p&gt;mdBook's architecture is pretty simple: your contents go into a directory tree
of Markdown files. mdBook then renders these into a book, with one file per
chapter. The book's output is HTML by default, but mdBook supports other
outputs like PDF.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://rust-lang.github.io/mdBook/for_developers/preprocessors.html"&gt;preprocessor mechanism&lt;/a&gt;
lets us register an arbitrary program that runs on the book's source after
it's loaded from Markdown files; this program can modify the book's contents in
any way it wishes before it all gets sent to the renderer for generating output.&lt;/p&gt;
&lt;img alt="Preprocessor flow for mdbook" class="align-center" src="https://eli.thegreenplace.net/images/2025/mdbook-preprocessor.png" /&gt;
&lt;p&gt;The official documentation &lt;a class="reference external" href="https://rust-lang.github.io/mdBook/for_developers/preprocessors.html#hooking-into-mdbook"&gt;explains this process very well&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sample-plugin"&gt;
&lt;h2&gt;Sample plugin&lt;/h2&gt;
&lt;p&gt;I rewrote &lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;my classical &amp;quot;nacrissist&amp;quot; plugin&lt;/a&gt;
for mdBook; the code is &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2025/plugin-mdbook"&gt;available here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In fact, there are two renditions of the same plugin there:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;One in Python, to demonstrate how mdBook can invoke preprocessors written in
any programming language.&lt;/li&gt;
&lt;li&gt;One in Rust, to demonstrate how mdBook exposes an application API to plugins
written in Rust (since mdBook is itself written in Rust).&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="fundamental-plugin-concepts-in-this-case-study"&gt;
&lt;h2&gt;Fundamental plugin concepts in this case study&lt;/h2&gt;
&lt;p&gt;Let's see how this case study of mdBook preprocessors measures against the
&lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;Fundamental plugin concepts&lt;/a&gt;
that were covered &lt;a class="reference external" href="https://eli.thegreenplace.net/tag/plugins"&gt;several times on this blog&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="discovery"&gt;
&lt;h3&gt;Discovery&lt;/h3&gt;
&lt;p&gt;Discovery in mdBook is very explicit. For every plugin we want mdBook to use,
it has to be listed in the project's &lt;tt class="docutils literal"&gt;book.toml&lt;/tt&gt; configuration file. For
example, in &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2025/plugin-mdbook"&gt;the code sample for this post&lt;/a&gt;, the Python narcissist plugin
is noted in &lt;tt class="docutils literal"&gt;book.toml&lt;/tt&gt; as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[preprocessor.narcissistpy]
command = &amp;quot;python3 ../preprocessor-python-narcissist/narcissist.py&amp;quot;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each preprocessor is a command for &lt;tt class="docutils literal"&gt;mdBook&lt;/tt&gt; to execute in a sub-process.
Here it uses Python, but it can be anything else that can be validly executed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="registration"&gt;
&lt;h3&gt;Registration&lt;/h3&gt;
&lt;p&gt;For the purpose of registration, &lt;tt class="docutils literal"&gt;mdBook&lt;/tt&gt; actually invokes the plugin command
&lt;em&gt;twice&lt;/em&gt;. The first time, it passes the arguments &lt;tt class="docutils literal"&gt;supports &amp;lt;renderer&amp;gt;&lt;/tt&gt; where
&lt;tt class="docutils literal"&gt;&amp;lt;renderer&amp;gt;&lt;/tt&gt; is the name of the renderer (e.g. &lt;tt class="docutils literal"&gt;html&lt;/tt&gt;). If the command
returns 0, it means the preprocessor supports this renderer; otherwise, it
doesn't.&lt;/p&gt;
&lt;p&gt;In the second invocation, &lt;tt class="docutils literal"&gt;mdBook&lt;/tt&gt; passes some metadata plus the entire book
in JSON format to the preprocessor through stdin, and expects the preprocessor
to return the modified book as JSON to stdout (using the same schema).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="hooks"&gt;
&lt;h3&gt;Hooks&lt;/h3&gt;
&lt;p&gt;In terms of hooks, &lt;tt class="docutils literal"&gt;mdBook&lt;/tt&gt; takes a very coarse-grained approach. The
preprocessor gets the &lt;em&gt;entire book&lt;/em&gt; in a single JSON object (along with a
context object that contains metadata), and is expected to emit the entire
modified book in a single JSON object.
It's up to the preprocessor to figure out which parts of the book to read and
which parts to modify.&lt;/p&gt;
&lt;p&gt;Given that books and other documentation typically have limited sizes, this
is a reasonable design choice. Even tens of MiB of JSON-encoded data are very
quick to pass between sub-processes via stdout and marshal/unmarshal. But we
wouldn't be able to implement Wikipedia using this design.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="exposing-an-application-api-to-plugins"&gt;
&lt;h3&gt;Exposing an application API to plugins&lt;/h3&gt;
&lt;p&gt;This is tricky, given that the preprocessor mechanism is language-agnostic.
Here, &lt;tt class="docutils literal"&gt;mdBook&lt;/tt&gt; only offers additional utilities to preprocessors implemented
in Rust. These get access to &lt;tt class="docutils literal"&gt;mdBook&lt;/tt&gt;'s API to unmarshal the JSON
representing the context metadata and book's contents. &lt;tt class="docutils literal"&gt;mdBook&lt;/tt&gt; offers the
&lt;a class="reference external" href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/trait.Preprocessor.html"&gt;Preprocessor trait&lt;/a&gt;
Rust preprocessors can implement, which makes it easier to wrangle the book's
contents. See &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2025/plugin-mdbook/preprocessor-rust-narcissist"&gt;my Rust version of the narcissist preprocessor&lt;/a&gt;
for a basic example of this.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="renderers-backends"&gt;
&lt;h2&gt;Renderers / backends&lt;/h2&gt;
&lt;p&gt;Actually, &lt;tt class="docutils literal"&gt;mdBook&lt;/tt&gt; has &lt;em&gt;another&lt;/em&gt; plugin mechanism, but it's very similar
conceptually to preprocessors. A &lt;em&gt;renderer&lt;/em&gt; (also called a &lt;em&gt;backend&lt;/em&gt; in some
of &lt;tt class="docutils literal"&gt;mdBook&lt;/tt&gt;'s own doc pages) takes the same input as a preprocessor, but is
free to do whatever it wants with it. The default renderer emits the HTML
for the book; &lt;a class="reference external" href="https://github.com/rust-lang/mdBook/wiki/Third-party-plugins#backends"&gt;other renderers&lt;/a&gt;
can do other things.&lt;/p&gt;
&lt;p&gt;The idea is that the book can go through multiple preprocessors, but at the
end a &lt;em&gt;single&lt;/em&gt; renderer.&lt;/p&gt;
&lt;p&gt;The data a renderer receives is exactly the same as a preprocessor - JSON
encoded book contents. Due to this similarity, there's no real point getting
deeper into renderers in this post.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Plugins"></category><category term="Python"></category><category term="Rust"></category></entry><entry><title>Plugins case study: Envoy WASM extensions</title><link href="https://eli.thegreenplace.net/2023/plugins-case-study-envoy-wasm-extensions/" rel="alternate"></link><published>2023-06-03T15:14:00-07:00</published><updated>2024-02-29T13:32:33-08:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2023-06-03:/2023/plugins-case-study-envoy-wasm-extensions/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.envoyproxy.io/"&gt;Envoy&lt;/a&gt; is a configurable proxy that serves
a prominent role in modern cloud-native projects; for example, it's
used in many k8s deployments to provide inter-service communication (through
Istio). In this post, I'd like to describe Envoy's extension mechanism as a case
study of using WebAssembly for plugins.&lt;/p&gt;
&lt;div class="section" id="some-background"&gt;
&lt;h2&gt;Some background&lt;/h2&gt;
&lt;p&gt;Imagine …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.envoyproxy.io/"&gt;Envoy&lt;/a&gt; is a configurable proxy that serves
a prominent role in modern cloud-native projects; for example, it's
used in many k8s deployments to provide inter-service communication (through
Istio). In this post, I'd like to describe Envoy's extension mechanism as a case
study of using WebAssembly for plugins.&lt;/p&gt;
&lt;div class="section" id="some-background"&gt;
&lt;h2&gt;Some background&lt;/h2&gt;
&lt;p&gt;Imagine a complex service-based infrastructure, in which service A has to
communicate with service B. This is typically done via HTTP/REST or some RPC
mechanism, but there are a lot of complex networking details to deal with:
service discovery (I just want to send a message to an instance of service B,
but which address/port is it on?), load balancing, retries, etc. Instead of
having each service deal with this complexity, we can set up Envoy to run as a
sidecar, and ask it to handle all of it. Then the actual services can focus on
our business logic.&lt;/p&gt;
&lt;p&gt;Here's a handy diagram from &lt;a class="reference external" href="https://istio.io"&gt;https://istio.io&lt;/a&gt; that demonstrates this (Envoy is
the &lt;em&gt;Proxy&lt;/em&gt; boxes):&lt;/p&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/2023/envoyarch.svg" style="width: 600px;" type="image/svg+xml"&gt;System diagram of envoy proxies communicating for microservices&lt;/object&gt;
&lt;p&gt;As expected - for such a sophisticated piece of software - Envoy users
frequently need to customize it in various ways for their projects. For example,
we may want to define &lt;a class="reference external" href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/listeners/listener_filters#arch-overview-network-filters"&gt;custom filters&lt;/a&gt;;
this is a kind of middleware.&lt;/p&gt;
&lt;p&gt;Envoy's original approach to extensions was to support writing C++ to link
custom filters with Envoy itself. This, of course, is awkward for many reasons -
such as having to distribute your own Envoy binaries instead of using the
standard ones. Also, the filter API was not really designed to be stable so
keeping up with changes was an issue; and finally, few people like writing C++
these days.&lt;/p&gt;
&lt;p&gt;So the Envoy team came up with an alternative approach:
&lt;a class="reference external" href="https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter"&gt;Lua extensions&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="Lua programming language logo" class="align-center" src="https://eli.thegreenplace.net/images/2023/lua-logo.png" style="width: 200px;" /&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Lua_(programming_language)"&gt;Lua programming language&lt;/a&gt; was designed
for extensions and plugins; it's a small and simple language, and its
implementation is also small and simple - making it easy to embed. You can write
some Lua code either directly in your configuration file or a separate file it
points to, and there's an API exposed to Lua that the extension can interact
with.&lt;/p&gt;
&lt;p&gt;The Lua extension method is fully supported in Envoy and is currently in
a &lt;em&gt;stable&lt;/em&gt; state, but some folks weren't too keen on learning yet another
programming language just for the sake of writing filters for their proxy. Lua
is not particularly prominent in the Cloud world (which is mostly dominated
by Go, Python, Java and some other languages). Therefore, the Envoy maintainers
have created yet another way to extend it &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt; - with WebAssembly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="wasm-extensions"&gt;
&lt;h2&gt;WASM extensions&lt;/h2&gt;
&lt;p&gt;WASM extensions are still experimental in Envoy at the time of writing, but
it's an intriguing approach and the main subject of this post. WASM elegantly
solves the problems of the other extension methods as follows:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The WASM extension is compiled into a &lt;tt class="docutils literal"&gt;.wasm&lt;/tt&gt; file that the Envoy config
can point at, and is loaded dynamically at runtime. It doesn't require
recompiling and distributing a custom version of Envoy.&lt;/li&gt;
&lt;li&gt;The extension can use any programming language that compiles down to WASM,
and that covers &lt;em&gt;a lot&lt;/em&gt; of languages these days.
Your entire service infrastructure is written in Go and you don't want to
wrangle C++ or learn Lua just for the proxy filters? No problem - Go compiles
to WASM and there's even &lt;a class="reference external" href="https://github.com/tetratelabs/proxy-wasm-go-sdk"&gt;an SDK&lt;/a&gt;
to help writing Envoy filters in it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To this end, Envoy embeds &lt;a class="reference external" href="https://v8.dev/"&gt;v8&lt;/a&gt; as a WASM VM. All that
remains is to define the interface between these WASM extension modules and
Envoy itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-proxy-wasm-abi"&gt;
&lt;h2&gt;The Proxy-Wasm ABI&lt;/h2&gt;
&lt;p&gt;WebAssembly itself defines:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A bytecode format (with an equivalent &lt;a class="reference external" href="https://eli.thegreenplace.net/2023/webassembly-text-code-samples/"&gt;text format&lt;/a&gt;)
and its execution semantics&lt;/li&gt;
&lt;li&gt;A way for WASM modules to export functions and data to the host environment&lt;/li&gt;
&lt;li&gt;A way for WASM modules to import functions and data from the host environment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that's about it. Everything else is left to the specific system implementer
to figure out. Moreover, the data types WASM supports are very limited -
essentially fixed-width integers and floats; users are expected to build
their own higher-level data structures on top of these using addresses into
WASM's linear heap memory, if needed.&lt;/p&gt;
&lt;p&gt;In a &lt;a class="reference external" href="https://eli.thegreenplace.net/2023/faas-in-go-with-wasm-wasi-and-rust/"&gt;previous post&lt;/a&gt; I've
talked about WASI - an API and ABI that enables OS-like functionality in WASM
code. While WASI is useful for exposing WASM modules to the outside world in
a vetted way, it's somewhat limited for complex host-wasm interactions, because
at the moment the only way for this to happen is via interfaces like
stdin/stdout &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Therefore, systems that require sophisticated interactions between the host
and WASM extensions are left to define their own interfaces. Which is exactly
what the Envoy developers ended up creating: the &lt;a class="reference external" href="https://github.com/proxy-wasm/spec"&gt;Proxy-Wasm
ABI&lt;/a&gt; &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The ABI is fairly low level, and it has two parts. One is
&lt;strong&gt;Functions implemented in the WASM module&lt;/strong&gt;. These are functions exported from
WASM (the custom extension) and imported by the host (Envoy or another proxy).
For example, &lt;tt class="docutils literal"&gt;proxy_on_request_headers&lt;/tt&gt; is exported by the WASM module as
a callback to handle headers for HTTP requests sailing through the proxy.&lt;/p&gt;
&lt;p&gt;This is the signature of &lt;tt class="docutils literal"&gt;proxy_on_request_headers&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;params:
    i32 (uint32_t) context_id
    i32 (size_t) num_headers
    i32 (bool) end_of_stream
returns:
    i32 (proxy_action_t) next_action
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The import is done in the &lt;a class="reference external" href="https://github.com/proxy-wasm/proxy-wasm-cpp-host/"&gt;proxy-wasm-cpp-host&lt;/a&gt; project
which is a dependency of Envoy. This project implements the host side of
Proxy-wasm for C++ hosts.&lt;/p&gt;
&lt;p&gt;What should the extension do within &lt;tt class="docutils literal"&gt;proxy_on_request_headers&lt;/tt&gt;, though? It
can do things like ask Envoy about the actual HTTP headers it sees with
&lt;tt class="docutils literal"&gt;proxy_get_header_map_value&lt;/tt&gt;. This is in the second part of the ABI,
&lt;strong&gt;Functions implemented in the host environment&lt;/strong&gt;. Its signature is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;params:
    i32 (proxy_map_type_t) map_type
    i32 (const char*) key_data
    i32 (size_t) key_size
    i32 (const char**) return_value_data
    i32 (size_t*) return_value_size
returns:
    i32 (proxy_result_t) call_result
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see this is &lt;em&gt;a very low level ABI&lt;/em&gt;; all parameters are either
pointers (addresses in WASM's linear memory) or constants of predefined types.
Since WASM severely restricts the types of function parameters and return
values, and both the WASM module and the host can be implemented in very diverse
programming languages, there's not much choice here. Writing the glue code on
the WASM-host interface is tedious and low-level.&lt;/p&gt;
&lt;p&gt;This is where the high-level SDKs come in.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-go-sdk-for-proxy-wasm"&gt;
&lt;h2&gt;The Go SDK for Proxy-wasm&lt;/h2&gt;
&lt;p&gt;Suppose we're writing our Envoy extension module in Go (a reasonable choice
given the dominance of Go in the Cloud Native / k8s / Istio ecosystem). It
seems like hooking up a simple extension to snoop on all the HTTP traffic
going through the proxy and logging the HTTP headers is quite a bit of work.&lt;/p&gt;
&lt;p&gt;Luckily, the good folks at Tetrate created the &lt;a class="reference external" href="https://github.com/tetratelabs/proxy-wasm-go-sdk/"&gt;Go SDK for Proxy-Wasm&lt;/a&gt;. This SDK handles all
the Proxy-Wasm ABI mechanics and presents a clean, pure Go API to extension
writers that won't have to worry about low level WASM details.&lt;/p&gt;
&lt;p&gt;Here's how the task of &amp;quot;snoop on HTTP traffic and log headers&amp;quot;
&lt;a class="reference external" href="https://github.com/tetratelabs/proxy-wasm-go-sdk/blob/main/examples/http_headers/main.go"&gt;looks using the Go SDK&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;httpHeaders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OnHttpRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numHeaders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;endOfStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;proxywasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetHttpRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;proxywasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogCriticalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;failed to get request headers: %v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;proxywasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogInfof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;request header --&amp;gt; %s: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ActionContinue&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's explore how both sides of the ABI (host-implemented and
module-implemented) are handled by the Go SDK. Starting with the WASM-calls-host
side, this is &lt;tt class="docutils literal"&gt;proxywasm.GetHttpRequestHeaders&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GetHttpRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MapTypeHttpRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It's just a wrapper around a more general &lt;tt class="docutils literal"&gt;getMap&lt;/tt&gt; function with a map type
that the ABI defines. The return type is a slice of 2-element arrays
(key/value).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mapType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MapType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rvs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;st&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ProxyGetHeaderMapPairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mapType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;rvs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;st&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusOK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusToError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;st&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ErrorStatusNotFound&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;bs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RawBytePtrToByteSlice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rvs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DeserializeMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;internal.ProxyGetHeaderMapPairs&lt;/tt&gt; is actually an ABI-defined function that's
imported from the host (as &lt;tt class="docutils literal"&gt;proxy_get_header_map_pairs&lt;/tt&gt;). It writes raw
pointers to its output parameters, so the rest of &lt;tt class="docutils literal"&gt;getMap&lt;/tt&gt; deals with
converting those into Go data types.&lt;/p&gt;
&lt;p&gt;On the host side, &lt;tt class="docutils literal"&gt;proxy_get_header_map_pairs&lt;/tt&gt; is mapped to a
C++ function &lt;a class="reference external" href="https://github.com/proxy-wasm/proxy-wasm-cpp-host/blob/e1fe5e99eedfb517bea92aee3f13f442d4bfa3b4/src/exports.cc#L1"&gt;in this file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now the host-calls-WASM side. The Go SDK has the following function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;//export proxy_on_request_headers&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;proxyOnRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contextID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;numHeaders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;endOfStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recordTiming&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logTiming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;proxyOnRequestHeaders&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;httpContexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;contextID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;invalid context on proxy_on_request_headers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setActiveContextID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contextID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OnHttpRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numHeaders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;endOfStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note the &lt;tt class="docutils literal"&gt;//export&lt;/tt&gt; annotation that tells the compiler to export this function
from the WASM module. To be clear, the entire SDK - along with our custom code -
gets compiled into a &lt;tt class="docutils literal"&gt;.wasm&lt;/tt&gt; file that the host loads, and the &lt;tt class="docutils literal"&gt;//export&lt;/tt&gt;
tag makes the Go compiler place this function in the WASM function export
table that the host has access to.&lt;/p&gt;
&lt;p&gt;Once the host invokes it, it calls the &lt;tt class="docutils literal"&gt;OnHttpRequestHeaders&lt;/tt&gt; method on the
context, which is user-defined as shown above. Hopefully this example gives a
taste of what the SDK does for us - it provides a higher-level,
language-idiomatic API on top of a low-level, language-agnostic ABI.&lt;/p&gt;
&lt;p&gt;The Go SDK is just an example; there are other SDKs that exist for developing
WASM extensions for Envoy - for example in Rust or in C++.&lt;/p&gt;
&lt;p&gt;One small wrinkle in this story is that the Go SDK only supports the &lt;a class="reference external" href="https://tinygo.org/"&gt;TinyGo&lt;/a&gt; compiler at this time, not the default Go toolchain.
This is because the default toolchain doesn't have sufficient WASM support yet,
but this situation is changing; in Go 1.21 it has added WASI support and work
is ongoing on additional features that should make it possible to develop
Envoy extensions using the standard toolchain.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="fundamental-plugin-concepts-in-this-case-study"&gt;
&lt;h2&gt;Fundamental plugin concepts in this case study&lt;/h2&gt;
&lt;p&gt;Let's see how this case study of Envoy extensions with WASM measures against the
&lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;Fundamental plugin concepts&lt;/a&gt;
that were covered &lt;a class="reference external" href="https://eli.thegreenplace.net/tag/plugins"&gt;several times on this blog&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="discovery"&gt;
&lt;h3&gt;Discovery&lt;/h3&gt;
&lt;p&gt;Envoy &amp;quot;discovers&amp;quot; available extensions trivially, because they have to be
explicitly specified in its configuration file. The config file lists the
extensions and where to find them; for WASM, this could be either a local
&lt;tt class="docutils literal"&gt;.wasm&lt;/tt&gt; file or a URL pointing to a file stored remotely (e.g. some
cloud storage bucket).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="registration"&gt;
&lt;h3&gt;Registration&lt;/h3&gt;
&lt;p&gt;The WASM extension registers functionality with Envoy by exporting certain
functions from the WASM module. When Envoy loads an extension, it scans the list
of exported functions for known names. For example, if the extension exports
&lt;tt class="docutils literal"&gt;proxy_on_request_headers&lt;/tt&gt;, Envoy will call it for HTTP headers. If the
extension doesn't export such a function, Envoy will assume it's not interested
in this particular callback.&lt;/p&gt;
&lt;p&gt;Another interesting example of how this functionality is used is the
&lt;tt class="docutils literal"&gt;proxy_abi_version_X_Y_Z&lt;/tt&gt; function. An extension will
export this function with an actual ABI version replacing X, Y an Z. Envoy
will look for a function with the &lt;tt class="docutils literal"&gt;proxy_abi_version_*&lt;/tt&gt; prefix, and from its
name will determine which version of the ABI the WASM module was compiled
against.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="hooks"&gt;
&lt;h3&gt;Hooks&lt;/h3&gt;
&lt;p&gt;This is mostly covered in the previous section. There are multiple callbacks
a WASM extension can register by exporting them from the WASM module;
&lt;tt class="docutils literal"&gt;proxy_on_request_headers&lt;/tt&gt; is one example out of many defined in the ABI.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="exposing-an-application-api-to-plugins"&gt;
&lt;h3&gt;Exposing an application API to plugins&lt;/h3&gt;
&lt;p&gt;This is the &lt;strong&gt;Functions implemented in the host environment&lt;/strong&gt; part of the
Proxy-Wasm ABI; we've seen an example of one of them -
&lt;tt class="docutils literal"&gt;proxy_get_header_map_pairs&lt;/tt&gt;. The ABI defines others, like &lt;tt class="docutils literal"&gt;proxy_log&lt;/tt&gt; for
emitting log messages to Envoy's log. These functions let extensions call
into Envoy.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As you can see from the string of posts this year, I'm pretty excited about
the non-browser uses of WASM, particularly in the area of plugins.
The &lt;a class="reference external" href="https://eli.thegreenplace.net/2023/faas-in-go-with-wasm-wasi-and-rust/"&gt;FAAS post&lt;/a&gt; presented one interesting
possibility - using the current (limited but functional) WASI for the
host/plugin interface.&lt;/p&gt;
&lt;p&gt;What this post shows is a case study of a much more advanced extension system;
the capabilities and performance requirements of custom network filter plugins
are just way beyond what WASI can provide, so the Envoy developers ended up
creating their own ABI. It's fascinating to study how such an ABI affects plugin
development and what kind of ecosystem it spawns.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Note that I'm not trying to criticize the existing extension mechanisms
in Envoy in any way. Both work, and are used to solve real business
problems. As a project like Envoy grows in popularity and usage, it's
inevitable that it will spawn more options for different people to
accomplish their tasks with it. Such is the way of software.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;The WASI folks are working on extensions to allow sockets and also
more complex data to be shared between WASM and hosts in an RPC-like
manner; this may enable greatly improved wasm-host interfaces in the
future.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;p class="first"&gt;This all sounds great - the way things should be - until reality kicks
in. While doing research for this post I discovered that the Proxy-wasm
ABI, while clearly and carefully specified, is in-fact
&lt;a class="reference external" href="https://github.com/proxy-wasm/spec/issues/36"&gt;out of date&lt;/a&gt; and
the &amp;quot;real&amp;quot; definition lives within the Envoy source code. It's yet
another case of &amp;quot;the ABI is whatever its main implementation does&amp;quot;, even
though other proxies implement it already
(&lt;a class="reference external" href="https://github.com/mosn/mosn"&gt;MOSN&lt;/a&gt; for example).&lt;/p&gt;
&lt;p&gt;This is especially often the case in my favorite domain - systems
programming. &lt;em&gt;Sigh&lt;/em&gt;, such is life. The rest of the post talks about
the &lt;em&gt;de-facto&lt;/em&gt; specification, relying on the Envoy source code more
than the written down ABI. Hopefully at some future point the ABI is
updated and I can rewrite this footnote.&lt;/p&gt;
&lt;p class="last"&gt;A shout out to &lt;a class="reference external" href="https://github.com/codefromthecrypt"&gt;Adrian Cole&lt;/a&gt;
and &lt;a class="reference external" href="https://github.com/mathetake"&gt;Takeshi Yoneda&lt;/a&gt; for
confirming these findings, and the useful chats about all things WASM,
WASI and Go in general.&lt;/p&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Go"></category><category term="Plugins"></category><category term="WebAssembly"></category><category term="C &amp; C++"></category></entry><entry><title>FAAS in Go with WASM, WASI and Rust</title><link href="https://eli.thegreenplace.net/2023/faas-in-go-with-wasm-wasi-and-rust/" rel="alternate"></link><published>2023-05-06T06:44:00-07:00</published><updated>2024-09-09T15:50:00-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2023-05-06:/2023/faas-in-go-with-wasm-wasi-and-rust/</id><summary type="html">&lt;p&gt;This post is best described as a technology demonstration; it melds together
web servers, plugins, WebAssembly, Go, Rust and ABIs. Here's what it shows:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;How to load WASM code with WASI in a Go environment and hook it up to a web
server.&lt;/li&gt;
&lt;li&gt;How to implement web server plugins in …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;This post is best described as a technology demonstration; it melds together
web servers, plugins, WebAssembly, Go, Rust and ABIs. Here's what it shows:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;How to load WASM code with WASI in a Go environment and hook it up to a web
server.&lt;/li&gt;
&lt;li&gt;How to implement web server plugins in any language that can be compiled to
WASM.&lt;/li&gt;
&lt;li&gt;How to translate Go programs into WASM that uses WASI.&lt;/li&gt;
&lt;li&gt;How to translate Rust programs into WASM that uses WASI.&lt;/li&gt;
&lt;li&gt;How to write WAT (WebAssembly Text) code that uses WASI to interact with
a non-JS environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We're going to build a simple &lt;strong&gt;FAAS&lt;/strong&gt; (Function as a Service) server in Go
that lets us write &lt;em&gt;modules&lt;/em&gt; in any language that has a WASM
target. Comparing to existing technologies, it's something
between GCP's &lt;a class="reference external" href="https://cloud.google.com/functions"&gt;Cloud Functions&lt;/a&gt;, &lt;a class="reference external" href="https://cloud.google.com/run"&gt;Cloud
Run&lt;/a&gt; and good old &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Common_Gateway_Interface"&gt;CGI&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="design"&gt;
&lt;h2&gt;Design&lt;/h2&gt;
&lt;p&gt;Let's start with a high-level diagram describing how the system works:&lt;/p&gt;
&lt;img alt="Diagram showing flow of events in this program, also described below" class="align-center" src="https://eli.thegreenplace.net/images/2023/wasm-faas.png" /&gt;
&lt;p&gt;The steps numbered in the diagram are:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The FAAS server receives an &lt;tt class="docutils literal"&gt;HTTP GET&lt;/tt&gt; request, with a path consisting of
a module name (&lt;tt class="docutils literal"&gt;func&lt;/tt&gt; in the example in the diagram) and an arbitrary
query string.&lt;/li&gt;
&lt;li&gt;The FAAS server finds and loads the WASM module corresponding to the module
name it was provided, and invokes it with a description of the HTTP request.&lt;/li&gt;
&lt;li&gt;The module emits output to its stdout, which is captured by the FAAS server.&lt;/li&gt;
&lt;li&gt;The FAAS server uses the module's stdout as the contents of an HTTP Response
to the request it received.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="the-faas-server"&gt;
&lt;h2&gt;The FAAS server&lt;/h2&gt;
&lt;p&gt;We'll start our deep dive with the FAAS server itself (&lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2023/wasm-faas"&gt;full code here&lt;/a&gt;). The
HTTP handling part is straightforward:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;want /{modulename} prefix&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;module %v requested with query %v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http_path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http_method&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http_host&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http_query&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;remote_addr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemoteAddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;modpath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;target/%v.wasm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;loading module %v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modpath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;invokeWasmModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;error loading module %v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modpath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unable to find module &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;modpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// The module&amp;#39;s stdout is written into the response.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;:8080&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This server listens on port 8080 (feel free to change this or make it
more configurable), and registers a catch-all handler for the root path. The
handler parses the actual request URL to find the module name. It then stores
some information to pass to the loaded module in the &lt;tt class="docutils literal"&gt;env&lt;/tt&gt; map.&lt;/p&gt;
&lt;p&gt;The loaded module is found with a filesystem lookup in the &lt;tt class="docutils literal"&gt;target&lt;/tt&gt; directory
relative to the FAAS server binary. All of this is just for demonstration
purposes and can be easily changed, of course. The handler then calls
&lt;tt class="docutils literal"&gt;invokeWasmModule&lt;/tt&gt;, which we'll get to shortly. This function returns the
invoked module's stdout, which the handler prints out into the HTTP response.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="running-wasm-code-in-go"&gt;
&lt;h2&gt;Running WASM code in Go&lt;/h2&gt;
&lt;p&gt;Given a WASM module, how do we run it programmatically in Go? There are several
high-quality WASM &lt;em&gt;runtimes&lt;/em&gt; that work outside the browser environment, and many
of them have Go bindings; for example &lt;a class="reference external" href="https://github.com/bytecodealliance/wasmtime-go"&gt;wasmtime-go&lt;/a&gt;. The one I like most,
however, is &lt;a class="reference external" href="https://github.com/tetratelabs/wazero"&gt;wazero&lt;/a&gt;; it's a
zero-dependency, pure Go runtime that doesn't have any prerequisites except
running a &lt;tt class="docutils literal"&gt;go get&lt;/tt&gt;. Our FAAS server is using &lt;tt class="docutils literal"&gt;wazero&lt;/tt&gt; to load and run
WASM modules.&lt;/p&gt;
&lt;p&gt;Here's &lt;tt class="docutils literal"&gt;invokeWasmModule&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// invokeWasmModule invokes the given WASM module (given as a file path),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c1"&gt;// setting its env vars according to env. Returns the module&amp;#39;s stdout.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;invokeWasmModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wazero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewRuntime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;wasi_snapshot_preview1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MustInstantiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Instantiate the wasm runtime, setting up exported functions from the host&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// that the wasm module can use for logging purposes.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewHostModuleBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;NewFunctionBuilder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;WithFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[%v]: %v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;log_i32&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;NewFunctionBuilder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;WithFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// Read the string from the module&amp;#39;s exported memory.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[%v]: %v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[%v]: log_string: unable to read wasm memory&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;modname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;log_string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Instantiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;wasmObj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wasmPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Set up stdout redirection and env vars for the module.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stdoutBuf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wazero&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewModuleConfig&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;WithStdout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;stdoutBuf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WithEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Instantiate the module. This invokes the _start function by default.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InstantiateWithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmObj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stdoutBuf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Interesting things to note about this code:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;wazero&lt;/tt&gt; supports WASI, which has to be instantiated explicitly to be usable
by the loaded modules.&lt;/li&gt;
&lt;li&gt;A lot of the code deals with exporting logging functions from the host (the
Go code of the FAAS server) to the WASM module.&lt;/li&gt;
&lt;li&gt;We set up the loaded module's stdout to be redirected to a buffer, and set up
its environment variables to match the &lt;tt class="docutils literal"&gt;env&lt;/tt&gt; map passed in.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are several way for host code to interact with WASM modules using only the
WASI API and ABI. Here, we opt for using environment variables for input and
stdout for output, but there are other options (see the &lt;em&gt;Other resources&lt;/em&gt;
section in the bottom for some pointers).&lt;/p&gt;
&lt;p&gt;This is it - the whole FAAS server, about 100 LOC of commented Go code. Now
let's move on to see some WASM modules this thing can load and run.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="writing-modules-in-go"&gt;
&lt;h2&gt;Writing modules in Go&lt;/h2&gt;
&lt;p&gt;We can compile Go code to WASM that uses WASI. Here's a basic Go program that
emits a greeting and a listing of its environment variables to stdout:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;goenv environment:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Environ&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To build using the standard Go toolchain:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ GOOS=wasip1 GOARCH=wasm gotip build -o target/goenv.wasm examples/goenv/goenv.go
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(the &lt;tt class="docutils literal"&gt;wasip1&lt;/tt&gt; target name refers to &amp;quot;WASI Preview 1&amp;quot;)&lt;/p&gt;
&lt;p&gt;Sharp-eyed readers will recall that the &lt;tt class="docutils literal"&gt;target/&lt;/tt&gt; directory is precisely where
the FAAS server looks for &lt;tt class="docutils literal"&gt;*.wasm&lt;/tt&gt; files to load as modules. Now that we've
placed a module named &lt;tt class="docutils literal"&gt;goenv.wasm&lt;/tt&gt; there, we're ready to launch our server
with &lt;tt class="docutils literal"&gt;go run .&lt;/tt&gt; in the root directory. We can issue a HTTP request to its
&lt;tt class="docutils literal"&gt;goenv&lt;/tt&gt; module in a separate terminal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ curl &amp;quot;localhost:8080/goenv?foo=bar&amp;amp;id=1234&amp;quot;
goenv environment:
  http_method=GET
  http_host=localhost:8080
  http_query=foo=bar&amp;amp;id=1234
  remote_addr=127.0.0.1:59268
  http_path=/goenv
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And looking at the terminal where the FAAS server runs we'll see some logging
like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;2023/04/29 06:35:59 module goenv requested with query map[foo:[bar] id:[1234]]
2023/04/29 06:35:59 loading module target/goenv.wasm
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Another option to build this is using
the &lt;a class="reference external" href="https://tinygo.org/"&gt;TinyGo&lt;/a&gt; compiler. In our
FAAS project structure, the invocation from the root directory is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ tinygo build -o target/goenv.wasm -target=wasi examples/goenv/goenv.go
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Before Go 1.21, TinyGo has been the only way to target WASI, but this support
has been part of the standard toolchain for a few releases now.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="writing-modules-in-rust"&gt;
&lt;h2&gt;Writing modules in Rust&lt;/h2&gt;
&lt;p&gt;Rust is another language that has good support for WASM and WASI in the build
system. After adding the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;wasm32-wasi&lt;/span&gt;&lt;/tt&gt; target with &lt;tt class="docutils literal"&gt;rustup&lt;/tt&gt;, it's as simple
as passing the target name to &lt;tt class="docutils literal"&gt;cargo&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ cargo build --target wasm32-wasi --release
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code is straightforward, similarly to the Go version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;rustenv environment:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;::&lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;  {key}: {value}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="writing-modules-in-webassembly-text-wat"&gt;
&lt;h2&gt;Writing modules in WebAssembly Text (WAT)&lt;/h2&gt;
&lt;p&gt;As we've seen, compiling Go and Rust code to WASM is fairly easy; looking for a
challenge, let's write a module in WAT! As &lt;a class="reference external" href="https://eli.thegreenplace.net/2023/webassembly-text-code-samples/"&gt;I've written before&lt;/a&gt;, I enjoy
writing directly in WAT; it's educational, and produces remarkably compact
binaries.&lt;/p&gt;
&lt;p&gt;The &amp;quot;educational&amp;quot; aspect quickly becomes apparent when thinking about our task.
How exactly am I supposed to write to stdout or read environment variables using
WASM? This is where WASI comes in. WASI defines both an API and ABI, both of
which will be visible in our sample. The following shows some code snippets with
explanations; for the full code check out the &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2023/wasm-faas"&gt;sample repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, I want to show how output to stdout is done; we start by importing
the &lt;tt class="docutils literal"&gt;fd_write&lt;/tt&gt; WASI system call:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wasi_snapshot_preview1&amp;quot;&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;fd_write&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nv"&gt;$fd_write&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;param&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Apparently, it has four &lt;tt class="docutils literal"&gt;i32&lt;/tt&gt; parameters and returns an &lt;tt class="docutils literal"&gt;i32&lt;/tt&gt;; what do all
of these mean? Unfortunately, WASI documentation could use a lot of work; the
resources I found useful are &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#fd_write"&gt;Legacy preview 1 specs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/wasi/api.h#L1754"&gt;C header descriptions of these functions&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this in hand, I was able to concoct a useful &lt;tt class="docutils literal"&gt;println&lt;/tt&gt; equivalent in
WAT that uses &lt;tt class="docutils literal"&gt;fd_write&lt;/tt&gt; under the hood:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;;; println prints a string to stdout using WASI.&lt;/span&gt;
&lt;span class="c1"&gt;;; It takes the string&amp;#39;s address and length as parameters.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nv"&gt;$println&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;param&lt;/span&gt; &lt;span class="nv"&gt;$strptr&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;param&lt;/span&gt; &lt;span class="nv"&gt;$len&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;;; Print the string pointed to by $strptr first.&lt;/span&gt;
    &lt;span class="c1"&gt;;;   fd=1&lt;/span&gt;
    &lt;span class="c1"&gt;;;   data vector with the pointer and length&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.store&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$datavec_addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$strptr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.store&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$datavec_len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$len&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;$fd_write&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$datavec_addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$fdwrite_ret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;drop&lt;/span&gt;

    &lt;span class="c1"&gt;;; Print out a newline.&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.store&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$datavec_addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mf"&gt;850&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.store&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$datavec_len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;$fd_write&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$datavec_addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$fdwrite_ret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;drop&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This uses some globals that you'll have to look up in the &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/blob/main/2023/wasm-faas/examples/watenv.wat"&gt;full code sample&lt;/a&gt;
if you're interested. Here's another helper function that prints out a
zero-terminated string to stdout:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;;; show_env emits a single env var pair to stdout. envptr points to it,&lt;/span&gt;
&lt;span class="c1"&gt;;; and it&amp;#39;s 0-terminated.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nv"&gt;$show_env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;param&lt;/span&gt; &lt;span class="nv"&gt;$envptr&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.set&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;;; for i = 0; envptr[i] != 0; i++&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;$count_loop&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;$break_count_loop&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.eqz&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.load8_u&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.add&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$envptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
        &lt;span class="nb"&gt;br_if&lt;/span&gt; &lt;span class="nv"&gt;$break_count_loop&lt;/span&gt;

        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.set&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.add&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="nb"&gt;br&lt;/span&gt; &lt;span class="nv"&gt;$count_loop&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;$println&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$envptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The fun part about writing assembly is that there are no abstractions.
Everything is out in the open. You know how strings are typically represented
using either zero termination (like in C) or a &lt;tt class="docutils literal"&gt;(start, len)&lt;/tt&gt; pair?
In manual WAT code that uses WASI we have the pleasure of using both approaches
in the same program :-)&lt;/p&gt;
&lt;p&gt;Finally, our main function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nv"&gt;$main&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;_start&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;$num_of_envs&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;$next_env_ptr&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;$log_string&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mf"&gt;750&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;;; Find out the number of env vars.&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;$environ_sizes_get&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$env_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$env_len&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;drop&lt;/span&gt;

    &lt;span class="c1"&gt;;; Get the env vars themselves into memory.&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;$environ_get&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$env_ptrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$env_buf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;drop&lt;/span&gt;

    &lt;span class="c1"&gt;;; Print out the preamble&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;$println&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mf"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;;; for i = 0; i != *env_count; i++&lt;/span&gt;
    &lt;span class="c1"&gt;;;   show env var i&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.set&lt;/span&gt; &lt;span class="nv"&gt;$num_of_envs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$env_count&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.set&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="nv"&gt;$envvar_loop&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;$break_envvar_loop&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.eq&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$num_of_envs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;br_if&lt;/span&gt; &lt;span class="nv"&gt;$break_envvar_loop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;;; next_env_ptr &amp;lt;- env_ptrs[i*4]&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.set&lt;/span&gt;
            &lt;span class="nv"&gt;$next_env_ptr&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.add&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;global.get&lt;/span&gt; &lt;span class="nv"&gt;$env_ptrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.mul&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)))))&lt;/span&gt;

        &lt;span class="c1"&gt;;; print out this env var&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;$show_env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$next_env_ptr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.set&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.add&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.const&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;br&lt;/span&gt; &lt;span class="nv"&gt;$envvar_loop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can now compile this WAT code into a FAAS module and re-run the server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ wat2wasm examples/watenv.wat -o target/watenv.wasm
$ go run .
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's try it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ curl &amp;quot;localhost:8080/watenv?foo=bar&amp;amp;id=1234&amp;quot;
watenv environment:
http_host=localhost:8080
http_query=foo=bar&amp;amp;id=1234
remote_addr=127.0.0.1:43868
http_path=/watenv
http_method=GET
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="wasi-api-and-abi"&gt;
&lt;h3&gt;WASI: API and ABI&lt;/h3&gt;
&lt;p&gt;I've mentioned the &lt;em&gt;WASI API and ABI&lt;/em&gt; earlier; now it's a good time to explain
what that means. An API is a set of functions that programs using WASI have
access to; one can think of it as a standard library of sorts. Go programmers
have access to the &lt;tt class="docutils literal"&gt;fmt&lt;/tt&gt; package and the &lt;tt class="docutils literal"&gt;Println&lt;/tt&gt; function within it.
Programs targeting WASI have access to the &lt;tt class="docutils literal"&gt;fd_write&lt;/tt&gt; system call in the
&lt;tt class="docutils literal"&gt;wasi_snapshow_preview1&lt;/tt&gt; module, and so on. The API of &lt;tt class="docutils literal"&gt;fd_write&lt;/tt&gt; also
defines how this function takes parameters and what it returns. Our sample uses
three WASI functions: &lt;tt class="docutils literal"&gt;fd_write&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;environ_sizes_get&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;environ_get&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;An ABI is a little bit less familiar to most programmers; it's the run-time
contract between a program and its environment. The WASI ABI is currently
unstable and is &lt;a class="reference external" href="https://github.com/WebAssembly/WASI/blob/main/legacy/application-abi.md"&gt;described here&lt;/a&gt;. In
our program, the ABI manifests in two ways:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;The main entry point we export is the &lt;tt class="docutils literal"&gt;_start&lt;/tt&gt; function. This is
automatically called by a WASI-supporting host after setup.&lt;/li&gt;
&lt;li&gt;Our WASM code exports its linear memory to the host with
&lt;tt class="docutils literal"&gt;(memory (export &amp;quot;memory&amp;quot;) 1)&lt;/tt&gt;. Since WASI APIs require passing pointers
to memory, both the host and the WASM module need a shared understanding
of how to access this memory.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Naturally, both the Go and Rust implementations of FAAS modules comply to the
WASI API and ABI, but this is hidden by the compiler from programmers. In the
Go program, for example, all we need to do is write a &lt;tt class="docutils literal"&gt;main&lt;/tt&gt; function as usual
and therein emit to stdout using &lt;tt class="docutils literal"&gt;Println&lt;/tt&gt;. The Go compiler will properly
export &lt;tt class="docutils literal"&gt;_start&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;memory&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ wasm-objdump -x target/goenv.wasm

... snip

Export[2]:
 - func[1028] &amp;lt;_rt0_wasm_wasip1&amp;gt; -&amp;gt; &amp;quot;_start&amp;quot;
 - memory[0] -&amp;gt; &amp;quot;memory&amp;quot;

... snip
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And will properly hook things up to call our code from &lt;tt class="docutils literal"&gt;_start&lt;/tt&gt;, etc.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="wasi-and-plugins"&gt;
&lt;h2&gt;WASI and plugins&lt;/h2&gt;
&lt;p&gt;The FAAS server presented in this post is clearly an example of developing
&lt;em&gt;plugins&lt;/em&gt; using WASM and WASI. This is an emerging and exciting area in
programming and lots of progress is being made on multiple fronts. Right now,
WASI modules are limited to interacting with the environment via means like
environment variables and stdin/stdout; while this is fine for interacting
with the outside world, for host-to-module communication it's not amazing, in
my experience. Therefore the WASM standards committee is working of further
improvements to WASI that may include sockets and other means of passing data
between hosts and modules.&lt;/p&gt;
&lt;p&gt;In the meantime, projects are making do with what they have. For example, the
&lt;a class="reference external" href="https://sqlc.dev/"&gt;sqlc Go package&lt;/a&gt; supports WASM plugins. The communication
with plugins happens as follows: the host encodes a command into a protobuf
and emits it to the plugin's stdin; it then reads the plugin's stdout for a
protobuf-encoded response.&lt;/p&gt;
&lt;p&gt;Other projects are taking more maverick approaches; for example, &lt;a class="reference external" href="https://www.envoyproxy.io/"&gt;the Envoy
proxy&lt;/a&gt;
&lt;a class="reference external" href="https://eli.thegreenplace.net/2023/plugins-case-study-envoy-wasm-extensions/"&gt;supports WASM plugins&lt;/a&gt;
by defining a custom API and ABI between the host and WASM modules. I'll
probably write more about this in a later post.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="other-resources"&gt;
&lt;h2&gt;Other resources&lt;/h2&gt;
&lt;p&gt;Here are some additional resources on the same topic as this post:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://blog.scottlogic.com/2022/04/16/wasm-faas.html"&gt;Building a WebAssembly-powered serverless platform&lt;/a&gt; - the
post that inspired this one. Other WASM-related articles from that blog are
also recommended.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/deislabs/wagi"&gt;wagi&lt;/a&gt; - a more featured approch with
the same idea, implemented in Rust.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://wasmer.io/posts/announcing-wcgi"&gt;WCGI&lt;/a&gt; - an even more
CGI-conformant approach.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;The main Go compiler supported WASM for a while, but only as a target
for browsers &amp;amp; JS (I wrote &lt;a class="reference external" href="https://eli.thegreenplace.net/2022/sudoku-go-and-webassembly/"&gt;a bit about it recently&lt;/a&gt;).
The news in 1.21 is the &lt;tt class="docutils literal"&gt;GOOS=wasip1&lt;/tt&gt; support, which hooks up Go's
internal syscalls to WASI APIs, sets up the ABI etc.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[2]&lt;/td&gt;&lt;td&gt;These resources tend to disappear and move around without notice; if you
notice a dead link, please drop me a note and I'll look for a more
up-to-date source.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Go"></category><category term="WebAssembly"></category><category term="Rust"></category><category term="Plugins"></category></entry><entry><title>RPC-based plugins in Go</title><link href="https://eli.thegreenplace.net/2023/rpc-based-plugins-in-go/" rel="alternate"></link><published>2023-03-28T20:05:00-07:00</published><updated>2024-05-04T19:46:23-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2023-03-28:/2023/rpc-based-plugins-in-go/</id><summary type="html">&lt;p&gt;This post is the next installment in my &lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;plugin series&lt;/a&gt;.
&lt;a class="reference external" href="https://eli.thegreenplace.net/2021/plugins-in-go/"&gt;The last post&lt;/a&gt;
discussed the two main approaches to developing plugins in Go: compile-time
plugins and run-time plugins. For run-time plugins, the post described how to
use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-buildmode=plugin&lt;/span&gt;&lt;/tt&gt; and shared libraries to load plugins at runtime, but
also hinted …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This post is the next installment in my &lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;plugin series&lt;/a&gt;.
&lt;a class="reference external" href="https://eli.thegreenplace.net/2021/plugins-in-go/"&gt;The last post&lt;/a&gt;
discussed the two main approaches to developing plugins in Go: compile-time
plugins and run-time plugins. For run-time plugins, the post described how to
use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-buildmode=plugin&lt;/span&gt;&lt;/tt&gt; and shared libraries to load plugins at runtime, but
also hinted at an alternative approach that uses separate processes and RPC.&lt;/p&gt;
&lt;p&gt;Now it's time to discuss the RPC approach in more detail, using the
&lt;a class="reference external" href="https://github.com/hashicorp/go-plugin"&gt;hashicorp/go-plugin&lt;/a&gt; package. Here's
a sketch of how such plugins work:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Each plugin is a separate Go binary, built using some code shared with the
main application.&lt;/li&gt;
&lt;li&gt;The main application loads plugins by running their binaries as sub-processes.&lt;/li&gt;
&lt;li&gt;The main application talks to plugins via RPC to access their functionality.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We'll start by explaining how the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; package works and how it helps
us write plugins. Then I'll present a re-implementation of the &lt;em&gt;htmlize&lt;/em&gt; program
we've been using throughout the plugin series, this time using &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;div class="section" id="the-go-plugin-package"&gt;
&lt;h2&gt;The go-plugin package&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/hashicorp/go-plugin"&gt;go-plugin&lt;/a&gt; was developed by
HashiCorp - a powerhouse of Go-based tooling, and has been used in production
by many of their tools (like Terraform and Vault) for years. This is one if its
greatest strengths - it's battle-tested.&lt;/p&gt;
&lt;p&gt;The basic idea behind &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; is a run-time plugin system wherein each
plugin is a separate binary and runs in its own OS process.&lt;/p&gt;
&lt;img alt="A plugin in its on OS process talking RPC with other plugins in their separate OS processes" class="align-center" src="https://eli.thegreenplace.net/images/2023/plugin-rpc-binaries.png" /&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; lets us pick which RPC mechanism to use; it supports
&lt;tt class="docutils literal"&gt;net/rpc&lt;/tt&gt; and gRPC out of the box. Therefore, its API is a bit odd at first
sight. Specifically, we are expected to define our own RPC methods both for
the server (plugin) and the client (main application), and register them with
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; by implementing its &lt;a class="reference external" href="https://pkg.go.dev/github.com/hashicorp/go-plugin#Plugin"&gt;Plugin interface&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This leaves one thinking - &amp;quot;wait, so what does &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; give me, anyway? If
I have to implement my own RPC, do I really need this helper package?&amp;quot; - which
is a valid question. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; provides several important capabilities,
however:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Handles the actual network connection between a client and multiple servers:
supporting Unix domain sockets on Linux &lt;a class="reference external" href="https://eli.thegreenplace.net/2019/unix-domain-sockets-in-go/"&gt;for performance&lt;/a&gt;, TCP
elsewhere.&lt;/li&gt;
&lt;li&gt;When the client launches a plugin server, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; handles &lt;em&gt;discovery&lt;/em&gt;
- figuring out which port/file the server is listening on, and establishing
the connection. This also includes verifying that the launched binary is
the right plugin, meant for this program and not something else.&lt;/li&gt;
&lt;li&gt;Supports protocol versioning, which ensures that the main binary doesn't try
to talk to plugins that are &amp;quot;too old&amp;quot;.&lt;/li&gt;
&lt;li&gt;Supports liveness pings to plugins.&lt;/li&gt;
&lt;li&gt;Can set up mTLS between the client and servers (useful when the plugin runs
on a different machine).&lt;/li&gt;
&lt;li&gt;Handles redirection of plugin stdin/stdout streams and logs back to the main
process.&lt;/li&gt;
&lt;li&gt;Allows having multiple logical plugins reside in the same binary/process,
each with its own RPC interface. All plugins share a single connection to the
main process via connection multiplexing that &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; implements. This
also allows plugins to call back into the main application - more on this
later.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="htmlize-plugins-with-go-plugin"&gt;
&lt;h2&gt;htmlize plugins with go-plugin&lt;/h2&gt;
&lt;p&gt;Let's rebuild our &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt; tool that we've been using for this demonstration
since the &lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;original plugins post&lt;/a&gt;
(including the &lt;a class="reference external" href="https://eli.thegreenplace.net/2021/plugins-in-go/"&gt;Go version&lt;/a&gt;), this time using
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt;. The full code for this version is &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2023/go-plugin-htmlize-rpc"&gt;on GitHub&lt;/a&gt;.
We will examine it using the &lt;a class="reference external" href="https://eli.thegreenplace.net/2012/08/07/fundamental-concepts-of-plugin-infrastructures"&gt;fundamental concepts of plugins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Discovery and registration:&lt;/strong&gt; since plugins are just binaries that can be
found anywhere, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; doesn't prescribe what approach to take here. It
only provides a &lt;tt class="docutils literal"&gt;Discover&lt;/tt&gt; function which is a basic wrapper around a
filesystem glob pattern. In our code, the &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/blob/main/2023/go-plugin-htmlize-rpc/plugin/manager.go"&gt;Manager type&lt;/a&gt; takes a
path where it will look for plugin binaries, and treats each file in that
directory as a potential plugin.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; does provide tools to ensure that a loaded binary is, in fact, a
plugin for the right application. When creating a new &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; client, we
have to pass in a &lt;a class="reference external" href="https://pkg.go.dev/github.com/hashicorp/go-plugin#HandshakeConfig"&gt;HandshakeConfig&lt;/a&gt;, which
has to match between the application and the plugin. This helps ensure that we
don't attempt to load a plugin meant for another application, or for a different
version of this application.&lt;/p&gt;
&lt;p&gt;As described earlier, at this point &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; takes over; it launches the
plugin in a subprocess, connects to its stdout to discover which address the
plugin server is listening on (could be a Unix domain socket or a TCP socket,
based on OS), and then sets up the RPC. The main application (client) is now
ready to invoke RPCs in the plugin (server), based on the agreed-upon interface.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Application hooks:&lt;/strong&gt; the central communication point between a plugin and an
application with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; is the plugin's &lt;em&gt;exposed interface&lt;/em&gt;. In our
case, the interface is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Htmlizer is the interface plugins have to implement. To avoid calling the&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c1"&gt;// plugin for roles it doesn&amp;#39;t support, it has to tell the plugin managers&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c1"&gt;// which roles it wants to be invoked on by implementing the Hooks() method.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Htmlizer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Hooks returns a list of the hooks this plugin wants to register.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Hooks can have one of the following forms:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;//&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;//  * &amp;quot;contents&amp;quot;: the plugin&amp;#39;s ProcessContents method will be called on&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;//                the post&amp;#39;s complete contents.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;//&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// * &amp;quot;role:NN&amp;quot;: the plugin&amp;#39;s ProcessRole method will be called with role=NN&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;//              and the role&amp;#39;s value when a :NN: role is encountered in the&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;//              input.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Hooks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// ProcessRole is called on roles the plugin requested in the list returned&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// by Hooks(). It takes the role name, role value in the input and the post&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// and should return the transformed role value.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ProcessRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// ProcessContents is called on the entire post contents, if requested in&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Hooks(). It takes the contents and the post and should return the&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// transformed contents.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;ProcessContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This interface is presented to the application via a RPC mechanism, so code
in the application simply invokes these methods on a value implementing the
interface; &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; translates this to RPC calls behind the scenes &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I recommend you to carefully read the comments on the &lt;tt class="docutils literal"&gt;Htmlizer&lt;/tt&gt; interface;
they describe an interesting nuance w.r.t. application hooks. In our &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt;
application, we want plugins to register for specific text &amp;quot;roles&amp;quot;. If a plugin
didn't register for a role, we don't want to invoke it when the role is
encountered - it's wasteful to call N RPCs for N plugins for each role, when in
reality at most one plugin likely cares about any given role.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; does not provide built-in support to handle this conditional
registration. A plugin exposes an interface via RPC, and that's it. But it turns
out to be fairly easy to implement in a custom way, as our &lt;tt class="docutils literal"&gt;Hooks&lt;/tt&gt; method
demonstrates. Here is how our plugin &lt;tt class="docutils literal"&gt;Manager&lt;/tt&gt; type handles this; first the
&lt;tt class="docutils literal"&gt;Manager&lt;/tt&gt; type itself:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Manager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;roleHooks&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nx"&gt;Htmlizer&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;contentsHooks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;Htmlizer&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;pluginClients&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;goplugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the relevant part from its &lt;tt class="docutils literal"&gt;LoadPlugins&lt;/tt&gt; method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Query the plugin for its capabilities -- the hooks it supports.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Based on this information, register the plugin with the appropriate&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c1"&gt;// role or contents hooks.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nx"&gt;capabilities&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Hooks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;capabilities&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;contents&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentsHooks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentsHooks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;role&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roleHooks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;impl&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It queries each plugins for its supported hooks, and then registers the right
hooks. As a result, when we encounter a role like &lt;tt class="docutils literal"&gt;:tt:&lt;/tt&gt;, it will only invoke
the plugin that asked to handle this role.&lt;/p&gt;
&lt;p&gt;In this code &lt;tt class="docutils literal"&gt;impl&lt;/tt&gt; refers to a value of the type &lt;tt class="docutils literal"&gt;PluginClientRPC&lt;/tt&gt;, which
implements the &lt;tt class="docutils literal"&gt;Htmlize&lt;/tt&gt; interface by issuing RPC calls to the plugin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// PluginClientRPC is used by clients (main application) to translate the&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Htmlize interface of plugins to RPC calls.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PluginClientRPC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;PluginClientRPC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Hooks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HooksReply&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Plugin.Hooks&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HooksArgs&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Hooks&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;PluginClientRPC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProcessContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ContentsReply&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Plugin.ProcessContents&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ContentsArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;PluginClientRPC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProcessRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RoleReply&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Plugin.ProcessRole&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;RoleArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Per convention, each RPC call has its own type for arguments and another for
the response (e.g. &lt;tt class="docutils literal"&gt;RoleArgs&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;RoleReply&lt;/tt&gt;). These are basic data
containers I'm leaving out of this post but you can see in the code.
A similar RPC wrapper is implemented on the server (plugin) side, doing the
translation the other way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exposing application capabilities back to plugins:&lt;/strong&gt; this is actually quite
tricky to accomplish in RPC-based systems, at least in the general case. Since
in this model plugins run in a separate process, we can't just pass a reference
to a big data structure into the plugin as we did before.&lt;/p&gt;
&lt;p&gt;For small and simple data structures, serializing them through the RPC is not
an issue. This is what our example does for the &lt;tt class="docutils literal"&gt;content.Post&lt;/tt&gt; type - as you
can see from the &lt;tt class="docutils literal"&gt;Htmlizer&lt;/tt&gt; interface code snippet above. But what about
larger types? What if we want to expose the entire DB to the plugin? Or have the
plugin invoke functionality in the main application.&lt;/p&gt;
&lt;p&gt;This is one of the capabilities &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; provides, via its &lt;em&gt;bidirectional
communication&lt;/em&gt; feature. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; can multiplex several RPC channels onto
the same connection between the plugin and the application (using the &lt;a class="reference external" href="https://github.com/hashicorp/yamux"&gt;yamux
package&lt;/a&gt;), and through this the client
can open its own RPC server available to the plugin to invoke.&lt;/p&gt;
&lt;p&gt;I left this out of our example because I didn't want to needlessly complicate
it (we don't really need this functionality for &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt;), but I created a
&lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2023/go-plugin-bidir-netrpc"&gt;separate sample&lt;/a&gt;
that shows how to do this with &lt;tt class="docutils literal"&gt;net/rpc&lt;/tt&gt; - it's on GitHub if you're interested
&lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-sample-plugin-for-htmlize"&gt;
&lt;h2&gt;A sample plugin for &lt;tt class="docutils literal"&gt;htmlize&lt;/tt&gt;&lt;/h2&gt;
&lt;p&gt;Here's the entire code for a sample plugin - one that implements rendering the
&lt;tt class="docutils literal"&gt;:tt:&lt;/tt&gt; role into the &lt;tt class="docutils literal"&gt;&amp;lt;tt&amp;gt;&lt;/tt&gt; HTML element:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;example.com/content&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;example.com/plugin&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;goplugin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/hashicorp/go-plugin&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TtHtmlizer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TtHtmlizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Hooks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;role:tt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TtHtmlizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProcessContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TtHtmlizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProcessRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;lt;tt&amp;gt;%s&amp;lt;/tt&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;goplugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;goplugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServeConfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;HandshakeConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handshake&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nx"&gt;goplugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;htmlize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HtmlizePlugin&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Impl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TtHtmlizer&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Using &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; involves a bit of work for setting up the RPC scaffolding,
but once that's all done, writing new plugins is quick and easy: just implement
an interface and invoke a plugin server registration function in &lt;tt class="docutils literal"&gt;main&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comparing-shared-libraries-vs-rpc-for-plugins"&gt;
&lt;h2&gt;Comparing shared libraries vs RPC for plugins&lt;/h2&gt;
&lt;p&gt;By now we've seen how to create run-time plugins in Go using two different
mechanisms: shared libraries (&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;--buildmode=plugin&lt;/span&gt;&lt;/tt&gt;) and RPC (via
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt;). Which one should you actually use?&lt;/p&gt;
&lt;p&gt;As the &lt;a class="reference external" href="https://eli.thegreenplace.net/2021/plugins-in-go/"&gt;previous post&lt;/a&gt;
discussed, shared library plugins can be awkward to work with, because they
require strict source code compatibility with the main application. Another
problem is portability: they don't currently work on Windows, for example, and
it's not clear if they ever will.&lt;/p&gt;
&lt;p&gt;On the other hand, shared library plugins have excellent performance: plugins
run in-process with the main application. Calling a plugin is a simple Go
function call and we can pass references to data structures across the boundary.
Compare that to RPC, where each call is a network interaction; even if this
&amp;quot;network&amp;quot; is very fast (Unix domain sockets or localhost TCP), it still implies
serializing the data into a linear buffer, then deserializing it at the other
end; this is orders of magnitude slower per invocation.&lt;/p&gt;
&lt;p&gt;RPC-based plugins have some clear benefits of their own, however. First and
foremost - isolation; a plugin runs in a process of its own. If it crashes,
it doesn't bring down the rest of the application with it.
Moreover, if the plugin is malicious there's limited damage it can do to the
application itself. Plugins can even be invoked with different system
permissions from the main application. With some tinkering it should be possible
to set up a plugin that runs inside a container.&lt;/p&gt;
&lt;p&gt;An additional benefit is that plugins can be distributed; since they can talk
RPC over TCP. It's easy to set up a plugin that runs on a different machine
(&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; supports this with its &lt;tt class="docutils literal"&gt;ReattachConfig&lt;/tt&gt; type).&lt;/p&gt;
&lt;p&gt;Finally, since the interface is RPC, the plugins don't even necessarily have to
be written in Go! If we use the gRPC interface, we can theoretically use any
language to write a plugin for a Go application; in this scenario, all the data
exchanged between the main application and plugins goes through protocol buffers
anyway.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="source-code"&gt;
&lt;h2&gt;Source code&lt;/h2&gt;
&lt;p&gt;The full source code for this sample is here: &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2023/go-plugin-htmlize-rpc"&gt;go-plugin-htmlize-rpc&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Separate sample that shows how to call back from plugins into the host with
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;net/rpc&lt;/tt&gt;: &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2023/go-plugin-bidir-netrpc"&gt;go-plugin-bidir-netrpc&lt;/a&gt;.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;To be precise, it's RPC scaffolding implemented by us + &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt;
that do this in tandem. As described earlier, in order to support
multiple RPC flavors, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; leaves the RPC layer scaffolding
for users to define.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;go-plugin&lt;/span&gt;&lt;/tt&gt; repository has an example of doing this for gRPC
but not for &lt;tt class="docutils literal"&gt;net/rpc&lt;/tt&gt;, as far as I could tell.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Go"></category><category term="Plugins"></category><category term="Network Programming"></category></entry></feed>