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 32b1cfdcbd8ac4b5b0a548135165c2df0be0f4f4 Mon Sep 17 00:00:00 2001 From: Mark Gaiser Date: Mon, 25 Sep 2023 22:22:11 +0200 Subject: [PATCH 16/17] Reword the compatibility section and fix some review feedback. --- integrations/GATEWAYS_FILE.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/integrations/GATEWAYS_FILE.md b/integrations/GATEWAYS_FILE.md index e75425d96..3fe7cf573 100644 --- a/integrations/GATEWAYS_FILE.md +++ b/integrations/GATEWAYS_FILE.md @@ -5,7 +5,7 @@ ## Summary -Defines how IPFS implementations must expose their gateway. This only affects the file-based method of exposiing a gateway. +Defines how IPFS implementations must expose their gateway. This only affects the file-based method of exposing a gateway. ## Motivation @@ -17,7 +17,7 @@ Users should be aware that this is just a list of gateways. It could contain a l 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: +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](#Gateways-file-placement). The gateway must be exposed as a single line in the following format: `http://:` @@ -36,15 +36,17 @@ As a reference, [this](https://github.com/cjbassi/platform-dirs-rs#path-list) li | (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 | +| (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 `gateways` file must be placed in the folder appropriate 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. +It will never be deleted! 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! +An implementation of this spec shall implement the [compatibility](#Compatibility) of the legacy gateway file too. + #### Local file The local file is for read-write access for the IPFS implementation. @@ -65,18 +67,18 @@ 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 +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. The existence of this file is now checked for (and it's content used) in a couple application supporting the IPFS protocols. This file, for backwards compatibility reasons, is therefore a requirement to be support. -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 (`~/.ipfs/gateway`) only contains a single line being the http gateway url. For example: "http://localhost:8080". -The file only contains a single line being the http gateway url. For example: "http://localhost:8080". +An implementation shall: -The file conditions: - 1. is named "gateway" - 2. only exists when Kubo starts a gateway - 3. is removed when Kubo shuts down +1. Inspect the content of `~/.ipfs/gateway` or create the file if it doesn't exist. +2. Replace it's content by the gateway the implementation exposes. +3. Keep the file limited to 1 single line. -Future Kubo implemenations will do the above via symlinking the `gateways` file to `gateway` while maintaining the same rules as stated above. +An implementation shall not remove this file or empty it at application shutdown. +In other terms, the file remains with the last used gateway. ### Security From 6ee4e19f6742bbf10e113cd79e67e8b1c808f92f Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Wed, 4 Oct 2023 01:29:57 +0200 Subject: [PATCH 17/17] ipip-280: update and split into specs and ipip --- integrations/GATEWAYS_FILE.md | 93 ---------------------- src/http-gateways/gateway-detection.md | 102 ++++++++++++++++++++++++ src/http-gateways/index.html | 2 + src/index.html | 4 + src/ipips/ipip-0280.md | 104 +++++++++++++++++++++++++ 5 files changed, 212 insertions(+), 93 deletions(-) delete mode 100644 integrations/GATEWAYS_FILE.md create mode 100644 src/http-gateways/gateway-detection.md create mode 100644 src/ipips/ipip-0280.md diff --git a/integrations/GATEWAYS_FILE.md b/integrations/GATEWAYS_FILE.md deleted file mode 100644 index 3fe7cf573..000000000 --- a/integrations/GATEWAYS_FILE.md +++ /dev/null @@ -1,93 +0,0 @@ -# IPIP 0280: Gateways file -- Start Date: 2022-12-08 -- Related Issues: - - https://github.com/ipfs/kubo/issues/8847 - -## Summary - -Defines how IPFS implementations must expose their gateway. This only affects the file-based method of exposing 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](#Gateways-file-placement). 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 | -| -------- | -------- | -------- | -| (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 appropriate 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 deleted! 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! - -An implementation of this spec shall implement the [compatibility](#Compatibility) of the legacy gateway file too. - -#### 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 - -## 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 - -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. The existence of this file is now checked for (and it's content used) in a couple application supporting the IPFS protocols. This file, for backwards compatibility reasons, is therefore a requirement to be support. - -The file (`~/.ipfs/gateway`) only contains a single line being the http gateway url. For example: "http://localhost:8080". - -An implementation shall: - -1. Inspect the content of `~/.ipfs/gateway` or create the file if it doesn't exist. -2. Replace it's content by the gateway the implementation exposes. -3. Keep the file limited to 1 single line. - -An implementation shall not remove this file or empty it at application shutdown. -In other terms, the file remains with the last used gateway. - -### 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/src/http-gateways/gateway-detection.md b/src/http-gateways/gateway-detection.md new file mode 100644 index 000000000..36ace1baf --- /dev/null +++ b/src/http-gateways/gateway-detection.md @@ -0,0 +1,102 @@ +--- +title: Detecting User-Preferred IPFS Gateway +description: > + Specification of the rules and standards for detecting and identifying + user-preferred IPFS gateways within applications, enabling seamless + integration and user control. +date: 2023-10-03 +maturity: reliable +editors: + - name: Mark Gaiser + github: markg85 + - name: Marcin Rataj + github: lidel + url: https://lidel.org/ + affiliation: + name: Protocol Labs + url: https://protocol.ai/ +tags: ['httpGateways', 'integratingHttpGateways'] +order: 99 +--- + + +## Introduction + +This document defines conventions for how applications can identify available +IPFS gateway, and how IPFS gateway implementations can signal own endpoint to +client applications. + +## Specification + +There are two ways of hinting user-prefered gateway URL: + +- Setting the `IPFS_GATEWAY` environment variable +- Creating a `gateway` file at a well-known path + +Applications SHOULD evaluate these hints in order and stop on the first match: + +1. Check if a valid `IPFS_GATEWAY` environment variable is set +2. Check if a valid `gateway` file is present at one of well-known filesystem paths + + +### `IPFS_GATEWAY` Environment Variable + +When `IPFS_GATEWAY` environment variable is set, the value MUST be interpreted +as URL of IPFS Gateway application to use. + +This variable SHOULD override gateway selection done by all other means, including +internal application configuration. + +### The `gateway` Configuration File + +Client application SHOULD check if file is present at specific filesystem paths, in order: + +1. If `IPFS_PATH` is set, try `$IPFS_PATH/gateway` +2. If `HOME` is set, try `$HOME/.ipfs/gateway` +3. If `$XDG_CONFIG_HOME` is set, try `$XDG_CONFIG_HOME/ipfs/gateway` +4. If `/etc` exists, try `/etc/ipfs/gateway` +5. Try OS-specific paths + - Windows + 1. `%LOCALAPPDATA%/ipfs/gateway` (local user) + 2. `%APPDATA%/ipfs/gateway` (roaming user) + 3. `%PROGRAMDATA%/ipfs/gateway` (global) + - macOS + 1. `$HOME/Library/Application Support/ipfs/gateway` (user) + 2. `/Library/Application Support/ipfs/gateway` (global) + - Linux + 1. `$HOME/.config/ipfs/gateway` (user) + 2. `/etc/ipfs/gateway` (global) + +When `gateway` file is present, the file contents MUST be interpreted as line +separated (`\n` or `\r\n`) `text/plain` file. + +The first line of `gateway` file MUST be a valid `http://` or `https://` URL. + +Implementations that only need one URL SHOULD use the first one and ignore the rest of the file. + +### Security + +Applications that integrate IPFS support via HTTP gateway: + +MUST NOT hard-code non-localhost URL as a default fallback. Instead, SHOULD ask +user to define preferred IPFS gateway using one of methods defined in this +document. + +SHOULD either warn user when non-localhost gateway is used for deserialized +responses (warning about the risk of MITM), or (preferred) limit HTTP use +outside of localhost to verifiable response types defined in +:cite[trustless-gateway]. + +### Privacy and User Control + +Applications SHOULD never default to public gateways. Instead, suggest to the +user how to run a local node. + +### Compatibility and Testing + +Implementers should test against implementations mentioned in :cite[ipip-0280] +as the baseline for making decisions around maximizing interoperability. + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/src/http-gateways/index.html b/src/http-gateways/index.html index 27b633a18..650248bf8 100644 --- a/src/http-gateways/index.html +++ b/src/http-gateways/index.html @@ -19,6 +19,8 @@

Web

origin-based security model.

{% include 'list.html', posts: collections.webHttpGateways %} +

Integration

+ {% include 'list.html', posts: collections.integratingHttpGateways %} {% include 'footer.html' %} diff --git a/src/index.html b/src/index.html index db6e7849e..01439e502 100644 --- a/src/index.html +++ b/src/index.html @@ -93,6 +93,10 @@

HTTP Gateways

Web semantics (for website hosting and web browsers):

{% include 'list.html', posts: collections.webHttpGateways %} +

+ Other integrations: +

+ {% include 'list.html', posts: collections.integratingHttpGateways %}

InterPlanetary Naming System

diff --git a/src/ipips/ipip-0280.md b/src/ipips/ipip-0280.md new file mode 100644 index 000000000..ea493ece9 --- /dev/null +++ b/src/ipips/ipip-0280.md @@ -0,0 +1,104 @@ +--- +title: "IPIP-0280: Conventions for HTTP Gateway Detection" +date: 2023-10-03 +ipip: proposal +editors: + - name: Mark Gaiser + github: markg85 + - name: Marcin Rataj + github: lidel + url: https://lidel.org/ + affiliation: + name: Protocol Labs + url: https://protocol.ai/ +relatedIssues: + - https://github.com/ipfs/kubo/issues/8847 + - https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/f889837e00d3b2388a24c0a9d075ad62f47da825 + - https://github.com/curl/curl/pull/8805 +order: 280 +tags: ['ipips'] +--- + +## Summary + +This IPIP aims to create conventions for how applications can identify available IPFS gateway, +and how IPFS gateway implementations can signal own endpoint. + +## Motivation + +Applications wanting to leverage IPFS Gateways are, without a common +convention, left to invent their own ways of finding a gateway, including naive +approaches such as localhost port scanning. + +This IPIP introduces specification that defines how an application wanting to +implement IPFS support can find a local or user-preferred gateways. + +## Detailed design + +We introduce two ways of hinting user-prefered gateway URL to cover +the majority of runtimes and use cases: + +- `IPFS_GATEWAY` environment variable +- `gateway` file and filesystem paths to look for it + +See: :cite[gateway-detection] for details. + +## Design rationale + +### User benefit + +End users can define their prefered gateway once, and benefit from +opportunistic support in applications they use. + +Application developers save time as they only need to implement support for +vendor-agnostic convention to be able to read user preferred gateway. + +### Compatibility + +#### Kubo + +Kubo ([>0.15.0](https://github.com/ipfs/kubo/blob/master/docs/changelogs/v0.15.md#-ipfs_pathgateway-file)) +creates a hint file in `$IPFS_PATH/gateway` (default being `$HOME/.ipfs/gateway`, see [kubo#8847](https://github.com/ipfs/kubo/issues/8847)). + +The file contains a single line being the local HTTP gateway URL. For example: `http://localhost:8080`. + +Every time `ipfs daemon` starts, it updates the the content of `$IPFS_PATH/gateway` or creates the file if it doesn't exist. + +#### IPFS Chromium + +ipfs-chromium uses `IPFS_GATEWAY` environment variable +([ipfs-chrompium#29](https://github.com/little-bear-labs/ipfs-chromium/issues/29)). + +It can be a single URL, or a whitespace-separated URLs to be used as the initial gateway pool. + +Ref. + +#### FFMPEG + +FFMPEG's libavformat supports both `$HOME/.ipfs/gateway` file and `IPFS_GATEWAY` environment variable +([ffmpeg.git/commit/f889837](https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/f889837e00d3b2388a24c0a9d075ad62f47da825)). + + +Ref. + +#### Curl + +Curl (>8.4.0, [curl#8805](https://github.com/curl/curl/pull/8805)) supports +will try the `IPFS_GATEWAY` environment variable first, and then look for +`$IPFS_PATH/gateway` or `$HOME/.ipfs/gateway`, if present. + +It expects a single URL. + +Ref. + +### Security + +See "Security" section of :cite[gateway-detection]. + +### Alternatives + +N/A. This IPIP covers both environment variable and configuration file. + +### Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).