WEB3DEV

Adriano P. Araujo
Adriano P. Araujo

Posted on

Gerenciando o armazenamento em um aplicativo Java com IPFS

Neste artigo, vamos aprender a interagir com IPFS ( Sistema de arquivos interplanetários - em inglês InterPlanetary File System ) em Java usando a biblioteca oficial java-ipfs-http-client. Esta biblioteca se conecta a um nó IPFS e engloba a maioria das operações oferecidas pela API HTTP.

O diagrama a seguir descreve um programa Java conectado a um nó IPFS através da biblioteca java-ipfs-http-client para um servidor API.

  • API Server( porta padrão: 5001 ): API completa

  • Gateway server ( porta padrão: 8080 ): API com acesso somente à leitura ( acesso apenas aos dados )

  • P2P ( porta padrão:4001 ): interface ponto a ponto

Pré-requisitos

Para fazer este tutorial, precisamos ter o seguinte instalado:

  • Linguagem de programação Java ( > 8 )
    java version "1.8.0_201"

  • Um gerenciador de pacotes e dependências, por exemplo Maven ou Gradle

  • Um IDE ( Ambiente de desenvolvimento integrado ), para este tutorial, usamos o Eclipse

  • Um nó IPFS em execução ( > 0.4.x )

    Seguir o seguinte artigo para aprender a instalar um nó IPFS ( go-ipfs )

Dependências

Para começar, importe a dependência java-ipfs-http-client

Maven

Para usar o Maven, primeiro precisamos configurar o repositório que hospeda a dependência e depois importar a dependência. Adicione o código abaixo antes do fechamento da tag </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>
Enter fullscreen mode Exit fullscreen mode

Gradle

O equivalente usando Gradle:

dependencies {

 compile "com.github.ipfs:java-ipfs-http-client:v1.2.3"

}
Enter fullscreen mode Exit fullscreen mode

Conecte-se ao IPFS

Uma vez importado  o java-ipfs-http-client, a primeira etapa do nosso aplicativo é conectar-se a um nó IPFS.

Conexão por host e por porta

Podemos nos conectar por um host e a uma porta como esta:

IPFS ipfs = new IPFS("localhost", 5001);

Conexão por multiaddr

Também é possível conectar por multiaddr. Um multiaddr representa um endereço de rede auto-descritivo.

Multiaddr é um formato para codificar endereços de vários protocolos de rede bem estabelecidos. É útil escrever aplicativos que protejam o futuro do uso de endereços e permitam a coexistência de vários protocolos e endereços de transporte.

IPFS ipfs = new IPFS ("/ip4 / 127.0.0.1 / tcp / 5001");

Se o nó IPFS estiver atrás de um proxy com SSL ( semelhante ao Infura), podemos configurar o java-ipfs-http-client para usar o https em vez de http, mas é necessário um multiaddr.

IPFS ipfs = new IPFS ("/dnsaddr/ipfs.infura.io/tcp/5001/https");

Adição de conteúdo ao IPFS

Ao adicionar um arquivo na rede IPFS, o arquivo é carregado no nó IPFS ao qual estamos conectados e armazenados em seu armazenamento de dados local. Esta operação retorna um identificador exclusivo do arquivo chamado “ multihash ” ( por exemplo: Qmaisz6NMhDB51cCvNWa1GMS7LU1pAxdF4Ld6Ft9kZEP2a)

Nós usamos o método ipfs.add(NamedStreamable file): List<MerkleNode> para armazenar conteúdo no nó IPFS ao qual estamos conectados. Este método leva um NamedStreamable ou um List<NamedStreamable> como entrada. NamedStreamable possui quatro implementações diferentes:

  • FileWrapper envolve um java.io.File

  • InputStreamWrapper envolve um java.io.InputStream

  • ByteArrayWrapper envolve um byte[]

  • DirWrapper envolve um (String name, List<NamedStreamable> children) para descrever uma estrutura de arquivos hierárquicos

Também podemos adicionar parâmetros opcionais ao método:

  • wrap[ boolean ]: envolve arquivos em um diretório.

  • hashOnly[ boolean ]: Apenas para chunk e hash - não escreva para o datastore.

Finalmente, o método retorna uma lista de MerkleNode que representa os objetos endereçáveis por conteúdo adicionados na rede IPFS.

Arquivo ( FileWrapper )

Nós podemos usar NamedStreamable.FileWrapper para passar um java.io.File para 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 )

Se você está lidando com um java.io.InputStream, use 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);

}

Enter fullscreen mode Exit fullscreen mode

Matriz de bytes ( ByteArrayWrapper )

Para armazenar um byte[], use 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);

}

Enter fullscreen mode Exit fullscreen mode

Diretório ( DirWrapper )

Por fim, para armazenar arquivos dentro das pastas, use NamedStreamable.DirWrapper. Por exemplo, com a estrutura de pastas abaixo:


pastas

| - hello.txt

| - hello2.txt
Enter fullscreen mode Exit fullscreen mode

Use:

try {

 NamedStreamable.FileWrapper file1 = new NamedStreamable.FileWrapper(new File("/home/gjeanmart/Documents/hello.txt"));

 NamedStreamable.FileWrapper file2 = new NamedStreamable.FileWrapper(new File("/home/gjeanmart/Documents/hello2.txt"));

 NamedStreamable.DirWrapper directory = new NamedStreamable.DirWrapper("folder", Arrays.asList(file1, file2));

 List<MerkleNode> response = ipfs.add(directory);

 response.forEach(merkleNode ->

 System.out.println("Hash (base 58): " + merkleNode.name.get() + " - " + merkleNode.hash.toBase58()));

} catch (IOException ex) {

 throw new RuntimeException("Error whilst communicating with the IPFS node", ex);
Enter fullscreen mode Exit fullscreen mode

 

MerkleNode

O IPFS é uma rede ponto a ponto usada essencialmente para compartilhar objetos vinculados de uma árvore gigante de Merkle. Ao adicionar um arquivo ou um diretório ao IPFS, esta operação retorna o novo ramo dedicado da árvore Merkle composto por um ou mais Objetos vinculados. Representamos esses ramos em Java como um List<MerkleNode>.

O MerkleNode é composto pelas seguintes informações:

  • hash ( multihash ): um identificador exclusivo do Objeto no IPFS

  • nome ( opcional ): Nome do objeto ( geralmente a pasta ou o nome do arquivo )

  • tamanho ( opcional ): Tamanho do objeto

  • links ( zero ou mais ): Uma lista de objetos-filho

MultiHash

Multihash (github) é um hash de autodescrição para identificar e localizar um objeto exclusivamente na árvore de Merkle do IPFS. Geralmente é representado em Base58, mas também podemos representá-lo em hexadecimal.

Um multihash consiste em diferentes partes:

Exemplo (em hexadecimal)

Leitura de um hash Base58 para Multihash

Multihash multicash = Multihash.fromBase58 ("QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o");

Leitura de um hash Base16 ( hexadecinal ) para Multihash

Multihash multicash = Multihash.fromHex ("122046d44814b9c5af141c3aaab7c05dc5e844ead5f91f12858b21eba45768b4ce");

Conversão de um Multihash em Base58

Hash de string = multihash.toBase58 ( );

Conversão de um Multihash em Base16

Hash de corda = multihash.toHex ( );

Conversão de um Multihash em uma matriz de bytes

byte[] hash = multihash.toBytes();

Leitura do conteúdo do IPFS

Para ler um arquivo na rede IPFS, precisamos passar o hash ( multihash ) do Objeto que queremos recuperar. Em seguida, o IPFS encontra e recupera o arquivo do par mais próximo que hospeda o arquivo através da rede ponto a ponto e uma Tabela de hash distribuída.

Usando java-ipfs-http-client, existem duas maneiras de ler o conteúdo da rede IPFS.

Leitura do conteúdo em uma matriz Byte

A maneira mais comum de encontrar e ler conteúdo do IPFS para um determinado hash é usar o método 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);

}
Enter fullscreen mode Exit fullscreen mode

Também é possível recuperar um arquivo de uma estrutura de diretórios passando o caminho do arquivo como este 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);

}
Enter fullscreen mode Exit fullscreen mode

Leitura do conteúdo em um fluxo

A segunda maneira consiste em usar o método ipfs.catStream(<hash>): InputStream para escrever a resposta em um fluxo.


 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);

}
Enter fullscreen mode Exit fullscreen mode

Conteúdo de pin / unpin

Adicionar um arquivo ao IPFS cria apenas uma cópia do arquivo em um local ( seu nó ), para que o arquivo seja legível em qualquer nó, a menos que seu nó fique offline. Fixar é a ação para replicar um arquivo ( já disponível em algum lugar da rede ) para o nosso nó local.

Este método é útil para trazer velocidade e alta disponibilidade a um arquivo.

Pin

O método ipfs.pin.add(<hash>): void oferece a fixação de um arquivo por hash em nosso nó.

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);

}
Enter fullscreen mode Exit fullscreen mode

A fixação de um objeto vinculado a outros Objetos ( filhos ), como um diretório, fixa automaticamente todos os filhos subsequentes.

Unpin

A operação reversa também é possível com o método ipfs.pin.rm(<hash>, <recursive>): void, que remove um arquivo do nosso nó.

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);
}
Enter fullscreen mode Exit fullscreen mode

Podemos usar o sinalizador

recursive [boolean]

para remover ( unpin ) todos os objetos vinculados subsequentes ao objeto identificado pelo hash ( padrão true ).

Lista

Por fim, podemos listar todo o conteúdo hospedado em nosso nó local com o método `

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);
}

 

Podemos solicitar diferentes tipos de chaves fixadas para listar:

  • all: Todos os objetos

  • direct: Objetos fixados diretamente

  • indirect: Objetos referenciados por pins recursivos

  • recursive: Raízes de pins recursivos ( mas também os pin dos filhos do objeto )

IPNS

IPNS significa “ InterPlanetary Naming System ” e representa um espaço de nome mutável global acessível de qualquer lugar da rede IPFS para atribuir um nome a um hash ( semelhante a um servidor DNS atribuindo um nome a um servidor IP ). Isso pode ser útil quando queremos compartilhar um link de um objeto mutável.

Digamos, por exemplo, que queremos hospedar um artigo no IPFS, o artigo ( versão 1 ) possui um hash exclusivo, mas se decidirmos atualizar o artigo e hospedá-lo no IPFS, o hash é diferente e teremos que recompartilhar o novo hash. Podemos usar o IPNS para evitar esse problema, é possível vincular um nome a um hash e atualizar o hash da maneira que quisermos, portanto, precisamos apenas reatribuir o hash do artigo a um nome e compartilhar o nome, se atualizarmos o artigo, precisamos apenas atualizar a resolução do nome para apontar para a versão mais recente.

Nota: O IPNS ainda está em evolução e é lento para usar, leva aproximadamente 1-2 minutos para publicar um nome.

Chaves

O IPNS é baseado em uma infraestrutura de chave pública distribuída ( PKI ). Para começar, precisamos de um par de chaves disponível em nosso nó IPFS.

Podemos usar o par de chaves para armazenar um par de chave / valor em que a chave representa o “ nome ” e “ valor ” do hash a ser resolvido.

Gerando uma chave

Primeiro, precisamos gerar um par de chaves usando o método 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);
}

 

A seguinte função retorna um objeto KeyInfo composto pelo nome e pelo ID ( multihash ) da chave que representa o nome que podemos usar para resolver um hash.

Exclusão de uma chave

Também é possível remover uma chave usando ipfs.key.rm(keyName): void.

try {
 ipfs.key.rm(keyName);
} catch (IOException ex) {
 throw new RuntimeException("Error whilst communicating with the IPFS node", ex);
}

Listagem de todas as chaves

O método ipfs.key.list() nos permite listar todas as chaves disponíveis em nosso nó.


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);
}

 

A chave self representa a chave padrão gerada quando lançamos o IPFS pela primeira vez.

Publicação

Uma vez que temos um par de chaves exclusivo disponível, podemos usá-lo para publicar um hash contra ele usando o método 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);
}

Observe que esta operação é particularmente lenta e pode levar até dois minutos para ser executada

Resolução

Assim como um DNS, ler um objeto de um nome IPNS é um processo de duas etapas:

  1. Resolução do hash em relação ao nome

  2. Leitura do conteúdo do hash

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);
}}

Outras operações

A biblioteca java-ipfs-http-client envolve muitas outras operações da API disponíveis no nó.

Versão Node

Para obter a versão Node à qual estamos conectados, a biblioteca fornece o método 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);
}

Pares de nós

Para recuperar a lista de pares conectados ao nosso nó local:

List<Multihash> peers = ipfs.refs.local()
peers.forEach(multihash ->
System.out.println("Peer ID: " + multihash));


Este artigo foi escrito por Greg Jeanmart  e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Top comments (0)