comparison mercurial/debugcommands.py @ 37012:fc8939825632

debugcommands: support connecting to HTTP peers Now that we have the plumbing for logging socket activity, let's hook it up to `hg debugwireproto` so we can collect low-level activity on sockets. The new code is a bit incomplete. But it is better than nothing: `hg debugwireproto` is still heavily evolving. The added test demonstrates some interesting behavior. For example, we're calling socket.makefile() and doing I/O on that. TIL. We're also sending an "Accept-Encoding: identity" request header. Differential Revision: https://phab.mercurial-scm.org/D2726
author Gregory Szorc <gregory.szorc@gmail.com>
date Tue, 13 Mar 2018 10:02:03 -0700
parents 143219fc2620
children b6a7070e7663
comparison
equal deleted inserted replaced
37011:02221d6fb041 37012:fc8939825632
46 extensions, 46 extensions,
47 filemerge, 47 filemerge,
48 fileset, 48 fileset,
49 formatter, 49 formatter,
50 hg, 50 hg,
51 httppeer,
51 localrepo, 52 localrepo,
52 lock as lockmod, 53 lock as lockmod,
53 logcmdutil, 54 logcmdutil,
54 merge as mergemod, 55 merge as mergemod,
55 obsolete, 56 obsolete,
2600 [ 2601 [
2601 ('', 'localssh', False, _('start an SSH server for this repo')), 2602 ('', 'localssh', False, _('start an SSH server for this repo')),
2602 ('', 'peer', '', _('construct a specific version of the peer')), 2603 ('', 'peer', '', _('construct a specific version of the peer')),
2603 ('', 'noreadstderr', False, _('do not read from stderr of the remote')), 2604 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2604 ] + cmdutil.remoteopts, 2605 ] + cmdutil.remoteopts,
2605 _('[REPO]'), 2606 _('[PATH]'),
2606 optionalrepo=True) 2607 optionalrepo=True)
2607 def debugwireproto(ui, repo, **opts): 2608 def debugwireproto(ui, repo, path=None, **opts):
2608 """send wire protocol commands to a server 2609 """send wire protocol commands to a server
2609 2610
2610 This command can be used to issue wire protocol commands to remote 2611 This command can be used to issue wire protocol commands to remote
2611 peers and to debug the raw data being exchanged. 2612 peers and to debug the raw data being exchanged.
2612 2613
2738 2739
2739 if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'): 2740 if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'):
2740 raise error.Abort(_('invalid value for --peer'), 2741 raise error.Abort(_('invalid value for --peer'),
2741 hint=_('valid values are "raw", "ssh1", and "ssh2"')) 2742 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2742 2743
2744 if path and opts['localssh']:
2745 raise error.Abort(_('cannot specify --localssh with an explicit '
2746 'path'))
2747
2743 if ui.interactive(): 2748 if ui.interactive():
2744 ui.write(_('(waiting for commands on stdin)\n')) 2749 ui.write(_('(waiting for commands on stdin)\n'))
2745 2750
2746 blocks = list(_parsewirelangblocks(ui.fin)) 2751 blocks = list(_parsewirelangblocks(ui.fin))
2747 2752
2748 proc = None 2753 proc = None
2754 stdin = None
2755 stdout = None
2756 stderr = None
2749 2757
2750 if opts['localssh']: 2758 if opts['localssh']:
2751 # We start the SSH server in its own process so there is process 2759 # We start the SSH server in its own process so there is process
2752 # separation. This prevents a whole class of potential bugs around 2760 # separation. This prevents a whole class of potential bugs around
2753 # shared state from interfering with server operation. 2761 # shared state from interfering with server operation.
2791 else: 2799 else:
2792 ui.write(_('creating ssh peer from handshake results\n')) 2800 ui.write(_('creating ssh peer from handshake results\n'))
2793 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr, 2801 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2794 autoreadstderr=autoreadstderr) 2802 autoreadstderr=autoreadstderr)
2795 2803
2804 elif path:
2805 # We bypass hg.peer() so we can proxy the sockets.
2806 # TODO consider not doing this because we skip
2807 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
2808 u = util.url(path)
2809 if u.scheme != 'http':
2810 raise error.Abort(_('only http:// paths are currently supported'))
2811
2812 url, authinfo = u.authinfo()
2813 openerargs = {}
2814
2815 # Turn pipes/sockets into observers so we can log I/O.
2816 if ui.verbose:
2817 openerargs = {
2818 r'loggingfh': ui,
2819 r'loggingname': b's',
2820 r'loggingopts': {
2821 r'logdata': True,
2822 },
2823 }
2824
2825 opener = urlmod.opener(ui, authinfo, **openerargs)
2826
2827 if opts['peer'] == 'raw':
2828 ui.write(_('using raw connection to peer\n'))
2829 peer = None
2830 elif opts['peer']:
2831 raise error.Abort(_('--peer %s not supported with HTTP peers') %
2832 opts['peer'])
2833 else:
2834 peer = httppeer.httppeer(ui, path, url, opener)
2835 peer._fetchcaps()
2836
2837 # We /could/ populate stdin/stdout with sock.makefile()...
2796 else: 2838 else:
2797 raise error.Abort(_('only --localssh is currently supported')) 2839 raise error.Abort(_('unsupported connection configuration'))
2798 2840
2799 batchedcommands = None 2841 batchedcommands = None
2800 2842
2801 # Now perform actions based on the parsed wire language instructions. 2843 # Now perform actions based on the parsed wire language instructions.
2802 for action, lines in blocks: 2844 for action, lines in blocks:
2803 if action in ('raw', 'raw+'): 2845 if action in ('raw', 'raw+'):
2846 if not stdin:
2847 raise error.Abort(_('cannot call raw/raw+ on this peer'))
2848
2804 # Concatenate the data together. 2849 # Concatenate the data together.
2805 data = ''.join(l.lstrip() for l in lines) 2850 data = ''.join(l.lstrip() for l in lines)
2806 data = util.unescapestr(data) 2851 data = util.unescapestr(data)
2807 stdin.write(data) 2852 stdin.write(data)
2808 2853
2809 if action == 'raw+': 2854 if action == 'raw+':
2810 stdin.flush() 2855 stdin.flush()
2811 elif action == 'flush': 2856 elif action == 'flush':
2857 if not stdin:
2858 raise error.Abort(_('cannot call flush on this peer'))
2812 stdin.flush() 2859 stdin.flush()
2813 elif action.startswith('command'): 2860 elif action.startswith('command'):
2814 if not peer: 2861 if not peer:
2815 raise error.Abort(_('cannot send commands unless peer instance ' 2862 raise error.Abort(_('cannot send commands unless peer instance '
2816 'is available')) 2863 'is available'))
2863 2910
2864 batchedcommands = None 2911 batchedcommands = None
2865 elif action == 'close': 2912 elif action == 'close':
2866 peer.close() 2913 peer.close()
2867 elif action == 'readavailable': 2914 elif action == 'readavailable':
2915 if not stdout or not stderr:
2916 raise error.Abort(_('readavailable not available on this peer'))
2917
2868 stdin.close() 2918 stdin.close()
2869 stdout.read() 2919 stdout.read()
2870 stderr.read() 2920 stderr.read()
2921
2871 elif action == 'readline': 2922 elif action == 'readline':
2923 if not stdout:
2924 raise error.Abort(_('readline not available on this peer'))
2872 stdout.readline() 2925 stdout.readline()
2873 elif action == 'ereadline': 2926 elif action == 'ereadline':
2927 if not stderr:
2928 raise error.Abort(_('ereadline not available on this peer'))
2874 stderr.readline() 2929 stderr.readline()
2875 elif action.startswith('read '): 2930 elif action.startswith('read '):
2876 count = int(action.split(' ', 1)[1]) 2931 count = int(action.split(' ', 1)[1])
2932 if not stdout:
2933 raise error.Abort(_('read not available on this peer'))
2877 stdout.read(count) 2934 stdout.read(count)
2878 elif action.startswith('eread '): 2935 elif action.startswith('eread '):
2879 count = int(action.split(' ', 1)[1]) 2936 count = int(action.split(' ', 1)[1])
2937 if not stderr:
2938 raise error.Abort(_('eread not available on this peer'))
2880 stderr.read(count) 2939 stderr.read(count)
2881 else: 2940 else:
2882 raise error.Abort(_('unknown action: %s') % action) 2941 raise error.Abort(_('unknown action: %s') % action)
2883 2942
2884 if batchedcommands is not None: 2943 if batchedcommands is not None: