使用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,我们首先需要配置托管依赖项的存储库,然后导入依赖项。在结束</project>标记之前添加以下代码:
<properties> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <java-ipfs-http-client.version>v1.2.3</java-ipfs-http-client.version> </properties><repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
<dependencies> <dependency> <groupId>com.github.ipfs</groupId> <artifactId>java-ipfs-http-client</artifactId> <version>${java-ipfs-http-client.version}</version> </dependency> </dependencies>
摇篮
使用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<MerkleNode>方法将内容存储在我们连接到的IPFS节点上。此方法将a NamedStreamable或a List<NamedStreamable>作为输入。NamedStreamable有四个不同的实现:
- FileWrapper 包装一个 java.io.File
- InputStreamWrapper 包装一个 java.io.InputStream
- ByteArrayWrapper 包装一个 byte[]
- DirWrapper包装(String name, List<NamedStreamable> 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<MerkleNode>。 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(<hash>): 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(<hash>, <path>): 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(<hash>): 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(<hash>): 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(<hash>, <recursive>): 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(<pinType>): Map<Multihash, Object>
try { Map<Multihash, Object> 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<String> keyType = Optional.of("rsa"); Optional<String> 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<KeyInfo> 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<Multihash> peers = ipfs.refs.local() peers.forEach(multihash -> System.out.println("Peer ID: " + multihash));
参考文献
本文来源于互联网:使用IPFS管理Java应用程序中的存储