Skip to content

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>

Gradle

O equivalente usando Gradle:

dependencies {

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

}

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

}

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

}

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

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

 

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

}

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

}

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

}

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

}

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

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

 ![](https://lh3.googleusercontent.com/C825LrTbNGgHC0nBFXL0DGpAlQc87vCKuorsm9Z0mt_AoCvTdUEO5CpJMMcSaOQWCr-31Din-o2fQKZ49uc_1dUMKFihRoWcOo5tAOk5GpBi36GSTmHHZaug2odXEnR498iuZHpK4AMcKKB0LhGvnnTQVljkrr69bzVSuUbT5DkOom9IElXlcLXA2A)


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`.


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

 ![](https://lh3.googleusercontent.com/bnXxPYzXX0UftLPa5-iUjFT-bBc6xTcOpGmvg75he94na45Z2kpJ7fpR07KcQUR9_MSY0EfT5ZnP1rM0ntsgS23WvEZKs6mh1-GGN0CHPBLO8naoWKbfLrOEZEJQ6xRGnlMa0FH81yoa_gm_C5Mg5AT9WHpxGxtFQ92qYh7erkYUcQbzqsXZbBzrMw)


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.`

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

**Listagem de todas as chaves**

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

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



 ![](https://lh6.googleusercontent.com/cQZsKo3xesZFlCsgPgq8qWnPs9AmAI78FxxS2vBjIcIIU2ax9Z72OnErHG42zEgYmsGLElo-wnwwXwenvPqzgxFzQ6-9Gy-9pyalF32ZQ2nyz8JgO6lz4CQYPxTk9Xi3jAmA0ZSPUAs05V4wQFq02Wj8ppwMLMybKPlnKeEFDIkO5pXGELPv3oWY-w)


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)`:



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

![](https://lh6.googleusercontent.com/yz1M5iI9ChQB_d-TQujoFJtE7X_gmCmBIwVdv9ggxXaNRyd5_M5RScja5_6XYJE2WHUAzpln68cx-sfA-ljYYHnegOXDJFgTvh5BOhNTfKUk06eZxj22eu0wZ2pB5N5kVeoHeQ8ayqEXYpnWHuaffCSUtpUqCZ_QkN-w3ETTTGCHEk4jTjMXUHYmag)

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


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


![](https://lh5.googleusercontent.com/n6BPYmn_2A7IeWJuLcHY5fM6BucdYnlH53QYoZlj7HiUzUzgzuIHJ0A-6mh9sOnAbENVHrbNzc_mD3IiqEw_0iZk4cFedB2EjMmApeonSwV5jTgYKqafwPrYPK7sIWZBacu-94jW79eXXr90Ok7raaST0dRc16PHVYEjab8aVByZWttuuR_qYymiRw)
    
### 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`

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

![](https://lh3.googleusercontent.com/2Sd7f80LIx5osfTWh76eaPR0Vx-xi-qFeNHK93cRCGJ5KBXTLLoXWyUInd9xBpfSiqJ_9jg9LW7al27gQ6P7CeaCzn22oed8ouSwYc1zc4IsdiL0znngjlwjXrUca3asZsJTIPg-_3OFG2mycWh0Q61c3RTGSTiYbFcws8ZOjXDKZ9EqvoHwVWjVAQ)

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


![](https://lh6.googleusercontent.com/LQVoO1voaktcjPnlhZoUIfOMIyQm7e4srUnVPGslFiyl9gJBCI_fSHKSUG76849Q0R7SEPXtBrCVLiE3a83w8-ZIYy7II1GEfY1KdqmlsxAMBGNBnhN7CFzZmdzkj9Wd3F6Ue0I4i5vGwtrgUSZ1scuYYzy1YUVaU026te_haSpCAn_W6mTjezpQWw)

  

-   [GitHub Repository](https://github.com/gjeanmart/kauri-content/tree/master/java-ipfs)
    
-   [API Server documentation](https://docs.ipfs.io/reference/api/http/)
    
-   [Introduction to IPFS (by Consensys)](https://medium.com/@ConsenSys/an-introduction-to-ipfs-9bba4860abd0)
    
-   [IPFS Introduction by Example (by Christian Lundkvist)](http://whatdoesthequantsay.com/2015/09/13/ipfs-introduction-by-example)
    
-   [The definitive guide to publishing content on the decentralized web (by Textile)](https://medium.com/textileio/the-definitive-guide-to-publishing-content-on-ipfs-ipns-dfe751f1e8d0)


---

 

Este artigo foi escrito por [Greg Jeanmart](https://greg.jeanmart.me/)  e traduzido por [Adriano P. de Araujo](https://www.linkedin.com/in/araujocode/). O original em inglês pode ser encontrado [aqui](https://greg.jeanmart.me/2019/08/14/managing-storage-in-a-java-application-with-ipfs/).