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