Na série IPFS com o Nabu, a API (Application Programming Interface) HTTP do post anterior escuta na máquina local. Este post leva um pedido HTTP até outro par, sem que esse par tenha um endereço público na internet.
A proposta
Dois programas já sabem falar HTTP (Hypertext Transfer Protocol): um verbo, um caminho, cabeçalhos e um corpo. Um deles está atrás de NAT (Network Address Translation, a reescrita de endereços do roteador). Abrir uma porta no roteador, ou alugar um servidor para os dois se encontrarem, repete o problema que o libp2p já resolve nos posts de transporte e de alcance.
A proposta é carregar o pedido e a resposta HTTP num stream libp2p. O encontro dos pares continua sendo por PeerId (o identificador do par). O conteúdo do stream continua sendo HTTP comum.
A Demonstração
O protocolo se chama /http. Um lado disca o outro pelo PeerId, abre um stream e escreve um pedido HTTP completo: método, URL (Uniform Resource Locator) relativo, headers e corpo. O outro lado trata o pedido e devolve o status, os headers e o corpo. O tratamento pode ser a própria aplicação, ou um encaminhamento para um servidor HTTP comum em localhost. Visto de fora, o par remoto parece um servidor. Visto da rede, é mais um protocolo no mesmo host que já fala Bitswap e Kademlia.
Quem chama precisa do endereço do par. Se o caderno já tem multiaddrs, a discagem usa essa lista. Se não tem, a DHT (Distributed Hash Table) procura o par, como no post da Kademlia. Um proxy local traduz um caminho /p2p/<peerid>/http/<caminho> nessa discagem. Um cliente HTTP comum, na mesma máquina, fala com 127.0.0.1 e não precisa saber o que é um PeerId. O proxy corta o PeerId do caminho, acha o par e escreve o resto do caminho no pedido que segue pelo stream.
flowchart LR
client[localHttpClient] --> proxy[p2pProxy]
proxy --> dht[addressOrDht]
dht --> stream[httpStream]
stream --> app[remoteHandlerOrLocalhost]
No Nabu
HttpProtocol, em src/main/java/org/peergos/protocol/http/HttpProtocol.java, registra o identificador /http no Binding. Há dois construtores de binding. Um recebe um SocketAddress e encaminha cada pedido para esse host e porta. O outro recebe um HttpRequestProcessor, a função da aplicação. O corpo máximo tratado de uma vez é MAX_BODY_SIZE, 8 MiB. O comentário no código marca isso como limite temporário, no lugar de um fluxo contínuo. Sender.send enfileira um future, escreve o FullHttpRequest no stream e completa quando onMessage copia status, headers e corpo para um DefaultFullHttpResponse. O stream fecha em seguida. onClosed, onReadClosed e onException falham os futures que ainda estavam na fila, com a mensagem de stream fechado ou a exceção original.
EmbeddedIpfs.build só acrescenta o protocolo quando a aplicação passa um handler:
Optional<HttpProtocol.Binding> httpHandler = handler.map(HttpProtocol.Binding::new);
httpHandler.ifPresent(protocols::add);
O campo p2pHttp do EmbeddedIpfs fica vazio sem isso. Bitswap e Kademlia seguem normais. Quem for discar mais tarde usa ipfs.p2pHttp.get() e recebe vazio se o build não registrou o protocolo.
No processo Nabu, o handler existe quando o config tem Addresses.ProxyTarget. proxyHandler chama HttpProtocol.proxyRequest para o host e a porta desse multiaddr, e passa o PeerId remoto. O mesmo processo publica HttpProxyHandler em /p2p/, a constante HttpProxyService.API_URL. O comentário do handler descreve o caminho: /p2p/$target_node_id/http/$target_path. O handler corta o prefixo /p2p/, separa o PeerId do caminho que segue, e pede ao HttpProxyService a discagem.
HttpProxyService.getAddresses olha o caderno do host. Se a coleção daquele PeerId não está vazia, devolve esses multiaddrs. Se está vazia, lookupAddresses chama dht.findClosestPeers pedindo um resultado. O comentário do método explica o segundo argumento useCached: a DHT responde uma busca de par a partir do caderno quando pode, e isso não serve para quem está tentando escapar de um endereço morto. useCached falso força a consulta. O resultado fica com o par cujo id coincide com o alvo. Endereço velho, de um par que mudou de rede, faria o proxy insistir num destino que não atende.
O buffer de 128 MiB do yamux, do post de transportes, existe por causa daqui. O yamux limita o buffer por conexão. Uma resposta perto de MAX_BODY_SIZE já ocupa uma fatia grande. Três respostas de 4 MB na mesma conexão estouravam o padrão de 10 MiB, e o muxer resetava os streams. writeAndFlush não devolve future, então o reset aparecia só como resposta que não chegava.
O exemplo mínimo de duas pessoas trocando mensagens por esse caminho é o nabu-chat: os PeerIds se encontram fora da banda, a conexão é libp2p, e as mensagens são pedidos HTTP. O módulo examples deste repositório tem um Chat com a mesma ideia. Cada linha vira um POST / no stream /http, e o handler do outro lado imprime received:. O próximo post o executa.
O que fica de fora: /http não é um gateway de conteúdo IPFS. Ele não resolve CID nem IPNS. Carrega HTTP opaco entre dois pares que já se encontram pelo libp2p. O corpo de 8 MiB é o teto desta implementação. Uma resposta maior precisa de outro desenho.
Nesta série
O anterior é A API HTTP e como rodar o Nabu. O próximo é Os exemplos do repositório.
Top comments (0)