注册
关闭
区块链大帝

区块链大帝

发布于 2周前 阅读数 2489

使用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"
  • 包和依赖项管理器,例如MavenGradle
  • 一个IDE(集成开发环境),对于本教程,我们使用Eclipse
  • 运行中的IPFS节点(> 0.4.x)请 遵循以下文章,以了解如何安装IPFS节点(go-ipfs)

依存关系

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

马文

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

pom.xml

 <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名称读取对象需要两个步骤:

  1. 根据名称解析哈希
  2. 从哈希读取内容
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));

参考文献

  • 0
区块链大帝
区块链大帝

0 条评论