From d5673ca0e468f423cabfc701be08c31bd8c53559 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Thu, 12 May 2022 23:39:10 +0200 Subject: [PATCH 01/17] Add INTEGRATION document This should help other application developers to have an idea of what's needed to add IPFS support to their application. --- INTEGRATION.md | 46 +++++++++++++++++++++++++++ img/gateway_decision_tree.drawio | 1 + img/gateway_decision_tree.drawio.png | Bin 0 -> 65528 bytes 3 files changed, 47 insertions(+) create mode 100644 INTEGRATION.md create mode 100644 img/gateway_decision_tree.drawio create mode 100644 img/gateway_decision_tree.drawio.png diff --git a/INTEGRATION.md b/INTEGRATION.md new file mode 100644 index 000000000..32f355d5a --- /dev/null +++ b/INTEGRATION.md @@ -0,0 +1,46 @@ +# ![](https://img.shields.io/badge/status-reliable-green.svg?style=flat-square) Integration + +**Author(s)**: +- [Mark Gaiser](https://github.com/markg85/) + +* * * + +**Abstract** +This integration spec defines the recommended way for a third party applications to integrate IPFS support in their application and thereby gaining easy access to resources stored on the IPFS platform. + +Integration here has 2 different meanings. + +1. The implementing application can handle IPFS resources with the `ipfs` and `ipns` protocols. As an example, the implementing application should be able to handle a url in this format `ipfs://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T` which in this case would give you the big buck bunny video. Likewise a url in the `ipns` should be handled too. Making these protocols usable is left up to the specific application implementing this support. + +2. One way to handle the protocols is to use the [http gateway](https://docs.ipfs.io/concepts/ipfs-gateway/). This document describes how to determine that gateway to use. + +## Decision tree +The below decision tree defines the decisions to be made in order to choose the proper gateway. Do note that the tree, when implementing this logic, is more elaborate then this tree makes you think it is. Validation here is left out of the tree but should be added in a local implementation. + + + +### Gateway from command argument +An application can opt to support a command line option to provide a gateway. A user should not _need_ to provide this and should therefore be considered optional. However, if a user does provide this option then it should overrule any other gateway detection and be used as the gateway to use. An example implementation that is doing this is ffmpeg with the ffplay utility. It allows the `-gateway` argument which by default is empty but can be set like: `-gateway http://127.0.0.1:8080` and would then be used to handle `ipfs://` or `ipns://`. + +### Gateway from IPFS_GATEWAY environment variable +When there is no command line argument, the `IPFS_GATEWAY` environment is next. If it contains a value, it should be used as gateway. + +### Gateway file from IPFS_PATH environment variable or home folder ipfs data +If the `IPFS_PATH` environment variable is defined, it should point to the ipfs data folder. If this environment variable isn't defined then an attempt should be made to see if `$USER/.ipfs` exists and consider that to be the IPFS data folder when it does. + +If this turns out to be an existing path the existence of the `gateway` file in that path should be checked for. If gateway file exists then the first line should be considered to be the full url to the local IPFS gateway. + +Do note that this gateway file logic relies on the future implementation of having the gateway file be auto-generated. It's specced [here](https://github.com/ipfs/go-ipfs/issues/8847) and is approved to be implemented. Implementers of this spec should act as if that gateway file already exists! + +## Validation and fallback gateway +All of the above describes what should ideally be done. Validation of any kind is up to the application implementing IPFS support. To provide an "always working" feeling, implementers are recommended to use a fallback gateway and they should pick `dweb.link`. That gateway is maintained by Protocol Labs and is allowed to be used for this purpose. + +## Current implementations +### ffmpeg +As of ffmpeg 5.1, it implements this logic. The source for it's implementation can be found [here](https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/ipfsgateway.c). + +### mpv +mpv itself isn't doing any of this, it relies on the ffmpeg implementation. + +### curl +The implementation for curl is currently a work in progress but it too follows the steps as outlined in the decision tree. \ No newline at end of file diff --git a/img/gateway_decision_tree.drawio b/img/gateway_decision_tree.drawio new file mode 100644 index 000000000..cef9394ef --- /dev/null +++ b/img/gateway_decision_tree.drawio @@ -0,0 +1 @@ +7VrbcqM4EP0aHpPiDnm0PblM1c5udjO1s5mXLdkI0EZGHiHH9n79SEZchLDj2M7gSlzlKtONaKD7NOdIYDij6fKWgln6hUQQG7YZLQ3nk2HblmvbhviZ0arwBL5TOBKKIjmodjyg/6F0mtI7RxHMlYGMEMzQTHVOSJbBCVN8gFKyUIfFBKtnnYEEao6HCcC69xuKWCq9lmnWO+4gSlJ56tCTO8Zg8pRQMs/k+TKSwWLPFJRh5NA8BRFZNFzOteGMKCGs2JouRxCLtJYZK4672bC3umQKM7bLAb4dXjlWGE7MGASOb1/ICM8Az2UaUpCLYgAGF2C1Tm0yn4rwxQ2wVZmvRYoYfJiBibAXHBOGM0zZFHPL4pvrhMBIWtWNCwODMcTDKmsjggnlu9Z540MZJU9VCcT4mGTsBkwRFsj6G9IIZEC6JYwsnskhwCjJuDHhVwt5wKGenfJ2IWVw2XDJbN1CMoWMituWe90SnRLUrjQXDYSUxU0b4PClD0hQJlXkujp8QxZox2I5WrEM28dM5GwGMr6diO3P9zcP/94Ovl5/GzyKdoQxyngd5MgxLceVHn4ZzcM/aJ19tc5W0GehXa3QvxOtLnXiTbUiL6UdUDYQz8uW7waJC1wHW9sy5R63YRa1juCexnhuNUb/BxlbSRvMGeEuQllKEpIB/BshsxIr+wFgU7FzMqcTuP05x+8rgWzDGNleMFJ4YiNkLsxLxzRtFTYyBoUYMPSsMksXRGT0e4LEE7YaQuI4h0zDUHUR+8HK02D1yAn3jKvywcIRRVf/iHNfeqX5WF0KNz4tFWslraNBkvnJ3fj7n4PPz/Fo8N1Nfvx14ZXjXgNL70pB5YVlnTIsS4pt4LKWHzElU/6nktq+DGUewlB2vwzlBCpDOXafDGXpwrGuGRIKMkY0F9jBSKhhE2VrH4Yfs3ptHemHvVbPPzPBsRWGs4PCKEHwime57Vtu62F+0hrDCs7a9XQ0RgcoOzWG/WpcWvaVisuy8CcBy21CapvMOHyxo2eS0hipAzMby2oHPU6Ct+FylxWP+8HXu4+03HFQpT2/RzHZWWl9beusSBTeWCJW0IbtSfNRRhDbNWsIY2U0KGRvsmmxwYvss62DX2Qg/5SEUD1Gnr997krOlzGKPMijtgRqN55ntgIVidICHYsDzyuLO3ZZ8C67zNvaZbzJQitUJYD963rMC47TY4HTb4/py6wgz7msNAShund/fLnmYS/RLNb5jUdHs1y0wQsy5AgawHfVNLm2t5sGCN9KA+irEl1qj///mIsXpsNKvpeOaonJ5A2as7xcd5qJ97BnRbh1gSpsNY3TtyIMO9BwJqo2Ue3DUkFfNOUdTQyGYfsVh38YUa06D9iFtlqN4+9LW76pBgranfXGtHWl9dt5BvYeZmC+3nQbXuec7PTLK5/9h/aY66vyNvjF0rDjVed5/vUO5l8dPdZd/1NaV+9YnPCORGWtGZjfnjK8dZvpS/0xwFh8qGk0v21cv6iOFnB8iVH2pPXhh1j099r65Q3fTHOz/ta1KHb9LbFz/RM= \ No newline at end of file diff --git a/img/gateway_decision_tree.drawio.png b/img/gateway_decision_tree.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..a40e9d05f5455ee5586cf304ad276165513e668e GIT binary patch literal 65528 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 literal 0 HcmV?d00001 From 9630240ed39705c45ea66b41abe6d70e235e2776 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Mon, 21 Nov 2022 14:56:31 +0100 Subject: [PATCH 02/17] Update INTEGRATION.md Co-authored-by: Marcin Rataj --- INTEGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INTEGRATION.md b/INTEGRATION.md index 32f355d5a..c27811207 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -12,7 +12,7 @@ Integration here has 2 different meanings. 1. The implementing application can handle IPFS resources with the `ipfs` and `ipns` protocols. As an example, the implementing application should be able to handle a url in this format `ipfs://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T` which in this case would give you the big buck bunny video. Likewise a url in the `ipns` should be handled too. Making these protocols usable is left up to the specific application implementing this support. -2. One way to handle the protocols is to use the [http gateway](https://docs.ipfs.io/concepts/ipfs-gateway/). This document describes how to determine that gateway to use. +2. One way to handle the protocols is to use the [HTTP gateway](https://docs.ipfs.tech/concepts/ipfs-gateway/). This document describes how to determine which gateway to use. ## Decision tree The below decision tree defines the decisions to be made in order to choose the proper gateway. Do note that the tree, when implementing this logic, is more elaborate then this tree makes you think it is. Validation here is left out of the tree but should be added in a local implementation. From f956141c46a446f28cb606bdd4f2570c7352d44b Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Sat, 26 Nov 2022 14:32:21 +0100 Subject: [PATCH 03/17] rewrote integration document --- INTEGRATION.md | 98 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/INTEGRATION.md b/INTEGRATION.md index c27811207..802aa2af6 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -1,11 +1,28 @@ -# ![](https://img.shields.io/badge/status-reliable-green.svg?style=flat-square) Integration +# Gateway Integration +![](https://img.shields.io/badge/status-reliable-green.svg?style=flat-square) **Author(s)**: - [Mark Gaiser](https://github.com/markg85/) +**Maintainer(s)**: +- [Mark Gaiser](https://github.com/markg85/) + * * * -**Abstract** +## Summary +Defines the decision model an IPFS enabled application should use to find a IPFS gateway. Simultaneously defines how an IPFS implementation should expose it's gateway. + +For clarity upon reading this document: +**IPFS implementation**, this is an application implementing (a set of) the IPFS specifications. It would expose a gateway for other applications to use. Examples here are KUBO and Iroh. + +**IPFS integration**, this is an application integrating the IPFS protocol support. Meaning that an application "integrating IPFS" can fetch IPFS resources. These applications often are using gateways to implement IPFS support. Examples here are ffmpeg and curl. + +## Motivation +Applications wanting to use IPFS resources are, without this spec, left to invent their own ways of finding a gateway. This spec defines how application wanting to implement IPFS support can find a gateway. + +Simultaneously the spec also defines how IPFS implementations should expose their gateway. + +## Detailed description This integration spec defines the recommended way for a third party applications to integrate IPFS support in their application and thereby gaining easy access to resources stored on the IPFS platform. Integration here has 2 different meanings. @@ -14,33 +31,80 @@ Integration here has 2 different meanings. 2. One way to handle the protocols is to use the [HTTP gateway](https://docs.ipfs.tech/concepts/ipfs-gateway/). This document describes how to determine which gateway to use. -## Decision tree +### Decision tree The below decision tree defines the decisions to be made in order to choose the proper gateway. Do note that the tree, when implementing this logic, is more elaborate then this tree makes you think it is. Validation here is left out of the tree but should be added in a local implementation. - +```mermaid + graph TD; + A[Has gateway argument] --> |yes| B(gateway from argument); + A[Has gateway argument] --> |no| C(try IPFS_GATEWAY env); + C --> |yes| D(gateway from IPFS_GATEWAY env); + C --> |no| E(try IPFS_PATH env); + E --> |yes| F; + E --> |no| G(assume $HOME/.ipfs); + G --> F(Gateway file exists in path); + F --> |yes| I(gateway is first line); + F --> |no| J(try racing gateway*); + J --> |yes| K(test n-th gateway from list); + K --> KA(find the best** gateway); + J --> |no| L(fallback); + L --> |yes| M(dweb.link***); + L --> |no| N(error); +``` +\* `try racing gateway` depends on the environment variable `IPFS_RACING_GATEWAY`. See below for details. + +\** `find the best* gateway`. The heuristics to find the best gateway are as follows. A racing gateway logic fires of a request to n-th gateways simultaneously (say 10 gateways out of a list of potentially 100's). Of those 10, the one that responds fastest is stored in a list. If this one is already responding within 50ms then this gateway is used. If this one is taking more then 200ms then the next batch of 10 gateways is probed. This flow continues till: +1. a gateway with a response below 50ms has been found +2. gateways have been probed for over 2 seconds (in which case it just stops and uses the fastest one). + +The result of this probe will be stored in `$CONFIG/ipfs/racing_gateway_response` as a single line being the gateway that responded fastest. In subsequent racing gateway requests this file will be read and used as starting point. If this gateway still responds within 50ms then no other gateways will be probed. + +\*** `dweb.link`, see the below `IPFS_FALLBACK_GATEWAY` for details. + +#### Environment variables +The decision tree is influences by a couple environement variables. + +**`IPFS_RACING_GATEWAY`** When this environemnt variable isn't found (the default), racing gateways should be attempted upon reaching this point in the flow. When the environment variable exists it's value should be used. If it's value is `1` (or `true`) then racing gateways should be attempted. If this value is `0` (or `false`) then racing gateways are off. The control flow proceeds in the `no` branch. + +**`IPFS_FALLBACK_GATEWAY`** When this variable doesn't exist `dweb.link` will be used. When the variable does exist it's value will be used instead. + ### Gateway from command argument -An application can opt to support a command line option to provide a gateway. A user should not _need_ to provide this and should therefore be considered optional. However, if a user does provide this option then it should overrule any other gateway detection and be used as the gateway to use. An example implementation that is doing this is ffmpeg with the ffplay utility. It allows the `-gateway` argument which by default is empty but can be set like: `-gateway http://127.0.0.1:8080` and would then be used to handle `ipfs://` or `ipns://`. +**This feature is optional and only for applications integrating IPFS support.** + +An application can opt to support a command line option to provide a gateway. If a user does provide this option then it should overrule any other gateway detection and be used as the gateway of choice. If implemented, it's recommended to go for either a `--gateway` or `--ipfs-gateway` argument. It depends very much on the application itself as to which option is most sensible. + +An example implementation that is doing this is ffmpeg with the ffplay utility. It allows the `-gateway` argument which by default is empty but can be set like: `-gateway http://127.0.0.1:8080` and would then be used to handle `ipfs://` or `ipns://`. ### Gateway from IPFS_GATEWAY environment variable -When there is no command line argument, the `IPFS_GATEWAY` environment is next. If it contains a value, it should be used as gateway. +When there is no command line argument, the `IPFS_GATEWAY` environment is next. If it contains a value, it will be used as gateway. + +### Gateway file +**This feature is only for IPFS implementers, not for IPFS application integrations.** + +When the implementation provides a gateway (and it's not disabled through other means) then it should make that known to other local applications. The gateway file serves this purpose. It's a file with only 1 single line containing the full http URL to your gateway. For example, it could contain the line: "http://localhost:8080". -### Gateway file from IPFS_PATH environment variable or home folder ipfs data -If the `IPFS_PATH` environment variable is defined, it should point to the ipfs data folder. If this environment variable isn't defined then an attempt should be made to see if `$USER/.ipfs` exists and consider that to be the IPFS data folder when it does. +The file conditions: + 1. is named "gateway" + 2. **only** exists when your implementation actually starts a gateway + 3. is removed when your implementation shuts down + +For historical and compatibility readons, this file shall be placed in: +$HOME/.ipfs/ thus the resulting end path is going to be $HOME/.ipfs/gateway -If this turns out to be an existing path the existence of the `gateway` file in that path should be checked for. If gateway file exists then the first line should be considered to be the full url to the local IPFS gateway. +Future implementers must follow the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) where the gateway file will placed in: +**$XDG_CONFIG_HOME/ipfs/gateway** -Do note that this gateway file logic relies on the future implementation of having the gateway file be auto-generated. It's specced [here](https://github.com/ipfs/go-ipfs/issues/8847) and is approved to be implemented. Implementers of this spec should act as if that gateway file already exists! +The conditions for this file are the same as those in $HOME/.ipfs/gateway. -## Validation and fallback gateway -All of the above describes what should ideally be done. Validation of any kind is up to the application implementing IPFS support. To provide an "always working" feeling, implementers are recommended to use a fallback gateway and they should pick `dweb.link`. That gateway is maintained by Protocol Labs and is allowed to be used for this purpose. +In the, admitedly rare, event of running multiple IPFS implementations each hosting their own gateway. First-come-first-serve applies here. The application that created the gateway file owns it and takes care of removing it. Subsequent instances or different application should not touch the file if it's already there. -## Current implementations +## Example implementations ### ffmpeg As of ffmpeg 5.1, it implements this logic. The source for it's implementation can be found [here](https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/ipfsgateway.c). -### mpv -mpv itself isn't doing any of this, it relies on the ffmpeg implementation. - ### curl -The implementation for curl is currently a work in progress but it too follows the steps as outlined in the decision tree. \ No newline at end of file +The implementation for curl is currently a work in progress but it too follows the steps as outlined in the decision tree. + +### libipfsclient +A reference implementation of this spec along with more functionality to retrieve data from IPFS. This is intended to be used by applications wanting to implement IPFS support. From 8a65c2c63577610066191c00621fc9c529051965 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Sat, 26 Nov 2022 14:34:20 +0100 Subject: [PATCH 04/17] Remove drawio files, we're using mermaid now --- img/gateway_decision_tree.drawio | 1 - img/gateway_decision_tree.drawio.png | Bin 65528 -> 0 bytes 2 files changed, 1 deletion(-) delete mode 100644 img/gateway_decision_tree.drawio delete mode 100644 img/gateway_decision_tree.drawio.png diff --git a/img/gateway_decision_tree.drawio b/img/gateway_decision_tree.drawio deleted file mode 100644 index cef9394ef..000000000 --- a/img/gateway_decision_tree.drawio +++ /dev/null @@ -1 +0,0 @@ -7VrbcqM4EP0aHpPiDnm0PblM1c5udjO1s5mXLdkI0EZGHiHH9n79SEZchLDj2M7gSlzlKtONaKD7NOdIYDij6fKWgln6hUQQG7YZLQ3nk2HblmvbhviZ0arwBL5TOBKKIjmodjyg/6F0mtI7RxHMlYGMEMzQTHVOSJbBCVN8gFKyUIfFBKtnnYEEao6HCcC69xuKWCq9lmnWO+4gSlJ56tCTO8Zg8pRQMs/k+TKSwWLPFJRh5NA8BRFZNFzOteGMKCGs2JouRxCLtJYZK4672bC3umQKM7bLAb4dXjlWGE7MGASOb1/ICM8Az2UaUpCLYgAGF2C1Tm0yn4rwxQ2wVZmvRYoYfJiBibAXHBOGM0zZFHPL4pvrhMBIWtWNCwODMcTDKmsjggnlu9Z540MZJU9VCcT4mGTsBkwRFsj6G9IIZEC6JYwsnskhwCjJuDHhVwt5wKGenfJ2IWVw2XDJbN1CMoWMituWe90SnRLUrjQXDYSUxU0b4PClD0hQJlXkujp8QxZox2I5WrEM28dM5GwGMr6diO3P9zcP/94Ovl5/GzyKdoQxyngd5MgxLceVHn4ZzcM/aJ19tc5W0GehXa3QvxOtLnXiTbUiL6UdUDYQz8uW7waJC1wHW9sy5R63YRa1juCexnhuNUb/BxlbSRvMGeEuQllKEpIB/BshsxIr+wFgU7FzMqcTuP05x+8rgWzDGNleMFJ4YiNkLsxLxzRtFTYyBoUYMPSsMksXRGT0e4LEE7YaQuI4h0zDUHUR+8HK02D1yAn3jKvywcIRRVf/iHNfeqX5WF0KNz4tFWslraNBkvnJ3fj7n4PPz/Fo8N1Nfvx14ZXjXgNL70pB5YVlnTIsS4pt4LKWHzElU/6nktq+DGUewlB2vwzlBCpDOXafDGXpwrGuGRIKMkY0F9jBSKhhE2VrH4Yfs3ptHemHvVbPPzPBsRWGs4PCKEHwime57Vtu62F+0hrDCs7a9XQ0RgcoOzWG/WpcWvaVisuy8CcBy21CapvMOHyxo2eS0hipAzMby2oHPU6Ct+FylxWP+8HXu4+03HFQpT2/RzHZWWl9beusSBTeWCJW0IbtSfNRRhDbNWsIY2U0KGRvsmmxwYvss62DX2Qg/5SEUD1Gnr997krOlzGKPMijtgRqN55ntgIVidICHYsDzyuLO3ZZ8C67zNvaZbzJQitUJYD963rMC47TY4HTb4/py6wgz7msNAShund/fLnmYS/RLNb5jUdHs1y0wQsy5AgawHfVNLm2t5sGCN9KA+irEl1qj///mIsXpsNKvpeOaonJ5A2as7xcd5qJ97BnRbh1gSpsNY3TtyIMO9BwJqo2Ue3DUkFfNOUdTQyGYfsVh38YUa06D9iFtlqN4+9LW76pBgranfXGtHWl9dt5BvYeZmC+3nQbXuec7PTLK5/9h/aY66vyNvjF0rDjVed5/vUO5l8dPdZd/1NaV+9YnPCORGWtGZjfnjK8dZvpS/0xwFh8qGk0v21cv6iOFnB8iVH2pPXhh1j099r65Q3fTHOz/ta1KHb9LbFz/RM= \ No newline at end of file diff --git a/img/gateway_decision_tree.drawio.png b/img/gateway_decision_tree.drawio.png deleted file mode 100644 index a40e9d05f5455ee5586cf304ad276165513e668e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65528 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 From 0e8fa0d867cc5a806e5bdc82616e4e2d725646db Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Sat, 26 Nov 2022 15:12:44 +0100 Subject: [PATCH 05/17] follow the template --- INTEGRATION.md | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/INTEGRATION.md b/INTEGRATION.md index 802aa2af6..898407a8c 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -22,7 +22,7 @@ Applications wanting to use IPFS resources are, without this spec, left to inven Simultaneously the spec also defines how IPFS implementations should expose their gateway. -## Detailed description +## Detailed design This integration spec defines the recommended way for a third party applications to integrate IPFS support in their application and thereby gaining easy access to resources stored on the IPFS platform. Integration here has 2 different meanings. @@ -99,12 +99,34 @@ The conditions for this file are the same as those in $HOME/.ipfs/gateway. In the, admitedly rare, event of running multiple IPFS implementations each hosting their own gateway. First-come-first-serve applies here. The application that created the gateway file owns it and takes care of removing it. Subsequent instances or different application should not touch the file if it's already there. -## Example implementations -### ffmpeg +### Example implementations +#### ffmpeg As of ffmpeg 5.1, it implements this logic. The source for it's implementation can be found [here](https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/ipfsgateway.c). -### curl +#### curl The implementation for curl is currently a work in progress but it too follows the steps as outlined in the decision tree. -### libipfsclient +#### libipfsclient A reference implementation of this spec along with more functionality to retrieve data from IPFS. This is intended to be used by applications wanting to implement IPFS support. + +## Test fixtures +N/A + +## Design rationale +To simplify IPFS integrations in third party applications it's important to know if said application can use a gateway. This spec defines the rules to find such a gateway and/or how to influence it. + +### User benefit +Users as in applications using IPFS. They get a defined way to find out if their host pc runs an IPFS gateway. Without this spec there is no defined way. + +### Compatibility +Kubo currently makes `$HOME/.ipfs/gateway`, this should stay for backwards compatibility. +A new version implementing this spec should create `$XDG_CONFIG_HOME/ipfs/gateway` in exactly the same fashion. + +### Security +N/A + +### Alternatives +There are no alternatives I'm aware of. There is [this](https://github.com/ipfs/kubo/issues/8847) issue that predates this very IPIP but also serves as starting block to this IPIP. + +### Copyright +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). From 81adfd0670ba6b62819a61254c88a8aae1f67699 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Sat, 26 Nov 2022 15:19:45 +0100 Subject: [PATCH 06/17] linter fixes --- INTEGRATION.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/INTEGRATION.md b/INTEGRATION.md index 898407a8c..7fcbe7146 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -1,4 +1,5 @@ # Gateway Integration + ![](https://img.shields.io/badge/status-reliable-green.svg?style=flat-square) **Author(s)**: @@ -10,6 +11,7 @@ * * * ## Summary + Defines the decision model an IPFS enabled application should use to find a IPFS gateway. Simultaneously defines how an IPFS implementation should expose it's gateway. For clarity upon reading this document: @@ -18,11 +20,13 @@ For clarity upon reading this document: **IPFS integration**, this is an application integrating the IPFS protocol support. Meaning that an application "integrating IPFS" can fetch IPFS resources. These applications often are using gateways to implement IPFS support. Examples here are ffmpeg and curl. ## Motivation + Applications wanting to use IPFS resources are, without this spec, left to invent their own ways of finding a gateway. This spec defines how application wanting to implement IPFS support can find a gateway. Simultaneously the spec also defines how IPFS implementations should expose their gateway. ## Detailed design + This integration spec defines the recommended way for a third party applications to integrate IPFS support in their application and thereby gaining easy access to resources stored on the IPFS platform. Integration here has 2 different meanings. @@ -32,6 +36,7 @@ Integration here has 2 different meanings. 2. One way to handle the protocols is to use the [HTTP gateway](https://docs.ipfs.tech/concepts/ipfs-gateway/). This document describes how to determine which gateway to use. ### Decision tree + The below decision tree defines the decisions to be made in order to choose the proper gateway. Do note that the tree, when implementing this logic, is more elaborate then this tree makes you think it is. Validation here is left out of the tree but should be added in a local implementation. ```mermaid @@ -51,6 +56,7 @@ The below decision tree defines the decisions to be made in order to choose the L --> |yes| M(dweb.link***); L --> |no| N(error); ``` + \* `try racing gateway` depends on the environment variable `IPFS_RACING_GATEWAY`. See below for details. \** `find the best* gateway`. The heuristics to find the best gateway are as follows. A racing gateway logic fires of a request to n-th gateways simultaneously (say 10 gateways out of a list of potentially 100's). Of those 10, the one that responds fastest is stored in a list. If this one is already responding within 50ms then this gateway is used. If this one is taking more then 200ms then the next batch of 10 gateways is probed. This flow continues till: @@ -62,14 +68,15 @@ The result of this probe will be stored in `$CONFIG/ipfs/racing_gateway_response \*** `dweb.link`, see the below `IPFS_FALLBACK_GATEWAY` for details. #### Environment variables + The decision tree is influences by a couple environement variables. **`IPFS_RACING_GATEWAY`** When this environemnt variable isn't found (the default), racing gateways should be attempted upon reaching this point in the flow. When the environment variable exists it's value should be used. If it's value is `1` (or `true`) then racing gateways should be attempted. If this value is `0` (or `false`) then racing gateways are off. The control flow proceeds in the `no` branch. **`IPFS_FALLBACK_GATEWAY`** When this variable doesn't exist `dweb.link` will be used. When the variable does exist it's value will be used instead. - ### Gateway from command argument + **This feature is optional and only for applications integrating IPFS support.** An application can opt to support a command line option to provide a gateway. If a user does provide this option then it should overrule any other gateway detection and be used as the gateway of choice. If implemented, it's recommended to go for either a `--gateway` or `--ipfs-gateway` argument. It depends very much on the application itself as to which option is most sensible. @@ -80,6 +87,7 @@ An example implementation that is doing this is ffmpeg with the ffplay utility. When there is no command line argument, the `IPFS_GATEWAY` environment is next. If it contains a value, it will be used as gateway. ### Gateway file + **This feature is only for IPFS implementers, not for IPFS application integrations.** When the implementation provides a gateway (and it's not disabled through other means) then it should make that known to other local applications. The gateway file serves this purpose. It's a file with only 1 single line containing the full http URL to your gateway. For example, it could contain the line: "http://localhost:8080". @@ -88,7 +96,7 @@ The file conditions: 1. is named "gateway" 2. **only** exists when your implementation actually starts a gateway 3. is removed when your implementation shuts down - + For historical and compatibility readons, this file shall be placed in: $HOME/.ipfs/ thus the resulting end path is going to be $HOME/.ipfs/gateway @@ -100,33 +108,44 @@ The conditions for this file are the same as those in $HOME/.ipfs/gateway. In the, admitedly rare, event of running multiple IPFS implementations each hosting their own gateway. First-come-first-serve applies here. The application that created the gateway file owns it and takes care of removing it. Subsequent instances or different application should not touch the file if it's already there. ### Example implementations + #### ffmpeg + As of ffmpeg 5.1, it implements this logic. The source for it's implementation can be found [here](https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/ipfsgateway.c). #### curl + The implementation for curl is currently a work in progress but it too follows the steps as outlined in the decision tree. #### libipfsclient + A reference implementation of this spec along with more functionality to retrieve data from IPFS. This is intended to be used by applications wanting to implement IPFS support. ## Test fixtures + N/A ## Design rationale + To simplify IPFS integrations in third party applications it's important to know if said application can use a gateway. This spec defines the rules to find such a gateway and/or how to influence it. ### User benefit + Users as in applications using IPFS. They get a defined way to find out if their host pc runs an IPFS gateway. Without this spec there is no defined way. ### Compatibility + Kubo currently makes `$HOME/.ipfs/gateway`, this should stay for backwards compatibility. A new version implementing this spec should create `$XDG_CONFIG_HOME/ipfs/gateway` in exactly the same fashion. ### Security + N/A ### Alternatives + There are no alternatives I'm aware of. There is [this](https://github.com/ipfs/kubo/issues/8847) issue that predates this very IPIP but also serves as starting block to this IPIP. ### Copyright + Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). From ed0fe2279bef683ae4f9595ffe14a156c9a51296 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Sat, 26 Nov 2022 15:25:54 +0100 Subject: [PATCH 07/17] a few more linter fixes --- INTEGRATION.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/INTEGRATION.md b/INTEGRATION.md index 7fcbe7146..58f2bee48 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -1,6 +1,6 @@ # Gateway Integration -![](https://img.shields.io/badge/status-reliable-green.svg?style=flat-square) +![Specification status is reliable](https://img.shields.io/badge/status-reliable-green.svg?style=flat-square) **Author(s)**: - [Mark Gaiser](https://github.com/markg85/) @@ -77,13 +77,14 @@ The decision tree is influences by a couple environement variables. ### Gateway from command argument -**This feature is optional and only for applications integrating IPFS support.** +**This feature is optional and only for applications integrating IPFS support.** An application can opt to support a command line option to provide a gateway. If a user does provide this option then it should overrule any other gateway detection and be used as the gateway of choice. If implemented, it's recommended to go for either a `--gateway` or `--ipfs-gateway` argument. It depends very much on the application itself as to which option is most sensible. An example implementation that is doing this is ffmpeg with the ffplay utility. It allows the `-gateway` argument which by default is empty but can be set like: `-gateway http://127.0.0.1:8080` and would then be used to handle `ipfs://` or `ipns://`. ### Gateway from IPFS_GATEWAY environment variable + When there is no command line argument, the `IPFS_GATEWAY` environment is next. If it contains a value, it will be used as gateway. ### Gateway file From 0d6b8d675ee6a6e9b0ff62a2aa9987ae9d03ac26 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Mon, 28 Nov 2022 19:55:17 +0100 Subject: [PATCH 08/17] wip image instead Co-authored-by: Marcin Rataj --- INTEGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INTEGRATION.md b/INTEGRATION.md index 58f2bee48..c82faead0 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -1,6 +1,6 @@ # Gateway Integration -![Specification status is reliable](https://img.shields.io/badge/status-reliable-green.svg?style=flat-square) +![wip](https://img.shields.io/badge/status-wip-orange.svg?style=flat-square) **Author(s)**: - [Mark Gaiser](https://github.com/markg85/) From 0dd13f2dc0284438cd1d45be29227d0f67c44a6f Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Tue, 29 Nov 2022 19:30:33 +0100 Subject: [PATCH 09/17] review feedback and updated to new concepts --- INTEGRATION.md | 93 +++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/INTEGRATION.md b/INTEGRATION.md index c82faead0..dcdc6b3bf 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -21,7 +21,7 @@ For clarity upon reading this document: ## Motivation -Applications wanting to use IPFS resources are, without this spec, left to invent their own ways of finding a gateway. This spec defines how application wanting to implement IPFS support can find a gateway. +Applications wanting to use IPFS resources are, without this spec, left to invent their own ways of finding a gateway. This spec defines how an application wanting to implement IPFS support can find (at least one) gateway to use. Simultaneously the spec also defines how IPFS implementations should expose their gateway. @@ -35,6 +35,39 @@ Integration here has 2 different meanings. 2. One way to handle the protocols is to use the [HTTP gateway](https://docs.ipfs.tech/concepts/ipfs-gateway/). This document describes how to determine which gateway to use. +### $CONFIG folder, $GWFILE and $LAGACY_GWFILE + +Throughout this spec you might see `$CONFIG/ipfs/...`. `$CONFIG` is a mere abbreviation for the config folder, the variable doesn't exist and only serves to make this spec more readable. This section defines where `$CONFIG` should point to in a cross-platform manner. + +As a reference, [this](https://github.com/cjbassi/platform-dirs-rs#path-list) list of configuration folders is used. + +| | with variables | full paths | +| -------- | -------- | -------- | +| Windows | %APPDATA%/ipfs | C:/Users/%USERNAME%/AppData/Roaming/ipfs | +| macOS | $XDG_CONFIG_HOME/ipfs | ~/Library/Application Support/ipfs | +| Linux | $XDG_CONFIG_HOME/ipfs | ~/.config/ipfs | + +In the same manner as $CONFIG, $GATEWAY and $LEGACY_GATEWAY are names for this spec only to simplify reading. + +`$GWFILE` = `$CONFIG/gateway` +`$LEGACY_GWFILE` = `~/.ipfs/gateway` + +### Gateway file and Legacy gateway file +#### For KUBO only +The legacy gateway file ($LAGACY_GWFILE) was made up as a response to a need to know which gateway an IPFS node would expose. While that file itself was never specced out, it served the need. Some applications are using this file therefore the file has to be maintained for the KUBO reference implemenation. Any other IPFS implementation should ignore $LAGACY_GWFILE. + +The file only contains a single line being the http gateway url. For example: "http://localhost:8080". + +The file conditions: + 1. is named "gateway" + 2. **only** exists when KUBO actually starts a gateway + 3. is removed when KUBO shuts down + +Future KUBO implemenations will do the above via symlinking `$LAGACY_GWFILE` to `$GWFILE` while maintaining the same rules as stated above. + +#### For all implemenations +The gateway file ($GWFILE) is a successor to the $LAGACY_GWFILE. This new file is stored in a vendor agnostic location and contians a list of gateways. The file can be empty and even non-existing. + ### Decision tree The below decision tree defines the decisions to be made in order to choose the proper gateway. Do note that the tree, when implementing this logic, is more elaborate then this tree makes you think it is. Validation here is left out of the tree but should be added in a local implementation. @@ -44,34 +77,37 @@ The below decision tree defines the decisions to be made in order to choose the A[Has gateway argument] --> |yes| B(gateway from argument); A[Has gateway argument] --> |no| C(try IPFS_GATEWAY env); C --> |yes| D(gateway from IPFS_GATEWAY env); - C --> |no| E(try IPFS_PATH env); - E --> |yes| F; - E --> |no| G(assume $HOME/.ipfs); - G --> F(Gateway file exists in path); - F --> |yes| I(gateway is first line); - F --> |no| J(try racing gateway*); + C --> |no| E(try $GWFILE exists*); + E --> |yes| F(read file); + E --> |no| L; + F --> |yes| I(gateway is any line); + F --> |no or empty file| J(try racing gateway**); J --> |yes| K(test n-th gateway from list); - K --> KA(find the best** gateway); J --> |no| L(fallback); - L --> |yes| M(dweb.link***); + L --> |yes| M(dweb.link****); L --> |no| N(error); + K --> KA(find the best*** gateways); + KA --> KB(append found gateways in $GWFILE); + KB --> E; ``` -\* `try racing gateway` depends on the environment variable `IPFS_RACING_GATEWAY`. See below for details. +\* `try $GWFILE exists` in case of KUBO, a legacy path should be done here when `$GWFILE` doesn't exists. It should check for `$LEGACY_GWFILE` and proceed with it's output instead. Any other implementation can ignore this point. + +\** `try racing gateway` depends on the environment variable `IPFS_RACING_GATEWAY`. See below for details. -\** `find the best* gateway`. The heuristics to find the best gateway are as follows. A racing gateway logic fires of a request to n-th gateways simultaneously (say 10 gateways out of a list of potentially 100's). Of those 10, the one that responds fastest is stored in a list. If this one is already responding within 50ms then this gateway is used. If this one is taking more then 200ms then the next batch of 10 gateways is probed. This flow continues till: -1. a gateway with a response below 50ms has been found -2. gateways have been probed for over 2 seconds (in which case it just stops and uses the fastest one). +\*** `find the best* gateways`. The heuristics to find the best gateway are as follows. A racing gateway logic fires a request to n-th gateways simultaneously (say 10 gateways out of a list of potentially 100's). Of those 10, those that respond within 300ms are appended in $GWFILE. This flow continues till: +1. all gateways in the list have been checked +2. more then 2 seconds have passed in which case it aborts all current and pending requests -The result of this probe will be stored in `$CONFIG/ipfs/racing_gateway_response` as a single line being the gateway that responded fastest. In subsequent racing gateway requests this file will be read and used as starting point. If this gateway still responds within 50ms then no other gateways will be probed. +The result of this probe will be stored in `$GWFILE` as a list of gateways that responded within the set bounds. -\*** `dweb.link`, see the below `IPFS_FALLBACK_GATEWAY` for details. +\**** `dweb.link`, see the below `IPFS_FALLBACK_GATEWAY` for details. #### Environment variables -The decision tree is influences by a couple environement variables. +The decision tree is influenced by a couple environement variables. -**`IPFS_RACING_GATEWAY`** When this environemnt variable isn't found (the default), racing gateways should be attempted upon reaching this point in the flow. When the environment variable exists it's value should be used. If it's value is `1` (or `true`) then racing gateways should be attempted. If this value is `0` (or `false`) then racing gateways are off. The control flow proceeds in the `no` branch. +**`IPFS_RACING_GATEWAY`** When this environemnt variable isn't found (the default), racing gateways will be attempted upon reaching this point in the flow. When the environment variable exists it's value should be used. If it's value is `1` (or `true`) then racing gateways should be attempted. If this value is `0` (or `false`) then racing gateways are off. The control flow proceeds in the `no` branch. **`IPFS_FALLBACK_GATEWAY`** When this variable doesn't exist `dweb.link` will be used. When the variable does exist it's value will be used instead. @@ -85,28 +121,7 @@ An example implementation that is doing this is ffmpeg with the ffplay utility. ### Gateway from IPFS_GATEWAY environment variable -When there is no command line argument, the `IPFS_GATEWAY` environment is next. If it contains a value, it will be used as gateway. - -### Gateway file - -**This feature is only for IPFS implementers, not for IPFS application integrations.** - -When the implementation provides a gateway (and it's not disabled through other means) then it should make that known to other local applications. The gateway file serves this purpose. It's a file with only 1 single line containing the full http URL to your gateway. For example, it could contain the line: "http://localhost:8080". - -The file conditions: - 1. is named "gateway" - 2. **only** exists when your implementation actually starts a gateway - 3. is removed when your implementation shuts down - -For historical and compatibility readons, this file shall be placed in: -$HOME/.ipfs/ thus the resulting end path is going to be $HOME/.ipfs/gateway - -Future implementers must follow the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) where the gateway file will placed in: -**$XDG_CONFIG_HOME/ipfs/gateway** - -The conditions for this file are the same as those in $HOME/.ipfs/gateway. - -In the, admitedly rare, event of running multiple IPFS implementations each hosting their own gateway. First-come-first-serve applies here. The application that created the gateway file owns it and takes care of removing it. Subsequent instances or different application should not touch the file if it's already there. +When there is no command line argument, the `IPFS_GATEWAY` environment is next. It should contain 1 and only 1 gateway http address. ### Example implementations From d362c62481bc93fe6679222312157c63dd205cc5 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Tue, 29 Nov 2022 19:57:13 +0100 Subject: [PATCH 10/17] Refer to the new file as gateways (plural). --- INTEGRATION.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/INTEGRATION.md b/INTEGRATION.md index dcdc6b3bf..3b8726b20 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -49,7 +49,7 @@ As a reference, [this](https://github.com/cjbassi/platform-dirs-rs#path-list) li In the same manner as $CONFIG, $GATEWAY and $LEGACY_GATEWAY are names for this spec only to simplify reading. -`$GWFILE` = `$CONFIG/gateway` +`$GWFILE` = `$CONFIG/gateways` `$LEGACY_GWFILE` = `~/.ipfs/gateway` ### Gateway file and Legacy gateway file @@ -151,8 +151,7 @@ Users as in applications using IPFS. They get a defined way to find out if their ### Compatibility -Kubo currently makes `$HOME/.ipfs/gateway`, this should stay for backwards compatibility. -A new version implementing this spec should create `$XDG_CONFIG_HOME/ipfs/gateway` in exactly the same fashion. +See [Gateway file and Legacy gateway file](#Gateway-file-and-Legacy-gateway-file). ### Security From 7ce0432c42d02aa745444fe2568a769f8205a78e Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Thu, 8 Dec 2022 17:35:07 +0100 Subject: [PATCH 11/17] Move integrations document to: integrations/GATEWAYS_FILE.md Reword the document to only handle the gateways file. --- INTEGRATION.md | 166 ---------------------------------- integrations/GATEWAYS_FILE.md | 86 ++++++++++++++++++ 2 files changed, 86 insertions(+), 166 deletions(-) delete mode 100644 INTEGRATION.md create mode 100644 integrations/GATEWAYS_FILE.md diff --git a/INTEGRATION.md b/INTEGRATION.md deleted file mode 100644 index 3b8726b20..000000000 --- a/INTEGRATION.md +++ /dev/null @@ -1,166 +0,0 @@ -# Gateway Integration - -![wip](https://img.shields.io/badge/status-wip-orange.svg?style=flat-square) - -**Author(s)**: -- [Mark Gaiser](https://github.com/markg85/) - -**Maintainer(s)**: -- [Mark Gaiser](https://github.com/markg85/) - -* * * - -## Summary - -Defines the decision model an IPFS enabled application should use to find a IPFS gateway. Simultaneously defines how an IPFS implementation should expose it's gateway. - -For clarity upon reading this document: -**IPFS implementation**, this is an application implementing (a set of) the IPFS specifications. It would expose a gateway for other applications to use. Examples here are KUBO and Iroh. - -**IPFS integration**, this is an application integrating the IPFS protocol support. Meaning that an application "integrating IPFS" can fetch IPFS resources. These applications often are using gateways to implement IPFS support. Examples here are ffmpeg and curl. - -## Motivation - -Applications wanting to use IPFS resources are, without this spec, left to invent their own ways of finding a gateway. This spec defines how an application wanting to implement IPFS support can find (at least one) gateway to use. - -Simultaneously the spec also defines how IPFS implementations should expose their gateway. - -## Detailed design - -This integration spec defines the recommended way for a third party applications to integrate IPFS support in their application and thereby gaining easy access to resources stored on the IPFS platform. - -Integration here has 2 different meanings. - -1. The implementing application can handle IPFS resources with the `ipfs` and `ipns` protocols. As an example, the implementing application should be able to handle a url in this format `ipfs://QmbGtJg23skhvFmu9mJiePVByhfzu5rwo74MEkVDYAmF5T` which in this case would give you the big buck bunny video. Likewise a url in the `ipns` should be handled too. Making these protocols usable is left up to the specific application implementing this support. - -2. One way to handle the protocols is to use the [HTTP gateway](https://docs.ipfs.tech/concepts/ipfs-gateway/). This document describes how to determine which gateway to use. - -### $CONFIG folder, $GWFILE and $LAGACY_GWFILE - -Throughout this spec you might see `$CONFIG/ipfs/...`. `$CONFIG` is a mere abbreviation for the config folder, the variable doesn't exist and only serves to make this spec more readable. This section defines where `$CONFIG` should point to in a cross-platform manner. - -As a reference, [this](https://github.com/cjbassi/platform-dirs-rs#path-list) list of configuration folders is used. - -| | with variables | full paths | -| -------- | -------- | -------- | -| Windows | %APPDATA%/ipfs | C:/Users/%USERNAME%/AppData/Roaming/ipfs | -| macOS | $XDG_CONFIG_HOME/ipfs | ~/Library/Application Support/ipfs | -| Linux | $XDG_CONFIG_HOME/ipfs | ~/.config/ipfs | - -In the same manner as $CONFIG, $GATEWAY and $LEGACY_GATEWAY are names for this spec only to simplify reading. - -`$GWFILE` = `$CONFIG/gateways` -`$LEGACY_GWFILE` = `~/.ipfs/gateway` - -### Gateway file and Legacy gateway file -#### For KUBO only -The legacy gateway file ($LAGACY_GWFILE) was made up as a response to a need to know which gateway an IPFS node would expose. While that file itself was never specced out, it served the need. Some applications are using this file therefore the file has to be maintained for the KUBO reference implemenation. Any other IPFS implementation should ignore $LAGACY_GWFILE. - -The file only contains a single line being the http gateway url. For example: "http://localhost:8080". - -The file conditions: - 1. is named "gateway" - 2. **only** exists when KUBO actually starts a gateway - 3. is removed when KUBO shuts down - -Future KUBO implemenations will do the above via symlinking `$LAGACY_GWFILE` to `$GWFILE` while maintaining the same rules as stated above. - -#### For all implemenations -The gateway file ($GWFILE) is a successor to the $LAGACY_GWFILE. This new file is stored in a vendor agnostic location and contians a list of gateways. The file can be empty and even non-existing. - -### Decision tree - -The below decision tree defines the decisions to be made in order to choose the proper gateway. Do note that the tree, when implementing this logic, is more elaborate then this tree makes you think it is. Validation here is left out of the tree but should be added in a local implementation. - -```mermaid - graph TD; - A[Has gateway argument] --> |yes| B(gateway from argument); - A[Has gateway argument] --> |no| C(try IPFS_GATEWAY env); - C --> |yes| D(gateway from IPFS_GATEWAY env); - C --> |no| E(try $GWFILE exists*); - E --> |yes| F(read file); - E --> |no| L; - F --> |yes| I(gateway is any line); - F --> |no or empty file| J(try racing gateway**); - J --> |yes| K(test n-th gateway from list); - J --> |no| L(fallback); - L --> |yes| M(dweb.link****); - L --> |no| N(error); - K --> KA(find the best*** gateways); - KA --> KB(append found gateways in $GWFILE); - KB --> E; -``` - -\* `try $GWFILE exists` in case of KUBO, a legacy path should be done here when `$GWFILE` doesn't exists. It should check for `$LEGACY_GWFILE` and proceed with it's output instead. Any other implementation can ignore this point. - -\** `try racing gateway` depends on the environment variable `IPFS_RACING_GATEWAY`. See below for details. - -\*** `find the best* gateways`. The heuristics to find the best gateway are as follows. A racing gateway logic fires a request to n-th gateways simultaneously (say 10 gateways out of a list of potentially 100's). Of those 10, those that respond within 300ms are appended in $GWFILE. This flow continues till: -1. all gateways in the list have been checked -2. more then 2 seconds have passed in which case it aborts all current and pending requests - -The result of this probe will be stored in `$GWFILE` as a list of gateways that responded within the set bounds. - -\**** `dweb.link`, see the below `IPFS_FALLBACK_GATEWAY` for details. - -#### Environment variables - -The decision tree is influenced by a couple environement variables. - -**`IPFS_RACING_GATEWAY`** When this environemnt variable isn't found (the default), racing gateways will be attempted upon reaching this point in the flow. When the environment variable exists it's value should be used. If it's value is `1` (or `true`) then racing gateways should be attempted. If this value is `0` (or `false`) then racing gateways are off. The control flow proceeds in the `no` branch. - -**`IPFS_FALLBACK_GATEWAY`** When this variable doesn't exist `dweb.link` will be used. When the variable does exist it's value will be used instead. - -### Gateway from command argument - -**This feature is optional and only for applications integrating IPFS support.** - -An application can opt to support a command line option to provide a gateway. If a user does provide this option then it should overrule any other gateway detection and be used as the gateway of choice. If implemented, it's recommended to go for either a `--gateway` or `--ipfs-gateway` argument. It depends very much on the application itself as to which option is most sensible. - -An example implementation that is doing this is ffmpeg with the ffplay utility. It allows the `-gateway` argument which by default is empty but can be set like: `-gateway http://127.0.0.1:8080` and would then be used to handle `ipfs://` or `ipns://`. - -### Gateway from IPFS_GATEWAY environment variable - -When there is no command line argument, the `IPFS_GATEWAY` environment is next. It should contain 1 and only 1 gateway http address. - -### Example implementations - -#### ffmpeg - -As of ffmpeg 5.1, it implements this logic. The source for it's implementation can be found [here](https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/ipfsgateway.c). - -#### curl - -The implementation for curl is currently a work in progress but it too follows the steps as outlined in the decision tree. - -#### libipfsclient - -A reference implementation of this spec along with more functionality to retrieve data from IPFS. This is intended to be used by applications wanting to implement IPFS support. - -## Test fixtures - -N/A - -## Design rationale - -To simplify IPFS integrations in third party applications it's important to know if said application can use a gateway. This spec defines the rules to find such a gateway and/or how to influence it. - -### User benefit - -Users as in applications using IPFS. They get a defined way to find out if their host pc runs an IPFS gateway. Without this spec there is no defined way. - -### Compatibility - -See [Gateway file and Legacy gateway file](#Gateway-file-and-Legacy-gateway-file). - -### Security - -N/A - -### Alternatives - -There are no alternatives I'm aware of. There is [this](https://github.com/ipfs/kubo/issues/8847) issue that predates this very IPIP but also serves as starting block to this IPIP. - -### Copyright - -Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/integrations/GATEWAYS_FILE.md b/integrations/GATEWAYS_FILE.md new file mode 100644 index 000000000..b21c588f8 --- /dev/null +++ b/integrations/GATEWAYS_FILE.md @@ -0,0 +1,86 @@ +# Gateway file + +![wip](https://img.shields.io/badge/status-wip-orange.svg?style=flat-square) + +**Author(s)**: +- [Mark Gaiser](https://github.com/markg85/) + +**Maintainer(s)**: +- [Mark Gaiser](https://github.com/markg85/) + +* * * + +## Summary + +Defines how IPFS implementations must expose their gateway. This only affects the file-based method of exposiing a gateway. + +## Motivation + +Applications wanting to use IPFS resources are, without this spec, left to invent their own ways of finding a gateway. This spec defines how an application wanting to implement IPFS support can find a list of gateways. + +Users should be aware that this is just a list of gateways. It could contain a local gateway but doesn't have to. It's up to the user to do filtering on that list to find an actual local gateway. + +## Detailed design + +This integration spec defines the recommended way for IPFS implementations to expose the gateway they host for IPFS integrations (think applications wanting to support IPFS) to find and use said gateways. + +An IPFS implementation must expose the gateway it serves in a file called `gateways` (more on where this file is located in the section below). The gateway must be exposed as a single line in the following format: + +`http://:` + +In case the `gateways` file already exists, this line must be **prepended** with the rest of the data left as-is. This is a soft guarantee that any locally running gateways are at the top of the list. In any other case the `gateways` file must be created and the `gateway` line inserted to it as one line terminated with `\r\n`. + +Upon shutdown of the IPFS implementation it is to remove it's specific gateway line from the `gateways` file and leave no gaps. It must take care potential empty lines and leave a file behind with the remaning gateways each on a single line and terminated - per line - with `\r\n`. + +### Gateways file placement + +As a reference, [this](https://github.com/cjbassi/platform-dirs-rs#path-list) list of configuration folders is used. + +| | with variables | full paths | +| -------- | -------- | -------- | +| Windows | %APPDATA%/ipfs | C:/Users/%USERNAME%/AppData/Roaming/ipfs | +| macOS | $XDG_CONFIG_HOME/ipfs | ~/Library/Application Support/ipfs | +| Linux | $XDG_CONFIG_HOME/ipfs | ~/.config/ipfs | + +The `gateways` file must be placed in the folder appropiate for the platform the IPFS implementation instance is running on. For linux that would be `~/.config/ipfs/gateways` or `$XDG_CONFIG_HOME/ipfs/gateways` + +The file will be created when it doesn't exist. +It will never be delated! This means the file will exist and be empty when an IPFS implementation removes it's own gateway from that file and if that gateway was the only line in the file. + +## Test fixtures + +N/A + +## Design rationale + +To simplify IPFS integrations in third party applications it's important to know if said application can use a gateway. This spec defines the rules to find such a gateway and/or how to influence it. + +### User benefit + +Users, applications using IPFS, get a defined way to find gateways to use. Without this spec there is no defined way. + +### Compatibility + +**This is only applicable to Kubo! Other IPFS implementations are to ignore this** +The legacy gateway file (placed in `~/.ipfs/gateway`) was made up as a response to a need to know which gateway an IPFS node would expose. While that file itself was never specced out, it served the need. Some applications are using this file therefore the file has to be maintained for the Kubo reference implemenation. Any other IPFS implementation should ignore this. + +The file only contains a single line being the http gateway url. For example: "http://localhost:8080". + +The file conditions: + 1. is named "gateway" + 2. **only** exists when Kubo starts a gateway + 3. is removed when Kubo shuts down + +Future Kubo implemenations will do the above via symlinking the `gateways` file to `gateway` while maintaining the same rules as stated above. + +### Security + +N/A + +### Alternatives + +There are no alternatives I'm aware of. There is [this](https://github.com/ipfs/kubo/issues/8847) issue that predates this very IPIP but also serves as starting block to this IPIP. + +### Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). From 9e5cd95a6ae4b8ced319c2b73853ab743ab51380 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Thu, 8 Dec 2022 17:43:35 +0100 Subject: [PATCH 12/17] template fixes --- integrations/GATEWAYS_FILE.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/integrations/GATEWAYS_FILE.md b/integrations/GATEWAYS_FILE.md index b21c588f8..36f98f361 100644 --- a/integrations/GATEWAYS_FILE.md +++ b/integrations/GATEWAYS_FILE.md @@ -1,14 +1,7 @@ -# Gateway file - -![wip](https://img.shields.io/badge/status-wip-orange.svg?style=flat-square) - -**Author(s)**: -- [Mark Gaiser](https://github.com/markg85/) - -**Maintainer(s)**: -- [Mark Gaiser](https://github.com/markg85/) - -* * * +# IPIP 0180: Gateways file +- Start Date: 2022-12-08 +- Related Issues: + - https://github.com/ipfs/kubo/issues/8847 ## Summary From 4d800400cdd8810b5aedca349178569ee16be62a Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Thu, 8 Dec 2022 17:45:14 +0100 Subject: [PATCH 13/17] 280. Not 180. --- integrations/GATEWAYS_FILE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/GATEWAYS_FILE.md b/integrations/GATEWAYS_FILE.md index 36f98f361..5e7e2d710 100644 --- a/integrations/GATEWAYS_FILE.md +++ b/integrations/GATEWAYS_FILE.md @@ -1,4 +1,4 @@ -# IPIP 0180: Gateways file +# IPIP 0280: Gateways file - Start Date: 2022-12-08 - Related Issues: - https://github.com/ipfs/kubo/issues/8847 From 50b70cfd8a0a59853e5e3dbf9ccf41ba76c1eba4 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Sun, 11 Dec 2022 20:59:41 +0100 Subject: [PATCH 14/17] Add global configuration details --- integrations/GATEWAYS_FILE.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/integrations/GATEWAYS_FILE.md b/integrations/GATEWAYS_FILE.md index 5e7e2d710..a2dd2ac80 100644 --- a/integrations/GATEWAYS_FILE.md +++ b/integrations/GATEWAYS_FILE.md @@ -31,15 +31,26 @@ As a reference, [this](https://github.com/cjbassi/platform-dirs-rs#path-list) li | | with variables | full paths | | -------- | -------- | -------- | -| Windows | %APPDATA%/ipfs | C:/Users/%USERNAME%/AppData/Roaming/ipfs | -| macOS | $XDG_CONFIG_HOME/ipfs | ~/Library/Application Support/ipfs | -| Linux | $XDG_CONFIG_HOME/ipfs | ~/.config/ipfs | +| (user) Windows | %APPDATA%/ipfs | C:/Users/%USERNAME%/AppData/Roaming/ipfs | +| (global) Windows | %PROGRAMDATA%/ipfs | C:/ProgramData/ipfs | +| (user) macOS | $XDG_CONFIG_HOME/ipfs | ~/Library/Application Support/ipfs | +| (global) macOS | N/A | /Library/Application Support/ipfs | +| (user) Linux | $XDG_CONFIG_HOME/ipfs | ~/.config/ipfs | +| (global) Linux | N/A | /etc/ipfs | The `gateways` file must be placed in the folder appropiate for the platform the IPFS implementation instance is running on. For linux that would be `~/.config/ipfs/gateways` or `$XDG_CONFIG_HOME/ipfs/gateways` The file will be created when it doesn't exist. It will never be delated! This means the file will exist and be empty when an IPFS implementation removes it's own gateway from that file and if that gateway was the only line in the file. +Creating and updating the `gateways` file only applies to the `(user)` prefixed file. The `(global)` prefixed file will not be written to by the IPFS implementation! + +#### Local file +The local file is for read-write access for the IPFS implementation. + +#### Global file +If the local file cannot be found or is empty then the IPFS implementation should try to read the global config file and read it's gateway values instead. This global file must not be changed by any IPFS implementation! + ## Test fixtures N/A From c6699f7432781a9e4c9c6257b54e0f45c3c165cb Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Sun, 11 Dec 2022 21:00:51 +0100 Subject: [PATCH 15/17] Little less bold --- integrations/GATEWAYS_FILE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integrations/GATEWAYS_FILE.md b/integrations/GATEWAYS_FILE.md index a2dd2ac80..e75425d96 100644 --- a/integrations/GATEWAYS_FILE.md +++ b/integrations/GATEWAYS_FILE.md @@ -65,14 +65,15 @@ Users, applications using IPFS, get a defined way to find gateways to use. Witho ### Compatibility -**This is only applicable to Kubo! Other IPFS implementations are to ignore this** +This is only applicable to Kubo! Other IPFS implementations are to ignore this + The legacy gateway file (placed in `~/.ipfs/gateway`) was made up as a response to a need to know which gateway an IPFS node would expose. While that file itself was never specced out, it served the need. Some applications are using this file therefore the file has to be maintained for the Kubo reference implemenation. Any other IPFS implementation should ignore this. The file only contains a single line being the http gateway url. For example: "http://localhost:8080". The file conditions: 1. is named "gateway" - 2. **only** exists when Kubo starts a gateway + 2. only exists when Kubo starts a gateway 3. is removed when Kubo shuts down Future Kubo implemenations will do the above via symlinking the `gateways` file to `gateway` while maintaining the same rules as stated above. From 15fee05f3132aaa904c94e41265794d0e65a27e2 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Thu, 15 Dec 2022 18:36:30 +0100 Subject: [PATCH 16/17] initial - draft - version of the multi gateway client spec --- integrations/MULTI_GATEWAY_CLIENT.md | 458 +++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 integrations/MULTI_GATEWAY_CLIENT.md diff --git a/integrations/MULTI_GATEWAY_CLIENT.md b/integrations/MULTI_GATEWAY_CLIENT.md new file mode 100644 index 000000000..102130cfc --- /dev/null +++ b/integrations/MULTI_GATEWAY_CLIENT.md @@ -0,0 +1,458 @@ +# IPIP 0000: Multi gateway client + +- Start Date: 2022-12-12 +- Related Issues: + - None + +## Summary + +Describes how to implement IPFS in a decentralized way with only gateways over HTTP. + +## Motivation + +There is a need for places that can't quite run full IPFS nodes to still have a great IPFS experience. This could be a multitude of reasons (like constrained network resources, lower powered hardware, etc...). Using a gateway one can already IPFS content in a read-only manner, which is enough for many usecases. With mutiple gateways this approach becomes very distributed. + +This IPIP defines the set of interface functions required to be able to access IPFS in this decentralized way through only gateways. + +Do note that this is a 2-step implementation approach. + +1. This IPIP merely defines the interface that some future library should expose. For simplicity, we call this "the library" or "library" in the other parts of this document. +2. Another application could then use the library from point 1 to create and actual "ipfsclient" application. This could for example be a stand-alone application but could also be browser integration or anything in between. For simplicity, we call this the "ipfsclient" or "client application". + +## Detailed design + +The following is a [WebIDL](https://en.wikipedia.org/wiki/Web_IDL) like attempt to describe the API. WebIDL and non-web programming doesn't fully map hence the very detailed description below it. + +```idl= + enum ErrorType + { + IO, + WEB + } + + interface Error + { + required long error_code; + required ErrorType error_type; + required String descriptive_message; + + required long code(ErrorType error_type); + } + + interface Url + { + String url; + bool is_valid; + bool _validate_url(); + } + + typedef String CID; + + interface NetworkInterface + { + required async > get_http_data(CID cid, &Url http_url); + required async Error done(CID cid); + required async void cancel(CID cid); + } + + interface DataSync + { + required async Error data(CID cid, sequence buffer); + required async void done(CID cid); + required async void error(CID cid); + } + + interface Api + { + NetworkInterface network_interface; + DataSync data_sync; + sequence gateways; + long gateway_concurrency; + long max_simultaneous_cids; + long max_total_simultaneous_gateways; + + required Error ipfs(&Url ipfs_url); + required random_gateway(&Url ipfs_url); + required String id(); + } +``` + +Notes with regards to the syntax: + +1. `required` here is intended to mean that the user of said function is required to check it's return value. +2. The `_validate_url` is intended to be a private function that should be an implementation detail. +3. Arguments are written in the C-family way of writing function arguments. So `type name` like `int value`. +4. `async` here means that the function itself runs async. + +### Flow +The next chapters will describe the above api in full detail. The flow here serves as an illustrative example to demonstrate the conceptual workings. + +```mermaid +sequenceDiagram + actor A as ipfsclient + participant B as library + participant C as NetworkInterface + participant D as DataSync + A->>+B: ::ipfs("ipfs://bafy") + B->>-A: 200 Ok + + par Handle request in different thread + B->>B: get gateway + B->>C: IPFS request on gateway + loop (async, non-blocking max gateway_concurrency) + C-->C: get block + C->>+B: data validation + B->>-D: valid data + par async + D-->D: handle valid data + end + end + end +``` + +### enum ErrorType + +This enum specifies the different kinds of errors that can be represented. An `IO` error means the error code is related to writing a file and the code maps to [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html) error codes. + +`WEB` arrors are errors that map to the [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) and apply to executing an URL. + +### interface Error + +The error interface has 3 members that all must be filled in with a value. +For `error_code` and `error_type`, see the above explenation of the `ErrorType` enum. + +The `descriptive_message` should contain a descriptive message applicable to the error. So for example if the `error_type` is `WEB`, the `error_code` is `404` then the error message should be `Not found.` + +The OK condition (meaning all went well, done without errors) of the `Error` object is different depending on the context. +For `IO` it can be considered OK when the code is `0` and the `descriptive_message` is empty. + +For `WEB` it must be the `200` code with `OK` as descriptive message. + +#### `required long code(ErrorType error_type);` + +**Description** +This `code` function is a required convenience function. It's existence allows code to be written like: +```cpp +while (error.code(WEB).error_code != 200) +{ + // get data +} +``` + +Without this convenience wrapper you'd have to manually check if the type is `WEB` (in this example case) that can now be abstracted awar in the `error` function itself. + +This function can return `-1` which means `Provided error_code doesn't match object error_code`. That should only happen when checking for a `WEB` error while the object contains an `IO` error or vice verse. + +**Parameters** +`ErrorType error_type` the type of error according to the enum `ErrorType`. + +**Return** +Returns the `error_code` value if the provided `error_type` matches the `error_type in the object`. Else returns -1. + +### interface Url + +The Url object will contain 1 URL that is schema valid for the URL structure. Libraries and tools often already have a Url class that does this for you. Rust for example has the Url class too that should be used in this context. In C++ land with Qt as framework, there is the QUrl class to allow URL validation. + +If your toolset of use has a Url class that has build-in URL validation support, use that instead. + +If your tool doesn't have it then create and Url class that does Url validation where at least http gateway URL's (`http://...`) and IPFS url's (`ipfs://...`) are considered valid. Within the context of this library, those are enough. The url - valid or not - must be stored in the `url` member variable. The result of a validation (handled by an internal function) must be stored in the `is_valid` member variable. + +### typedef String CID + +Parts of this spec will refer to `CID`. For clarity it's named as that type, it's implementation can just be a string. + +### interface NetworkInterface + +The network interface API is intended to be an interface to the API. It's implementation must come from the client application. The reasoning here is that each application implementing this API and/or the client application likely has their own network functionality already that should be reused for handing network traffic. + +An example of how these functions are to be used from an API point fo view is as follows: + +```cpp= +// client side +API api +NetworkInterface network_interface; +DataSync data_sync; +api.network_interface = network_interface; +api.ipfs("ipfs://bafyA") + +// api side +while (network_interface.done("ipfs://bafyA").code(WEB) != 200) +{ + auto [error, data] = get_http_data(&Url http_url); + if (error.error_code != 1) + { + // print error + } + else + { + // do something with the data and send it to the client side + } +} +``` + +The below details to implement this interface are for the client implementation ony. The API implementations can ignore this part. + +#### `required async > get_http_data(CID cid, &Url http_url);` + +**Description** +This function will be implemented on the client side and do the actual network requests. The client side is in full control of how it wants to implement the handling of the URL. A client implementation should be aware that this function might be called multiple times with the same `CID` but a different `Url` (the Url contians the CID too). The client should interpret this scenaria on one of 2 ways: + +1. The client downloads whole files, no chunking involved. If a client receives a `CID` multiple times, each with a different gateway, then the file as a whole must be downloaded from multiple gateways. The data from the gateway that had send it fastest must be send back to the library with all other requests of the same `CID` being cancelled and ignored. This is the more bandwidth intensive way because of N request only 1 will be used. +2. The client downloads in chunks. If a client receives a `CID` multiple times, each with a different gateway, then the client shall download each individual chunk with a different gateway. Which chunk the current request shall download is bookkeeping specific to this variant of getting data. Upon completion of each chunk, it's blob must be send back to the library. + +The library will call this function in an async matter. This will be up to the number defined in `Api.gateway_concurrency`. That effectively defines how many different gateways will potentially be used. + +**Parameters** +`CID cid` is the `CID` we want to get the date from. Note that in multiple requests for the same `CID` this value will remain the same. The `http_url` will differ. + +`&Url http_url` is the `ipfs://` translated to already a random gateway in the format of `/ipfs/` (or ipns) which could look like `https://localhost:8080/ipfs/`. + +**Return** +The return type is a pair where the first value is the `Error` object with an appropiate error if any. The `Error.error_code` must be `200` when there is no error and the download completed. The second return argument must be the raw data! While you, as the client, do see the raw data, you should not use it! The library side will handle the raw data and do data validation. + +#### `required async Error done(CID cid);` + +**Description** +This function is called in a loop by the library side. It's being used to check if there is more data to be downloaded from a given IPFS request. The client side should maintain a sort of mapping to CID -> how much data to download -> how much is downloaded. This function must return an error value indicating the download is completed `(200 Ok)`. While that is not the case it should return a value `(206 Partial Content)` indicating there is potentially still more data to download. + +**Parameters** +`CID cid` is the `CID` for which we want to know if it's done handling all the data. + +**Return** +Returns an `Error` object with the status of the download. + +#### `required async void cancel(CID cid);` + +**Description** +The cancel function must be implemented by the client and be able to stop a running `get_http_data` request. The client does **not** call this function! The library, upon receiving an `error` or the result of `get_http_data` will determine if any of it's requests are still pending a return and will call `cancel` on those applicable. + +**Parameters** +`CID cid` the CID fow which we want to cancel all pending requests. + + +### interface DataSync +If we take the earlier example +```cpp= +// client side +API api +NetworkInterface network_interface; +DataSync data_sync; +api.network_interface = network_interface; +api.ipfs("ipfs://bafyA") + +// api side +while (network_interface.done("ipfs://bafyA").code(WEB) != 200) +{ + auto [error, data] = get_http_data(&Url http_url); + if (error.error_code != 1) + { + // print error + } + else + { + // verify data (both CAR and non-CAR), "DataVerification(...)" is a hypothetical class to ilustrate the purpose + if (DataVerification(data) == true) + { + // send verified data back to client + data_sync.data("ipfs://bafyA", data); + } + else + { + // verification error, malicious data or whatever other reason + data_sync.error("ipfs://bafyA"); + } + } +} +``` + +We can now see that the intent for the `DataSync` is to handle verified data. The library will do the verification of this data. Id there is an error the client will only be notified of an error in the data, not what the error is. The library might or might not expose more details of this error. + +#### `required async Error data(CID cid, sequence buffer);` + +**Description** +This function get called on the library side when data validation succeeded. The client side in it's implementation of this function is responsible for handling this now verified data. Depending on the purpose of the client this can mean different things. In for example a IFFS client downloading application this function would store this data in a file that is going to represent `cid`. + +**Parameters** +`CID cid` the `CID` for which we're receiving verified data. +`sequence buffer` the verified byte array of data to handle. + +**Return** +The return must be an `IO` typed `Error` object with code `0` is there is no error. Or an applicable [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html) error is there is an error. + +#### `required async void done(CID cid);` + +**Description** +This function tells of all the data is processed on the **library** side. Due to the logic the library should use, this must also mean that the client had received the last `data(cid, buffer)` to complete handling the given `cid`. Depending on the client usecase this function might or might not be useful. If the client rwites `data(...)` output to files then this function should be used to close the file. + +Within the `DataSync` object the `done` and `error` functions are mutually exclusive! If one is received the other must nbever be triggered for the same `cid`. + +**Parameters** +`CID cid` the `cid` that is now done processing. + +#### `required async void error(CID cid);` + +**Description** +The error function is only tells the client that the library encountered an error while processing data. This could for example be a failed verification. On the client side you don't get to know what the error is only that there was an error. On the library side the error is known. + +The client implementation should abort and data handling for `cid`. +The error function can only be called if `done` wasn't called. + +Within the `DataSync` object the `done` and `error` functions are mutually exclusive! If one is received the other must nbever be triggered for the same `cid`. + +**Parameters** +`CID cid` the `cid` that had an error while processing it's data on the library side. + +### Api + +The `Api` object is the object the client needs to instantiate to do IPFS requests. + +#### `NetworkInterface network_interface;` + +**Description** +The client must provide an implementation of the `NetworkInterface` interface. This could for example be handled on the client side with a network stack already available in the context of the application. + +This implementation has to be assigned to the `network_interface` variable. + +#### `DataSync data_sync;` + +**Description** +This description is from before 12-12-2022. Refer to [this](#12-12-2022) note to see what changed. + +The client must provide an implementation of the `DataSync` interface. Whn data is downloaded it's verified too, after this verification step the data is send back the the client though the `DataSync` interface. This allows you to threat the data as verified and handle it however you seem fit. It could for example be to store as a file. + +#### `sequence gateways;` + +**Description** +For a detailed description see IPIP-AAAA that details how multiclient gateways are found and maintained. + +This variable effectively stores all potential gateways that can be used. +If you want to be in control of this yourself then you have to initialize this variable with a non-empty list of gateways (could just be one) which will always use what you provided and not try anything on it's own with managing gateways. + +#### `long gateway_concurrency;` + +**Description** +This variable determines the maximum amount of gateways to be used to handle an IPFS request. A sane default would be 5. + +You can compare this variable with download clients and how many threads a client uses to download the same file. Just in IPFS speak that is now gateways as opposed to threads. + +### `long max_simultaneous_cids;` + +**Description** +This variable determines the maximum number om simultaneous - but different CIDs - to handle at any time. A CID here is determined by what it put in with the `ipfs(...)` function. The seperate chunks - or blocks - the CID is represented by all count as 1 CID handling. + +You do need to be aware that the number of gateway connections goes up exponentially the more simultaneous CIDs you want to handle. To give an example calculation: +```cpp= + // ... + long gateway_concurrency = 10; + long max_simultaneous_cids = 5; + long potential_max_connections = gateway_concurrency * max_simultaneous_cids; + + // so 50 in this example +``` + +You can also compare this with download clients where you can download a maximum of N files at the same time. + +#### `long max_total_simultaneous_gateways;` + +**Description** +In the example of `max_simultaneous_cids` you can see that fairly trivial defaults could quickly mean a lot of open http connections. + +This variable, `max_total_simultaneous_gateways`, exists to put an upper bould limit on these connections. Say your upper limit is 25 connections but you still want to have the same settings as above (5 http connections per each CID to be handled through the `ipfs(...)` function). + +If you run the following with 2 requests you could get this: + +```cpp= + API api; + api.max_total_simultaneous_gateways = 25; + // ... + api.ipfs("ipfs://bafyA"); // 10 http connections + api.ipfs("ipfs://bafyB"); // 10 http connections +``` + +That example is within all set bounds. +If you run more requests, say 4, then you're well above your `max_total_simultaneous_gateways` of 25. +The logic behind the `ipfs(...)` function must detect this and spread the available maximum connections over the requests. So with 4 requests this would look like: + +```cpp= + API api; + api.max_total_simultaneous_gateways = 25; + // ... + api.ipfs("ipfs://bafyA"); // 7 http connections + api.ipfs("ipfs://bafyB"); // 6 http connections + api.ipfs("ipfs://bafyC"); // 6 http connections + api.ipfs("ipfs://bafyD"); // 6 http connections +``` + +It should simply do a sum like `max_total_simultaneous_gateways / num_of_requests` (25 / 4) which would be 6.25. That should be round down to 6. + +Next it should check to see if the new total connections used (6 * 4 = 24) is smaller then the `max_total_simultaneous_gateways`. That difference, 1 in this case, should be used in the first active connection that doesn't yet meet the `gateway_concurrency` maximum. This should end up in connections being spread as evenly as possible among requests. + +There could also be the case, in this same example, where 2 requests where started early and already each have 10 http connections allocated to them. When a 3rd request comes in the connections should already be re-balanced. Same with a 4th connection. Internally it is calculated how many connections each request can have (6 for each with one of 7). It will then ask those requests that currently have too many connections to release some of their connection slots as soon as their current chunk/block handling is processed. This is gracefully handing over connection slots to other requests to even things out. + +A connection in this mechanism is **never** canceled! + +A proper implementation would be for each request to check on a per connection base (which is per block it downloads) to see if it can proceed onto the next block or if it needs to wait in line for the next connection. + +#### `required Error ipfs(&Url ipfs_url);` + +**Description** +This function triggers the whole flow of getting data from IPFS. +Initially each request that is received in this function is internally put on a queue of requests to handle. If the entry can be handled immediately a `200` status code is returned from this function. A `202` code is returned if the request cannot be handled just yet and remains on the queue. + +**Parameters** +`&Url ipfs_url` this is the `ipfs://...` or `ipns://...` url which needs to be handled. + +**Return** +Returns an error object with a `WEB` status code. This code will be `202` with the description being `Accepted` when the library can handle the request but not "immediately". You could consider this to be notification that the request is pending in the queue to be handled. + +If the request can be handled immediately the response `WEB` code will be `200` with the description being `Ok`. This means your `NetworkInterface` implementation will now get subsequent requests to handle data blobs for the `ipfs_url` you provided. + +#### `required random_gateway(&Url ipfs_url);` + +**Description** +This is a convenience function for the client. The client implements the network functionality (`NetowrkInterface`) and might choose to do all the block handling in there. In such an example the client has the option to request a new random gateway to handle that request. + +This is to offer flexibility. It would be better to let the client handle data in a blocksize manner and return that data to the library. The library then gives you a new request for the next block with a different gateway too. + +**Parameters** +`&Url ipfs_url` this is the `ipfs://...` url for which you will get a potentially different gateway for each time you call this function. Note that this heavily depends on the number of gateways available. + +**Return** +Returns a gateway url in the format `//` so like `http://localhost:8080/ipfs/bafy...`. In the event of no gateways an error will be returned of the `WEB` type with `503` as code and `Service Unavailable` as description. + +#### `required String id();` + +**Description** +An implementation must combine a bunch of variables to form a unique string akin to the user-agent string. + +The implementation should combine the following: +* IPIP spec number of this spec along with a version. Example: IPIP-0288-V1 +* gateway_concurrency +* max_simultaneous_cids +* max_total_simultaneous_gateways + +It must be formatted like this in the order of the above: +`IPIP-0288-V1,10,5,25` + +Comma seperated without trailing comma nor space. + +This ID should be send along with the request to the HTTP gateway in the - for now - `IPFS-AGENT` header. + +**Parameters** +None + +**Return** +A string in the specified format. + +## Conceptual changes and reasons + +### 12-12-2022 + +After a quick meeting with Lidel and Dietrich we seemed to come to the conclusion - for the DataSync class - that any CID -> gateway mapping need on the client side should not be needed. Before this the thinking was as follows. The library would need to know a list of CID -> gateway mappings just to maintain a list of commands to potentially cancel if one of the requests were to fail. The client would also need to maintain a list of the same CID -> gateway mapping for it to later use to construct a file with the incoming verified blocks of data. This line of thinking is now changed to: the client needs no mapping! The effect is that the DataSync class is now implemented on the library side, not on the client side. + +We also made up the idea of adding a uniquely identifying sting. The reasoning here is to be able to detect (on the webserver side) if a connection came from an in-browser node (like brave) or from a custom ipfs download client, etc... It's conceptually similar to the user-agent, it should probably be added in the user agent in some form too. + +## Open questions + +1. The API is async in nature. Should there be a `Error status(&Url ipfs_url)` function that gives back the last known status for a given CID? For example, calling `API::ipfs(...)` could return`200` but also `202` and while requesting data individual data requests likely will return `206 Partial Content`. A status function providing the last known status and details about it's fetched content might make sense. This could potentially be used for progressbar purposes? +2. For the `id()` function. It's still to be determined how this ID is going to look like exactly and how it's going to be send along with each request. It could be a modified user-agent header, it could also be a specific IPFS header (ipfs-agent perhaps?). From f23c161e8756b684226b6ae1c1af394922eeab17 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Thu, 15 Dec 2022 19:10:56 +0100 Subject: [PATCH 17/17] Update integrations/MULTI_GATEWAY_CLIENT.md Change IPIP name, this is the IPFSClient API. --- integrations/MULTI_GATEWAY_CLIENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/MULTI_GATEWAY_CLIENT.md b/integrations/MULTI_GATEWAY_CLIENT.md index 102130cfc..2fc5b7f7f 100644 --- a/integrations/MULTI_GATEWAY_CLIENT.md +++ b/integrations/MULTI_GATEWAY_CLIENT.md @@ -1,4 +1,4 @@ -# IPIP 0000: Multi gateway client +# IPIP 0000: IPFSClient API - Start Date: 2022-12-12 - Related Issues: