Coverage for aiocoap/cli/proxy.py: 68%
93 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-16 16:09 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-16 16:09 +0000
1# SPDX-FileCopyrightText: Christian Amsüss and the aiocoap contributors
2#
3# SPDX-License-Identifier: MIT
5"""a plain CoAP proxy that can work both as forward and as reverse proxy"""
7import sys
8import argparse
10import aiocoap
11from aiocoap.proxy.server import ForwardProxyWithPooledObservations, ProxyWithPooledObservations, NameBasedVirtualHost, SubdomainVirtualHost, SubresourceVirtualHost, UnconditionalRedirector
12from aiocoap.util.cli import AsyncCLIDaemon
13from aiocoap.cli.common import add_server_arguments, server_context_from_arguments
15def build_parser():
16 p = argparse.ArgumentParser(description=__doc__)
18 mode = p.add_argument_group("mode", "Required argument for setting the operation mode")
19 mode.add_argument('--forward', help="Run as forward proxy", action='store_true')
20 mode.add_argument('--reverse', help="Run as reverse proxy", action='store_true')
22 details = p.add_argument_group("details", "Options that govern how requests go in and out")
23 add_server_arguments(details)
24 details.add_argument("--register", help="Register with a Resource directory", metavar='RD-URI', nargs='?', default=False)
25 details.add_argument("--register-as", help="Endpoint name (with possibly a domain after a dot) to register as", metavar='EP[.D]', default=None)
26 details.add_argument("--register-proxy", help="Ask the RD to serve as a reverse proxy. Note that this is only practical for --unconditional or --pathbased reverse proxies.", action='store_true')
28 r = p.add_argument_group('Rules', description="Sequence of forwarding rules "
29 "that, if matched by a request, specify a forwarding destination. Destinations can be prefixed to change their behavior: With an '@' sign, they are treated as forward proxies. With a '!' sign, the destination is set as Uri-Host.")
30 class TypedAppend(argparse.Action):
31 def __call__(self, parser, namespace, values, option_string=None):
32 if getattr(namespace, self.dest) is None:
33 setattr(namespace, self.dest, [])
34 getattr(namespace, self.dest).append((option_string, values))
35 r.add_argument('--namebased', help="If Uri-Host matches NAME, route to DEST", metavar="NAME:DEST", action=TypedAppend, dest='r')
36 r.add_argument('--subdomainbased', help="If Uri-Host is anything.NAME, route to DEST", metavar="NAME:DEST", action=TypedAppend, dest='r')
37 r.add_argument('--pathbased', help="If a requested path starts with PATH, split that part off and route to DEST", metavar="PATH:DEST", action=TypedAppend, dest='r')
38 r.add_argument('--unconditional', help="Route all requests not previously matched to DEST", metavar="DEST", action=TypedAppend, dest='r')
40 return p
42def destsplit(dest):
43 use_as_proxy = False
44 rewrite_uri_host = False
45 if dest.startswith('!'):
46 dest = dest[1:]
47 rewrite_uri_host = True
48 if dest.startswith('@'):
49 dest = dest[1:]
50 use_as_proxy = True
51 return dest, rewrite_uri_host, use_as_proxy
53class Main(AsyncCLIDaemon):
54 async def start(self, args=None):
55 parser = build_parser()
56 options = parser.parse_args(args if args is not None else sys.argv[1:])
57 self.options = options
59 if not options.forward and not options.reverse:
60 raise parser.error("At least one of --forward and --reverse must be given.")
62 self.outgoing_context = await aiocoap.Context.create_client_context()
63 if options.forward:
64 proxy = ForwardProxyWithPooledObservations(self.outgoing_context)
65 else:
66 proxy = ProxyWithPooledObservations(self.outgoing_context)
67 for kind, data in options.r or ():
68 if kind in ('--namebased', '--subdomainbased'):
69 try:
70 name, dest = data.split(':', 1)
71 except Exception:
72 raise parser.error("%s needs NAME:DEST as arguments" % kind)
73 dest, rewrite_uri_host, use_as_proxy = destsplit(dest)
74 if rewrite_uri_host and kind == '--subdomainbased':
75 parser.error("The flag '!' makes no sense for subdomain based redirection as the subdomain data would be lost")
76 r = (NameBasedVirtualHost if kind == '--namebased' else SubdomainVirtualHost)(name, dest, rewrite_uri_host, use_as_proxy)
77 elif kind == '--pathbased':
78 try:
79 path, dest = data.split(':', 1)
80 except Exception:
81 raise parser.error("--pathbased needs PATH:DEST as arguments")
82 r = SubresourceVirtualHost(path.split('/'), dest)
83 elif kind == '--unconditional':
84 dest, rewrite_uri_host, use_as_proxy = destsplit(data)
85 if rewrite_uri_host:
86 parser.error("The flag '!' makes no sense for unconditional redirection as the host name data would be lost")
87 r = UnconditionalRedirector(dest, use_as_proxy)
88 else:
89 raise AssertionError('Unknown redirectory kind')
90 proxy.add_redirector(r)
92 self.proxy_context = await server_context_from_arguments(proxy, options)
94 if options.register is not False:
95 from aiocoap.resourcedirectory.client.register import Registerer
97 params = {}
98 if options.register_as:
99 ep, _, d = options.register_as.partition('.')
100 params['ep'] = ep
101 if d:
102 params['d'] = d
103 if options.register_proxy:
104 # FIXME: Check this in discovery
105 params['proxy'] = 'on'
106 # FIXME: Construct this from settings (path-based), and forward results
107 proxy.get_resources_as_linkheader = lambda: ""
108 self.registerer = Registerer(self.proxy_context, rd=options.register, lt=60,
109 registration_parameters=params)
111 async def shutdown(self):
112 if self.options.register is not False:
113 await self.registerer.shutdown()
114 await self.outgoing_context.shutdown()
115 await self.proxy_context.shutdown()
117sync_main = Main.sync_main
119if __name__ == "__main__":
120 # if you want to run this using `python3 -m`, see http://bugs.python.org/issue22480
121 sync_main()