/ Examples / Request.py
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)