Request/Response Demo

This example demonstrates the high-level libp2p request/response helper using a single JSON request and response over a dedicated protocol stream.

$ request-response-demo
Listener ready, listening on:
...

Copy the printed command into another terminal, for example:

$ request-response-demo -d /ip4/127.0.0.1/tcp/8000/p2p/<PEER_ID> --message hello
Sent: hello
Received: {'message': 'hello', 'echo': 'HELLO', 'peer': '<PEER_ID>'}

The full source code for this example is below:

  1from __future__ import annotations
  2
  3import argparse
  4import logging
  5import random
  6import secrets
  7
  8import multiaddr
  9import trio
 10
 11from libp2p import new_host
 12from libp2p.crypto.secp256k1 import create_new_key_pair
 13from libp2p.custom_types import TProtocol
 14from libp2p.peer.peerinfo import info_from_p2p_addr
 15from libp2p.request_response import JSONCodec, RequestResponse
 16from libp2p.utils.address_validation import (
 17    find_free_port,
 18    get_available_interfaces,
 19    get_optimal_binding_address,
 20)
 21
 22logging.basicConfig(level=logging.WARNING)
 23logging.getLogger("multiaddr").setLevel(logging.WARNING)
 24logging.getLogger("libp2p").setLevel(logging.WARNING)
 25
 26PROTOCOL_ID = TProtocol("/example/request-response/1.0.0")
 27
 28
 29async def run(
 30    port: int,
 31    destination: str | None,
 32    message: str,
 33    seed: int | None = None,
 34) -> None:
 35    if port <= 0:
 36        port = find_free_port()
 37    listen_addr = get_available_interfaces(port)
 38
 39    if seed is not None:
 40        random.seed(seed)
 41        secret_number = random.getrandbits(32 * 8)
 42        secret = secret_number.to_bytes(length=32, byteorder="big")
 43    else:
 44        secret = secrets.token_bytes(32)
 45
 46    host = new_host(key_pair=create_new_key_pair(secret))
 47    rr = RequestResponse(host)
 48    codec = JSONCodec()
 49
 50    async def handler(request: dict[str, str], context) -> dict[str, str]:
 51        return {
 52            "message": request["message"],
 53            "echo": request["message"].upper(),
 54            "peer": str(context.peer_id),
 55        }
 56
 57    async with host.run(listen_addrs=listen_addr), trio.open_nursery() as nursery:
 58        nursery.start_soon(host.get_peerstore().start_cleanup_task, 60)
 59        print(f"I am {host.get_id().to_string()}")
 60
 61        if not destination:
 62            rr.set_handler(PROTOCOL_ID, handler=handler, codec=codec)
 63            peer_id = host.get_id().to_string()
 64            print("Listener ready, listening on:\n")
 65            for addr in listen_addr:
 66                print(f"{addr}/p2p/{peer_id}")
 67
 68            optimal_addr = get_optimal_binding_address(port)
 69            optimal_addr_with_peer = f"{optimal_addr}/p2p/{peer_id}"
 70            print(
 71                "\nRun this from the same folder in another console:\n\n"
 72                f"request-response-demo -d {optimal_addr_with_peer} --message hello\n"
 73            )
 74            print("Waiting for incoming requests...")
 75            await trio.sleep_forever()
 76
 77        destination_str = destination
 78        if destination_str is None:
 79            raise ValueError("destination is required in dialer mode")
 80        maddr = multiaddr.Multiaddr(destination_str)
 81        info = info_from_p2p_addr(maddr)
 82        await host.connect(info)
 83        response = await rr.send_request(
 84            peer_id=info.peer_id,
 85            protocol_ids=[PROTOCOL_ID],
 86            request={"message": message},
 87            codec=codec,
 88        )
 89        print(f"Sent: {message}")
 90        print(f"Received: {response}")
 91
 92
 93def main() -> None:
 94    description = """
 95    Demonstrates the request/response helper with a single JSON request and response.
 96    Run once without -d to start a listener, then run again with -d to send a request.
 97    """
 98    example_maddr = (
 99        "/ip4/[HOST_IP]/tcp/8000/p2p/QmQn4SwGkDZKkUEpBRBvTmheQycxAHJUNmVEnjA2v1qe8Q"
100    )
101    parser = argparse.ArgumentParser(description=description)
102    parser.add_argument("-p", "--port", default=0, type=int, help="source port")
103    parser.add_argument(
104        "-d",
105        "--destination",
106        type=str,
107        help=f"destination multiaddr string, e.g. {example_maddr}",
108    )
109    parser.add_argument(
110        "--message",
111        type=str,
112        default="hello",
113        help="JSON message payload to send",
114    )
115    parser.add_argument(
116        "-s",
117        "--seed",
118        type=int,
119        help="seed the RNG to make peer IDs reproducible",
120    )
121    args = parser.parse_args()
122    try:
123        trio.run(run, args.port, args.destination, args.message, args.seed)
124    except KeyboardInterrupt:
125        pass
126
127
128if __name__ == "__main__":
129    main()