DEV Community

Pedro Pietroluongo
Pedro Pietroluongo

Posted on

A API HTTP e como rodar o Nabu

Na série IPFS com o Nabu, o nó já guarda blocos e fala com a rede. Este post é a porta de fora: o processo que sobe sozinho e a biblioteca que outra aplicação embute.

A proposta

Quem opera um nó quer o mesmo tipo de chamada que já usa com o Kubo, a implementação de referência do IPFS em Go: HTTP (Hypertext Transfer Protocol) local, um caminho /api/v0/..., o CID (Content Identifier) na query. Quem escreve uma aplicação quer o contrário: nenhuma porta HTTP, o nó como objeto Java dentro do processo.

A proposta do Nabu é oferecer os dois. A superfície HTTP é de propósito menor que a do Kubo. O objeto embutido expõe o mesmo nó que essa superfície usa por baixo.

A Demonstração

O modo standalone lê um diretório de repositório, no estilo do Kubo. A variável de ambiente ou o argumento IPFS_PATH aponta para ele. Na ausência, o diretório é ~/.ipfs, debaixo do home do usuário. Lá estão o arquivo config e os dados. O config é JSON (JavaScript Object Notation) com a identidade, os endereços e o datastore. O processo abre a API (Application Programming Interface) HTTP no host e na porta que o config chama de Addresses.API.

Uma chamada típica é POST /api/v0/block/get?arg=<cid>. O corpo da resposta são os bytes do bloco. block/put manda os bytes num corpo multipart, o formato em que cada parte traz um arquivo, e recebe de volta um JSON com o campo Hash.

O modo embutido não abre essa porta. A aplicação chama EmbeddedIpfs.build com as peças que escolhe: store de registros, blockstore, se deve anunciar blocos, endereços de swarm, bootstrap, identidade, authoriser e, se quiser, um handler HTTP entre pares. start entra na rede. A partir daí getBlocks, publishValue e o blockstore são chamadas locais, no mesmo processo.

A API compatível com o Kubo cobre bloco, identidade, refs, um pedaço da DHT (Distributed Hash Table) e a leitura IPNS (InterPlanetary Name System). Caminhos de arquivo, add de diretório e pin não estão nessa lista.

No Nabu

A classe do processo é Nabu, em src/main/java/org/peergos/Nabu.java. getIPFSPath lê o argumento IPFS_PATH ou, sem ele, HOME mais .ipfs. readConfig abre config nesse diretório. Se o arquivo não existe, grava um config padrão. O argumento s3.datastore, quando vem na linha de comando, preenche o mount S3 antes dessa gravação. O authoriser é (c, p, a) -> CompletableFuture.completedFuture(true): toda leitura de bloco é autorizada. O store de IPNS é um DatabaseRecordStore em datastore/h2-v2.datastore. O blockstore sai de buildBlockStore. EmbeddedIpfs.build recebe provideBlocks verdadeiro, os endereços de swarm, o bootstrap e a identidade do config.

Se config.addresses.enableUPnP está ligado, enablePortForwarding roda antes de start. start(false) faz o bootstrap de forma síncrona. O endereço da API é o argumento Addresses.API, se foi passado, ou config.addresses.apiAddress. O servidor é com.sun.net.httpserver.HttpServer, com fila de 500 conexões e um pool de 50 threads. O contexto /api/v0/ recebe um APIHandler. Se o config tem Addresses.ProxyTarget, o HTTP entre pares encaminha para esse host e porta, e o servidor ganha o contexto /p2p/. Um shutdown hook para o servidor HTTP com espera de 3 segundos. Métricas, se config.metrics.enabled, sobem noutro endereço, o do MetricsSection.

APIHandler exige que o caminho comece com /api/v0/. O resto entra num switch. Os casos, em src/main/java/org/peergos/net/APIHandler.java:

  • id devolve JSON com o PeerId em base58, no campo ID.
  • version devolve o campo Version. A constante do handler é 0.9.0.
  • block/get exige um arg. Monta um Want com o CID e o auth opcional. peers vira um conjunto de PeerIds. persist vem da query e, se falta, vale verdadeiro. A resposta são os bytes, ou status 400 se a lista volta vazia.
  • block/put exige format. Lê um único arquivo do multipart, cujo boundary vem do header Content-Type. Recusa tamanho acima de maxBlockSize, o teto da mensagem Bitswap, 2 MiB. O codec sai de Cid.Codec.lookupIPLDName. A resposta JSON traz Hash.
  • block/rm apaga um CID. Sucesso devolve Hash e Error vazio. Falha responde 400.
  • block/rm/bulk lê uma lista cids no corpo JSON.
  • block/stat busca o bloco com persist falso e devolve só Size. A lista de pares vai vazia. Falha responde 400.
  • block/stat/bulk lê {"wants":[...]}, busca com persist verdadeiro e, para cada bloco, devolve size, os links que não são multihash do tipo identity, e os BATs (Block Access Token, o segredo de leitura) encontrados no bloco. Responde 400 se o número de blocos não fecha com o de wants.
  • block/has responde os bytes true ou false.
  • refs/local lista os CIDs de blockstore.refs. O parâmetro use-block-store vale falso se não vier. Cada CID sai num JSON com o campo Ref.
  • bloom/add chama blockstore.bloomAdd e responde o booleano.
  • dht/findprovs exige o CID. num-providers positivo substitui o padrão 20. Cada provedor vira ID e Addrs dentro de Responses.
  • ipns/get exige o argumento signer, um PeerId em base58. Chama resolveRecords pedindo pelo menos um resultado, ordena, e devolve sig e data em hexadecimal do registro de maior sequence. Lista vazia vira Couldn't resolve.

block/get com peers vazio deixa o BitswapBlockService tentar os conectados e depois a DHT. persist falso busca sem gravar no store local.

O cliente Java dessa API é NabuClient, em src/main/java/org/peergos/client/NabuClient.java. O construtor de host e porta assume /api/v0/. O construtor de multiaddr, por exemplo /ip4/127.0.0.1/tcp/5001, lê host e porta dali. Timeout de conexão: 10 segundos. Timeout de leitura: 60 segundos. Os exemplos do último post usam esse cliente contra um daemon.

Embutir segue o trecho do README. A identidade pode ser gerada na hora:

HostBuilder builder = new HostBuilder().generateIdentity();
PrivKey privKey = builder.getPrivateKey();
PeerId peerId = builder.getPeerId();
IdentitySection identity = new IdentitySection(privKey.bytes(), peerId);
Enter fullscreen mode Exit fullscreen mode

EmbeddedIpfs.build recebe o RamRecordStore ou um store durável, o FileBlockstore, a lista de swarm e de bootstrap, e o authoriser. ipfs.start() bloqueia até o host escutar, o identify estar registrado e o bootstrap da tabela ter corrido. getBlocks devolve List<HashedBlock>. Cada item traz o CID e o byte[].

O que fica de fora: a API não replica o Kubo inteiro. Não há files, dag, name/publish HTTP, nem gateway de conteúdo no estilo ipfs.io. O campo Addresses.Gateway existe no config e é lido por AddressesSection. O handler que sobe é o da API, mais o proxy /p2p/ quando há alvo. O pacote Maven publicado pelo JitPack é com.github.peergos:nabu.

Nesta série

O anterior é Onde o bloco mora. O próximo é HTTP entre pares.

Top comments (0)