Thicket data repository for the EEG
1{
2 "id": "https://www.tunbury.org/2025/03/17/capnproto",
3 "title": "Playing with Cap’n Proto",
4 "link": "https://www.tunbury.org/2025/03/17/capnproto/",
5 "updated": "2025-03-17T00:00:00",
6 "published": "2025-03-17T00:00:00",
7 "summary": "Cap’n Proto has become a hot topic recently and while this is used for many OCaml-CI services, I spent some time creating a minimal application.",
8 "content": "<p>Cap’n Proto has become a hot topic recently and while this is used for many OCaml-CI services, I spent some time creating a minimal application.</p>\n\n<p>Firstly create a schema with a single interface whch accepts a file name and returns the content.</p>\n\n<div><div><pre><code>interface Foo {\n get @0 (name :Text) -> (reply :Text);\n}\n</code></pre></div></div>\n\n<p>This schema can then be compiled into the bindings for your required language. e.g. <code>capnp compile -o ocaml:. schema.capnp</code></p>\n\n<p>In practice this need not be done by hand as we can use a <code>dune</code> rule to do this.</p>\n\n<div><div><pre><code>(rule\n (targets foo_api.ml foo_api.mli)\n (deps foo_api.capnp)\n (action (run capnp compile -o %{bin:capnpc-ocaml} %{deps})))\n</code></pre></div></div>\n\n<p>On the server side we now need to extend the automatically generate code to actually implement the interface. This code is largely boilerplate.</p>\n\n<div><div><pre><code><span>module</span> <span>Api</span> <span>=</span> <span>Foo_api</span><span>.</span><span>MakeRPC</span><span>(</span><span>Capnp_rpc</span><span>)</span>\n\n<span>open</span> <span>Capnp_rpc</span><span>.</span><span>Std</span>\n\n<span>let</span> <span>read_from_file</span> <span>filename</span> <span>=</span> <span>In_channel</span><span>.</span><span>with_open_text</span> <span>filename</span> <span>@@</span> <span>fun</span> <span>ic</span> <span>-></span> <span>In_channel</span><span>.</span><span>input_all</span> <span>ic</span>\n\n<span>let</span> <span>local</span> <span>=</span>\n <span>let</span> <span>module</span> <span>Foo</span> <span>=</span> <span>Api</span><span>.</span><span>Service</span><span>.</span><span>Foo</span> <span>in</span>\n <span>Foo</span><span>.</span><span>local</span> <span>@@</span> <span>object</span>\n <span>inherit</span> <span>Foo</span><span>.</span><span>service</span>\n\n <span>method</span> <span>get_impl</span> <span>params</span> <span>release_param_caps</span> <span>=</span>\n <span>let</span> <span>open</span> <span>Foo</span><span>.</span><span>Get</span> <span>in</span>\n <span>let</span> <span>name</span> <span>=</span> <span>Params</span><span>.</span><span>name_get</span> <span>params</span> <span>in</span>\n <span>release_param_caps</span> <span>()</span><span>;</span>\n <span>let</span> <span>response</span><span>,</span> <span>results</span> <span>=</span> <span>Service</span><span>.</span><span>Response</span><span>.</span><span>create</span> <span>Results</span><span>.</span><span>init_pointer</span> <span>in</span>\n <span>Results</span><span>.</span><span>reply_set</span> <span>results</span> <span>(</span><span>read_from_file</span> <span>name</span><span>);</span>\n <span>Service</span><span>.</span><span>return</span> <span>response</span>\n <span>end</span>\n</code></pre></div></div>\n\n<p>The server needs to generate the capability file needed to access the service and wait for incoming connections.</p>\n\n<div><div><pre><code><span>let</span> <span>cap_file</span> <span>=</span> <span>\"echo.cap\"</span>\n\n<span>let</span> <span>serve</span> <span>config</span> <span>=</span>\n <span>Switch</span><span>.</span><span>run</span> <span>@@</span> <span>fun</span> <span>sw</span> <span>-></span>\n <span>let</span> <span>service_id</span> <span>=</span> <span>Capnp_rpc_unix</span><span>.</span><span>Vat_config</span><span>.</span><span>derived_id</span> <span>config</span> <span>\"main\"</span> <span>in</span>\n <span>let</span> <span>restore</span> <span>=</span> <span>Restorer</span><span>.</span><span>single</span> <span>service_id</span> <span>(</span><span>Foo</span><span>.</span><span>local</span><span>)</span> <span>in</span>\n <span>let</span> <span>vat</span> <span>=</span> <span>Capnp_rpc_unix</span><span>.</span><span>serve</span> <span>~</span><span>sw</span> <span>~</span><span>restore</span> <span>config</span> <span>in</span>\n <span>match</span> <span>Capnp_rpc_unix</span><span>.</span><span>Cap_file</span><span>.</span><span>save_service</span> <span>vat</span> <span>service_id</span> <span>cap_file</span> <span>with</span>\n <span>|</span> <span>Error</span> <span>`Msg</span> <span>m</span> <span>-></span> <span>failwith</span> <span>m</span>\n <span>|</span> <span>Ok</span> <span>()</span> <span>-></span>\n <span>traceln</span> <span>\"Server running. Connect using %S.\"</span> <span>cap_file</span><span>;</span>\n <span>Fiber</span><span>.</span><span>await_cancel</span> <span>()</span>\n</code></pre></div></div>\n\n<p>The client application imports the capability file and calls the service <code>Foo.get</code>.</p>\n\n<div><div><pre><code><span>let</span> <span>run_client</span> <span>service</span> <span>=</span>\n <span>let</span> <span>x</span> <span>=</span> <span>Foo</span><span>.</span><span>get</span> <span>service</span> <span>\"client.ml\"</span> <span>in</span>\n <span>traceln</span> <span>\"%S\"</span> <span>x</span>\n\n<span>let</span> <span>connect</span> <span>net</span> <span>uri</span> <span>=</span>\n <span>Switch</span><span>.</span><span>run</span> <span>@@</span> <span>fun</span> <span>sw</span> <span>-></span>\n <span>let</span> <span>client_vat</span> <span>=</span> <span>Capnp_rpc_unix</span><span>.</span><span>client_only_vat</span> <span>~</span><span>sw</span> <span>net</span> <span>in</span>\n <span>let</span> <span>sr</span> <span>=</span> <span>Capnp_rpc_unix</span><span>.</span><span>Vat</span><span>.</span><span>import_exn</span> <span>client_vat</span> <span>uri</span> <span>in</span>\n <span>Capnp_rpc_unix</span><span>.</span><span>with_cap_exn</span> <span>sr</span> <span>run_client</span>\n</code></pre></div></div>\n\n<p>Where <code>Foo.get</code> is defined like this</p>\n\n<div><div><pre><code><span>module</span> <span>Foo</span> <span>=</span> <span>Api</span><span>.</span><span>Client</span><span>.</span><span>Foo</span>\n\n<span>let</span> <span>get</span> <span>t</span> <span>name</span> <span>=</span>\n <span>let</span> <span>open</span> <span>Foo</span><span>.</span><span>Get</span> <span>in</span>\n <span>let</span> <span>request</span><span>,</span> <span>params</span> <span>=</span> <span>Capability</span><span>.</span><span>Request</span><span>.</span><span>create</span> <span>Params</span><span>.</span><span>init_pointer</span> <span>in</span>\n <span>Params</span><span>.</span><span>name_set</span> <span>params</span> <span>name</span><span>;</span>\n <span>Capability</span><span>.</span><span>call_for_value_exn</span> <span>t</span> <span>method_id</span> <span>request</span> <span>|></span> <span>Results</span><span>.</span><span>reply_get</span>\n</code></pre></div></div>\n\n<p>Run the server application passing it parameters of where to save the private key and which interface/port to listen on.</p>\n\n<div><div><pre><code><span>$ </span>dune <span>exec</span> <span>--</span> ./server.exe <span>--capnp-secret-key-file</span> ./server.pem <span>--capnp-listen-address</span> tcp:127.0.0.1:7000\n+Server running. Connect using <span>\"echo.cap\"</span><span>.</span>\n</code></pre></div></div>\n\n<p>The <code>.cap</code> looks like this</p>\n\n<div><div><pre><code>capnp://sha-256:f5BAo2n_2gVxUdkyzYsIuitpA1YT_7xFg31FIdNKVls@127.0.0.1:7000/6v45oIvGQ6noMaLOh5GHAJnGJPWEO5A3Qkt0Egke4Ic\n</code></pre></div></div>\n\n<p>In another window, invoke the client.</p>\n\n<div><div><pre><code><span>$ </span>dune <span>exec</span> <span>--</span> ./client.exe ./echo.cap\n</code></pre></div></div>\n\n<p>The full code is available on <a href=\"https://github.com/mtelvers/capnp-minimum\">Github</a>.</p>",
9 "content_type": "html",
10 "author": {
11 "name": "Mark Elvers",
12 "email": "mark.elvers@tunbury.org",
13 "uri": null
14 },
15 "categories": [
16 "capnpproto",
17 "tunbury.org"
18 ],
19 "source": "https://www.tunbury.org/atom.xml"
20}