From 5402f963254a9bd5e24768914252ad073d37132a Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Fri, 10 May 2024 13:09:44 +0200 Subject: [PATCH 01/18] wip --- src/build-and-deploy-an-app.md | 4 ++-- src/my_first_app/chapter_1.md | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/build-and-deploy-an-app.md b/src/build-and-deploy-an-app.md index 3ddebd59..bc06c5d8 100644 --- a/src/build-and-deploy-an-app.md +++ b/src/build-and-deploy-an-app.md @@ -2,9 +2,9 @@ Welcome! In these tutorials, you'll setup your development environment and learn about the `kit` tools. -You'll learn about templates and also walk through writing an application from the group up, backend and frontend. +You'll learn about templates and also walk through writing an application from the ground up, backend and frontend. And finally, you'll learn how to deploy applications through the Kinode app store. -For the purposes of this documentation, terminal commands are provided as-is for ease of copying EXCEPT when the output of the command is also shown. +For the purposes of this documentation, terminal commands are provided as-is for ease of copying except when the output of the command is also shown. In that case, the command is prepended with a `$ ` to distinguish the command from the output. The `$ ` should not be copied into the terminal. \ No newline at end of file diff --git a/src/my_first_app/chapter_1.md b/src/my_first_app/chapter_1.md index f57c0add..3cdd9cec 100644 --- a/src/my_first_app/chapter_1.md +++ b/src/my_first_app/chapter_1.md @@ -53,19 +53,21 @@ Kinode packages are sets of one or more Kinode [processes](../process/processes. A Kinode package is represented in Unix as a directory that has a `pkg/` directory within. Each process within the package is its own directory. By default, the `kit new` command creates a simple, one-process package, a chat app. -Other templates, including a Python template and a UI-enabled template can be used by passing different flags to `kit new` (see `kit new --help`). +Other templates, including a Python template and a UI-enabled template can be used by passing [different flags to `kit new`](../kit/new.html#discussion). The default template looks like: ```bash $ tree my_chat_app my_chat_app -├── metadata.json -├── my_chat_app -│   ├── Cargo.toml -│   └── src -│   └── lib.rs -└── pkg - └── manifest.json + ├── Cargo.toml + ├── metadata.json + ├── my_chat_app + │ ├── Cargo.toml + │ └── src + │ └── lib.rs + └── pkg + ├── manifest.json + └── scripts.json ``` The `my_chat_app/` package here contains one process, also named `my_chat_app/`. From 6670a22c8bf334b759c840ec0364df2a1c6a5798 Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Fri, 10 May 2024 15:32:13 +0200 Subject: [PATCH 02/18] section 5 chapter 1 --- src/my_first_app/chapter_1.md | 36 ++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/my_first_app/chapter_1.md b/src/my_first_app/chapter_1.md index 3cdd9cec..a213e514 100644 --- a/src/my_first_app/chapter_1.md +++ b/src/my_first_app/chapter_1.md @@ -78,7 +78,7 @@ It is exhaustively defined [here](https://doc.rust-lang.org/cargo/reference/mani The `src/` directory is where the code for the process lives. Also within the package directory is a `pkg/` directory. -The `pkg/` directory contains two files, `manifest.json` and `metadata.json`, that specify information the Kinode needs to run the package, which will be enumerated below. +The `pkg/` dirctory contains two files, `manifest.json`, which specifes information the Kinode needs to run the package, and `scripts.json`. The `pkg/` directory is also where `.wasm` binaries will be deposited by [`kit build`](#building-the-package). The files in the `pkg/` directory are injected into the Kinode with [`kit start-package`](#starting-the-package). @@ -126,8 +126,8 @@ Key | Value Type `"process_wasm_path"` | String | The path to the process `"on_exit"` | String (`"None"` or `"Restart"`) or Object (covered [elsewhere](./chapter_2.md#aside-on_exit)) | What to do in case the process exits `"request_networking"` | Boolean | Whether to ask for networking capabilities from kernel -`"request_capabilities"` | Array of Strings or Objects | Strings are process IDs to request messaging capabilties from; Objects have a `"process"` field (process ID to request from) and a `"params"` field (capability to request) -`"grant_capabilities"` | Array of Strings or Objects | Strings are process IDs to grant messaging capabilties to; Objects have a `"process"` field (process ID to grant to) and a `"params"` field (capability to grant) +`"request_capabilities"` | Array of Strings or Objects | Strings are `processID`s to request messaging capabilties from; Objects have a `"process"` field (`processID` to request from) and a `"params"` field (capability to request) +`"grant_capabilities"` | Array of Strings or Objects | Strings are `processIDs` to grant messaging capabilties to; Objects have a `"process"` field (`processID` to grant to) and a `"params"` field (capability to grant) `"public"` | Boolean | Whether to allow any process to message us ### `metadata.json` @@ -156,21 +156,21 @@ $ cat my_chat_app/metadata.json "animation_url": "" } ``` -Here, the `publisher` is some default value, but for a real package, this field should contain the KNS id of the publishing node. +Here, the `publisher` is some default value, but for a real package, this field should contain the KNS ID of the publishing node. The `publisher` can also be set with a `kit new --publisher` flag. -The rest of these fields are not required for development, but become important when publishing a package with the `app_store`. +The rest of these fields are not required for development, but become important when publishing a package with the [`app_store`](https://github.com/kinode-dao/kinode/tree/main/kinode/packages/app_store). -As an aside: each process has a unique process ID, used to address messages to that process, that looks like +As an aside: each process has a unique `processID`, used to address messages to that process, that looks like ``` :: ``` -You can read more about process IDs [here](../process/processes.md#overview). +You can read more about `processID`s [here](../process/processes.md#overview). ## Building the Package -To build the package, use the `kit build` tool. +To build the package, use the [`kit build`](../kit/build.md#) tool. This tool accepts an optional directory path as the first argument, or, if none is provided, attempts to build the current working directory. As such, either of the following will work: @@ -190,7 +190,7 @@ kit build Often, it is optimal to develop on a fake node. Fake nodes are simple to set up, easy to restart if broken, and mocked networking makes development testing very straightforward. -To boot a fake Kinode for development purposes, use the `kit boot-fake-node` tool. +To boot a fake Kinode for development purposes, use the [`kit boot-fake-node` tool](../kit/boot-fake-node.md). `kit boot-fake-node` downloads the OS- and architecture-appropriate Kinode core binary and runs it without connecting to the live network. Instead, it connects to a mocked local network, allowing different fake nodes on the same machine to communicate with each other. @@ -221,9 +221,11 @@ kit boot-fake-node --runtime-path ~/path/to/kinode where `~/path/to/kinode` must be replaced with a path to the Kinode core repo. +Note that your node will be named `fake.dev`, as opposed to `fake.os`. The `.dev` suffix is used for development nodes. + ## Optional: Starting a Real Kinode -Alternatively, development sometimes calls for a real node, which has access to the actual Kinode network and its providers, such as integrated LLMs. +Alternatively, development sometimes calls for a real node, which has access to the actual Kinode network and its providers. To develop on a real Kinode, connect to the network and follow the instructions to [setup a Kinode](../install.md). @@ -252,7 +254,7 @@ or, if you are already in the correct package directory: kit start-package -p 8080 ``` -where here the port provided following `-p` must match the port bound by the node or fake node (see discussion [above](#booting-a-fake-kinode-node)). +where here the port provided following `-p` must match the port bound by the node or fake node (see discussion [above](#booting-a-fake-kinode)). The node's terminal should display something like @@ -267,7 +269,7 @@ Congratulations: you've now built and installed your first application on Kinode To test out the functionality of `my_chat_app`, spin up another fake node to chat with in a new terminal: ```bash -kit boot-fake-node -h /tmp/kinode-fake-node-2 -p 8081 -f fake2.os +kit boot-fake-node -h /tmp/kinode-fake-node-2 -p 8081 -f fake2 ``` The fake nodes communicate over a mocked local network. @@ -287,20 +289,20 @@ kit start-package -p 8081 To send a chat message from the first node, run the following in its terminal: ``` -m our@my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake2.os", "message": "hello world"}}' +m our@my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake2.dev", "message": "hello world"}}' ``` and replying, from the other terminal: ``` -m our@my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake.os", "message": "wow, it works!"}}' +m our@my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake.dev", "message": "wow, it works!"}}' ``` Messages can also be injected from the outside. From a bash terminal, use `kit inject-message`, like so: ```bash -kit inject-message my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake2.os", "message": "hello from the outside world"}}' -kit inject-message my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake.os", "message": "replying from fake2.os using first method..."}}' --node fake2.os -kit inject-message my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake.os", "message": "and second!"}}' -p 8081 +kit inject-message my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake2.dev", "message": "hello from the outside world"}}' +kit inject-message my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake.dev", "message": "replying from fake2.dev using first method..."}}' --node fake2.dev +kit inject-message my_chat_app:my_chat_app:template.os '{"Send": {"target": "fake.dev", "message": "and second!"}}' -p 8081 ``` From 068f82d0db181b1dcb7f2101cf674d6dbfcd47df Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Fri, 10 May 2024 16:29:59 +0200 Subject: [PATCH 03/18] section 5 wip --- src/my_first_app/chapter_2.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/my_first_app/chapter_2.md b/src/my_first_app/chapter_2.md index 7ce41542..389adaaa 100644 --- a/src/my_first_app/chapter_2.md +++ b/src/my_first_app/chapter_2.md @@ -13,6 +13,8 @@ The last chapter explained packages, the package manifest, and metadata. Every package contains one or more processes, which are the actual Wasm programs that will run on a node. In order to compile properly to the Kinode environment, every process must generate the WIT bindings for the `process` "world". +From `/my_chat_app/src/lib.rs` in our `my_chat_app` package: + ```rust wit_bindgen::generate!({ path: "wit", @@ -41,7 +43,7 @@ fn my_init_fn(our: Address) { ``` Every Kinode process written in Rust will need code that does the same thing as the above. -The [`Address` parameter](https://docs.rs/kinode_process_lib/latest/kinode_process_lib/kinode/process/standard/struct.Address.html) tells our process what its globally-unique name is. +The globally-unique name of each process is found in the [`Address` parameter](https://docs.rs/kinode_process_lib/latest/kinode_process_lib/kinode/process/standard/struct.Address.html). Let's fill out the init function with code that will stop it from exiting immediately. Here's an infinite loop that will wait for a message and then print it out. From c95e631eec17cfd9dd9b7c148df0e4d0cee86e20 Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Mon, 13 May 2024 11:04:53 +0200 Subject: [PATCH 04/18] wip --- src/my_first_app/chapter_2.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/my_first_app/chapter_2.md b/src/my_first_app/chapter_2.md index 389adaaa..7d5a784c 100644 --- a/src/my_first_app/chapter_2.md +++ b/src/my_first_app/chapter_2.md @@ -81,8 +81,7 @@ to see this code in the node you set up in the last chapter. ## Sending a Message -Let's send a message to another process. -The `Request` type in [process_lib](../process_stdlib/overview.md) will provide all the necessary functionality. +To send a message to another process, `use` the [`Request`] (https://docs.rs/kinode_process_lib/latest/kinode_process_lib/struct.Request.html) type from the [process_lib](../process_stdlib/overview.md), which will provide all the necessary functionality. ```rust use kinode_process_lib::{await_message, call_init, println, Address, Request}; ``` From e1023adcfaa3a441ad3302c03dd16d65be071a1a Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Mon, 13 May 2024 12:59:29 +0200 Subject: [PATCH 05/18] section 5.2. --- src/my_first_app/chapter_1.md | 2 +- src/my_first_app/chapter_2.md | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/my_first_app/chapter_1.md b/src/my_first_app/chapter_1.md index a213e514..eb24e5ab 100644 --- a/src/my_first_app/chapter_1.md +++ b/src/my_first_app/chapter_1.md @@ -1,6 +1,6 @@ # Environment Setup -In this chapter, you'll walk through setting up a Kinode development environment. +In this section, you'll walk through setting up a Kinode development environment. By the end, you will have created a Kinode application, or package, composed of one or more processes that run on a live Kinode. The application will be a simple chat interface: `my_chat_app`. diff --git a/src/my_first_app/chapter_2.md b/src/my_first_app/chapter_2.md index 7d5a784c..4a383542 100644 --- a/src/my_first_app/chapter_2.md +++ b/src/my_first_app/chapter_2.md @@ -1,20 +1,26 @@ # Sending Some Messages, Using Some Tools -This chapter assumes you've completed the steps outlined in [Chapter 1](./chapter_1.md) to construct your dev environment or otherwise have a basic Kinode app open in your code editor of choice. +In this section you will learn how to use different parts of a process, how request-response handling works, and other implementation details with regards to messaging. +The process you will build is simple — it messages itself and responds to itself, printing whenever it gets messages. + +Note — the app you will build in Sections 2. through 5. is *not* `my_chat_app`; it is simply a series of examples designed to demonstrate how to use the system's features. + +## Requirements + +This section assumes you've completed the steps outlined in [Environment Setup](./chapter_1.md) to construct your development environment or otherwise have a basic Kinode app open in your code editor of choice. You should also be actively running a Kinode ([live](../login.md) or [fake](./chapter_1.md#booting-a-fake-kinode-node)) such that you can quickly compile and test your code! Tight feedback loops when building: very important. + ## Starting from Scratch If you want to hit the ground running, you can take the template code or the [chess tutorial](../chess_app/chess_engine.md) and start hacking away. Here, you'll start from scratch and learn about every line of boilerplate. -The last chapter explained packages, the package manifest, and metadata. +The last section explained packages, the package manifest, and metadata. Every package contains one or more processes, which are the actual Wasm programs that will run on a node. In order to compile properly to the Kinode environment, every process must generate the WIT bindings for the `process` "world". -From `/my_chat_app/src/lib.rs` in our `my_chat_app` package: - ```rust wit_bindgen::generate!({ path: "wit", @@ -77,7 +83,7 @@ kit build your_pkg_name kit start-package your_pkg_name -p 8080 ``` -to see this code in the node you set up in the last chapter. +to see this code in the node you set up in the last section. ## Sending a Message @@ -95,7 +101,7 @@ Request::new() .send(); ``` -Because this process might not have capabilities to message any other (local or remote) processes, just send the message to itself. +Because this process might not have capabilities to message any other (local or remote) processes, for the purposes of this tutorial, just send the message to itself. ```rust Request::new() @@ -243,11 +249,11 @@ fn my_init_fn(our: Address) { This basic structure can be found in the majority of Kinode processes. The other common structure is a thread-like process, that sends and handles a fixed series of messages and then exits. -In the next chapter, we will cover how to turn this very basic request-response pattern into something that can be extensible and composable. +In the next section, we will cover how to turn this very basic request-response pattern into something that can be extensible and composable. ## Aside: `on_exit` -As mentioned in the [previous chapter](./chapter_1.md#pkgmanifestjson), one of the fields in the `manifest.json` is `on_exit`. +As mentioned in the [previous section](./chapter_1.md#pkgmanifestjson), one of the fields in the `manifest.json` is `on_exit`. When the process exits, it does one of: `on_exit` Setting | Behavior When Process Exits From a6e35f9914cabe9fd40383bb66dd691b29583ec8 Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Mon, 13 May 2024 14:10:33 +0200 Subject: [PATCH 06/18] section 5.2. --- src/my_first_app/chapter_2.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/my_first_app/chapter_2.md b/src/my_first_app/chapter_2.md index 4a383542..8cf83460 100644 --- a/src/my_first_app/chapter_2.md +++ b/src/my_first_app/chapter_2.md @@ -11,11 +11,11 @@ This section assumes you've completed the steps outlined in [Environment Setup]( You should also be actively running a Kinode ([live](../login.md) or [fake](./chapter_1.md#booting-a-fake-kinode-node)) such that you can quickly compile and test your code! Tight feedback loops when building: very important. - ## Starting from Scratch -If you want to hit the ground running, you can take the template code or the [chess tutorial](../chess_app/chess_engine.md) and start hacking away. +If you want to hit the ground running by yourself, you can take the template code or the [chess tutorial](../chess_app/chess_engine.md) and start hacking away. Here, you'll start from scratch and learn about every line of boilerplate. +Open `src/lib.rs`, clear its contents so it's empty, and code along! The last section explained packages, the package manifest, and metadata. Every package contains one or more processes, which are the actual Wasm programs that will run on a node. @@ -32,7 +32,7 @@ After generating the bindings, every process must define a `Component` struct an This is the entry point for the process, and the `init()` function is the first function called by the Kinode runtime when the process is started. The definition of the `Component` struct can be done manually, but it's easier to import the [`kinode_process_lib`](../process_stdlib/overview.md) crate (a sort of standard library for Kinode processes written in Rust) and use the `call_init!` macro. -Note that running the process below [can lead to an infinite loop](#aside-on_exit): +Note that the process below can be made to perpetually restart and [lead to an infinite loop](#aside-on_exit): ```rust use kinode_process_lib::{call_init, println, Address}; @@ -49,7 +49,7 @@ fn my_init_fn(our: Address) { ``` Every Kinode process written in Rust will need code that does the same thing as the above. -The globally-unique name of each process is found in the [`Address` parameter](https://docs.rs/kinode_process_lib/latest/kinode_process_lib/kinode/process/standard/struct.Address.html). +The [`Address` parameter](https://docs.rs/kinode_process_lib/latest/kinode_process_lib/kinode/process/standard/struct.Address.html) tells the process what its globally-unique name is. Let's fill out the init function with code that will stop it from exiting immediately. Here's an infinite loop that will wait for a message and then print it out. @@ -79,15 +79,15 @@ These imports are the necessary "system calls" for talking to other processes an Run ```bash -kit build your_pkg_name -kit start-package your_pkg_name -p 8080 +kit build your_pkg_directory +kit start-package your_pkg_directory -p 8080 ``` to see this code in the node you set up in the last section. ## Sending a Message -To send a message to another process, `use` the [`Request`] (https://docs.rs/kinode_process_lib/latest/kinode_process_lib/struct.Request.html) type from the [process_lib](../process_stdlib/overview.md), which will provide all the necessary functionality. +To send a message to another process, `use` the [`Request`](https://docs.rs/kinode_process_lib/latest/kinode_process_lib/struct.Request.html) type from the [process_lib](../process_stdlib/overview.md), which will provide all the necessary functionality. ```rust use kinode_process_lib::{await_message, call_init, println, Address, Request}; ``` @@ -145,10 +145,12 @@ However, you'll see the "hello world" message as a byte vector. Let's modify our request to expect a response, and our message-handling to send one back, as well as parse the received request into a string. ```rust -Request::to(&our) +Request::new() + .target(&our) .body(b"hello world") .expects_response(5) .send() + .unwrap(); ``` The `expects_response` method takes a timeout in seconds. From 2cb0b2d514d3ae1e04a507260b34a394be4e3f5a Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Mon, 13 May 2024 15:14:15 +0200 Subject: [PATCH 07/18] restructuring section 5.2 and 5.3 --- src/SUMMARY.md | 2 +- src/my_first_app/chapter_2.md | 23 +++-------------- src/my_first_app/chapter_3.md | 47 ++++++++++++++++++++++++++++------- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 0620cfef..ae061b2d 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -32,7 +32,7 @@ - [`reset-cache`](./kit/reset-cache.md) - [My First Kinode Application](./build-and-deploy-an-app.md) - [Environment Setup](./my_first_app/chapter_1.md) - - [Sending Some Messages, Using Some Tools](./my_first_app/chapter_2.md) + - [Sending and Responding to a Message](./my_first_app/chapter_2.md) - [Defining Your Protocol](./my_first_app/chapter_3.md) - [Frontend Time](./my_first_app/chapter_4.md) - [Sharing with the World](./my_first_app/chapter_5.md) diff --git a/src/my_first_app/chapter_2.md b/src/my_first_app/chapter_2.md index 8cf83460..dd1c0655 100644 --- a/src/my_first_app/chapter_2.md +++ b/src/my_first_app/chapter_2.md @@ -1,4 +1,4 @@ -# Sending Some Messages, Using Some Tools +# Sending and Responding to a Message In this section you will learn how to use different parts of a process, how request-response handling works, and other implementation details with regards to messaging. The process you will build is simple — it messages itself and responds to itself, printing whenever it gets messages. @@ -32,7 +32,6 @@ After generating the bindings, every process must define a `Component` struct an This is the entry point for the process, and the `init()` function is the first function called by the Kinode runtime when the process is started. The definition of the `Component` struct can be done manually, but it's easier to import the [`kinode_process_lib`](../process_stdlib/overview.md) crate (a sort of standard library for Kinode processes written in Rust) and use the `call_init!` macro. -Note that the process below can be made to perpetually restart and [lead to an infinite loop](#aside-on_exit): ```rust use kinode_process_lib::{call_init, println, Address}; @@ -157,6 +156,8 @@ The `expects_response` method takes a timeout in seconds. If the timeout is reached, the request will be returned to the process that sent it as an error. If you add that to the code above, you'll see the error after 5 seconds in your node's terminal. +## Responding to a Message + Now, let's add some code to handle the request. The `await_message()` function returns a type that looks like this: ```rust Result @@ -252,21 +253,3 @@ This basic structure can be found in the majority of Kinode processes. The other common structure is a thread-like process, that sends and handles a fixed series of messages and then exits. In the next section, we will cover how to turn this very basic request-response pattern into something that can be extensible and composable. - -## Aside: `on_exit` - -As mentioned in the [previous section](./chapter_1.md#pkgmanifestjson), one of the fields in the `manifest.json` is `on_exit`. -When the process exits, it does one of: - -`on_exit` Setting | Behavior When Process Exits ------------------ | --------------------------- -`"None"` | Do nothing -`"Restart"` | Restart the process -JSON object | Send the requests described by the JSON object - -A process intended to do something once and exit should have `"None"` or a JSON object `on_exit`. -If it has `"Restart"`, it will repeat in an infinite loop, as referenced [above](#starting-from-scratch). - -A process intended to run over a period of time and serve requests and responses will often have `"Restart"` `on_exit` so that, in case of crash, it will start again. -Alternatively, a JSON object `on_exit` can be used to inform another process of its untimely demise. -In this way, Kinode processes become quite similar to Erlang processes in that crashing can be [designed into your process to increase reliability](https://ferd.ca/the-zen-of-erlang.html). diff --git a/src/my_first_app/chapter_3.md b/src/my_first_app/chapter_3.md index 1b11e861..6ad05681 100644 --- a/src/my_first_app/chapter_3.md +++ b/src/my_first_app/chapter_3.md @@ -1,15 +1,22 @@ -# Defining Your Protocol +# Messaging with Larger Data Types -In the last chapter, you created a simple request-response pattern that uses strings as a `body` field type. +In this section, you will upgrade your app so that it can handle messages with more elaborate data typs such as `enum`s and `struct`s. Additionally, you will learn why and how to exit out of a process. + +## (De)Serialization With Serde + +In the last section, you created a simple request-response pattern that uses strings as a `body` field type. This is fine for certain limited cases, but in practice, most Kinode processes written in Rust use a `body` type that is serialized and deserialized to bytes using [Serde](https://serde.rs/). There are a multitude of libraries that implement Serde's `Serialize` and `Deserialize` traits, and the process developer is responsible for selecting a strategy that is appropriate for their use case. Some popular options are `bincode` and `serde_json`. -In this chapter, you will use `serde_json` to serialize your Rust structs to a byte vector of JSON. +In this section, you will use `serde_json` to serialize your Rust structs to a byte vector of JSON. + +### Defining the `body` Type Our old request looked like this: ```rust -Request::to(&our) +Request::new() + .targer(&our) .body(b"hello world") .expects_response(5) .send(); @@ -44,7 +51,7 @@ impl MyBody { } ``` -Now, when you form requests and response, instead of sticking a string in the `body` field, you can use the new `body` type. +Now, when you form requests and responses, instead of sticking a string in the `body` field, you can use the new `MyBody` type. This comes with a number of benefits: - You can now use the `body` field to send arbitrary data, not just strings. @@ -56,6 +63,8 @@ Defining `body` types is just one step towards writing interoperable code. It's also critical to document the overall structure of the program along with message `blob`s and `metadata` used, if any. Writing interoperable code is necessary for enabling permissionless composability, and Kinode OS aims to make this the default kind of program, unlike the centralized web. +### Handling Messages + First, create a request that uses the new `body` type (and stop expecting a response): ```rust Request::new() @@ -66,7 +75,7 @@ Request::new() Next, edit the way you handle a message in your process to use your new `body` type. The process should attempt to parse every message into the `MyBody` enum, handle the two cases, and handle any message that doesn't comport to the type. -This code goes into the `Ok(message)` case of the `match` statement on `await_message()`: +This part of code goes into the `Ok(message)` case of the `match` statement on `await_message()`: ```rust let Ok(body) = MyBody::parse(message.body()) else { println!("{our}: received a message with weird `body`!"); @@ -95,6 +104,8 @@ if message.is_request() { } ``` +### Granting Capabilities + Finally, edit your `pkg/manifest.json` to grant the terminal process permission to send messages to this process. That way, you can use the terminal to send Hello and Goodbye messages. Go into the manifest, and under the process name, edit (or add) the `grant_capabilities` field like so: @@ -106,6 +117,8 @@ Go into the manifest, and under the process name, edit (or add) the `grant_capab ... ``` +### Build and Run the Code! + After all this, your code should look like: ```rust use serde::{Serialize, Deserialize}; @@ -185,7 +198,7 @@ You should be able to build and start your package, then see that initial Hello At this point, you can use the terminal to test your message types! First, try a hello. Get the address of your process by looking at the "started" printout that came from it in the terminal. -As a reminder, these values are set in the `metadata.json` and `manifest.json` package files. +As a reminder, these values (``, ``, ``) can be found in the `metadata.json` and `manifest.json` package files. ```bash m our@:: '{"Hello": "hey there"}' ``` @@ -196,8 +209,24 @@ This will cause the process to exit. m our@:: '"Goodbye"' ``` -If you try to send another Hello now, nothing will happen, because the process has exited [(assuming you have set `on_exit: "None"`; with `on_exit: "Restart"` it will immediately start up again)](./chapter_2.md#aside-on_exit). +If you try to send another 'Hello' now, nothing will happen, because the process has exited [(assuming you have set `on_exit: "None"`; with `on_exit: "Restart"` it will immediately start up again)](#aside-on_exit). Nice! You can use `kit start-package` to try again. -In the next chapter, you'll add some basic HTTP logic to serve a frontend from your simple process. +## Aside: `on_exit` + +As mentioned in the [previous section](./chapter_1.md#pkgmanifestjson), one of the fields in the `manifest.json` is `on_exit`. +When the process exits, it does one of: + +`on_exit` Setting | Behavior When Process Exits +----------------- | --------------------------- +`"None"` | Do nothing +`"Restart"` | Restart the process +JSON object | Send the requests described by the JSON object + +A process intended to do something once and exit should have `"None"` or a JSON object `on_exit`. +If it has `"Restart"`, it will repeat in an infinite loop. + +A process intended to run over a period of time and serve requests and responses will often have `"Restart"` `on_exit` so that, in case of crash, it will start again. +Alternatively, a JSON object `on_exit` can be used to inform another process of its untimely demise. +In this way, Kinode processes become quite similar to Erlang processes in that crashing can be [designed into your process to increase reliability](https://ferd.ca/the-zen-of-erlang.html). From 92d636289ada8b5901bef781ca895901b74aac44 Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Mon, 13 May 2024 15:26:10 +0200 Subject: [PATCH 08/18] chapter -> section --- src/my_first_app/chapter_4.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/my_first_app/chapter_4.md b/src/my_first_app/chapter_4.md index d108b6b9..a1f72870 100644 --- a/src/my_first_app/chapter_4.md +++ b/src/my_first_app/chapter_4.md @@ -1,9 +1,9 @@ # Frontend Time -After the last chapter, you should have a simple process that responds to two commands from the terminal. -In this chapter, you'll add some basic HTTP logic to serve a frontend and accept an HTTP PUT request that contains a command. +After the last section, you should have a simple process that responds to two commands from the terminal. +In this section, you'll add some basic HTTP logic to serve a frontend and accept an HTTP PUT request that contains a command. -If you're the type of person that prefers to learn by looking at a complete example, check out the [chess frontend chapter](../chess_app/frontend.md) for a fleshed-out example and a link to some frontend code. +If you're the type of person that prefers to learn by looking at a complete example, check out the [chess frontend section](../chess_app/frontend.md) for a fleshed-out example and a link to some frontend code. ## Adding HTTP request handling @@ -62,7 +62,7 @@ loop { Note that different apps will want to discriminate between incoming messages differently. This code doesn't check the `source.node` at all, for example. -The `handle_hello_message` will look just like what was in chapter 3. +The `handle_hello_message` will look just like what was in [Section 5.3.](./chapter_3.md) However, since this logic is no longer inside the main loop, return a boolean to indicate whether or not to exit out of the loop. Request handling can be separated out into as many functions is needed to keep the code clean. ```rust @@ -359,4 +359,4 @@ Note that you can now set `authenticated` to `true` in the `/api` binding and th This frontend is now fully packaged with the process — there are no more steps! Of course, this can be made arbitrarily complex with various frontend frameworks that produce a static build. -In the next and final chapter, learn about the package metadata and how to share this app across the Kinode network. +In the next and final section, learn about the package metadata and how to share this app across the Kinode network. From 9c07acee77d43243a73e1ca498321bd44db48f88 Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Mon, 13 May 2024 15:31:44 +0200 Subject: [PATCH 09/18] add subsections to 5.4 --- src/my_first_app/chapter_4.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/my_first_app/chapter_4.md b/src/my_first_app/chapter_4.md index a1f72870..6119ca33 100644 --- a/src/my_first_app/chapter_4.md +++ b/src/my_first_app/chapter_4.md @@ -97,6 +97,8 @@ fn handle_hello_message(message: &Message) -> bool { } ``` +### Handling an HTTP Message + Finally, let's define `handle_http_message`. ```rust fn handle_http_message(our: &Address, message: &Message) { @@ -151,6 +153,8 @@ Request::to(our).body(body.bytes).send().unwrap(); Putting it all together, you get a process which you can build and start, then use cURL to send Hello and Goodbye requests via HTTP PUTs! +### Requesting Capabilities + Also, remember to request the capability to message `http_server` in `manifest.json`: ```json ... @@ -160,7 +164,8 @@ Also, remember to request the capability to message `http_server` in `manifest.j ... ``` -Here's the full code: +### The Full Code + ```rust use serde::{Deserialize, Serialize}; use kinode_process_lib::{ From f0bbd1ee0854d44fc119760a2a08d5a46bb5c8de Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Tue, 14 May 2024 09:38:49 +0200 Subject: [PATCH 10/18] wip --- src/my_first_app/chapter_1.md | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/my_first_app/chapter_1.md b/src/my_first_app/chapter_1.md index eb24e5ab..47ba6dc6 100644 --- a/src/my_first_app/chapter_1.md +++ b/src/my_first_app/chapter_1.md @@ -65,23 +65,36 @@ my_chat_app │ ├── Cargo.toml │ └── src │ └── lib.rs - └── pkg - ├── manifest.json - └── scripts.json + ├── pkg + │ ├── manifest.json + │ └── scripts.json + └── send + ├── Cargo.toml + └── src + └── lib.rs ``` -The `my_chat_app/` package here contains one process, also named `my_chat_app/`. -The process directory contains source files and other metadata for compiling that process. +The `my_chat_app/` package here contains two processes: +- `my_chat_app/` — containing the main application code, and +- `send/` — containing a [script](../cookbook/writing_scripts.html). -In Rust processes, the standard Rust `Cargo.toml` file is included: it specifies dependencies. +Each process directory contains: +- `src/` - source files where the code for the process lives, and +- `Cargo.toml` - other metadata for compiling that process. + +The standard Rust `Cargo.toml` file which specifies dependencies is also included in `my_chat_app/` root. It is exhaustively defined [here](https://doc.rust-lang.org/cargo/reference/manifest.html). -The `src/` directory is where the code for the process lives. Also within the package directory is a `pkg/` directory. -The `pkg/` dirctory contains two files, `manifest.json`, which specifes information the Kinode needs to run the package, and `scripts.json`. +The `pkg/` dirctory contains two files: +- `manifest.json` - specifes information the Kinode needs to run the package, and +- `scripts.json` - specifies details needed to run [scripts](../cookbook/writing_scripts.html). + The `pkg/` directory is also where `.wasm` binaries will be deposited by [`kit build`](#building-the-package). The files in the `pkg/` directory are injected into the Kinode with [`kit start-package`](#starting-the-package). +Lastly, `metadata.json` contains app metadata which is used in the Kinode [App Store](./chapter_5.html) + Though not included in this template, packages with a frontend have a `ui/` directory as well. For an example, look at the result of: ```bash @@ -91,6 +104,9 @@ tree my_chat_app_with_ui Note that not all templates have a UI-enabled version. As of 240118, only the Rust chat template has a UI-enabled version. +[link](../kit/new.html#existshas-ui-enabled-vesion) + + ### `pkg/manifest.json` The `manifest.json` file contains information the Kinode needs in order to run the package: From 820a7d5b3d683b6a54ce9a0fba66a1a0130014bf Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Tue, 14 May 2024 09:50:09 +0200 Subject: [PATCH 11/18] section 5.1 --- src/my_first_app/chapter_1.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/my_first_app/chapter_1.md b/src/my_first_app/chapter_1.md index 47ba6dc6..be25a1d8 100644 --- a/src/my_first_app/chapter_1.md +++ b/src/my_first_app/chapter_1.md @@ -101,11 +101,7 @@ For an example, look at the result of: kit new my_chat_app_with_ui --ui tree my_chat_app_with_ui ``` -Note that not all templates have a UI-enabled version. -As of 240118, only the Rust chat template has a UI-enabled version. - -[link](../kit/new.html#existshas-ui-enabled-vesion) - +Note that not all templates have a UI-enabled version. More details about templates can be found [here](../kit/new.html#existshas-ui-enabled-version). ### `pkg/manifest.json` From fe365f0dc80e93ed5e2d53be4a1ca289c1812f1a Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Tue, 14 May 2024 12:09:01 +0200 Subject: [PATCH 12/18] wasm explanations --- src/my_first_app/chapter_2.md | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/my_first_app/chapter_2.md b/src/my_first_app/chapter_2.md index dd1c0655..3baa1045 100644 --- a/src/my_first_app/chapter_2.md +++ b/src/my_first_app/chapter_2.md @@ -19,7 +19,29 @@ Open `src/lib.rs`, clear its contents so it's empty, and code along! The last section explained packages, the package manifest, and metadata. Every package contains one or more processes, which are the actual Wasm programs that will run on a node. -In order to compile properly to the Kinode environment, every process must generate the WIT bindings for the `process` "world". + +The [Generating WIT Bindings](#generating-wit-bindings) and [`init()` Function](#init-function) subsections explain the boilerplate code in detail, so if you just want to run some code, you can skip to [Running First Bits of Code](#running-first-bits-of-code). + +### Generating WIT Bindings + +For the purposes of this tutorial, crucial information from this [WASM documentation](https://component-model.bytecodealliance.org/design/why-component-model.html) has been abridged in this small subsection. + +A [Wasm component](https://component-model.bytecodealliance.org/design/components.html) is a wrapper around a core module that specifies its imports and exports. +E.g. a Go component can communicate directly and safely with a C or Rust component. +It need not even know which language another component was written in - it needs only the component interface, expressed in WIT. + +The external interface of a component - its imports and exports - is described by a [`world`](https://component-model.bytecodealliance.org/design/wit.html#worlds). +Exports are provided by the component, and define what consumers of the component may call; imports are things the component may call. +The component, however, internally defines how that `world` is implemented. +This interface is defined via [WIT](https://component-model.bytecodealliance.org/design/wit.html). + +WIT bindings are the glue code which is necessary for the interaction between WASM modules and their host environment. +They may be written in any WASM-compatible language — for Kinode they are written in Rust. +The `world`, types, imports, and exports are all declared in a [WIT file](https://github.com/kinode-dao/kinode-wit/blob/master/kinode.wit), and using that file, [`wit_bindgen`](https://github.com/bytecodealliance/wit-bindgen) generates the code for the bindings. + +So, to bring it all together... + +In order to compile properly to the Kinode environment, based on the WIT file, every process must generate the WIT bindings for the `process` `world`, which is an interface for the Kinode kernel. ```rust wit_bindgen::generate!({ @@ -28,7 +50,10 @@ wit_bindgen::generate!({ }); ``` -After generating the bindings, every process must define a `Component` struct and implement the `Guest` trait for it defining a single function, `init()`. +### `init()` Function + +After generating the bindings, every process must define a `Component` struct which implements the `Guest` trait (i.e. a wrapper around the process which defines the export interface, as discussed [above](#generating-wit-bindings)). +The `Guest` trait should define a single function — `init()`. This is the entry point for the process, and the `init()` function is the first function called by the Kinode runtime when the process is started. The definition of the `Component` struct can be done manually, but it's easier to import the [`kinode_process_lib`](../process_stdlib/overview.md) crate (a sort of standard library for Kinode processes written in Rust) and use the `call_init!` macro. @@ -47,7 +72,10 @@ fn my_init_fn(our: Address) { } ``` -Every Kinode process written in Rust will need code that does the same thing as the above. +### Running First Bits of Code + +Every Kinode process written in Rust will need code that does the same thing as the code above (i.e. to use the `wit_bindgen` and `call_init!` macros). + The [`Address` parameter](https://docs.rs/kinode_process_lib/latest/kinode_process_lib/kinode/process/standard/struct.Address.html) tells the process what its globally-unique name is. Let's fill out the init function with code that will stop it from exiting immediately. From ec03bab3bd444681196ad6a3eaf98da68b5779c2 Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Tue, 14 May 2024 12:40:23 +0200 Subject: [PATCH 13/18] section 5.3. --- src/my_first_app/chapter_3.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/my_first_app/chapter_3.md b/src/my_first_app/chapter_3.md index 6ad05681..3838c56b 100644 --- a/src/my_first_app/chapter_3.md +++ b/src/my_first_app/chapter_3.md @@ -65,12 +65,15 @@ Writing interoperable code is necessary for enabling permissionless composabilit ### Handling Messages -First, create a request that uses the new `body` type (and stop expecting a response): +In this example, you will learn how to handle a Request. +So, create a request that uses the new `body` type (you won't need to send a Response back, so we can remove `.expect_response()`): + ```rust Request::new() .target(&our) .body(MyBody::hello("hello world")) - .send(); + .send() + .unwrap(); ``` Next, edit the way you handle a message in your process to use your new `body` type. @@ -197,14 +200,16 @@ fn my_init_fn(our: Address) { You should be able to build and start your package, then see that initial Hello message. At this point, you can use the terminal to test your message types! -First, try a hello. Get the address of your process by looking at the "started" printout that came from it in the terminal. +First, try sending a hello using the [`m` terminal script](../terminal.md#m---message-a-process). Get the address of your process by looking at the "started" printout that came from it in the terminal. As a reminder, these values (``, ``, ``) can be found in the `metadata.json` and `manifest.json` package files. + ```bash m our@:: '{"Hello": "hey there"}' ``` You should see the message text printed. Next, try a goodbye. This will cause the process to exit. + ```bash m our@:: '"Goodbye"' ``` From 6881aef2e14addfa6b80741e7c9a83e4c68c1ca7 Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Tue, 14 May 2024 14:12:55 +0200 Subject: [PATCH 14/18] add unwrap() --- src/my_first_app/chapter_3.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/my_first_app/chapter_3.md b/src/my_first_app/chapter_3.md index 3838c56b..03a7e46b 100644 --- a/src/my_first_app/chapter_3.md +++ b/src/my_first_app/chapter_3.md @@ -16,10 +16,11 @@ In this section, you will use `serde_json` to serialize your Rust structs to a b Our old request looked like this: ```rust Request::new() - .targer(&our) + .target(&our) .body(b"hello world") .expects_response(5) - .send(); + .send() + .unwrap(); ``` What if you want to have two kinds of messages, which your process can handle differently? @@ -159,7 +160,8 @@ fn my_init_fn(our: Address) { Request::new() .target(&our) .body(MyBody::hello("hello world")) - .send(); + .send() + .unwrap(); loop { match await_message() { From 27c74ad5495fd8515e1788d64638401f81c8f67d Mon Sep 17 00:00:00 2001 From: jurij-jukic Date: Tue, 14 May 2024 16:22:34 +0200 Subject: [PATCH 15/18] section 5.4. --- src/SUMMARY.md | 2 +- src/my_first_app/chapter_4.md | 73 +++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index dfa03d8d..5ce6b9be 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -35,7 +35,7 @@ - [My First Kinode Application](./build-and-deploy-an-app.md) - [Environment Setup](./my_first_app/chapter_1.md) - [Sending and Responding to a Message](./my_first_app/chapter_2.md) - - [Defining Your Protocol](./my_first_app/chapter_3.md) + - [Messaging with Larger Data Types](./my_first_app/chapter_3.md) - [Frontend Time](./my_first_app/chapter_4.md) - [Sharing with the World](./my_first_app/chapter_5.md) - [In-Depth Guide: Chess App](./chess_app.md) diff --git a/src/my_first_app/chapter_4.md b/src/my_first_app/chapter_4.md index 6119ca33..7a71fe33 100644 --- a/src/my_first_app/chapter_4.md +++ b/src/my_first_app/chapter_4.md @@ -11,33 +11,37 @@ Using the built-in HTTP server will require handling a new type of request in ou The [process_lib](../process_stdlib/overview.md) contains types and functions for doing so. At the top of your process, import `http`, `get_blob`, and `Message` from [`kinode_process_lib`](../process_stdlib/overview.md) along with the rest of the imports. -You'll use `get_blob()` to grab the body bytes of an incoming HTTP request. +You'll use `get_blob()` to grab the `body` bytes of an incoming HTTP request. ```rust use kinode_process_lib::{ await_message, call_init, get_blob, http, println, Address, Message, Request, Response, }; ``` -Keep the custom `body` type the same, and keep using that for terminal input. +Keep the custom `body` type (i.e. `MyBody`) the same, and keep using that for terminal input. -At the beginning of the init function, in order to receive HTTP requests, you must use the `kinode_process_lib::http` library to bind a new path. Binding a path will cause the process to receive all HTTP requests that match that path. +At the beginning of the init function (here `my_init_fn()`), in order to receive HTTP requests, you must use the [`kinode_process_lib::http`](https://docs.rs/kinode_process_lib/latest/kinode_process_lib/http/index.html) library to bind a new path. +Binding a path will cause the process to receive all HTTP requests that match that path. You can also bind static content to a path using another function in the library. + ```rust // ... fn my_init_fn(our: Address) { println!("{our}: started"); - // the first argument is the path to bind. Note that requests will be namespaced - // under the process name, so this will be accessible at /my_process/ - // the second argument marks whether to serve the path only to authenticated clients, - // and the third argument marks whether to only serve the path locally. - // in order to skip authentication, set the second argument to false here. http::bind_http_path("/", false, false).unwrap(); // ... } // ... ``` +`http::binf_http_path("/", false, false)` arguments mean the following: +- The first argument is the path to bind. +Note that requests will be namespaced under the process name, so this will be accessible at e.g. `/my_process_name/`. +- The second argument marks whether to serve the path only to authenticated clients +In order to skip authentication, set the second argument to false here. +- The third argument marks whether to only serve the path locally. + Now that you're handling multiple kinds of requests, let's refactor the loop to be more concise and move the request-specific logic to dedicated functions. Put this right under the bind command: ```rust @@ -60,7 +64,7 @@ loop { ``` Note that different apps will want to discriminate between incoming messages differently. -This code doesn't check the `source.node` at all, for example. +This code doesn't check the [`source`](https://docs.rs/kinode_process_lib/latest/kinode_process_lib/enum.Message.html#method.source)[`.node`](https://docs.rs/kinode_process_lib/latest/kinode_process_lib/kinode/process/standard/struct.Address.html#method.node) at all, for example. The `handle_hello_message` will look just like what was in [Section 5.3.](./chapter_3.md) However, since this logic is no longer inside the main loop, return a boolean to indicate whether or not to exit out of the loop. @@ -106,7 +110,8 @@ fn handle_http_message(our: &Address, message: &Message) { } ``` -Instead of parsing our `body` type from the message, parse the type that the `http_server` process gives us. This type is defined in the `kinode_process_lib::http` module for us: +Instead of directly parsing the `body` type from the message, parse the type that the `http_server` process gives us. +This type is defined in the `kinode_process_lib::http` module for us: ```rust // ... let Ok(server_request) = http::HttpServerRequest::from_bytes(message.body()) else { @@ -116,9 +121,10 @@ let Ok(server_request) = http::HttpServerRequest::from_bytes(message.body()) els // ... ``` -Next, you must parse out the HTTP request from the general type. +Next, you must parse out the HTTP request from the `HttpServerRequest`. This is necessary because the `HttpServerRequest` enum contains both HTTP protocol requests and requests related to WebSockets. -Note that it's quite possible to streamline this series of request refinements if you're only interested in one type of request — this example is overly thorough for demonstration purposes. +If your application only needs to handle one type of request (e.g., only HTTP requests), you could simplify the code by directly handling that type without having to check for a specific request type from the `HttpServerRequest` enum each time. +This example is overly thorough for demonstration purposes. ```rust // ... @@ -139,7 +145,7 @@ if http_request.method().unwrap() != http::Method::PUT { // ... ``` -Finally, grab the `blob` from the request, send a 200 OK response to the client, and handle the `blob`, by sending a Request to ourselves with the `blob` as the `body`. +Finally, grab the `blob` from the request, send a `200 OK` response to the client, and handle the `blob` by sending a `Request` to ourselves with the `blob` as the `body`. This could be done in a different way, but this simple pattern is useful for letting HTTP requests masquerade as in-Kinode requests. ```rust // ... @@ -147,11 +153,11 @@ let Some(body) = get_blob() else { println!("received a PUT HTTP request with no body, skipping"); return; }; -http::send_response(http::StatusCode::OK, None, vec![]).unwrap(); -Request::to(our).body(body.bytes).send().unwrap(); +http::send_response(http::StatusCode::OK, None, vec![]); +Request::new().target(our).body(body.bytes).send().unwrap(); ``` -Putting it all together, you get a process which you can build and start, then use cURL to send Hello and Goodbye requests via HTTP PUTs! +Putting it all together, you get a process which you can build and start, then use cURL to send 'Hello' and 'Goodbye' requests via HTTP PUTs! ### Requesting Capabilities @@ -245,8 +251,8 @@ fn handle_http_message(our: &Address, message: &Message) { println!("received a PUT HTTP request with no body, skipping"); return; }; - http::send_response(http::StatusCode::OK, None, vec![]).unwrap(); - Request::to(our).body(body.bytes).send().unwrap(); + http::send_response(http::StatusCode::OK, None, vec![]); + Request::new().target(our).body(body.bytes).send().unwrap(); } /// Returns true if the process should exit. @@ -282,10 +288,11 @@ fn handle_hello_message(message: &Message) -> bool { A cURL command to send a Hello request looks like this. Make sure to replace the URL with your node's local port and the correct process name. -Note: if you had not set `authenticated` to false in the bind command, you would need to add an `Authorization` header to this request with the JWT cookie of your node. +Note: if you had not set `authenticated` to false in the bind command, you would need to add an `Authorization` header to this request with the [JWT](https://jwt.io/) cookie of your node. This is saved in your browser automatically on login. + ```bash -curl -X PUT -H "Content-Type: application/json" -d '{"Hello": "greetings"}' "http://localhost:8080/tutorial:tutorial:template.os" +curl -X PUT -H "Content-Type: application/json" -d '{"Hello": "greetings"}' "http://localhost:8080/my_process:my_package:template.os" ``` ## Serving a static frontend @@ -293,18 +300,23 @@ curl -X PUT -H "Content-Type: application/json" -d '{"Hello": "greetings"}' "htt If you just want to serve an API, you've seen enough now to handle PUTs and GETs to your heart's content. But the classic personal node app also serves a webpage that provides a user interface for your program. -You *could* add handling to our `/` path to dynamically serve some HTML on every GET. -But for maximum ease and efficiency, use the static bind command on `/` and move our PUT handling to `/api`. +You *could* add handling to root `/` path to dynamically serve some HTML on every GET. +But for maximum ease and efficiency, use the static bind command on `/` and move the PUT handling to `/api`. To do this, edit the bind commands in `my_init_fn` to look like this: + ```rust http::bind_http_path("/api", true, false).unwrap(); -http::serve_index_html(&our, "ui").unwrap(); +http::serve_index_html(&our, "ui", true, false, vec!["/"]).unwrap(); ``` +Note that you are setting `authenticated` to `true` in the `serve_index_html` and `bind_http_path` calls. +The result of this is that the webpage will be able to get served by the browser, but not by the raw cURL request. + Now you can add a static `index.html` file to the package. UI files are stored in the `ui/` directory and built into the application by `kit build` automatically. -Create a new file in `ui/index.html` with the following contents. +Create a `ui/` directory in the package root, and then a new file in `ui/index.html` with the following contents. **Make sure to replace the fetch URL with your process ID!** + ```html @@ -323,7 +335,7 @@ Create a new file in `ui/index.html` with the following contents.