Request.py
1 ########################################################## 2 # This RNS example demonstrates how to perform requests # 3 # and receive responses over a link. # 4 ########################################################## 5 6 import os 7 import sys 8 import time 9 import random 10 import argparse 11 import RNS 12 13 # Let's define an app name. We'll use this for all 14 # destinations we create. Since this echo example 15 # is part of a range of example utilities, we'll put 16 # them all within the app namespace "example_utilities" 17 APP_NAME = "example_utilities" 18 19 ########################################################## 20 #### Server Part ######################################### 21 ########################################################## 22 23 # A reference to the latest client link that connected 24 latest_client_link = None 25 26 def random_text_generator(path, data, request_id, link_id, remote_identity, requested_at): 27 RNS.log("Generating response to request "+RNS.prettyhexrep(request_id)+" on link "+RNS.prettyhexrep(link_id)) 28 texts = ["They looked up", "On each full moon", "Becky was upset", "I’ll stay away from it", "The pet shop stocks everything"] 29 return texts[random.randint(0, len(texts)-1)] 30 31 # This initialisation is executed when the users chooses 32 # to run as a server 33 def server(configpath): 34 # We must first initialise Reticulum 35 reticulum = RNS.Reticulum(configpath) 36 37 # Randomly create a new identity for our link example 38 server_identity = RNS.Identity() 39 40 # We create a destination that clients can connect to. We 41 # want clients to create links to this destination, so we 42 # need to create a "single" destination type. 43 server_destination = RNS.Destination( 44 server_identity, 45 RNS.Destination.IN, 46 RNS.Destination.SINGLE, 47 APP_NAME, 48 "requestexample" 49 ) 50 51 # We configure a function that will get called every time 52 # a new client creates a link to this destination. 53 server_destination.set_link_established_callback(client_connected) 54 55 # We register a request handler for handling incoming 56 # requests over any established links. 57 server_destination.register_request_handler( 58 "/random/text", 59 response_generator = random_text_generator, 60 allow = RNS.Destination.ALLOW_ALL 61 ) 62 63 # Everything's ready! 64 # Let's Wait for client requests or user input 65 server_loop(server_destination) 66 67 def server_loop(destination): 68 # Let the user know that everything is ready 69 RNS.log( 70 "Request example "+ 71 RNS.prettyhexrep(destination.hash)+ 72 " running, waiting for a connection." 73 ) 74 75 RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)") 76 77 # We enter a loop that runs until the users exits. 78 # If the user hits enter, we will announce our server 79 # destination on the network, which will let clients 80 # know how to create messages directed towards it. 81 while True: 82 entered = input() 83 destination.announce() 84 RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash)) 85 86 # When a client establishes a link to our server 87 # destination, this function will be called with 88 # a reference to the link. 89 def client_connected(link): 90 global latest_client_link 91 92 RNS.log("Client connected") 93 link.set_link_closed_callback(client_disconnected) 94 latest_client_link = link 95 96 def client_disconnected(link): 97 RNS.log("Client disconnected") 98 99 100 ########################################################## 101 #### Client Part ######################################### 102 ########################################################## 103 104 # A reference to the server link 105 server_link = None 106 107 # This initialisation is executed when the users chooses 108 # to run as a client 109 def client(destination_hexhash, configpath): 110 # We need a binary representation of the destination 111 # hash that was entered on the command line 112 try: 113 dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2 114 if len(destination_hexhash) != dest_len: 115 raise ValueError( 116 "Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2) 117 ) 118 119 destination_hash = bytes.fromhex(destination_hexhash) 120 except: 121 RNS.log("Invalid destination entered. Check your input!\n") 122 sys.exit(0) 123 124 # We must first initialise Reticulum 125 reticulum = RNS.Reticulum(configpath) 126 127 # Check if we know a path to the destination 128 if not RNS.Transport.has_path(destination_hash): 129 RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...") 130 RNS.Transport.request_path(destination_hash) 131 while not RNS.Transport.has_path(destination_hash): 132 time.sleep(0.1) 133 134 # Recall the server identity 135 server_identity = RNS.Identity.recall(destination_hash) 136 137 # Inform the user that we'll begin connecting 138 RNS.log("Establishing link with server...") 139 140 # When the server identity is known, we set 141 # up a destination 142 server_destination = RNS.Destination( 143 server_identity, 144 RNS.Destination.OUT, 145 RNS.Destination.SINGLE, 146 APP_NAME, 147 "requestexample" 148 ) 149 150 # And create a link 151 link = RNS.Link(server_destination) 152 153 # We'll set up functions to inform the 154 # user when the link is established or closed 155 link.set_link_established_callback(link_established) 156 link.set_link_closed_callback(link_closed) 157 158 # Everything is set up, so let's enter a loop 159 # for the user to interact with the example 160 client_loop() 161 162 def client_loop(): 163 global server_link 164 165 # Wait for the link to become active 166 while not server_link: 167 time.sleep(0.1) 168 169 should_quit = False 170 while not should_quit: 171 try: 172 print("> ", end=" ") 173 text = input() 174 175 # Check if we should quit the example 176 if text == "quit" or text == "q" or text == "exit": 177 should_quit = True 178 server_link.teardown() 179 180 else: 181 server_link.request( 182 "/random/text", 183 data = None, 184 response_callback = got_response, 185 failed_callback = request_failed 186 ) 187 188 189 except Exception as e: 190 RNS.log("Error while sending request over the link: "+str(e)) 191 should_quit = True 192 server_link.teardown() 193 194 def got_response(request_receipt): 195 request_id = request_receipt.request_id 196 response = request_receipt.response 197 198 RNS.log("Got response for request "+RNS.prettyhexrep(request_id)+": "+str(response)) 199 200 def request_received(request_receipt): 201 RNS.log("The request "+RNS.prettyhexrep(request_receipt.request_id)+" was received by the remote peer.") 202 203 def request_failed(request_receipt): 204 RNS.log("The request "+RNS.prettyhexrep(request_receipt.request_id)+" failed.") 205 206 207 # This function is called when a link 208 # has been established with the server 209 def link_established(link): 210 # We store a reference to the link 211 # instance for later use 212 global server_link 213 server_link = link 214 215 # Inform the user that the server is 216 # connected 217 RNS.log("Link established with server, hit enter to perform a request, or type in \"quit\" to quit") 218 219 # When a link is closed, we'll inform the 220 # user, and exit the program 221 def link_closed(link): 222 if link.teardown_reason == RNS.Link.TIMEOUT: 223 RNS.log("The link timed out, exiting now") 224 elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED: 225 RNS.log("The link was closed by the server, exiting now") 226 else: 227 RNS.log("Link closed, exiting now") 228 229 time.sleep(1.5) 230 sys.exit(0) 231 232 233 ########################################################## 234 #### Program Startup ##################################### 235 ########################################################## 236 237 # This part of the program runs at startup, 238 # and parses input of from the user, and then 239 # starts up the desired program mode. 240 if __name__ == "__main__": 241 try: 242 parser = argparse.ArgumentParser(description="Simple request/response example") 243 244 parser.add_argument( 245 "-s", 246 "--server", 247 action="store_true", 248 help="wait for incoming requests from clients" 249 ) 250 251 parser.add_argument( 252 "--config", 253 action="store", 254 default=None, 255 help="path to alternative Reticulum config directory", 256 type=str 257 ) 258 259 parser.add_argument( 260 "destination", 261 nargs="?", 262 default=None, 263 help="hexadecimal hash of the server destination", 264 type=str 265 ) 266 267 args = parser.parse_args() 268 269 if args.config: 270 configarg = args.config 271 else: 272 configarg = None 273 274 if args.server: 275 server(configarg) 276 else: 277 if (args.destination == None): 278 print("") 279 parser.print_help() 280 print("") 281 else: 282 client(args.destination, configarg) 283 284 except KeyboardInterrupt: 285 print("") 286 sys.exit(0)