Coverage for aiocoap/cli/proxy.py: 68%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

93 statements  

1# This file is part of the Python aiocoap library project. 

2# 

3# Copyright (c) 2012-2014 Maciej Wasilak <http://sixpinetrees.blogspot.com/>, 

4# 2013-2014 Christian Amsüss <c.amsuess@energyharvesting.at> 

5# 

6# aiocoap is free software, this file is published under the MIT license as 

7# described in the accompanying LICENSE file. 

8 

9"""a plain CoAP proxy that can work both as forward and as reverse proxy""" 

10 

11import sys 

12import argparse 

13 

14import aiocoap 

15from aiocoap.proxy.server import ForwardProxyWithPooledObservations, ProxyWithPooledObservations, NameBasedVirtualHost, SubdomainVirtualHost, SubresourceVirtualHost, UnconditionalRedirector 

16from aiocoap.util.cli import AsyncCLIDaemon 

17from aiocoap.cli.common import add_server_arguments, server_context_from_arguments 

18 

19def build_parser(): 

20 p = argparse.ArgumentParser(description=__doc__) 

21 

22 mode = p.add_argument_group("mode", "Required argument for setting the operation mode") 

23 mode.add_argument('--forward', help="Run as forward proxy", action='store_true') 

24 mode.add_argument('--reverse', help="Run as reverse proxy", action='store_true') 

25 

26 details = p.add_argument_group("details", "Options that govern how requests go in and out") 

27 add_server_arguments(details) 

28 details.add_argument("--register", help="Register with a Resource directory", metavar='RD-URI', nargs='?', default=False) 

29 details.add_argument("--register-as", help="Endpoint name (with possibly a domain after a dot) to register as", metavar='EP[.D]', default=None) 

30 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') 

31 

32 r = p.add_argument_group('Rules', description="Sequence of forwarding rules " 

33 "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.") 

34 class TypedAppend(argparse.Action): 

35 def __call__(self, parser, namespace, values, option_string=None): 

36 if getattr(namespace, self.dest) is None: 

37 setattr(namespace, self.dest, []) 

38 getattr(namespace, self.dest).append((option_string, values)) 

39 r.add_argument('--namebased', help="If Uri-Host matches NAME, route to DEST", metavar="NAME:DEST", action=TypedAppend, dest='r') 

40 r.add_argument('--subdomainbased', help="If Uri-Host is anything.NAME, route to DEST", metavar="NAME:DEST", action=TypedAppend, dest='r') 

41 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') 

42 r.add_argument('--unconditional', help="Route all requests not previously matched to DEST", metavar="DEST", action=TypedAppend, dest='r') 

43 

44 return p 

45 

46def destsplit(dest): 

47 use_as_proxy = False 

48 rewrite_uri_host = False 

49 if dest.startswith('!'): 

50 dest = dest[1:] 

51 rewrite_uri_host = True 

52 if dest.startswith('@'): 

53 dest = dest[1:] 

54 use_as_proxy = True 

55 return dest, rewrite_uri_host, use_as_proxy 

56 

57class Main(AsyncCLIDaemon): 

58 async def start(self, args=None): 

59 parser = build_parser() 

60 options = parser.parse_args(args if args is not None else sys.argv[1:]) 

61 self.options = options 

62 

63 if not options.forward and not options.reverse: 

64 raise parser.error("At least one of --forward and --reverse must be given.") 

65 

66 self.outgoing_context = await aiocoap.Context.create_client_context() 

67 if options.forward: 

68 proxy = ForwardProxyWithPooledObservations(self.outgoing_context) 

69 else: 

70 proxy = ProxyWithPooledObservations(self.outgoing_context) 

71 for kind, data in options.r or (): 

72 if kind in ('--namebased', '--subdomainbased'): 

73 try: 

74 name, dest = data.split(':', 1) 

75 except Exception: 

76 raise parser.error("%s needs NAME:DEST as arguments" % kind) 

77 dest, rewrite_uri_host, use_as_proxy = destsplit(dest) 

78 if rewrite_uri_host and kind == '--subdomainbased': 

79 parser.error("The flag '!' makes no sense for subdomain based redirection as the subdomain data would be lost") 

80 r = (NameBasedVirtualHost if kind == '--namebased' else SubdomainVirtualHost)(name, dest, rewrite_uri_host, use_as_proxy) 

81 elif kind == '--pathbased': 

82 try: 

83 path, dest = data.split(':', 1) 

84 except Exception: 

85 raise parser.error("--pathbased needs PATH:DEST as arguments") 

86 r = SubresourceVirtualHost(path.split('/'), dest) 

87 elif kind == '--unconditional': 

88 dest, rewrite_uri_host, use_as_proxy = destsplit(data) 

89 if rewrite_uri_host: 

90 parser.error("The flag '!' makes no sense for unconditional redirection as the host name data would be lost") 

91 r = UnconditionalRedirector(dest, use_as_proxy) 

92 else: 

93 raise AssertionError('Unknown redirectory kind') 

94 proxy.add_redirector(r) 

95 

96 self.proxy_context = await server_context_from_arguments(proxy, options) 

97 

98 if options.register is not False: 

99 from aiocoap.resourcedirectory.client.register import Registerer 

100 

101 params = {} 

102 if options.register_as: 

103 ep, _, d = options.register_as.partition('.') 

104 params['ep'] = ep 

105 if d: 

106 params['d'] = d 

107 if options.register_proxy: 

108 # FIXME: Check this in discovery 

109 params['proxy'] = 'on' 

110 # FIXME: Construct this from settings (path-based), and forward results 

111 proxy.get_resources_as_linkheader = lambda: "" 

112 self.registerer = Registerer(self.proxy_context, rd=options.register, lt=60, 

113 registration_parameters=params) 

114 

115 async def shutdown(self): 

116 if self.options.register is not False: 

117 await self.registerer.shutdown() 

118 await self.outgoing_context.shutdown() 

119 await self.proxy_context.shutdown() 

120 

121sync_main = Main.sync_main 

122 

123if __name__ == "__main__": 

124 # if you want to run this using `python3 -m`, see http://bugs.python.org/issue22480 

125 sync_main()