使用IPFS管理Java应用程序中的存储

来源:https://kauri.io/article/3e8494f4f56f48c4bb77f1f925c6d926

在本文中,我们将学习如何使用官方的java-ipfs-http-client库与Java中的IPFS(行星际文件系统)进行交互。该库连接到IPFS节点,并包装HTTP API提供的大多数操作。

下图描述了一个Java程序,该程序通过java-ipfs-http-client库连接到IPFS节点到API服务器。

API服务器(默认端口:5001):完整的API
网关服务器(默认端口:8080):只读API(仅访问数据)
P2P(默认端口:4001):对等接口

先决条件

要运行本教程,我们必须安装以下软件:
Java编程语言(> 8)
$ java -version java version “1.8.0_201”
包和依赖项管理器,例如Maven或Gradle
一个IDE(集成开发环境),对于本教程,我们使用Eclipse
运行中的IPFS节点(> 0.4.x)请 遵循以下文章,以了解如何安装IPFS节点(go-ipfs)

依存关系

首先,导入java-ipfs-http-client依赖项

马文

使用Maven,我们首先需要配置托管依赖项的存储库,然后导入依赖项。在结束标记之前添加以下代码:
pom.xml

1.8 1.8 v1.2.3
jitpack.io https://jitpack.io
com.github.ipfs java-ipfs-http-client ${java-ipfs-http-client.version}

摇篮

使用Gradle等效:
dependencies { compile “com.github.ipfs:java-ipfs-http-client:v1.2.3” }

连接到IPFS

导入后java-ipfs-http-client,应用程序的第一步是连接到IPFS节点。

通过主机和端口连接

我们可以通过主机和端口进行连接,如下所示:
IPFS ipfs = new IPFS(“localhost”, 5001);

通过multiaddr连接

也可以通过multiaddr连接。一个多地址代表一个自描述的网络地址。
Multiaddr是一种用于编码来自各种公认的网络协议的地址的格式。编写应用程序以确保将来的地址使用是可行的,并允许多个传输协议和地址共存。

IPFS ipfs = new IPFS("/ip4/127.0.0.1/tcp/5001");
如果IPFS节点位于具有SSL的代理(例如Infura)的后面,我们可以配置java-ipfs-http-client为使用https而不是,http而是需要multiaddr。
IPFS ipfs = new IPFS("/dnsaddr/ipfs.infura.io/tcp/5001/https");
*

向IPFS添加内容

在IPFS网络上添加文件时,该文件将上传到我们连接到的IPFS节点并存储在其本地数据存储中。此操作返回名为“ multihash”的文件的唯一标识符(例如:)Qmaisz6NMhDB51cCvNWa1GMS7LU1pAxdF4Ld6Ft9kZEP2a。

我们使用该ipfs.add(NamedStreamable file): List方法将内容存储在我们连接到的IPFS节点上。此方法将a NamedStreamable或a List作为输入。NamedStreamable有四个不同的实现:

FileWrapper 包装一个 java.io.File
InputStreamWrapper 包装一个 java.io.InputStream
ByteArrayWrapper 包装一个 byte[]
DirWrapper包装(String name, List children)以描述分层文件结构
我们还可以向该方法添加可选参数:
wrap [布尔值]:将文件包装到目录中。
hashOnly [布尔值]:仅块和哈希-不写入数据存储区。
最后,该方法返回一个列表,MerkleNode该列表表示刚添加到IPFS网络上的内容可寻址对象。

文件(FileWrapper)

我们可以使用NamedStreamable.FileWrapper将a传递java.io.File给IPFS。
try { NamedStreamable.FileWrapper file = new NamedStreamable.FileWrapper(new File("/home/gjeanmart/Documents/hello.txt")); MerkleNode response = ipfs.add(file).get(0); System.out.println("Hash (base 58): " + response.hash.toBase58()); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }

InputStream(InputStreamWrapper)

如果您正在处理java.io.InputStream,请使用NamedStreamable.InputStreamWrapper:
try { NamedStreamable.InputStreamWrapper is = new NamedStreamable.InputStreamWrapper(new FileInputStream("/home/gjeanmart/Documents/hello.txt")); MerkleNode response = ipfs.add(is).get(0); System.out.println("Hash (base 58): " + response.name.get() + " - " + addResponse.hash.toBase58()); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }
字节数组(ByteArrayWrapper)
要存储byte[],请使用NamedStreamable.ByteArrayWrapper。
try { NamedStreamable.ByteArrayWrapper bytearray = new NamedStreamable.ByteArrayWrapper(“hello”.getBytes()); MerkleNode response = ipfs.add(bytearray).get(0); System.out.println("Hash (base 58): " + response.hash.toBase58()); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }

目录(DirWrapper)

最后,要将文件存储在文件夹中,请使用NamedStreamable.DirWrapper。例如,使用下面的文件夹结构: folder |-- hello.txt |-- hello2.txt
使用:

MerkleNode

IPFS是一种点对点网络,主要用于共享巨型Merkle树中的链接对象。将一个文件或目录添加到IPFS时,此操作将返回由一个或多个链接对象组成的Merkle树的新专用分支。我们将Java中的这些分支表示为List。 A MerkleNode由以下信息组成: 哈希(multihash):IPFS中对象的唯一标识符 名称(可选):对象的名称(通常是文件夹或文件名) 大小(可选):对象的大小 链接(零个或多个):子对象列表

多哈希

Multihash(github)是一种自我描述的哈希,用于唯一地标识对象并将其定位到IPFS Merkle树中。它通常用Base58表示,但我们也可以用十六进制表示。 多重哈希由不同部分组成: 例如(十六进制) 将Base58哈希读取到Multihash Multihash multihash = Multihash.fromBase58(“QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o”); 将Base16(十六进制)哈希读取到Multihash Multihash multihash = Multihash.fromHex(“122046d44814b9c5af141c3aaab7c05dc5e844ead5f91f12858b21eba45768b4ce”);

将Multihash转换为Base58
String hash = multihash.toBase58();
将Multihash转换为Base16
String hash = multihash.toHex();
将Multihash转换为字节数组
byte[] hash = multihash.toBytes();

从IPFS读取内容

为了读取IPFS网络上的文件,我们需要传递我们要检索的对象的哈希(多重哈希)。然后,IPFS通过对等网络和Distributed Hash Table从托管该文件的最近对等节点中查找并检索该文件。 使用java-ipfs-http-client,有两种方法可以从IPFS网络读取内容。

将内容读入Byte数组

从IPFS查找和读取给定哈希的内容的最常见方法是使用该方法 ipfs.cat(): byte[] try { String hash = “QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o”; // Hash of a file Multihash multihash = Multihash.fromBase58(hash); byte[] content = ipfs.cat(multihash); System.out.println("Content of " + hash + ": " + new String(content)); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); } 通过传递这样的文件路径,也可以从目录结构中检索文件ipfs.cat(, ): byte[]: try { String hash = “QmNoQbeckeCN7FWt6mVcxTf7CAyyHUMsqtCWtMLFdsUayN”; // Hash of a directory Multihash multihash = Multihash.fromBase58(hash); byte[] content = ipfs.cat(multihash, “/hello2.txt”); System.out.println("Content of " + hash + "/hello2.txt : " + new String(content)); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }

将内容读入流

第二种方法是使用该方法ipfs.catStream(): InputStream将响应写入流中。
try{ String hash = “QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o”; // Hash of a file Multihash multihash = Multihash.fromBase58(hash); InputStream inputStream = infuraIPFS.catStream(filePoinhashter2); Files.copy(inputStream, Paths.get("/home/gjeanmart/Documents/helloResult.txt")); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }

固定/取消固定内容

在IPFS上添加文件只会在一个位置(您的节点)上创建该文件的副本,因此,除非您的节点脱机,否则任何节点都可以读取该文件。固定是将文件(已在网络上的某个位置可用)复制到我们的本地节点的操作。
此方法对提高文件的速度和高可用性很有用。

该方法ipfs.pin.add(): void提供了通过哈希将文件固定在我们的节点上的方法。

try { String hash = “QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o”; // Hash of a file Multihash multihash = Multihash.fromBase58(hash); ipfs.pin.add(multihash); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }
固定链接到其他对象(子级)(例如目录)的对象会自动固定所有后续子级。
取消固定
使用ipfs.pin.rm(, ): void从我们的节点中删除文件的方法,也可以执行反向操作。
try { String hash = “QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o”; // Hash of a file Multihash multihash = Multihash.fromBase58(hash); ipfs.pin.rm(multihash) } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }
我们可以使用该标志recursive [boolean]将所有后续链接的对象删除(取消固定)到由哈希标识的对象(默认为true)。
清单
最后,我们可以使用方法列出本地节点上托管的所有内容 ipfs.pin.ls(): Map
try { Map list = ipfs.pin.ls(PinType.all); list.forEach((hash, type) -> System.out.println("Multihash: " + hash + " - type: " + type)); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }
我们可以请求不同类型的固定键列出:
all:所有对象
direct:直接固定的对象
indirect:递归引脚引用的对象
recursive:递归图钉的根(例如直接图钉,也可以钉住对象的子代)

IPNS

IPNS代表“行星际命名系统”,代表可从IPFS网络上的任何位置访问的全局可变名称空间,以根据哈希分配名称(类似于DNS服务器根据服务器IP分配名称)。当我们要共享可变对象的链接时,这很有用。
举例来说,假设我们要在IPFS上托管一篇文章,而该文章(版本1)具有唯一的哈希,但是如果我们决定更新该文章并将其托管在IPFS上,则哈希是不同的,我们必须重新共享新的哈希哈希。我们可以使用IPNS来防止此问题,可以将名称链接到哈希并根据需要进行很多更新,因此,如果我们更新,则只需将文章的哈希重新分配给名称并共享名称即可。在本文中,我们只需要更新名称解析以指向最新版本即可。

注意:IPNS仍在开发中,使用缓慢,发布名称大约需要1-2分钟。

按键

IPNS基于分布式公钥基础结构(PKI)。首先,我们需要IPFS节点上有可用的密钥对。
我们可以使用键对来存储一个键/值对,其中键代表要解析的哈希的“名称”和“值”。

产生金钥

首先,我们需要使用方法生成密钥对ipfs.key.gen(name, type, size): KeyInfo。
try { String keyName =“myarticle”; Optional keyType = Optional.of(“rsa”); Optional keySize = Optional.of(“2048”);
KeyInfo key = ipfs.key.gen(keyName, keyType, keySize); System.out.println("key name: " + key.name); System.out.println("key.hash: " + key.id); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }

以下函数返回一个KeyInfo对象,该对象由名称和代表该名称的键的ID(多重哈希)组成,可用于解析哈希。

删除金钥

也可以使用删除键ipfs.key.rm(keyName): void。
try { ipfs.key.rm(keyName); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }

列出所有按键

该方法ipfs.key.list()允许我们列出节点上所有可用的密钥。
try { List keys = ipfs.key.list(); keys.forEach(key -> System.out.println(“keyInfo: name=” + key.name + “, hash=” + key.id)); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }
该self密钥代表我们首次启动IPFS时生成的默认密钥。
发布
一旦有了可用的专用密钥对,就可以使用以下方法使用它来发布针对它的哈希值ipfs.name.publish(hash, keyName):
try { String hash = “QmWfVY9y3xjsixTgbd9AorQxH7VtMpzfx2HaWtsoUYecaX” // Hash of “hello Map response = ipfs.name.publish(hash, Optional.of(keyName)); System.out.println(“publish(hash=”+hash+”, key="+keyName+"): " + response); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }
请注意,此操作特别慢,最多可能需要两分钟才能执行

解决

就像DNS一样,从IPNS名称读取对象需要两个步骤:
根据名称解析哈希
从哈希读取内容
try { KeyInfo key = ipfs.key.list().stream() .filter(k -> k.name.equals(keyName)) .findAny() .orElseThrow(() -> new RuntimeException(“Key " + keyName + " not found”));
String resolveResponse = ipfs.name.resolve(key.id); System.out.println(“resolve(key=”+key.id+"): " + resolveResponse);

byte[] content = ipfs.cat(Multihash.fromBase58(resolveResponse.substring(6))); System.out.println("Content: " + new String(content)); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); } }

其他作业

该java-ipfs-http-client库包装了节点上可用的许多其他API操作。

节点版本

为了获得我们连接的Node版本,该库提供了方法 ipfs.version(): String
try { String version = ipfs.version(); System.out.println("Node version: " + version); } catch (IOException ex) { throw new RuntimeException(“Error whilst communicating with the IPFS node”, ex); }

节点对等

要检索连接到我们本地节点的对等方列表:
List peers = ipfs.refs.local() peers.forEach(multihash -> System.out.println("Peer ID: " + multihash));

参考文献

GitHub储存库
API服务器文档
IPFS简介(通过Consensys)
IPFS示例介绍(克里斯蒂安·隆德克维斯特)
在去中心化网络上发布内容的权威指南(按纺织品)

你可能感兴趣的:(星际大陆IPFS,IPFS)