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>
:
<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 umjava.io.File
InputStreamWrapper
envolve umjava.io.InputStream
ByteArrayWrapper
envolve umbyte[]
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:
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);
}
Podemos solicitar diferentes tipos de chaves fixadas para listar:
all
: Todos os objetosdirect
: Objetos fixados diretamenteindirect
: Objetos referenciados por pins recursivosrecursive
: 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:
Resolução do hash em relação ao nome
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.
Oldest comments (0)