##// END OF EJS Templates
wireprotov2: implement commands as a generator of objects...
Gregory Szorc -
r39595:07b58266 default
parent child Browse files
Show More
@@ -1,310 +1,321 b''
1 # error.py - Mercurial exceptions
1 # error.py - Mercurial exceptions
2 #
2 #
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Mercurial exceptions.
8 """Mercurial exceptions.
9
9
10 This allows us to catch exceptions at higher levels without forcing
10 This allows us to catch exceptions at higher levels without forcing
11 imports.
11 imports.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 # Do not import anything but pycompat here, please
16 # Do not import anything but pycompat here, please
17 from . import pycompat
17 from . import pycompat
18
18
19 def _tobytes(exc):
19 def _tobytes(exc):
20 """Byte-stringify exception in the same way as BaseException_str()"""
20 """Byte-stringify exception in the same way as BaseException_str()"""
21 if not exc.args:
21 if not exc.args:
22 return b''
22 return b''
23 if len(exc.args) == 1:
23 if len(exc.args) == 1:
24 return pycompat.bytestr(exc.args[0])
24 return pycompat.bytestr(exc.args[0])
25 return b'(%s)' % b', '.join(b"'%s'" % pycompat.bytestr(a) for a in exc.args)
25 return b'(%s)' % b', '.join(b"'%s'" % pycompat.bytestr(a) for a in exc.args)
26
26
27 class Hint(object):
27 class Hint(object):
28 """Mix-in to provide a hint of an error
28 """Mix-in to provide a hint of an error
29
29
30 This should come first in the inheritance list to consume a hint and
30 This should come first in the inheritance list to consume a hint and
31 pass remaining arguments to the exception class.
31 pass remaining arguments to the exception class.
32 """
32 """
33 def __init__(self, *args, **kw):
33 def __init__(self, *args, **kw):
34 self.hint = kw.pop(r'hint', None)
34 self.hint = kw.pop(r'hint', None)
35 super(Hint, self).__init__(*args, **kw)
35 super(Hint, self).__init__(*args, **kw)
36
36
37 class RevlogError(Hint, Exception):
37 class RevlogError(Hint, Exception):
38 __bytes__ = _tobytes
38 __bytes__ = _tobytes
39
39
40 class FilteredIndexError(IndexError):
40 class FilteredIndexError(IndexError):
41 __bytes__ = _tobytes
41 __bytes__ = _tobytes
42
42
43 class LookupError(RevlogError, KeyError):
43 class LookupError(RevlogError, KeyError):
44 def __init__(self, name, index, message):
44 def __init__(self, name, index, message):
45 self.name = name
45 self.name = name
46 self.index = index
46 self.index = index
47 # this can't be called 'message' because at least some installs of
47 # this can't be called 'message' because at least some installs of
48 # Python 2.6+ complain about the 'message' property being deprecated
48 # Python 2.6+ complain about the 'message' property being deprecated
49 self.lookupmessage = message
49 self.lookupmessage = message
50 if isinstance(name, bytes) and len(name) == 20:
50 if isinstance(name, bytes) and len(name) == 20:
51 from .node import short
51 from .node import short
52 name = short(name)
52 name = short(name)
53 RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
53 RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
54
54
55 def __bytes__(self):
55 def __bytes__(self):
56 return RevlogError.__bytes__(self)
56 return RevlogError.__bytes__(self)
57
57
58 def __str__(self):
58 def __str__(self):
59 return RevlogError.__str__(self)
59 return RevlogError.__str__(self)
60
60
61 class AmbiguousPrefixLookupError(LookupError):
61 class AmbiguousPrefixLookupError(LookupError):
62 pass
62 pass
63
63
64 class FilteredLookupError(LookupError):
64 class FilteredLookupError(LookupError):
65 pass
65 pass
66
66
67 class ManifestLookupError(LookupError):
67 class ManifestLookupError(LookupError):
68 pass
68 pass
69
69
70 class CommandError(Exception):
70 class CommandError(Exception):
71 """Exception raised on errors in parsing the command line."""
71 """Exception raised on errors in parsing the command line."""
72 __bytes__ = _tobytes
72 __bytes__ = _tobytes
73
73
74 class InterventionRequired(Hint, Exception):
74 class InterventionRequired(Hint, Exception):
75 """Exception raised when a command requires human intervention."""
75 """Exception raised when a command requires human intervention."""
76 __bytes__ = _tobytes
76 __bytes__ = _tobytes
77
77
78 class Abort(Hint, Exception):
78 class Abort(Hint, Exception):
79 """Raised if a command needs to print an error and exit."""
79 """Raised if a command needs to print an error and exit."""
80 __bytes__ = _tobytes
80 __bytes__ = _tobytes
81
81
82 class HookLoadError(Abort):
82 class HookLoadError(Abort):
83 """raised when loading a hook fails, aborting an operation
83 """raised when loading a hook fails, aborting an operation
84
84
85 Exists to allow more specialized catching."""
85 Exists to allow more specialized catching."""
86
86
87 class HookAbort(Abort):
87 class HookAbort(Abort):
88 """raised when a validation hook fails, aborting an operation
88 """raised when a validation hook fails, aborting an operation
89
89
90 Exists to allow more specialized catching."""
90 Exists to allow more specialized catching."""
91
91
92 class ConfigError(Abort):
92 class ConfigError(Abort):
93 """Exception raised when parsing config files"""
93 """Exception raised when parsing config files"""
94
94
95 class UpdateAbort(Abort):
95 class UpdateAbort(Abort):
96 """Raised when an update is aborted for destination issue"""
96 """Raised when an update is aborted for destination issue"""
97
97
98 class MergeDestAbort(Abort):
98 class MergeDestAbort(Abort):
99 """Raised when an update is aborted for destination issues"""
99 """Raised when an update is aborted for destination issues"""
100
100
101 class NoMergeDestAbort(MergeDestAbort):
101 class NoMergeDestAbort(MergeDestAbort):
102 """Raised when an update is aborted because there is nothing to merge"""
102 """Raised when an update is aborted because there is nothing to merge"""
103
103
104 class ManyMergeDestAbort(MergeDestAbort):
104 class ManyMergeDestAbort(MergeDestAbort):
105 """Raised when an update is aborted because destination is ambiguous"""
105 """Raised when an update is aborted because destination is ambiguous"""
106
106
107 class ResponseExpected(Abort):
107 class ResponseExpected(Abort):
108 """Raised when an EOF is received for a prompt"""
108 """Raised when an EOF is received for a prompt"""
109 def __init__(self):
109 def __init__(self):
110 from .i18n import _
110 from .i18n import _
111 Abort.__init__(self, _('response expected'))
111 Abort.__init__(self, _('response expected'))
112
112
113 class OutOfBandError(Hint, Exception):
113 class OutOfBandError(Hint, Exception):
114 """Exception raised when a remote repo reports failure"""
114 """Exception raised when a remote repo reports failure"""
115 __bytes__ = _tobytes
115 __bytes__ = _tobytes
116
116
117 class ParseError(Hint, Exception):
117 class ParseError(Hint, Exception):
118 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
118 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
119 __bytes__ = _tobytes
119 __bytes__ = _tobytes
120
120
121 class PatchError(Exception):
121 class PatchError(Exception):
122 __bytes__ = _tobytes
122 __bytes__ = _tobytes
123
123
124 class UnknownIdentifier(ParseError):
124 class UnknownIdentifier(ParseError):
125 """Exception raised when a {rev,file}set references an unknown identifier"""
125 """Exception raised when a {rev,file}set references an unknown identifier"""
126
126
127 def __init__(self, function, symbols):
127 def __init__(self, function, symbols):
128 from .i18n import _
128 from .i18n import _
129 ParseError.__init__(self, _("unknown identifier: %s") % function)
129 ParseError.__init__(self, _("unknown identifier: %s") % function)
130 self.function = function
130 self.function = function
131 self.symbols = symbols
131 self.symbols = symbols
132
132
133 class RepoError(Hint, Exception):
133 class RepoError(Hint, Exception):
134 __bytes__ = _tobytes
134 __bytes__ = _tobytes
135
135
136 class RepoLookupError(RepoError):
136 class RepoLookupError(RepoError):
137 pass
137 pass
138
138
139 class FilteredRepoLookupError(RepoLookupError):
139 class FilteredRepoLookupError(RepoLookupError):
140 pass
140 pass
141
141
142 class CapabilityError(RepoError):
142 class CapabilityError(RepoError):
143 pass
143 pass
144
144
145 class RequirementError(RepoError):
145 class RequirementError(RepoError):
146 """Exception raised if .hg/requires has an unknown entry."""
146 """Exception raised if .hg/requires has an unknown entry."""
147
147
148 class StdioError(IOError):
148 class StdioError(IOError):
149 """Raised if I/O to stdout or stderr fails"""
149 """Raised if I/O to stdout or stderr fails"""
150
150
151 def __init__(self, err):
151 def __init__(self, err):
152 IOError.__init__(self, err.errno, err.strerror)
152 IOError.__init__(self, err.errno, err.strerror)
153
153
154 # no __bytes__() because error message is derived from the standard IOError
154 # no __bytes__() because error message is derived from the standard IOError
155
155
156 class UnsupportedMergeRecords(Abort):
156 class UnsupportedMergeRecords(Abort):
157 def __init__(self, recordtypes):
157 def __init__(self, recordtypes):
158 from .i18n import _
158 from .i18n import _
159 self.recordtypes = sorted(recordtypes)
159 self.recordtypes = sorted(recordtypes)
160 s = ' '.join(self.recordtypes)
160 s = ' '.join(self.recordtypes)
161 Abort.__init__(
161 Abort.__init__(
162 self, _('unsupported merge state records: %s') % s,
162 self, _('unsupported merge state records: %s') % s,
163 hint=_('see https://mercurial-scm.org/wiki/MergeStateRecords for '
163 hint=_('see https://mercurial-scm.org/wiki/MergeStateRecords for '
164 'more information'))
164 'more information'))
165
165
166 class UnknownVersion(Abort):
166 class UnknownVersion(Abort):
167 """generic exception for aborting from an encounter with an unknown version
167 """generic exception for aborting from an encounter with an unknown version
168 """
168 """
169
169
170 def __init__(self, msg, hint=None, version=None):
170 def __init__(self, msg, hint=None, version=None):
171 self.version = version
171 self.version = version
172 super(UnknownVersion, self).__init__(msg, hint=hint)
172 super(UnknownVersion, self).__init__(msg, hint=hint)
173
173
174 class LockError(IOError):
174 class LockError(IOError):
175 def __init__(self, errno, strerror, filename, desc):
175 def __init__(self, errno, strerror, filename, desc):
176 IOError.__init__(self, errno, strerror, filename)
176 IOError.__init__(self, errno, strerror, filename)
177 self.desc = desc
177 self.desc = desc
178
178
179 # no __bytes__() because error message is derived from the standard IOError
179 # no __bytes__() because error message is derived from the standard IOError
180
180
181 class LockHeld(LockError):
181 class LockHeld(LockError):
182 def __init__(self, errno, filename, desc, locker):
182 def __init__(self, errno, filename, desc, locker):
183 LockError.__init__(self, errno, 'Lock held', filename, desc)
183 LockError.__init__(self, errno, 'Lock held', filename, desc)
184 self.locker = locker
184 self.locker = locker
185
185
186 class LockUnavailable(LockError):
186 class LockUnavailable(LockError):
187 pass
187 pass
188
188
189 # LockError is for errors while acquiring the lock -- this is unrelated
189 # LockError is for errors while acquiring the lock -- this is unrelated
190 class LockInheritanceContractViolation(RuntimeError):
190 class LockInheritanceContractViolation(RuntimeError):
191 __bytes__ = _tobytes
191 __bytes__ = _tobytes
192
192
193 class ResponseError(Exception):
193 class ResponseError(Exception):
194 """Raised to print an error with part of output and exit."""
194 """Raised to print an error with part of output and exit."""
195 __bytes__ = _tobytes
195 __bytes__ = _tobytes
196
196
197 class UnknownCommand(Exception):
197 class UnknownCommand(Exception):
198 """Exception raised if command is not in the command table."""
198 """Exception raised if command is not in the command table."""
199 __bytes__ = _tobytes
199 __bytes__ = _tobytes
200
200
201 class AmbiguousCommand(Exception):
201 class AmbiguousCommand(Exception):
202 """Exception raised if command shortcut matches more than one command."""
202 """Exception raised if command shortcut matches more than one command."""
203 __bytes__ = _tobytes
203 __bytes__ = _tobytes
204
204
205 # derived from KeyboardInterrupt to simplify some breakout code
205 # derived from KeyboardInterrupt to simplify some breakout code
206 class SignalInterrupt(KeyboardInterrupt):
206 class SignalInterrupt(KeyboardInterrupt):
207 """Exception raised on SIGTERM and SIGHUP."""
207 """Exception raised on SIGTERM and SIGHUP."""
208
208
209 class SignatureError(Exception):
209 class SignatureError(Exception):
210 __bytes__ = _tobytes
210 __bytes__ = _tobytes
211
211
212 class PushRaced(RuntimeError):
212 class PushRaced(RuntimeError):
213 """An exception raised during unbundling that indicate a push race"""
213 """An exception raised during unbundling that indicate a push race"""
214 __bytes__ = _tobytes
214 __bytes__ = _tobytes
215
215
216 class ProgrammingError(Hint, RuntimeError):
216 class ProgrammingError(Hint, RuntimeError):
217 """Raised if a mercurial (core or extension) developer made a mistake"""
217 """Raised if a mercurial (core or extension) developer made a mistake"""
218 __bytes__ = _tobytes
218 __bytes__ = _tobytes
219
219
220 class WdirUnsupported(Exception):
220 class WdirUnsupported(Exception):
221 """An exception which is raised when 'wdir()' is not supported"""
221 """An exception which is raised when 'wdir()' is not supported"""
222 __bytes__ = _tobytes
222 __bytes__ = _tobytes
223
223
224 # bundle2 related errors
224 # bundle2 related errors
225 class BundleValueError(ValueError):
225 class BundleValueError(ValueError):
226 """error raised when bundle2 cannot be processed"""
226 """error raised when bundle2 cannot be processed"""
227 __bytes__ = _tobytes
227 __bytes__ = _tobytes
228
228
229 class BundleUnknownFeatureError(BundleValueError):
229 class BundleUnknownFeatureError(BundleValueError):
230 def __init__(self, parttype=None, params=(), values=()):
230 def __init__(self, parttype=None, params=(), values=()):
231 self.parttype = parttype
231 self.parttype = parttype
232 self.params = params
232 self.params = params
233 self.values = values
233 self.values = values
234 if self.parttype is None:
234 if self.parttype is None:
235 msg = 'Stream Parameter'
235 msg = 'Stream Parameter'
236 else:
236 else:
237 msg = parttype
237 msg = parttype
238 entries = self.params
238 entries = self.params
239 if self.params and self.values:
239 if self.params and self.values:
240 assert len(self.params) == len(self.values)
240 assert len(self.params) == len(self.values)
241 entries = []
241 entries = []
242 for idx, par in enumerate(self.params):
242 for idx, par in enumerate(self.params):
243 val = self.values[idx]
243 val = self.values[idx]
244 if val is None:
244 if val is None:
245 entries.append(val)
245 entries.append(val)
246 else:
246 else:
247 entries.append("%s=%r" % (par, pycompat.maybebytestr(val)))
247 entries.append("%s=%r" % (par, pycompat.maybebytestr(val)))
248 if entries:
248 if entries:
249 msg = '%s - %s' % (msg, ', '.join(entries))
249 msg = '%s - %s' % (msg, ', '.join(entries))
250 ValueError.__init__(self, msg)
250 ValueError.__init__(self, msg)
251
251
252 class ReadOnlyPartError(RuntimeError):
252 class ReadOnlyPartError(RuntimeError):
253 """error raised when code tries to alter a part being generated"""
253 """error raised when code tries to alter a part being generated"""
254 __bytes__ = _tobytes
254 __bytes__ = _tobytes
255
255
256 class PushkeyFailed(Abort):
256 class PushkeyFailed(Abort):
257 """error raised when a pushkey part failed to update a value"""
257 """error raised when a pushkey part failed to update a value"""
258
258
259 def __init__(self, partid, namespace=None, key=None, new=None, old=None,
259 def __init__(self, partid, namespace=None, key=None, new=None, old=None,
260 ret=None):
260 ret=None):
261 self.partid = partid
261 self.partid = partid
262 self.namespace = namespace
262 self.namespace = namespace
263 self.key = key
263 self.key = key
264 self.new = new
264 self.new = new
265 self.old = old
265 self.old = old
266 self.ret = ret
266 self.ret = ret
267 # no i18n expected to be processed into a better message
267 # no i18n expected to be processed into a better message
268 Abort.__init__(self, 'failed to update value for "%s/%s"'
268 Abort.__init__(self, 'failed to update value for "%s/%s"'
269 % (namespace, key))
269 % (namespace, key))
270
270
271 class CensoredNodeError(RevlogError):
271 class CensoredNodeError(RevlogError):
272 """error raised when content verification fails on a censored node
272 """error raised when content verification fails on a censored node
273
273
274 Also contains the tombstone data substituted for the uncensored data.
274 Also contains the tombstone data substituted for the uncensored data.
275 """
275 """
276
276
277 def __init__(self, filename, node, tombstone):
277 def __init__(self, filename, node, tombstone):
278 from .node import short
278 from .node import short
279 RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
279 RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
280 self.tombstone = tombstone
280 self.tombstone = tombstone
281
281
282 class CensoredBaseError(RevlogError):
282 class CensoredBaseError(RevlogError):
283 """error raised when a delta is rejected because its base is censored
283 """error raised when a delta is rejected because its base is censored
284
284
285 A delta based on a censored revision must be formed as single patch
285 A delta based on a censored revision must be formed as single patch
286 operation which replaces the entire base with new content. This ensures
286 operation which replaces the entire base with new content. This ensures
287 the delta may be applied by clones which have not censored the base.
287 the delta may be applied by clones which have not censored the base.
288 """
288 """
289
289
290 class InvalidBundleSpecification(Exception):
290 class InvalidBundleSpecification(Exception):
291 """error raised when a bundle specification is invalid.
291 """error raised when a bundle specification is invalid.
292
292
293 This is used for syntax errors as opposed to support errors.
293 This is used for syntax errors as opposed to support errors.
294 """
294 """
295 __bytes__ = _tobytes
295 __bytes__ = _tobytes
296
296
297 class UnsupportedBundleSpecification(Exception):
297 class UnsupportedBundleSpecification(Exception):
298 """error raised when a bundle specification is not supported."""
298 """error raised when a bundle specification is not supported."""
299 __bytes__ = _tobytes
299 __bytes__ = _tobytes
300
300
301 class CorruptedState(Exception):
301 class CorruptedState(Exception):
302 """error raised when a command is not able to read its state from file"""
302 """error raised when a command is not able to read its state from file"""
303 __bytes__ = _tobytes
303 __bytes__ = _tobytes
304
304
305 class PeerTransportError(Abort):
305 class PeerTransportError(Abort):
306 """Transport-level I/O error when communicating with a peer repo."""
306 """Transport-level I/O error when communicating with a peer repo."""
307
307
308 class InMemoryMergeConflictsError(Exception):
308 class InMemoryMergeConflictsError(Exception):
309 """Exception raised when merge conflicts arose during an in-memory merge."""
309 """Exception raised when merge conflicts arose during an in-memory merge."""
310 __bytes__ = _tobytes
310 __bytes__ = _tobytes
311
312 class WireprotoCommandError(Exception):
313 """Represents an error during execution of a wire protocol command.
314
315 Should only be thrown by wire protocol version 2 commands.
316
317 The error is a formatter string and an optional iterable of arguments.
318 """
319 def __init__(self, message, args=None):
320 self.message = message
321 self.messageargs = args
@@ -1,516 +1,519 b''
1 **Experimental and under development**
1 **Experimental and under development**
2
2
3 This document describe's Mercurial's transport-agnostic remote procedure
3 This document describe's Mercurial's transport-agnostic remote procedure
4 call (RPC) protocol which is used to perform interactions with remote
4 call (RPC) protocol which is used to perform interactions with remote
5 servers. This protocol is also referred to as ``hgrpc``.
5 servers. This protocol is also referred to as ``hgrpc``.
6
6
7 The protocol has the following high-level features:
7 The protocol has the following high-level features:
8
8
9 * Concurrent request and response support (multiple commands can be issued
9 * Concurrent request and response support (multiple commands can be issued
10 simultaneously and responses can be streamed simultaneously).
10 simultaneously and responses can be streamed simultaneously).
11 * Supports half-duplex and full-duplex connections.
11 * Supports half-duplex and full-duplex connections.
12 * All data is transmitted within *frames*, which have a well-defined
12 * All data is transmitted within *frames*, which have a well-defined
13 header and encode their length.
13 header and encode their length.
14 * Side-channels for sending progress updates and printing output. Text
14 * Side-channels for sending progress updates and printing output. Text
15 output from the remote can be localized locally.
15 output from the remote can be localized locally.
16 * Support for simultaneous and long-lived compression streams, even across
16 * Support for simultaneous and long-lived compression streams, even across
17 requests.
17 requests.
18 * Uses CBOR for data exchange.
18 * Uses CBOR for data exchange.
19
19
20 The protocol is not specific to Mercurial and could be used by other
20 The protocol is not specific to Mercurial and could be used by other
21 applications.
21 applications.
22
22
23 High-level Overview
23 High-level Overview
24 ===================
24 ===================
25
25
26 To operate the protocol, a bi-directional, half-duplex pipe supporting
26 To operate the protocol, a bi-directional, half-duplex pipe supporting
27 ordered sends and receives is required. That is, each peer has one pipe
27 ordered sends and receives is required. That is, each peer has one pipe
28 for sending data and another for receiving. Full-duplex pipes are also
28 for sending data and another for receiving. Full-duplex pipes are also
29 supported.
29 supported.
30
30
31 All data is read and written in atomic units called *frames*. These
31 All data is read and written in atomic units called *frames*. These
32 are conceptually similar to TCP packets. Higher-level functionality
32 are conceptually similar to TCP packets. Higher-level functionality
33 is built on the exchange and processing of frames.
33 is built on the exchange and processing of frames.
34
34
35 All frames are associated with a *stream*. A *stream* provides a
35 All frames are associated with a *stream*. A *stream* provides a
36 unidirectional grouping of frames. Streams facilitate two goals:
36 unidirectional grouping of frames. Streams facilitate two goals:
37 content encoding and parallelism. There is a dedicated section on
37 content encoding and parallelism. There is a dedicated section on
38 streams below.
38 streams below.
39
39
40 The protocol is request-response based: the client issues requests to
40 The protocol is request-response based: the client issues requests to
41 the server, which issues replies to those requests. Server-initiated
41 the server, which issues replies to those requests. Server-initiated
42 messaging is not currently supported, but this specification carves
42 messaging is not currently supported, but this specification carves
43 out room to implement it.
43 out room to implement it.
44
44
45 All frames are associated with a numbered request. Frames can thus
45 All frames are associated with a numbered request. Frames can thus
46 be logically grouped by their request ID.
46 be logically grouped by their request ID.
47
47
48 Frames
48 Frames
49 ======
49 ======
50
50
51 Frames begin with an 8 octet header followed by a variable length
51 Frames begin with an 8 octet header followed by a variable length
52 payload::
52 payload::
53
53
54 +------------------------------------------------+
54 +------------------------------------------------+
55 | Length (24) |
55 | Length (24) |
56 +--------------------------------+---------------+
56 +--------------------------------+---------------+
57 | Request ID (16) | Stream ID (8) |
57 | Request ID (16) | Stream ID (8) |
58 +------------------+-------------+---------------+
58 +------------------+-------------+---------------+
59 | Stream Flags (8) |
59 | Stream Flags (8) |
60 +-----------+------+
60 +-----------+------+
61 | Type (4) |
61 | Type (4) |
62 +-----------+
62 +-----------+
63 | Flags (4) |
63 | Flags (4) |
64 +===========+===================================================|
64 +===========+===================================================|
65 | Frame Payload (0...) ...
65 | Frame Payload (0...) ...
66 +---------------------------------------------------------------+
66 +---------------------------------------------------------------+
67
67
68 The length of the frame payload is expressed as an unsigned 24 bit
68 The length of the frame payload is expressed as an unsigned 24 bit
69 little endian integer. Values larger than 65535 MUST NOT be used unless
69 little endian integer. Values larger than 65535 MUST NOT be used unless
70 given permission by the server as part of the negotiated capabilities
70 given permission by the server as part of the negotiated capabilities
71 during the handshake. The frame header is not part of the advertised
71 during the handshake. The frame header is not part of the advertised
72 frame length. The payload length is the over-the-wire length. If there
72 frame length. The payload length is the over-the-wire length. If there
73 is content encoding applied to the payload as part of the frame's stream,
73 is content encoding applied to the payload as part of the frame's stream,
74 the length is the output of that content encoding, not the input.
74 the length is the output of that content encoding, not the input.
75
75
76 The 16-bit ``Request ID`` field denotes the integer request identifier,
76 The 16-bit ``Request ID`` field denotes the integer request identifier,
77 stored as an unsigned little endian integer. Odd numbered requests are
77 stored as an unsigned little endian integer. Odd numbered requests are
78 client-initiated. Even numbered requests are server-initiated. This
78 client-initiated. Even numbered requests are server-initiated. This
79 refers to where the *request* was initiated - not where the *frame* was
79 refers to where the *request* was initiated - not where the *frame* was
80 initiated, so servers will send frames with odd ``Request ID`` in
80 initiated, so servers will send frames with odd ``Request ID`` in
81 response to client-initiated requests. Implementations are advised to
81 response to client-initiated requests. Implementations are advised to
82 start ordering request identifiers at ``1`` and ``0``, increment by
82 start ordering request identifiers at ``1`` and ``0``, increment by
83 ``2``, and wrap around if all available numbers have been exhausted.
83 ``2``, and wrap around if all available numbers have been exhausted.
84
84
85 The 8-bit ``Stream ID`` field denotes the stream that the frame is
85 The 8-bit ``Stream ID`` field denotes the stream that the frame is
86 associated with. Frames belonging to a stream may have content
86 associated with. Frames belonging to a stream may have content
87 encoding applied and the receiver may need to decode the raw frame
87 encoding applied and the receiver may need to decode the raw frame
88 payload to obtain the original data. Odd numbered IDs are
88 payload to obtain the original data. Odd numbered IDs are
89 client-initiated. Even numbered IDs are server-initiated.
89 client-initiated. Even numbered IDs are server-initiated.
90
90
91 The 8-bit ``Stream Flags`` field defines stream processing semantics.
91 The 8-bit ``Stream Flags`` field defines stream processing semantics.
92 See the section on streams below.
92 See the section on streams below.
93
93
94 The 4-bit ``Type`` field denotes the type of frame being sent.
94 The 4-bit ``Type`` field denotes the type of frame being sent.
95
95
96 The 4-bit ``Flags`` field defines special, per-type attributes for
96 The 4-bit ``Flags`` field defines special, per-type attributes for
97 the frame.
97 the frame.
98
98
99 The sections below define the frame types and their behavior.
99 The sections below define the frame types and their behavior.
100
100
101 Command Request (``0x01``)
101 Command Request (``0x01``)
102 --------------------------
102 --------------------------
103
103
104 This frame contains a request to run a command.
104 This frame contains a request to run a command.
105
105
106 The payload consists of a CBOR map defining the command request. The
106 The payload consists of a CBOR map defining the command request. The
107 bytestring keys of that map are:
107 bytestring keys of that map are:
108
108
109 name
109 name
110 Name of the command that should be executed (bytestring).
110 Name of the command that should be executed (bytestring).
111 args
111 args
112 Map of bytestring keys to various value types containing the named
112 Map of bytestring keys to various value types containing the named
113 arguments to this command.
113 arguments to this command.
114
114
115 Each command defines its own set of argument names and their expected
115 Each command defines its own set of argument names and their expected
116 types.
116 types.
117
117
118 This frame type MUST ONLY be sent from clients to servers: it is illegal
118 This frame type MUST ONLY be sent from clients to servers: it is illegal
119 for a server to send this frame to a client.
119 for a server to send this frame to a client.
120
120
121 The following flag values are defined for this type:
121 The following flag values are defined for this type:
122
122
123 0x01
123 0x01
124 New command request. When set, this frame represents the beginning
124 New command request. When set, this frame represents the beginning
125 of a new request to run a command. The ``Request ID`` attached to this
125 of a new request to run a command. The ``Request ID`` attached to this
126 frame MUST NOT be active.
126 frame MUST NOT be active.
127 0x02
127 0x02
128 Command request continuation. When set, this frame is a continuation
128 Command request continuation. When set, this frame is a continuation
129 from a previous command request frame for its ``Request ID``. This
129 from a previous command request frame for its ``Request ID``. This
130 flag is set when the CBOR data for a command request does not fit
130 flag is set when the CBOR data for a command request does not fit
131 in a single frame.
131 in a single frame.
132 0x04
132 0x04
133 Additional frames expected. When set, the command request didn't fit
133 Additional frames expected. When set, the command request didn't fit
134 into a single frame and additional CBOR data follows in a subsequent
134 into a single frame and additional CBOR data follows in a subsequent
135 frame.
135 frame.
136 0x08
136 0x08
137 Command data frames expected. When set, command data frames are
137 Command data frames expected. When set, command data frames are
138 expected to follow the final command request frame for this request.
138 expected to follow the final command request frame for this request.
139
139
140 ``0x01`` MUST be set on the initial command request frame for a
140 ``0x01`` MUST be set on the initial command request frame for a
141 ``Request ID``.
141 ``Request ID``.
142
142
143 ``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
143 ``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
144 a series of command request frames.
144 a series of command request frames.
145
145
146 If command data frames are to be sent, ``0x08`` MUST be set on ALL
146 If command data frames are to be sent, ``0x08`` MUST be set on ALL
147 command request frames.
147 command request frames.
148
148
149 Command Data (``0x02``)
149 Command Data (``0x02``)
150 -----------------------
150 -----------------------
151
151
152 This frame contains raw data for a command.
152 This frame contains raw data for a command.
153
153
154 Most commands can be executed by specifying arguments. However,
154 Most commands can be executed by specifying arguments. However,
155 arguments have an upper bound to their length. For commands that
155 arguments have an upper bound to their length. For commands that
156 accept data that is beyond this length or whose length isn't known
156 accept data that is beyond this length or whose length isn't known
157 when the command is initially sent, they will need to stream
157 when the command is initially sent, they will need to stream
158 arbitrary data to the server. This frame type facilitates the sending
158 arbitrary data to the server. This frame type facilitates the sending
159 of this data.
159 of this data.
160
160
161 The payload of this frame type consists of a stream of raw data to be
161 The payload of this frame type consists of a stream of raw data to be
162 consumed by the command handler on the server. The format of the data
162 consumed by the command handler on the server. The format of the data
163 is command specific.
163 is command specific.
164
164
165 The following flag values are defined for this type:
165 The following flag values are defined for this type:
166
166
167 0x01
167 0x01
168 Command data continuation. When set, the data for this command
168 Command data continuation. When set, the data for this command
169 continues into a subsequent frame.
169 continues into a subsequent frame.
170
170
171 0x02
171 0x02
172 End of data. When set, command data has been fully sent to the
172 End of data. When set, command data has been fully sent to the
173 server. The command has been fully issued and no new data for this
173 server. The command has been fully issued and no new data for this
174 command will be sent. The next frame will belong to a new command.
174 command will be sent. The next frame will belong to a new command.
175
175
176 Command Response Data (``0x03``)
176 Command Response Data (``0x03``)
177 --------------------------------
177 --------------------------------
178
178
179 This frame contains response data to an issued command.
179 This frame contains response data to an issued command.
180
180
181 Response data ALWAYS consists of a series of 1 or more CBOR encoded
181 Response data ALWAYS consists of a series of 1 or more CBOR encoded
182 values. A CBOR value may be using indefinite length encoding. And the
182 values. A CBOR value may be using indefinite length encoding. And the
183 bytes constituting the value may span several frames.
183 bytes constituting the value may span several frames.
184
184
185 The following flag values are defined for this type:
185 The following flag values are defined for this type:
186
186
187 0x01
187 0x01
188 Data continuation. When set, an additional frame containing response data
188 Data continuation. When set, an additional frame containing response data
189 will follow.
189 will follow.
190 0x02
190 0x02
191 End of data. When set, the response data has been fully sent and
191 End of data. When set, the response data has been fully sent and
192 no additional frames for this response will be sent.
192 no additional frames for this response will be sent.
193
193
194 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
194 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
195
195
196 Error Occurred (``0x05``)
196 Error Occurred (``0x05``)
197 -------------------------
197 -------------------------
198
198
199 Some kind of error occurred.
199 Some kind of error occurred.
200
200
201 There are 3 general kinds of failures that can occur:
201 There are 3 general kinds of failures that can occur:
202
202
203 * Command error encountered before any response issued
203 * Command error encountered before any response issued
204 * Command error encountered after a response was issued
204 * Command error encountered after a response was issued
205 * Protocol or stream level error
205 * Protocol or stream level error
206
206
207 This frame type is used to capture the latter cases. (The general
207 This frame type is used to capture the latter cases. (The general
208 command error case is handled by the leading CBOR map in
208 command error case is handled by the leading CBOR map in
209 ``Command Response`` frames.)
209 ``Command Response`` frames.)
210
210
211 The payload of this frame contains a CBOR map detailing the error. That
211 The payload of this frame contains a CBOR map detailing the error. That
212 map has the following bytestring keys:
212 map has the following bytestring keys:
213
213
214 type
214 type
215 (bytestring) The overall type of error encountered. Can be one of the
215 (bytestring) The overall type of error encountered. Can be one of the
216 following values:
216 following values:
217
217
218 protocol
218 protocol
219 A protocol-level error occurred. This typically means someone
219 A protocol-level error occurred. This typically means someone
220 is violating the framing protocol semantics and the server is
220 is violating the framing protocol semantics and the server is
221 refusing to proceed.
221 refusing to proceed.
222
222
223 server
223 server
224 A server-level error occurred. This typically indicates some kind of
224 A server-level error occurred. This typically indicates some kind of
225 logic error on the server, likely the fault of the server.
225 logic error on the server, likely the fault of the server.
226
226
227 command
227 command
228 A command-level error, likely the fault of the client.
228 A command-level error, likely the fault of the client.
229
229
230 message
230 message
231 (array of maps) A richly formatted message that is intended for
231 (array of maps) A richly formatted message that is intended for
232 human consumption. See the ``Human Output Side-Channel`` frame
232 human consumption. See the ``Human Output Side-Channel`` frame
233 section for a description of the format of this data structure.
233 section for a description of the format of this data structure.
234
234
235 Human Output Side-Channel (``0x06``)
235 Human Output Side-Channel (``0x06``)
236 ------------------------------------
236 ------------------------------------
237
237
238 This frame contains a message that is intended to be displayed to
238 This frame contains a message that is intended to be displayed to
239 people. Whereas most frames communicate machine readable data, this
239 people. Whereas most frames communicate machine readable data, this
240 frame communicates textual data that is intended to be shown to
240 frame communicates textual data that is intended to be shown to
241 humans.
241 humans.
242
242
243 The frame consists of a series of *formatting requests*. Each formatting
243 The frame consists of a series of *formatting requests*. Each formatting
244 request consists of a formatting string, arguments for that formatting
244 request consists of a formatting string, arguments for that formatting
245 string, and labels to apply to that formatting string.
245 string, and labels to apply to that formatting string.
246
246
247 A formatting string is a printf()-like string that allows variable
247 A formatting string is a printf()-like string that allows variable
248 substitution within the string. Labels allow the rendered text to be
248 substitution within the string. Labels allow the rendered text to be
249 *decorated*. Assuming use of the canonical Mercurial code base, a
249 *decorated*. Assuming use of the canonical Mercurial code base, a
250 formatting string can be the input to the ``i18n._`` function. This
250 formatting string can be the input to the ``i18n._`` function. This
251 allows messages emitted from the server to be localized. So even if
251 allows messages emitted from the server to be localized. So even if
252 the server has different i18n settings, people could see messages in
252 the server has different i18n settings, people could see messages in
253 their *native* settings. Similarly, the use of labels allows
253 their *native* settings. Similarly, the use of labels allows
254 decorations like coloring and underlining to be applied using the
254 decorations like coloring and underlining to be applied using the
255 client's configured rendering settings.
255 client's configured rendering settings.
256
256
257 Formatting strings are similar to ``printf()`` strings or how
257 Formatting strings are similar to ``printf()`` strings or how
258 Python's ``%`` operator works. The only supported formatting sequences
258 Python's ``%`` operator works. The only supported formatting sequences
259 are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
259 are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
260 at that position resolves to. ``%%`` will be replaced by ``%``. All
260 at that position resolves to. ``%%`` will be replaced by ``%``. All
261 other 2-byte sequences beginning with ``%`` represent a literal
261 other 2-byte sequences beginning with ``%`` represent a literal
262 ``%`` followed by that character. However, future versions of the
262 ``%`` followed by that character. However, future versions of the
263 wire protocol reserve the right to allow clients to opt in to receiving
263 wire protocol reserve the right to allow clients to opt in to receiving
264 formatting strings with additional formatters, hence why ``%%`` is
264 formatting strings with additional formatters, hence why ``%%`` is
265 required to represent the literal ``%``.
265 required to represent the literal ``%``.
266
266
267 The frame payload consists of a CBOR array of CBOR maps. Each map
267 The frame payload consists of a CBOR array of CBOR maps. Each map
268 defines an *atom* of text data to print. Each *atom* has the following
268 defines an *atom* of text data to print. Each *atom* has the following
269 bytestring keys:
269 bytestring keys:
270
270
271 msg
271 msg
272 (bytestring) The formatting string. Content MUST be ASCII.
272 (bytestring) The formatting string. Content MUST be ASCII.
273 args (optional)
273 args (optional)
274 Array of bytestrings defining arguments to the formatting string.
274 Array of bytestrings defining arguments to the formatting string.
275 labels (optional)
275 labels (optional)
276 Array of bytestrings defining labels to apply to this atom.
276 Array of bytestrings defining labels to apply to this atom.
277
277
278 All data to be printed MUST be encoded into a single frame: this frame
278 All data to be printed MUST be encoded into a single frame: this frame
279 does not support spanning data across multiple frames.
279 does not support spanning data across multiple frames.
280
280
281 All textual data encoded in these frames is assumed to be line delimited.
281 All textual data encoded in these frames is assumed to be line delimited.
282 The last atom in the frame SHOULD end with a newline (``\n``). If it
282 The last atom in the frame SHOULD end with a newline (``\n``). If it
283 doesn't, clients MAY add a newline to facilitate immediate printing.
283 doesn't, clients MAY add a newline to facilitate immediate printing.
284
284
285 Progress Update (``0x07``)
285 Progress Update (``0x07``)
286 --------------------------
286 --------------------------
287
287
288 This frame holds the progress of an operation on the peer. Consumption
288 This frame holds the progress of an operation on the peer. Consumption
289 of these frames allows clients to display progress bars, estimated
289 of these frames allows clients to display progress bars, estimated
290 completion times, etc.
290 completion times, etc.
291
291
292 Each frame defines the progress of a single operation on the peer. The
292 Each frame defines the progress of a single operation on the peer. The
293 payload consists of a CBOR map with the following bytestring keys:
293 payload consists of a CBOR map with the following bytestring keys:
294
294
295 topic
295 topic
296 Topic name (string)
296 Topic name (string)
297 pos
297 pos
298 Current numeric position within the topic (integer)
298 Current numeric position within the topic (integer)
299 total
299 total
300 Total/end numeric position of this topic (unsigned integer)
300 Total/end numeric position of this topic (unsigned integer)
301 label (optional)
301 label (optional)
302 Unit label (string)
302 Unit label (string)
303 item (optional)
303 item (optional)
304 Item name (string)
304 Item name (string)
305
305
306 Progress state is created when a frame is received referencing a
306 Progress state is created when a frame is received referencing a
307 *topic* that isn't currently tracked. Progress tracking for that
307 *topic* that isn't currently tracked. Progress tracking for that
308 *topic* is finished when a frame is received reporting the current
308 *topic* is finished when a frame is received reporting the current
309 position of that topic as ``-1``.
309 position of that topic as ``-1``.
310
310
311 Multiple *topics* may be active at any given time.
311 Multiple *topics* may be active at any given time.
312
312
313 Rendering of progress information is not mandated or governed by this
313 Rendering of progress information is not mandated or governed by this
314 specification: implementations MAY render progress information however
314 specification: implementations MAY render progress information however
315 they see fit, including not at all.
315 they see fit, including not at all.
316
316
317 The string data describing the topic SHOULD be static strings to
317 The string data describing the topic SHOULD be static strings to
318 facilitate receivers localizing that string data. The emitter
318 facilitate receivers localizing that string data. The emitter
319 MUST normalize all string data to valid UTF-8 and receivers SHOULD
319 MUST normalize all string data to valid UTF-8 and receivers SHOULD
320 validate that received data conforms to UTF-8. The topic name
320 validate that received data conforms to UTF-8. The topic name
321 SHOULD be ASCII.
321 SHOULD be ASCII.
322
322
323 Stream Encoding Settings (``0x08``)
323 Stream Encoding Settings (``0x08``)
324 -----------------------------------
324 -----------------------------------
325
325
326 This frame type holds information defining the content encoding
326 This frame type holds information defining the content encoding
327 settings for a *stream*.
327 settings for a *stream*.
328
328
329 This frame type is likely consumed by the protocol layer and is not
329 This frame type is likely consumed by the protocol layer and is not
330 passed on to applications.
330 passed on to applications.
331
331
332 This frame type MUST ONLY occur on frames having the *Beginning of Stream*
332 This frame type MUST ONLY occur on frames having the *Beginning of Stream*
333 ``Stream Flag`` set.
333 ``Stream Flag`` set.
334
334
335 The payload of this frame defines what content encoding has (possibly)
335 The payload of this frame defines what content encoding has (possibly)
336 been applied to the payloads of subsequent frames in this stream.
336 been applied to the payloads of subsequent frames in this stream.
337
337
338 The payload begins with an 8-bit integer defining the length of the
338 The payload begins with an 8-bit integer defining the length of the
339 encoding *profile*, followed by the string name of that profile, which
339 encoding *profile*, followed by the string name of that profile, which
340 must be an ASCII string. All bytes that follow can be used by that
340 must be an ASCII string. All bytes that follow can be used by that
341 profile for supplemental settings definitions. See the section below
341 profile for supplemental settings definitions. See the section below
342 on defined encoding profiles.
342 on defined encoding profiles.
343
343
344 Stream States and Flags
344 Stream States and Flags
345 =======================
345 =======================
346
346
347 Streams can be in two states: *open* and *closed*. An *open* stream
347 Streams can be in two states: *open* and *closed*. An *open* stream
348 is active and frames attached to that stream could arrive at any time.
348 is active and frames attached to that stream could arrive at any time.
349 A *closed* stream is not active. If a frame attached to a *closed*
349 A *closed* stream is not active. If a frame attached to a *closed*
350 stream arrives, that frame MUST have an appropriate stream flag
350 stream arrives, that frame MUST have an appropriate stream flag
351 set indicating beginning of stream. All streams are in the *closed*
351 set indicating beginning of stream. All streams are in the *closed*
352 state by default.
352 state by default.
353
353
354 The ``Stream Flags`` field denotes a set of bit flags for defining
354 The ``Stream Flags`` field denotes a set of bit flags for defining
355 the relationship of this frame within a stream. The following flags
355 the relationship of this frame within a stream. The following flags
356 are defined:
356 are defined:
357
357
358 0x01
358 0x01
359 Beginning of stream. The first frame in the stream MUST set this
359 Beginning of stream. The first frame in the stream MUST set this
360 flag. When received, the ``Stream ID`` this frame is attached to
360 flag. When received, the ``Stream ID`` this frame is attached to
361 becomes ``open``.
361 becomes ``open``.
362
362
363 0x02
363 0x02
364 End of stream. The last frame in a stream MUST set this flag. When
364 End of stream. The last frame in a stream MUST set this flag. When
365 received, the ``Stream ID`` this frame is attached to becomes
365 received, the ``Stream ID`` this frame is attached to becomes
366 ``closed``. Any content encoding context associated with this stream
366 ``closed``. Any content encoding context associated with this stream
367 can be destroyed after processing the payload of this frame.
367 can be destroyed after processing the payload of this frame.
368
368
369 0x04
369 0x04
370 Apply content encoding. When set, any content encoding settings
370 Apply content encoding. When set, any content encoding settings
371 defined by the stream should be applied when attempting to read
371 defined by the stream should be applied when attempting to read
372 the frame. When not set, the frame payload isn't encoded.
372 the frame. When not set, the frame payload isn't encoded.
373
373
374 Streams
374 Streams
375 =======
375 =======
376
376
377 Streams - along with ``Request IDs`` - facilitate grouping of frames.
377 Streams - along with ``Request IDs`` - facilitate grouping of frames.
378 But the purpose of each is quite different and the groupings they
378 But the purpose of each is quite different and the groupings they
379 constitute are independent.
379 constitute are independent.
380
380
381 A ``Request ID`` is essentially a tag. It tells you which logical
381 A ``Request ID`` is essentially a tag. It tells you which logical
382 request a frame is associated with.
382 request a frame is associated with.
383
383
384 A *stream* is a sequence of frames grouped for the express purpose
384 A *stream* is a sequence of frames grouped for the express purpose
385 of applying a stateful encoding or for denoting sub-groups of frames.
385 of applying a stateful encoding or for denoting sub-groups of frames.
386
386
387 Unlike ``Request ID``s which span the request and response, a stream
387 Unlike ``Request ID``s which span the request and response, a stream
388 is unidirectional and stream IDs are independent from client to
388 is unidirectional and stream IDs are independent from client to
389 server.
389 server.
390
390
391 There is no strict hierarchical relationship between ``Request IDs``
391 There is no strict hierarchical relationship between ``Request IDs``
392 and *streams*. A stream can contain frames having multiple
392 and *streams*. A stream can contain frames having multiple
393 ``Request IDs``. Frames belonging to the same ``Request ID`` can
393 ``Request IDs``. Frames belonging to the same ``Request ID`` can
394 span multiple streams.
394 span multiple streams.
395
395
396 One goal of streams is to facilitate content encoding. A stream can
396 One goal of streams is to facilitate content encoding. A stream can
397 define an encoding to be applied to frame payloads. For example, the
397 define an encoding to be applied to frame payloads. For example, the
398 payload transmitted over the wire may contain output from a
398 payload transmitted over the wire may contain output from a
399 zstandard compression operation and the receiving end may decompress
399 zstandard compression operation and the receiving end may decompress
400 that payload to obtain the original data.
400 that payload to obtain the original data.
401
401
402 The other goal of streams is to facilitate concurrent execution. For
402 The other goal of streams is to facilitate concurrent execution. For
403 example, a server could spawn 4 threads to service a request that can
403 example, a server could spawn 4 threads to service a request that can
404 be easily parallelized. Each of those 4 threads could write into its
404 be easily parallelized. Each of those 4 threads could write into its
405 own stream. Those streams could then in turn be delivered to 4 threads
405 own stream. Those streams could then in turn be delivered to 4 threads
406 on the receiving end, with each thread consuming its stream in near
406 on the receiving end, with each thread consuming its stream in near
407 isolation. The *main* thread on both ends merely does I/O and
407 isolation. The *main* thread on both ends merely does I/O and
408 encodes/decodes frame headers: the bulk of the work is done by worker
408 encodes/decodes frame headers: the bulk of the work is done by worker
409 threads.
409 threads.
410
410
411 In addition, since content encoding is defined per stream, each
411 In addition, since content encoding is defined per stream, each
412 *worker thread* could perform potentially CPU bound work concurrently
412 *worker thread* could perform potentially CPU bound work concurrently
413 with other threads. This approach of applying encoding at the
413 with other threads. This approach of applying encoding at the
414 sub-protocol / stream level eliminates a potential resource constraint
414 sub-protocol / stream level eliminates a potential resource constraint
415 on the protocol stream as a whole (it is common for the throughput of
415 on the protocol stream as a whole (it is common for the throughput of
416 a compression engine to be smaller than the throughput of a network).
416 a compression engine to be smaller than the throughput of a network).
417
417
418 Having multiple streams - each with their own encoding settings - also
418 Having multiple streams - each with their own encoding settings - also
419 facilitates the use of advanced data compression techniques. For
419 facilitates the use of advanced data compression techniques. For
420 example, a transmitter could see that it is generating data faster
420 example, a transmitter could see that it is generating data faster
421 and slower than the receiving end is consuming it and adjust its
421 and slower than the receiving end is consuming it and adjust its
422 compression settings to trade CPU for compression ratio accordingly.
422 compression settings to trade CPU for compression ratio accordingly.
423
423
424 While streams can define a content encoding, not all frames within
424 While streams can define a content encoding, not all frames within
425 that stream must use that content encoding. This can be useful when
425 that stream must use that content encoding. This can be useful when
426 data is being served from caches and being derived dynamically. A
426 data is being served from caches and being derived dynamically. A
427 cache could pre-compressed data so the server doesn't have to
427 cache could pre-compressed data so the server doesn't have to
428 recompress it. The ability to pick and choose which frames are
428 recompress it. The ability to pick and choose which frames are
429 compressed allows servers to easily send data to the wire without
429 compressed allows servers to easily send data to the wire without
430 involving potentially expensive encoding overhead.
430 involving potentially expensive encoding overhead.
431
431
432 Content Encoding Profiles
432 Content Encoding Profiles
433 =========================
433 =========================
434
434
435 Streams can have named content encoding *profiles* associated with
435 Streams can have named content encoding *profiles* associated with
436 them. A profile defines a shared understanding of content encoding
436 them. A profile defines a shared understanding of content encoding
437 settings and behavior.
437 settings and behavior.
438
438
439 The following profiles are defined:
439 The following profiles are defined:
440
440
441 TBD
441 TBD
442
442
443 Command Protocol
443 Command Protocol
444 ================
444 ================
445
445
446 A client can request that a remote run a command by sending it
446 A client can request that a remote run a command by sending it
447 frames defining that command. This logical stream is composed of
447 frames defining that command. This logical stream is composed of
448 1 or more ``Command Request`` frames and and 0 or more ``Command Data``
448 1 or more ``Command Request`` frames and and 0 or more ``Command Data``
449 frames.
449 frames.
450
450
451 All frames composing a single command request MUST be associated with
451 All frames composing a single command request MUST be associated with
452 the same ``Request ID``.
452 the same ``Request ID``.
453
453
454 Clients MAY send additional command requests without waiting on the
454 Clients MAY send additional command requests without waiting on the
455 response to a previous command request. If they do so, they MUST ensure
455 response to a previous command request. If they do so, they MUST ensure
456 that the ``Request ID`` field of outbound frames does not conflict
456 that the ``Request ID`` field of outbound frames does not conflict
457 with that of an active ``Request ID`` whose response has not yet been
457 with that of an active ``Request ID`` whose response has not yet been
458 fully received.
458 fully received.
459
459
460 Servers MAY respond to commands in a different order than they were
460 Servers MAY respond to commands in a different order than they were
461 sent over the wire. Clients MUST be prepared to deal with this. Servers
461 sent over the wire. Clients MUST be prepared to deal with this. Servers
462 also MAY start executing commands in a different order than they were
462 also MAY start executing commands in a different order than they were
463 received, or MAY execute multiple commands concurrently.
463 received, or MAY execute multiple commands concurrently.
464
464
465 If there is a dependency between commands or a race condition between
465 If there is a dependency between commands or a race condition between
466 commands executing (e.g. a read-only command that depends on the results
466 commands executing (e.g. a read-only command that depends on the results
467 of a command that mutates the repository), then clients MUST NOT send
467 of a command that mutates the repository), then clients MUST NOT send
468 frames issuing a command until a response to all dependent commands has
468 frames issuing a command until a response to all dependent commands has
469 been received.
469 been received.
470 TODO think about whether we should express dependencies between commands
470 TODO think about whether we should express dependencies between commands
471 to avoid roundtrip latency.
471 to avoid roundtrip latency.
472
472
473 A command is defined by a command name, 0 or more command arguments,
473 A command is defined by a command name, 0 or more command arguments,
474 and optional command data.
474 and optional command data.
475
475
476 Arguments are the recommended mechanism for transferring fixed sets of
476 Arguments are the recommended mechanism for transferring fixed sets of
477 parameters to a command. Data is appropriate for transferring variable
477 parameters to a command. Data is appropriate for transferring variable
478 data. Thinking in terms of HTTP, arguments would be headers and data
478 data. Thinking in terms of HTTP, arguments would be headers and data
479 would be the message body.
479 would be the message body.
480
480
481 It is recommended for servers to delay the dispatch of a command
481 It is recommended for servers to delay the dispatch of a command
482 until all argument have been received. Servers MAY impose limits on the
482 until all argument have been received. Servers MAY impose limits on the
483 maximum argument size.
483 maximum argument size.
484 TODO define failure mechanism.
484 TODO define failure mechanism.
485
485
486 Servers MAY dispatch to commands immediately once argument data
486 Servers MAY dispatch to commands immediately once argument data
487 is available or delay until command data is received in full.
487 is available or delay until command data is received in full.
488
488
489 Once a ``Command Request`` frame is sent, a client must be prepared to
489 Once a ``Command Request`` frame is sent, a client must be prepared to
490 receive any of the following frames associated with that request:
490 receive any of the following frames associated with that request:
491 ``Command Response``, ``Error Response``, ``Human Output Side-Channel``,
491 ``Command Response``, ``Error Response``, ``Human Output Side-Channel``,
492 ``Progress Update``.
492 ``Progress Update``.
493
493
494 The *main* response for a command will be in ``Command Response`` frames.
494 The *main* response for a command will be in ``Command Response`` frames.
495 The payloads of these frames consist of 1 or more CBOR encoded values.
495 The payloads of these frames consist of 1 or more CBOR encoded values.
496 The first CBOR value on the first ``Command Response`` frame is special
496 The first CBOR value on the first ``Command Response`` frame is special
497 and denotes the overall status of the command. This CBOR map contains
497 and denotes the overall status of the command. This CBOR map contains
498 the following bytestring keys:
498 the following bytestring keys:
499
499
500 status
500 status
501 (bytestring) A well-defined message containing the overall status of
501 (bytestring) A well-defined message containing the overall status of
502 this command request. The following values are defined:
502 this command request. The following values are defined:
503
503
504 ok
504 ok
505 The command was received successfully and its response follows.
505 The command was received successfully and its response follows.
506 error
506 error
507 There was an error processing the command. More details about the
507 There was an error processing the command. More details about the
508 error are encoded in the ``error`` key.
508 error are encoded in the ``error`` key.
509
509
510 error (optional)
510 error (optional)
511 A map containing information about an encountered error. The map has the
511 A map containing information about an encountered error. The map has the
512 following keys:
512 following keys:
513
513
514 message
514 message
515 (array of maps) A message describing the error. The message uses the
515 (array of maps) A message describing the error. The message uses the
516 same format as those in the ``Human Output Side-Channel`` frame.
516 same format as those in the ``Human Output Side-Channel`` frame.
517
518 TODO formalize when error frames can be seen and how errors can be
519 recognized midway through a command response.
@@ -1,1169 +1,1230 b''
1 # wireprotoframing.py - unified framing protocol for wire protocol
1 # wireprotoframing.py - unified framing protocol for wire protocol
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # This file contains functionality to support the unified frame-based wire
8 # This file contains functionality to support the unified frame-based wire
9 # protocol. For details about the protocol, see
9 # protocol. For details about the protocol, see
10 # `hg help internals.wireprotocol`.
10 # `hg help internals.wireprotocol`.
11
11
12 from __future__ import absolute_import
12 from __future__ import absolute_import
13
13
14 import collections
14 import collections
15 import struct
15 import struct
16
16
17 from .i18n import _
17 from .i18n import _
18 from .thirdparty import (
18 from .thirdparty import (
19 attr,
19 attr,
20 )
20 )
21 from . import (
21 from . import (
22 encoding,
22 encoding,
23 error,
23 error,
24 util,
24 util,
25 )
25 )
26 from .utils import (
26 from .utils import (
27 cborutil,
27 cborutil,
28 stringutil,
28 stringutil,
29 )
29 )
30
30
31 FRAME_HEADER_SIZE = 8
31 FRAME_HEADER_SIZE = 8
32 DEFAULT_MAX_FRAME_SIZE = 32768
32 DEFAULT_MAX_FRAME_SIZE = 32768
33
33
34 STREAM_FLAG_BEGIN_STREAM = 0x01
34 STREAM_FLAG_BEGIN_STREAM = 0x01
35 STREAM_FLAG_END_STREAM = 0x02
35 STREAM_FLAG_END_STREAM = 0x02
36 STREAM_FLAG_ENCODING_APPLIED = 0x04
36 STREAM_FLAG_ENCODING_APPLIED = 0x04
37
37
38 STREAM_FLAGS = {
38 STREAM_FLAGS = {
39 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
39 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
40 b'stream-end': STREAM_FLAG_END_STREAM,
40 b'stream-end': STREAM_FLAG_END_STREAM,
41 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
41 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
42 }
42 }
43
43
44 FRAME_TYPE_COMMAND_REQUEST = 0x01
44 FRAME_TYPE_COMMAND_REQUEST = 0x01
45 FRAME_TYPE_COMMAND_DATA = 0x02
45 FRAME_TYPE_COMMAND_DATA = 0x02
46 FRAME_TYPE_COMMAND_RESPONSE = 0x03
46 FRAME_TYPE_COMMAND_RESPONSE = 0x03
47 FRAME_TYPE_ERROR_RESPONSE = 0x05
47 FRAME_TYPE_ERROR_RESPONSE = 0x05
48 FRAME_TYPE_TEXT_OUTPUT = 0x06
48 FRAME_TYPE_TEXT_OUTPUT = 0x06
49 FRAME_TYPE_PROGRESS = 0x07
49 FRAME_TYPE_PROGRESS = 0x07
50 FRAME_TYPE_STREAM_SETTINGS = 0x08
50 FRAME_TYPE_STREAM_SETTINGS = 0x08
51
51
52 FRAME_TYPES = {
52 FRAME_TYPES = {
53 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
53 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
54 b'command-data': FRAME_TYPE_COMMAND_DATA,
54 b'command-data': FRAME_TYPE_COMMAND_DATA,
55 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
55 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
56 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
56 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
57 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
57 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
58 b'progress': FRAME_TYPE_PROGRESS,
58 b'progress': FRAME_TYPE_PROGRESS,
59 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
59 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
60 }
60 }
61
61
62 FLAG_COMMAND_REQUEST_NEW = 0x01
62 FLAG_COMMAND_REQUEST_NEW = 0x01
63 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
63 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
64 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
64 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
65 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
65 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
66
66
67 FLAGS_COMMAND_REQUEST = {
67 FLAGS_COMMAND_REQUEST = {
68 b'new': FLAG_COMMAND_REQUEST_NEW,
68 b'new': FLAG_COMMAND_REQUEST_NEW,
69 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
69 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
70 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
70 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
71 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
71 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
72 }
72 }
73
73
74 FLAG_COMMAND_DATA_CONTINUATION = 0x01
74 FLAG_COMMAND_DATA_CONTINUATION = 0x01
75 FLAG_COMMAND_DATA_EOS = 0x02
75 FLAG_COMMAND_DATA_EOS = 0x02
76
76
77 FLAGS_COMMAND_DATA = {
77 FLAGS_COMMAND_DATA = {
78 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
78 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
79 b'eos': FLAG_COMMAND_DATA_EOS,
79 b'eos': FLAG_COMMAND_DATA_EOS,
80 }
80 }
81
81
82 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
82 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
83 FLAG_COMMAND_RESPONSE_EOS = 0x02
83 FLAG_COMMAND_RESPONSE_EOS = 0x02
84
84
85 FLAGS_COMMAND_RESPONSE = {
85 FLAGS_COMMAND_RESPONSE = {
86 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
86 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
87 b'eos': FLAG_COMMAND_RESPONSE_EOS,
87 b'eos': FLAG_COMMAND_RESPONSE_EOS,
88 }
88 }
89
89
90 # Maps frame types to their available flags.
90 # Maps frame types to their available flags.
91 FRAME_TYPE_FLAGS = {
91 FRAME_TYPE_FLAGS = {
92 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
92 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
93 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
93 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
94 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
94 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
95 FRAME_TYPE_ERROR_RESPONSE: {},
95 FRAME_TYPE_ERROR_RESPONSE: {},
96 FRAME_TYPE_TEXT_OUTPUT: {},
96 FRAME_TYPE_TEXT_OUTPUT: {},
97 FRAME_TYPE_PROGRESS: {},
97 FRAME_TYPE_PROGRESS: {},
98 FRAME_TYPE_STREAM_SETTINGS: {},
98 FRAME_TYPE_STREAM_SETTINGS: {},
99 }
99 }
100
100
101 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
101 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
102
102
103 def humanflags(mapping, value):
103 def humanflags(mapping, value):
104 """Convert a numeric flags value to a human value, using a mapping table."""
104 """Convert a numeric flags value to a human value, using a mapping table."""
105 namemap = {v: k for k, v in mapping.iteritems()}
105 namemap = {v: k for k, v in mapping.iteritems()}
106 flags = []
106 flags = []
107 val = 1
107 val = 1
108 while value >= val:
108 while value >= val:
109 if value & val:
109 if value & val:
110 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
110 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
111 val <<= 1
111 val <<= 1
112
112
113 return b'|'.join(flags)
113 return b'|'.join(flags)
114
114
115 @attr.s(slots=True)
115 @attr.s(slots=True)
116 class frameheader(object):
116 class frameheader(object):
117 """Represents the data in a frame header."""
117 """Represents the data in a frame header."""
118
118
119 length = attr.ib()
119 length = attr.ib()
120 requestid = attr.ib()
120 requestid = attr.ib()
121 streamid = attr.ib()
121 streamid = attr.ib()
122 streamflags = attr.ib()
122 streamflags = attr.ib()
123 typeid = attr.ib()
123 typeid = attr.ib()
124 flags = attr.ib()
124 flags = attr.ib()
125
125
126 @attr.s(slots=True, repr=False)
126 @attr.s(slots=True, repr=False)
127 class frame(object):
127 class frame(object):
128 """Represents a parsed frame."""
128 """Represents a parsed frame."""
129
129
130 requestid = attr.ib()
130 requestid = attr.ib()
131 streamid = attr.ib()
131 streamid = attr.ib()
132 streamflags = attr.ib()
132 streamflags = attr.ib()
133 typeid = attr.ib()
133 typeid = attr.ib()
134 flags = attr.ib()
134 flags = attr.ib()
135 payload = attr.ib()
135 payload = attr.ib()
136
136
137 @encoding.strmethod
137 @encoding.strmethod
138 def __repr__(self):
138 def __repr__(self):
139 typename = '<unknown 0x%02x>' % self.typeid
139 typename = '<unknown 0x%02x>' % self.typeid
140 for name, value in FRAME_TYPES.iteritems():
140 for name, value in FRAME_TYPES.iteritems():
141 if value == self.typeid:
141 if value == self.typeid:
142 typename = name
142 typename = name
143 break
143 break
144
144
145 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
145 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
146 'type=%s; flags=%s)' % (
146 'type=%s; flags=%s)' % (
147 len(self.payload), self.requestid, self.streamid,
147 len(self.payload), self.requestid, self.streamid,
148 humanflags(STREAM_FLAGS, self.streamflags), typename,
148 humanflags(STREAM_FLAGS, self.streamflags), typename,
149 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
149 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
150
150
151 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
151 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
152 """Assemble a frame into a byte array."""
152 """Assemble a frame into a byte array."""
153 # TODO assert size of payload.
153 # TODO assert size of payload.
154 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
154 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
155
155
156 # 24 bits length
156 # 24 bits length
157 # 16 bits request id
157 # 16 bits request id
158 # 8 bits stream id
158 # 8 bits stream id
159 # 8 bits stream flags
159 # 8 bits stream flags
160 # 4 bits type
160 # 4 bits type
161 # 4 bits flags
161 # 4 bits flags
162
162
163 l = struct.pack(r'<I', len(payload))
163 l = struct.pack(r'<I', len(payload))
164 frame[0:3] = l[0:3]
164 frame[0:3] = l[0:3]
165 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
165 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
166 frame[7] = (typeid << 4) | flags
166 frame[7] = (typeid << 4) | flags
167 frame[8:] = payload
167 frame[8:] = payload
168
168
169 return frame
169 return frame
170
170
171 def makeframefromhumanstring(s):
171 def makeframefromhumanstring(s):
172 """Create a frame from a human readable string
172 """Create a frame from a human readable string
173
173
174 Strings have the form:
174 Strings have the form:
175
175
176 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
176 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
177
177
178 This can be used by user-facing applications and tests for creating
178 This can be used by user-facing applications and tests for creating
179 frames easily without having to type out a bunch of constants.
179 frames easily without having to type out a bunch of constants.
180
180
181 Request ID and stream IDs are integers.
181 Request ID and stream IDs are integers.
182
182
183 Stream flags, frame type, and flags can be specified by integer or
183 Stream flags, frame type, and flags can be specified by integer or
184 named constant.
184 named constant.
185
185
186 Flags can be delimited by `|` to bitwise OR them together.
186 Flags can be delimited by `|` to bitwise OR them together.
187
187
188 If the payload begins with ``cbor:``, the following string will be
188 If the payload begins with ``cbor:``, the following string will be
189 evaluated as Python literal and the resulting object will be fed into
189 evaluated as Python literal and the resulting object will be fed into
190 a CBOR encoder. Otherwise, the payload is interpreted as a Python
190 a CBOR encoder. Otherwise, the payload is interpreted as a Python
191 byte string literal.
191 byte string literal.
192 """
192 """
193 fields = s.split(b' ', 5)
193 fields = s.split(b' ', 5)
194 requestid, streamid, streamflags, frametype, frameflags, payload = fields
194 requestid, streamid, streamflags, frametype, frameflags, payload = fields
195
195
196 requestid = int(requestid)
196 requestid = int(requestid)
197 streamid = int(streamid)
197 streamid = int(streamid)
198
198
199 finalstreamflags = 0
199 finalstreamflags = 0
200 for flag in streamflags.split(b'|'):
200 for flag in streamflags.split(b'|'):
201 if flag in STREAM_FLAGS:
201 if flag in STREAM_FLAGS:
202 finalstreamflags |= STREAM_FLAGS[flag]
202 finalstreamflags |= STREAM_FLAGS[flag]
203 else:
203 else:
204 finalstreamflags |= int(flag)
204 finalstreamflags |= int(flag)
205
205
206 if frametype in FRAME_TYPES:
206 if frametype in FRAME_TYPES:
207 frametype = FRAME_TYPES[frametype]
207 frametype = FRAME_TYPES[frametype]
208 else:
208 else:
209 frametype = int(frametype)
209 frametype = int(frametype)
210
210
211 finalflags = 0
211 finalflags = 0
212 validflags = FRAME_TYPE_FLAGS[frametype]
212 validflags = FRAME_TYPE_FLAGS[frametype]
213 for flag in frameflags.split(b'|'):
213 for flag in frameflags.split(b'|'):
214 if flag in validflags:
214 if flag in validflags:
215 finalflags |= validflags[flag]
215 finalflags |= validflags[flag]
216 else:
216 else:
217 finalflags |= int(flag)
217 finalflags |= int(flag)
218
218
219 if payload.startswith(b'cbor:'):
219 if payload.startswith(b'cbor:'):
220 payload = b''.join(cborutil.streamencode(
220 payload = b''.join(cborutil.streamencode(
221 stringutil.evalpythonliteral(payload[5:])))
221 stringutil.evalpythonliteral(payload[5:])))
222
222
223 else:
223 else:
224 payload = stringutil.unescapestr(payload)
224 payload = stringutil.unescapestr(payload)
225
225
226 return makeframe(requestid=requestid, streamid=streamid,
226 return makeframe(requestid=requestid, streamid=streamid,
227 streamflags=finalstreamflags, typeid=frametype,
227 streamflags=finalstreamflags, typeid=frametype,
228 flags=finalflags, payload=payload)
228 flags=finalflags, payload=payload)
229
229
230 def parseheader(data):
230 def parseheader(data):
231 """Parse a unified framing protocol frame header from a buffer.
231 """Parse a unified framing protocol frame header from a buffer.
232
232
233 The header is expected to be in the buffer at offset 0 and the
233 The header is expected to be in the buffer at offset 0 and the
234 buffer is expected to be large enough to hold a full header.
234 buffer is expected to be large enough to hold a full header.
235 """
235 """
236 # 24 bits payload length (little endian)
236 # 24 bits payload length (little endian)
237 # 16 bits request ID
237 # 16 bits request ID
238 # 8 bits stream ID
238 # 8 bits stream ID
239 # 8 bits stream flags
239 # 8 bits stream flags
240 # 4 bits frame type
240 # 4 bits frame type
241 # 4 bits frame flags
241 # 4 bits frame flags
242 # ... payload
242 # ... payload
243 framelength = data[0] + 256 * data[1] + 16384 * data[2]
243 framelength = data[0] + 256 * data[1] + 16384 * data[2]
244 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
244 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
245 typeflags = data[7]
245 typeflags = data[7]
246
246
247 frametype = (typeflags & 0xf0) >> 4
247 frametype = (typeflags & 0xf0) >> 4
248 frameflags = typeflags & 0x0f
248 frameflags = typeflags & 0x0f
249
249
250 return frameheader(framelength, requestid, streamid, streamflags,
250 return frameheader(framelength, requestid, streamid, streamflags,
251 frametype, frameflags)
251 frametype, frameflags)
252
252
253 def readframe(fh):
253 def readframe(fh):
254 """Read a unified framing protocol frame from a file object.
254 """Read a unified framing protocol frame from a file object.
255
255
256 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
256 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
257 None if no frame is available. May raise if a malformed frame is
257 None if no frame is available. May raise if a malformed frame is
258 seen.
258 seen.
259 """
259 """
260 header = bytearray(FRAME_HEADER_SIZE)
260 header = bytearray(FRAME_HEADER_SIZE)
261
261
262 readcount = fh.readinto(header)
262 readcount = fh.readinto(header)
263
263
264 if readcount == 0:
264 if readcount == 0:
265 return None
265 return None
266
266
267 if readcount != FRAME_HEADER_SIZE:
267 if readcount != FRAME_HEADER_SIZE:
268 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
268 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
269 (readcount, header))
269 (readcount, header))
270
270
271 h = parseheader(header)
271 h = parseheader(header)
272
272
273 payload = fh.read(h.length)
273 payload = fh.read(h.length)
274 if len(payload) != h.length:
274 if len(payload) != h.length:
275 raise error.Abort(_('frame length error: expected %d; got %d') %
275 raise error.Abort(_('frame length error: expected %d; got %d') %
276 (h.length, len(payload)))
276 (h.length, len(payload)))
277
277
278 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
278 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
279 payload)
279 payload)
280
280
281 def createcommandframes(stream, requestid, cmd, args, datafh=None,
281 def createcommandframes(stream, requestid, cmd, args, datafh=None,
282 maxframesize=DEFAULT_MAX_FRAME_SIZE):
282 maxframesize=DEFAULT_MAX_FRAME_SIZE):
283 """Create frames necessary to transmit a request to run a command.
283 """Create frames necessary to transmit a request to run a command.
284
284
285 This is a generator of bytearrays. Each item represents a frame
285 This is a generator of bytearrays. Each item represents a frame
286 ready to be sent over the wire to a peer.
286 ready to be sent over the wire to a peer.
287 """
287 """
288 data = {b'name': cmd}
288 data = {b'name': cmd}
289 if args:
289 if args:
290 data[b'args'] = args
290 data[b'args'] = args
291
291
292 data = b''.join(cborutil.streamencode(data))
292 data = b''.join(cborutil.streamencode(data))
293
293
294 offset = 0
294 offset = 0
295
295
296 while True:
296 while True:
297 flags = 0
297 flags = 0
298
298
299 # Must set new or continuation flag.
299 # Must set new or continuation flag.
300 if not offset:
300 if not offset:
301 flags |= FLAG_COMMAND_REQUEST_NEW
301 flags |= FLAG_COMMAND_REQUEST_NEW
302 else:
302 else:
303 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
303 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
304
304
305 # Data frames is set on all frames.
305 # Data frames is set on all frames.
306 if datafh:
306 if datafh:
307 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
307 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
308
308
309 payload = data[offset:offset + maxframesize]
309 payload = data[offset:offset + maxframesize]
310 offset += len(payload)
310 offset += len(payload)
311
311
312 if len(payload) == maxframesize and offset < len(data):
312 if len(payload) == maxframesize and offset < len(data):
313 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
313 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
314
314
315 yield stream.makeframe(requestid=requestid,
315 yield stream.makeframe(requestid=requestid,
316 typeid=FRAME_TYPE_COMMAND_REQUEST,
316 typeid=FRAME_TYPE_COMMAND_REQUEST,
317 flags=flags,
317 flags=flags,
318 payload=payload)
318 payload=payload)
319
319
320 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
320 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
321 break
321 break
322
322
323 if datafh:
323 if datafh:
324 while True:
324 while True:
325 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
325 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
326
326
327 done = False
327 done = False
328 if len(data) == DEFAULT_MAX_FRAME_SIZE:
328 if len(data) == DEFAULT_MAX_FRAME_SIZE:
329 flags = FLAG_COMMAND_DATA_CONTINUATION
329 flags = FLAG_COMMAND_DATA_CONTINUATION
330 else:
330 else:
331 flags = FLAG_COMMAND_DATA_EOS
331 flags = FLAG_COMMAND_DATA_EOS
332 assert datafh.read(1) == b''
332 assert datafh.read(1) == b''
333 done = True
333 done = True
334
334
335 yield stream.makeframe(requestid=requestid,
335 yield stream.makeframe(requestid=requestid,
336 typeid=FRAME_TYPE_COMMAND_DATA,
336 typeid=FRAME_TYPE_COMMAND_DATA,
337 flags=flags,
337 flags=flags,
338 payload=data)
338 payload=data)
339
339
340 if done:
340 if done:
341 break
341 break
342
342
343 def createcommandresponseframesfrombytes(stream, requestid, data,
343 def createcommandresponseframesfrombytes(stream, requestid, data,
344 maxframesize=DEFAULT_MAX_FRAME_SIZE):
344 maxframesize=DEFAULT_MAX_FRAME_SIZE):
345 """Create a raw frame to send a bytes response from static bytes input.
345 """Create a raw frame to send a bytes response from static bytes input.
346
346
347 Returns a generator of bytearrays.
347 Returns a generator of bytearrays.
348 """
348 """
349 # Automatically send the overall CBOR response map.
349 # Automatically send the overall CBOR response map.
350 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
350 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
351 if len(overall) > maxframesize:
351 if len(overall) > maxframesize:
352 raise error.ProgrammingError('not yet implemented')
352 raise error.ProgrammingError('not yet implemented')
353
353
354 # Simple case where we can fit the full response in a single frame.
354 # Simple case where we can fit the full response in a single frame.
355 if len(overall) + len(data) <= maxframesize:
355 if len(overall) + len(data) <= maxframesize:
356 flags = FLAG_COMMAND_RESPONSE_EOS
356 flags = FLAG_COMMAND_RESPONSE_EOS
357 yield stream.makeframe(requestid=requestid,
357 yield stream.makeframe(requestid=requestid,
358 typeid=FRAME_TYPE_COMMAND_RESPONSE,
358 typeid=FRAME_TYPE_COMMAND_RESPONSE,
359 flags=flags,
359 flags=flags,
360 payload=overall + data)
360 payload=overall + data)
361 return
361 return
362
362
363 # It's easier to send the overall CBOR map in its own frame than to track
363 # It's easier to send the overall CBOR map in its own frame than to track
364 # offsets.
364 # offsets.
365 yield stream.makeframe(requestid=requestid,
365 yield stream.makeframe(requestid=requestid,
366 typeid=FRAME_TYPE_COMMAND_RESPONSE,
366 typeid=FRAME_TYPE_COMMAND_RESPONSE,
367 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
367 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
368 payload=overall)
368 payload=overall)
369
369
370 offset = 0
370 offset = 0
371 while True:
371 while True:
372 chunk = data[offset:offset + maxframesize]
372 chunk = data[offset:offset + maxframesize]
373 offset += len(chunk)
373 offset += len(chunk)
374 done = offset == len(data)
374 done = offset == len(data)
375
375
376 if done:
376 if done:
377 flags = FLAG_COMMAND_RESPONSE_EOS
377 flags = FLAG_COMMAND_RESPONSE_EOS
378 else:
378 else:
379 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
379 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
380
380
381 yield stream.makeframe(requestid=requestid,
381 yield stream.makeframe(requestid=requestid,
382 typeid=FRAME_TYPE_COMMAND_RESPONSE,
382 typeid=FRAME_TYPE_COMMAND_RESPONSE,
383 flags=flags,
383 flags=flags,
384 payload=chunk)
384 payload=chunk)
385
385
386 if done:
386 if done:
387 break
387 break
388
388
389 def createbytesresponseframesfromgen(stream, requestid, gen,
389 def createbytesresponseframesfromgen(stream, requestid, gen,
390 maxframesize=DEFAULT_MAX_FRAME_SIZE):
390 maxframesize=DEFAULT_MAX_FRAME_SIZE):
391 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
391 """Generator of frames from a generator of byte chunks.
392
392
393 yield stream.makeframe(requestid=requestid,
393 This assumes that another frame will follow whatever this emits. i.e.
394 typeid=FRAME_TYPE_COMMAND_RESPONSE,
394 this always emits the continuation flag and never emits the end-of-stream
395 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
395 flag.
396 payload=overall)
396 """
397
398 cb = util.chunkbuffer(gen)
397 cb = util.chunkbuffer(gen)
399
398 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
400 flags = 0
401
399
402 while True:
400 while True:
403 chunk = cb.read(maxframesize)
401 chunk = cb.read(maxframesize)
404 if not chunk:
402 if not chunk:
405 break
403 break
406
404
407 yield stream.makeframe(requestid=requestid,
405 yield stream.makeframe(requestid=requestid,
408 typeid=FRAME_TYPE_COMMAND_RESPONSE,
406 typeid=FRAME_TYPE_COMMAND_RESPONSE,
409 flags=flags,
407 flags=flags,
410 payload=chunk)
408 payload=chunk)
411
409
412 flags |= FLAG_COMMAND_RESPONSE_CONTINUATION
410 flags |= FLAG_COMMAND_RESPONSE_CONTINUATION
413
411
414 flags ^= FLAG_COMMAND_RESPONSE_CONTINUATION
412 def createcommandresponseokframe(stream, requestid):
415 flags |= FLAG_COMMAND_RESPONSE_EOS
413 overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
416 yield stream.makeframe(requestid=requestid,
414
417 typeid=FRAME_TYPE_COMMAND_RESPONSE,
415 return stream.makeframe(requestid=requestid,
418 flags=flags,
416 typeid=FRAME_TYPE_COMMAND_RESPONSE,
419 payload=b'')
417 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
418 payload=overall)
419
420 def createcommandresponseeosframe(stream, requestid):
421 """Create an empty payload frame representing command end-of-stream."""
422 return stream.makeframe(requestid=requestid,
423 typeid=FRAME_TYPE_COMMAND_RESPONSE,
424 flags=FLAG_COMMAND_RESPONSE_EOS,
425 payload=b'')
420
426
421 def createcommanderrorresponse(stream, requestid, message, args=None):
427 def createcommanderrorresponse(stream, requestid, message, args=None):
422 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
428 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
423 # formatting works consistently?
429 # formatting works consistently?
424 m = {
430 m = {
425 b'status': b'error',
431 b'status': b'error',
426 b'error': {
432 b'error': {
427 b'message': message,
433 b'message': message,
428 }
434 }
429 }
435 }
430
436
431 if args:
437 if args:
432 m[b'error'][b'args'] = args
438 m[b'error'][b'args'] = args
433
439
434 overall = b''.join(cborutil.streamencode(m))
440 overall = b''.join(cborutil.streamencode(m))
435
441
436 yield stream.makeframe(requestid=requestid,
442 yield stream.makeframe(requestid=requestid,
437 typeid=FRAME_TYPE_COMMAND_RESPONSE,
443 typeid=FRAME_TYPE_COMMAND_RESPONSE,
438 flags=FLAG_COMMAND_RESPONSE_EOS,
444 flags=FLAG_COMMAND_RESPONSE_EOS,
439 payload=overall)
445 payload=overall)
440
446
441 def createerrorframe(stream, requestid, msg, errtype):
447 def createerrorframe(stream, requestid, msg, errtype):
442 # TODO properly handle frame size limits.
448 # TODO properly handle frame size limits.
443 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
449 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
444
450
445 payload = b''.join(cborutil.streamencode({
451 payload = b''.join(cborutil.streamencode({
446 b'type': errtype,
452 b'type': errtype,
447 b'message': [{b'msg': msg}],
453 b'message': [{b'msg': msg}],
448 }))
454 }))
449
455
450 yield stream.makeframe(requestid=requestid,
456 yield stream.makeframe(requestid=requestid,
451 typeid=FRAME_TYPE_ERROR_RESPONSE,
457 typeid=FRAME_TYPE_ERROR_RESPONSE,
452 flags=0,
458 flags=0,
453 payload=payload)
459 payload=payload)
454
460
455 def createtextoutputframe(stream, requestid, atoms,
461 def createtextoutputframe(stream, requestid, atoms,
456 maxframesize=DEFAULT_MAX_FRAME_SIZE):
462 maxframesize=DEFAULT_MAX_FRAME_SIZE):
457 """Create a text output frame to render text to people.
463 """Create a text output frame to render text to people.
458
464
459 ``atoms`` is a 3-tuple of (formatting string, args, labels).
465 ``atoms`` is a 3-tuple of (formatting string, args, labels).
460
466
461 The formatting string contains ``%s`` tokens to be replaced by the
467 The formatting string contains ``%s`` tokens to be replaced by the
462 corresponding indexed entry in ``args``. ``labels`` is an iterable of
468 corresponding indexed entry in ``args``. ``labels`` is an iterable of
463 formatters to be applied at rendering time. In terms of the ``ui``
469 formatters to be applied at rendering time. In terms of the ``ui``
464 class, each atom corresponds to a ``ui.write()``.
470 class, each atom corresponds to a ``ui.write()``.
465 """
471 """
466 atomdicts = []
472 atomdicts = []
467
473
468 for (formatting, args, labels) in atoms:
474 for (formatting, args, labels) in atoms:
469 # TODO look for localstr, other types here?
475 # TODO look for localstr, other types here?
470
476
471 if not isinstance(formatting, bytes):
477 if not isinstance(formatting, bytes):
472 raise ValueError('must use bytes formatting strings')
478 raise ValueError('must use bytes formatting strings')
473 for arg in args:
479 for arg in args:
474 if not isinstance(arg, bytes):
480 if not isinstance(arg, bytes):
475 raise ValueError('must use bytes for arguments')
481 raise ValueError('must use bytes for arguments')
476 for label in labels:
482 for label in labels:
477 if not isinstance(label, bytes):
483 if not isinstance(label, bytes):
478 raise ValueError('must use bytes for labels')
484 raise ValueError('must use bytes for labels')
479
485
480 # Formatting string must be ASCII.
486 # Formatting string must be ASCII.
481 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
487 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
482
488
483 # Arguments must be UTF-8.
489 # Arguments must be UTF-8.
484 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
490 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
485
491
486 # Labels must be ASCII.
492 # Labels must be ASCII.
487 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
493 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
488 for l in labels]
494 for l in labels]
489
495
490 atom = {b'msg': formatting}
496 atom = {b'msg': formatting}
491 if args:
497 if args:
492 atom[b'args'] = args
498 atom[b'args'] = args
493 if labels:
499 if labels:
494 atom[b'labels'] = labels
500 atom[b'labels'] = labels
495
501
496 atomdicts.append(atom)
502 atomdicts.append(atom)
497
503
498 payload = b''.join(cborutil.streamencode(atomdicts))
504 payload = b''.join(cborutil.streamencode(atomdicts))
499
505
500 if len(payload) > maxframesize:
506 if len(payload) > maxframesize:
501 raise ValueError('cannot encode data in a single frame')
507 raise ValueError('cannot encode data in a single frame')
502
508
503 yield stream.makeframe(requestid=requestid,
509 yield stream.makeframe(requestid=requestid,
504 typeid=FRAME_TYPE_TEXT_OUTPUT,
510 typeid=FRAME_TYPE_TEXT_OUTPUT,
505 flags=0,
511 flags=0,
506 payload=payload)
512 payload=payload)
507
513
508 class stream(object):
514 class stream(object):
509 """Represents a logical unidirectional series of frames."""
515 """Represents a logical unidirectional series of frames."""
510
516
511 def __init__(self, streamid, active=False):
517 def __init__(self, streamid, active=False):
512 self.streamid = streamid
518 self.streamid = streamid
513 self._active = active
519 self._active = active
514
520
515 def makeframe(self, requestid, typeid, flags, payload):
521 def makeframe(self, requestid, typeid, flags, payload):
516 """Create a frame to be sent out over this stream.
522 """Create a frame to be sent out over this stream.
517
523
518 Only returns the frame instance. Does not actually send it.
524 Only returns the frame instance. Does not actually send it.
519 """
525 """
520 streamflags = 0
526 streamflags = 0
521 if not self._active:
527 if not self._active:
522 streamflags |= STREAM_FLAG_BEGIN_STREAM
528 streamflags |= STREAM_FLAG_BEGIN_STREAM
523 self._active = True
529 self._active = True
524
530
525 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
531 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
526 payload)
532 payload)
527
533
528 def ensureserverstream(stream):
534 def ensureserverstream(stream):
529 if stream.streamid % 2:
535 if stream.streamid % 2:
530 raise error.ProgrammingError('server should only write to even '
536 raise error.ProgrammingError('server should only write to even '
531 'numbered streams; %d is not even' %
537 'numbered streams; %d is not even' %
532 stream.streamid)
538 stream.streamid)
533
539
534 class serverreactor(object):
540 class serverreactor(object):
535 """Holds state of a server handling frame-based protocol requests.
541 """Holds state of a server handling frame-based protocol requests.
536
542
537 This class is the "brain" of the unified frame-based protocol server
543 This class is the "brain" of the unified frame-based protocol server
538 component. While the protocol is stateless from the perspective of
544 component. While the protocol is stateless from the perspective of
539 requests/commands, something needs to track which frames have been
545 requests/commands, something needs to track which frames have been
540 received, what frames to expect, etc. This class is that thing.
546 received, what frames to expect, etc. This class is that thing.
541
547
542 Instances are modeled as a state machine of sorts. Instances are also
548 Instances are modeled as a state machine of sorts. Instances are also
543 reactionary to external events. The point of this class is to encapsulate
549 reactionary to external events. The point of this class is to encapsulate
544 the state of the connection and the exchange of frames, not to perform
550 the state of the connection and the exchange of frames, not to perform
545 work. Instead, callers tell this class when something occurs, like a
551 work. Instead, callers tell this class when something occurs, like a
546 frame arriving. If that activity is worthy of a follow-up action (say
552 frame arriving. If that activity is worthy of a follow-up action (say
547 *run a command*), the return value of that handler will say so.
553 *run a command*), the return value of that handler will say so.
548
554
549 I/O and CPU intensive operations are purposefully delegated outside of
555 I/O and CPU intensive operations are purposefully delegated outside of
550 this class.
556 this class.
551
557
552 Consumers are expected to tell instances when events occur. They do so by
558 Consumers are expected to tell instances when events occur. They do so by
553 calling the various ``on*`` methods. These methods return a 2-tuple
559 calling the various ``on*`` methods. These methods return a 2-tuple
554 describing any follow-up action(s) to take. The first element is the
560 describing any follow-up action(s) to take. The first element is the
555 name of an action to perform. The second is a data structure (usually
561 name of an action to perform. The second is a data structure (usually
556 a dict) specific to that action that contains more information. e.g.
562 a dict) specific to that action that contains more information. e.g.
557 if the server wants to send frames back to the client, the data structure
563 if the server wants to send frames back to the client, the data structure
558 will contain a reference to those frames.
564 will contain a reference to those frames.
559
565
560 Valid actions that consumers can be instructed to take are:
566 Valid actions that consumers can be instructed to take are:
561
567
562 sendframes
568 sendframes
563 Indicates that frames should be sent to the client. The ``framegen``
569 Indicates that frames should be sent to the client. The ``framegen``
564 key contains a generator of frames that should be sent. The server
570 key contains a generator of frames that should be sent. The server
565 assumes that all frames are sent to the client.
571 assumes that all frames are sent to the client.
566
572
567 error
573 error
568 Indicates that an error occurred. Consumer should probably abort.
574 Indicates that an error occurred. Consumer should probably abort.
569
575
570 runcommand
576 runcommand
571 Indicates that the consumer should run a wire protocol command. Details
577 Indicates that the consumer should run a wire protocol command. Details
572 of the command to run are given in the data structure.
578 of the command to run are given in the data structure.
573
579
574 wantframe
580 wantframe
575 Indicates that nothing of interest happened and the server is waiting on
581 Indicates that nothing of interest happened and the server is waiting on
576 more frames from the client before anything interesting can be done.
582 more frames from the client before anything interesting can be done.
577
583
578 noop
584 noop
579 Indicates no additional action is required.
585 Indicates no additional action is required.
580
586
581 Known Issues
587 Known Issues
582 ------------
588 ------------
583
589
584 There are no limits to the number of partially received commands or their
590 There are no limits to the number of partially received commands or their
585 size. A malicious client could stream command request data and exhaust the
591 size. A malicious client could stream command request data and exhaust the
586 server's memory.
592 server's memory.
587
593
588 Partially received commands are not acted upon when end of input is
594 Partially received commands are not acted upon when end of input is
589 reached. Should the server error if it receives a partial request?
595 reached. Should the server error if it receives a partial request?
590 Should the client send a message to abort a partially transmitted request
596 Should the client send a message to abort a partially transmitted request
591 to facilitate graceful shutdown?
597 to facilitate graceful shutdown?
592
598
593 Active requests that haven't been responded to aren't tracked. This means
599 Active requests that haven't been responded to aren't tracked. This means
594 that if we receive a command and instruct its dispatch, another command
600 that if we receive a command and instruct its dispatch, another command
595 with its request ID can come in over the wire and there will be a race
601 with its request ID can come in over the wire and there will be a race
596 between who responds to what.
602 between who responds to what.
597 """
603 """
598
604
599 def __init__(self, deferoutput=False):
605 def __init__(self, deferoutput=False):
600 """Construct a new server reactor.
606 """Construct a new server reactor.
601
607
602 ``deferoutput`` can be used to indicate that no output frames should be
608 ``deferoutput`` can be used to indicate that no output frames should be
603 instructed to be sent until input has been exhausted. In this mode,
609 instructed to be sent until input has been exhausted. In this mode,
604 events that would normally generate output frames (such as a command
610 events that would normally generate output frames (such as a command
605 response being ready) will instead defer instructing the consumer to
611 response being ready) will instead defer instructing the consumer to
606 send those frames. This is useful for half-duplex transports where the
612 send those frames. This is useful for half-duplex transports where the
607 sender cannot receive until all data has been transmitted.
613 sender cannot receive until all data has been transmitted.
608 """
614 """
609 self._deferoutput = deferoutput
615 self._deferoutput = deferoutput
610 self._state = 'idle'
616 self._state = 'idle'
611 self._nextoutgoingstreamid = 2
617 self._nextoutgoingstreamid = 2
612 self._bufferedframegens = []
618 self._bufferedframegens = []
613 # stream id -> stream instance for all active streams from the client.
619 # stream id -> stream instance for all active streams from the client.
614 self._incomingstreams = {}
620 self._incomingstreams = {}
615 self._outgoingstreams = {}
621 self._outgoingstreams = {}
616 # request id -> dict of commands that are actively being received.
622 # request id -> dict of commands that are actively being received.
617 self._receivingcommands = {}
623 self._receivingcommands = {}
618 # Request IDs that have been received and are actively being processed.
624 # Request IDs that have been received and are actively being processed.
619 # Once all output for a request has been sent, it is removed from this
625 # Once all output for a request has been sent, it is removed from this
620 # set.
626 # set.
621 self._activecommands = set()
627 self._activecommands = set()
622
628
623 def onframerecv(self, frame):
629 def onframerecv(self, frame):
624 """Process a frame that has been received off the wire.
630 """Process a frame that has been received off the wire.
625
631
626 Returns a dict with an ``action`` key that details what action,
632 Returns a dict with an ``action`` key that details what action,
627 if any, the consumer should take next.
633 if any, the consumer should take next.
628 """
634 """
629 if not frame.streamid % 2:
635 if not frame.streamid % 2:
630 self._state = 'errored'
636 self._state = 'errored'
631 return self._makeerrorresult(
637 return self._makeerrorresult(
632 _('received frame with even numbered stream ID: %d') %
638 _('received frame with even numbered stream ID: %d') %
633 frame.streamid)
639 frame.streamid)
634
640
635 if frame.streamid not in self._incomingstreams:
641 if frame.streamid not in self._incomingstreams:
636 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
642 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
637 self._state = 'errored'
643 self._state = 'errored'
638 return self._makeerrorresult(
644 return self._makeerrorresult(
639 _('received frame on unknown inactive stream without '
645 _('received frame on unknown inactive stream without '
640 'beginning of stream flag set'))
646 'beginning of stream flag set'))
641
647
642 self._incomingstreams[frame.streamid] = stream(frame.streamid)
648 self._incomingstreams[frame.streamid] = stream(frame.streamid)
643
649
644 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
650 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
645 # TODO handle decoding frames
651 # TODO handle decoding frames
646 self._state = 'errored'
652 self._state = 'errored'
647 raise error.ProgrammingError('support for decoding stream payloads '
653 raise error.ProgrammingError('support for decoding stream payloads '
648 'not yet implemented')
654 'not yet implemented')
649
655
650 if frame.streamflags & STREAM_FLAG_END_STREAM:
656 if frame.streamflags & STREAM_FLAG_END_STREAM:
651 del self._incomingstreams[frame.streamid]
657 del self._incomingstreams[frame.streamid]
652
658
653 handlers = {
659 handlers = {
654 'idle': self._onframeidle,
660 'idle': self._onframeidle,
655 'command-receiving': self._onframecommandreceiving,
661 'command-receiving': self._onframecommandreceiving,
656 'errored': self._onframeerrored,
662 'errored': self._onframeerrored,
657 }
663 }
658
664
659 meth = handlers.get(self._state)
665 meth = handlers.get(self._state)
660 if not meth:
666 if not meth:
661 raise error.ProgrammingError('unhandled state: %s' % self._state)
667 raise error.ProgrammingError('unhandled state: %s' % self._state)
662
668
663 return meth(frame)
669 return meth(frame)
664
670
665 def oncommandresponseready(self, stream, requestid, data):
671 def oncommandresponseready(self, stream, requestid, data):
666 """Signal that a bytes response is ready to be sent to the client.
672 """Signal that a bytes response is ready to be sent to the client.
667
673
668 The raw bytes response is passed as an argument.
674 The raw bytes response is passed as an argument.
669 """
675 """
670 ensureserverstream(stream)
676 ensureserverstream(stream)
671
677
672 def sendframes():
678 def sendframes():
673 for frame in createcommandresponseframesfrombytes(stream, requestid,
679 for frame in createcommandresponseframesfrombytes(stream, requestid,
674 data):
680 data):
675 yield frame
681 yield frame
676
682
677 self._activecommands.remove(requestid)
683 self._activecommands.remove(requestid)
678
684
679 result = sendframes()
685 result = sendframes()
680
686
681 if self._deferoutput:
687 if self._deferoutput:
682 self._bufferedframegens.append(result)
688 self._bufferedframegens.append(result)
683 return 'noop', {}
689 return 'noop', {}
684 else:
690 else:
685 return 'sendframes', {
691 return 'sendframes', {
686 'framegen': result,
692 'framegen': result,
687 }
693 }
688
694
689 def oncommandresponsereadygen(self, stream, requestid, gen):
695 def oncommandresponsereadyobjects(self, stream, requestid, objs):
690 """Signal that a bytes response is ready, with data as a generator."""
696 """Signal that objects are ready to be sent to the client.
697
698 ``objs`` is an iterable of objects (typically a generator) that will
699 be encoded via CBOR and added to frames, which will be sent to the
700 client.
701 """
691 ensureserverstream(stream)
702 ensureserverstream(stream)
692
703
704 # We need to take care over exception handling. Uncaught exceptions
705 # when generating frames could lead to premature end of the frame
706 # stream and the possibility of the server or client process getting
707 # in a bad state.
708 #
709 # Keep in mind that if ``objs`` is a generator, advancing it could
710 # raise exceptions that originated in e.g. wire protocol command
711 # functions. That is why we differentiate between exceptions raised
712 # when iterating versus other exceptions that occur.
713 #
714 # In all cases, when the function finishes, the request is fully
715 # handled and no new frames for it should be seen.
716
693 def sendframes():
717 def sendframes():
694 for frame in createbytesresponseframesfromgen(stream, requestid,
718 emitted = False
695 gen):
719 while True:
696 yield frame
720 try:
721 o = next(objs)
722 except StopIteration:
723 if emitted:
724 yield createcommandresponseeosframe(stream, requestid)
725 break
726
727 except error.WireprotoCommandError as e:
728 for frame in createcommanderrorresponse(
729 stream, requestid, e.message, e.messageargs):
730 yield frame
731 break
732
733 except Exception as e:
734 for frame in createerrorframe(stream, requestid,
735 '%s' % e,
736 errtype='server'):
737 yield frame
738
739 break
740
741 try:
742 if not emitted:
743 yield createcommandresponseokframe(stream, requestid)
744 emitted = True
745
746 # TODO buffer chunks so emitted frame payloads can be
747 # larger.
748 for frame in createbytesresponseframesfromgen(
749 stream, requestid, cborutil.streamencode(o)):
750 yield frame
751 except Exception as e:
752 for frame in createerrorframe(stream, requestid,
753 '%s' % e,
754 errtype='server'):
755 yield frame
756
757 break
697
758
698 self._activecommands.remove(requestid)
759 self._activecommands.remove(requestid)
699
760
700 return self._handlesendframes(sendframes())
761 return self._handlesendframes(sendframes())
701
762
702 def oninputeof(self):
763 def oninputeof(self):
703 """Signals that end of input has been received.
764 """Signals that end of input has been received.
704
765
705 No more frames will be received. All pending activity should be
766 No more frames will be received. All pending activity should be
706 completed.
767 completed.
707 """
768 """
708 # TODO should we do anything about in-flight commands?
769 # TODO should we do anything about in-flight commands?
709
770
710 if not self._deferoutput or not self._bufferedframegens:
771 if not self._deferoutput or not self._bufferedframegens:
711 return 'noop', {}
772 return 'noop', {}
712
773
713 # If we buffered all our responses, emit those.
774 # If we buffered all our responses, emit those.
714 def makegen():
775 def makegen():
715 for gen in self._bufferedframegens:
776 for gen in self._bufferedframegens:
716 for frame in gen:
777 for frame in gen:
717 yield frame
778 yield frame
718
779
719 return 'sendframes', {
780 return 'sendframes', {
720 'framegen': makegen(),
781 'framegen': makegen(),
721 }
782 }
722
783
723 def _handlesendframes(self, framegen):
784 def _handlesendframes(self, framegen):
724 if self._deferoutput:
785 if self._deferoutput:
725 self._bufferedframegens.append(framegen)
786 self._bufferedframegens.append(framegen)
726 return 'noop', {}
787 return 'noop', {}
727 else:
788 else:
728 return 'sendframes', {
789 return 'sendframes', {
729 'framegen': framegen,
790 'framegen': framegen,
730 }
791 }
731
792
732 def onservererror(self, stream, requestid, msg):
793 def onservererror(self, stream, requestid, msg):
733 ensureserverstream(stream)
794 ensureserverstream(stream)
734
795
735 def sendframes():
796 def sendframes():
736 for frame in createerrorframe(stream, requestid, msg,
797 for frame in createerrorframe(stream, requestid, msg,
737 errtype='server'):
798 errtype='server'):
738 yield frame
799 yield frame
739
800
740 self._activecommands.remove(requestid)
801 self._activecommands.remove(requestid)
741
802
742 return self._handlesendframes(sendframes())
803 return self._handlesendframes(sendframes())
743
804
744 def oncommanderror(self, stream, requestid, message, args=None):
805 def oncommanderror(self, stream, requestid, message, args=None):
745 """Called when a command encountered an error before sending output."""
806 """Called when a command encountered an error before sending output."""
746 ensureserverstream(stream)
807 ensureserverstream(stream)
747
808
748 def sendframes():
809 def sendframes():
749 for frame in createcommanderrorresponse(stream, requestid, message,
810 for frame in createcommanderrorresponse(stream, requestid, message,
750 args):
811 args):
751 yield frame
812 yield frame
752
813
753 self._activecommands.remove(requestid)
814 self._activecommands.remove(requestid)
754
815
755 return self._handlesendframes(sendframes())
816 return self._handlesendframes(sendframes())
756
817
757 def makeoutputstream(self):
818 def makeoutputstream(self):
758 """Create a stream to be used for sending data to the client."""
819 """Create a stream to be used for sending data to the client."""
759 streamid = self._nextoutgoingstreamid
820 streamid = self._nextoutgoingstreamid
760 self._nextoutgoingstreamid += 2
821 self._nextoutgoingstreamid += 2
761
822
762 s = stream(streamid)
823 s = stream(streamid)
763 self._outgoingstreams[streamid] = s
824 self._outgoingstreams[streamid] = s
764
825
765 return s
826 return s
766
827
767 def _makeerrorresult(self, msg):
828 def _makeerrorresult(self, msg):
768 return 'error', {
829 return 'error', {
769 'message': msg,
830 'message': msg,
770 }
831 }
771
832
772 def _makeruncommandresult(self, requestid):
833 def _makeruncommandresult(self, requestid):
773 entry = self._receivingcommands[requestid]
834 entry = self._receivingcommands[requestid]
774
835
775 if not entry['requestdone']:
836 if not entry['requestdone']:
776 self._state = 'errored'
837 self._state = 'errored'
777 raise error.ProgrammingError('should not be called without '
838 raise error.ProgrammingError('should not be called without '
778 'requestdone set')
839 'requestdone set')
779
840
780 del self._receivingcommands[requestid]
841 del self._receivingcommands[requestid]
781
842
782 if self._receivingcommands:
843 if self._receivingcommands:
783 self._state = 'command-receiving'
844 self._state = 'command-receiving'
784 else:
845 else:
785 self._state = 'idle'
846 self._state = 'idle'
786
847
787 # Decode the payloads as CBOR.
848 # Decode the payloads as CBOR.
788 entry['payload'].seek(0)
849 entry['payload'].seek(0)
789 request = cborutil.decodeall(entry['payload'].getvalue())[0]
850 request = cborutil.decodeall(entry['payload'].getvalue())[0]
790
851
791 if b'name' not in request:
852 if b'name' not in request:
792 self._state = 'errored'
853 self._state = 'errored'
793 return self._makeerrorresult(
854 return self._makeerrorresult(
794 _('command request missing "name" field'))
855 _('command request missing "name" field'))
795
856
796 if b'args' not in request:
857 if b'args' not in request:
797 request[b'args'] = {}
858 request[b'args'] = {}
798
859
799 assert requestid not in self._activecommands
860 assert requestid not in self._activecommands
800 self._activecommands.add(requestid)
861 self._activecommands.add(requestid)
801
862
802 return 'runcommand', {
863 return 'runcommand', {
803 'requestid': requestid,
864 'requestid': requestid,
804 'command': request[b'name'],
865 'command': request[b'name'],
805 'args': request[b'args'],
866 'args': request[b'args'],
806 'data': entry['data'].getvalue() if entry['data'] else None,
867 'data': entry['data'].getvalue() if entry['data'] else None,
807 }
868 }
808
869
809 def _makewantframeresult(self):
870 def _makewantframeresult(self):
810 return 'wantframe', {
871 return 'wantframe', {
811 'state': self._state,
872 'state': self._state,
812 }
873 }
813
874
814 def _validatecommandrequestframe(self, frame):
875 def _validatecommandrequestframe(self, frame):
815 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
876 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
816 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
877 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
817
878
818 if new and continuation:
879 if new and continuation:
819 self._state = 'errored'
880 self._state = 'errored'
820 return self._makeerrorresult(
881 return self._makeerrorresult(
821 _('received command request frame with both new and '
882 _('received command request frame with both new and '
822 'continuation flags set'))
883 'continuation flags set'))
823
884
824 if not new and not continuation:
885 if not new and not continuation:
825 self._state = 'errored'
886 self._state = 'errored'
826 return self._makeerrorresult(
887 return self._makeerrorresult(
827 _('received command request frame with neither new nor '
888 _('received command request frame with neither new nor '
828 'continuation flags set'))
889 'continuation flags set'))
829
890
830 def _onframeidle(self, frame):
891 def _onframeidle(self, frame):
831 # The only frame type that should be received in this state is a
892 # The only frame type that should be received in this state is a
832 # command request.
893 # command request.
833 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
894 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
834 self._state = 'errored'
895 self._state = 'errored'
835 return self._makeerrorresult(
896 return self._makeerrorresult(
836 _('expected command request frame; got %d') % frame.typeid)
897 _('expected command request frame; got %d') % frame.typeid)
837
898
838 res = self._validatecommandrequestframe(frame)
899 res = self._validatecommandrequestframe(frame)
839 if res:
900 if res:
840 return res
901 return res
841
902
842 if frame.requestid in self._receivingcommands:
903 if frame.requestid in self._receivingcommands:
843 self._state = 'errored'
904 self._state = 'errored'
844 return self._makeerrorresult(
905 return self._makeerrorresult(
845 _('request with ID %d already received') % frame.requestid)
906 _('request with ID %d already received') % frame.requestid)
846
907
847 if frame.requestid in self._activecommands:
908 if frame.requestid in self._activecommands:
848 self._state = 'errored'
909 self._state = 'errored'
849 return self._makeerrorresult(
910 return self._makeerrorresult(
850 _('request with ID %d is already active') % frame.requestid)
911 _('request with ID %d is already active') % frame.requestid)
851
912
852 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
913 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
853 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
914 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
854 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
915 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
855
916
856 if not new:
917 if not new:
857 self._state = 'errored'
918 self._state = 'errored'
858 return self._makeerrorresult(
919 return self._makeerrorresult(
859 _('received command request frame without new flag set'))
920 _('received command request frame without new flag set'))
860
921
861 payload = util.bytesio()
922 payload = util.bytesio()
862 payload.write(frame.payload)
923 payload.write(frame.payload)
863
924
864 self._receivingcommands[frame.requestid] = {
925 self._receivingcommands[frame.requestid] = {
865 'payload': payload,
926 'payload': payload,
866 'data': None,
927 'data': None,
867 'requestdone': not moreframes,
928 'requestdone': not moreframes,
868 'expectingdata': bool(expectingdata),
929 'expectingdata': bool(expectingdata),
869 }
930 }
870
931
871 # This is the final frame for this request. Dispatch it.
932 # This is the final frame for this request. Dispatch it.
872 if not moreframes and not expectingdata:
933 if not moreframes and not expectingdata:
873 return self._makeruncommandresult(frame.requestid)
934 return self._makeruncommandresult(frame.requestid)
874
935
875 assert moreframes or expectingdata
936 assert moreframes or expectingdata
876 self._state = 'command-receiving'
937 self._state = 'command-receiving'
877 return self._makewantframeresult()
938 return self._makewantframeresult()
878
939
879 def _onframecommandreceiving(self, frame):
940 def _onframecommandreceiving(self, frame):
880 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
941 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
881 # Process new command requests as such.
942 # Process new command requests as such.
882 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
943 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
883 return self._onframeidle(frame)
944 return self._onframeidle(frame)
884
945
885 res = self._validatecommandrequestframe(frame)
946 res = self._validatecommandrequestframe(frame)
886 if res:
947 if res:
887 return res
948 return res
888
949
889 # All other frames should be related to a command that is currently
950 # All other frames should be related to a command that is currently
890 # receiving but is not active.
951 # receiving but is not active.
891 if frame.requestid in self._activecommands:
952 if frame.requestid in self._activecommands:
892 self._state = 'errored'
953 self._state = 'errored'
893 return self._makeerrorresult(
954 return self._makeerrorresult(
894 _('received frame for request that is still active: %d') %
955 _('received frame for request that is still active: %d') %
895 frame.requestid)
956 frame.requestid)
896
957
897 if frame.requestid not in self._receivingcommands:
958 if frame.requestid not in self._receivingcommands:
898 self._state = 'errored'
959 self._state = 'errored'
899 return self._makeerrorresult(
960 return self._makeerrorresult(
900 _('received frame for request that is not receiving: %d') %
961 _('received frame for request that is not receiving: %d') %
901 frame.requestid)
962 frame.requestid)
902
963
903 entry = self._receivingcommands[frame.requestid]
964 entry = self._receivingcommands[frame.requestid]
904
965
905 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
966 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
906 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
967 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
907 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
968 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
908
969
909 if entry['requestdone']:
970 if entry['requestdone']:
910 self._state = 'errored'
971 self._state = 'errored'
911 return self._makeerrorresult(
972 return self._makeerrorresult(
912 _('received command request frame when request frames '
973 _('received command request frame when request frames '
913 'were supposedly done'))
974 'were supposedly done'))
914
975
915 if expectingdata != entry['expectingdata']:
976 if expectingdata != entry['expectingdata']:
916 self._state = 'errored'
977 self._state = 'errored'
917 return self._makeerrorresult(
978 return self._makeerrorresult(
918 _('mismatch between expect data flag and previous frame'))
979 _('mismatch between expect data flag and previous frame'))
919
980
920 entry['payload'].write(frame.payload)
981 entry['payload'].write(frame.payload)
921
982
922 if not moreframes:
983 if not moreframes:
923 entry['requestdone'] = True
984 entry['requestdone'] = True
924
985
925 if not moreframes and not expectingdata:
986 if not moreframes and not expectingdata:
926 return self._makeruncommandresult(frame.requestid)
987 return self._makeruncommandresult(frame.requestid)
927
988
928 return self._makewantframeresult()
989 return self._makewantframeresult()
929
990
930 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
991 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
931 if not entry['expectingdata']:
992 if not entry['expectingdata']:
932 self._state = 'errored'
993 self._state = 'errored'
933 return self._makeerrorresult(_(
994 return self._makeerrorresult(_(
934 'received command data frame for request that is not '
995 'received command data frame for request that is not '
935 'expecting data: %d') % frame.requestid)
996 'expecting data: %d') % frame.requestid)
936
997
937 if entry['data'] is None:
998 if entry['data'] is None:
938 entry['data'] = util.bytesio()
999 entry['data'] = util.bytesio()
939
1000
940 return self._handlecommanddataframe(frame, entry)
1001 return self._handlecommanddataframe(frame, entry)
941 else:
1002 else:
942 self._state = 'errored'
1003 self._state = 'errored'
943 return self._makeerrorresult(_(
1004 return self._makeerrorresult(_(
944 'received unexpected frame type: %d') % frame.typeid)
1005 'received unexpected frame type: %d') % frame.typeid)
945
1006
946 def _handlecommanddataframe(self, frame, entry):
1007 def _handlecommanddataframe(self, frame, entry):
947 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
1008 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
948
1009
949 # TODO support streaming data instead of buffering it.
1010 # TODO support streaming data instead of buffering it.
950 entry['data'].write(frame.payload)
1011 entry['data'].write(frame.payload)
951
1012
952 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
1013 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
953 return self._makewantframeresult()
1014 return self._makewantframeresult()
954 elif frame.flags & FLAG_COMMAND_DATA_EOS:
1015 elif frame.flags & FLAG_COMMAND_DATA_EOS:
955 entry['data'].seek(0)
1016 entry['data'].seek(0)
956 return self._makeruncommandresult(frame.requestid)
1017 return self._makeruncommandresult(frame.requestid)
957 else:
1018 else:
958 self._state = 'errored'
1019 self._state = 'errored'
959 return self._makeerrorresult(_('command data frame without '
1020 return self._makeerrorresult(_('command data frame without '
960 'flags'))
1021 'flags'))
961
1022
962 def _onframeerrored(self, frame):
1023 def _onframeerrored(self, frame):
963 return self._makeerrorresult(_('server already errored'))
1024 return self._makeerrorresult(_('server already errored'))
964
1025
965 class commandrequest(object):
1026 class commandrequest(object):
966 """Represents a request to run a command."""
1027 """Represents a request to run a command."""
967
1028
968 def __init__(self, requestid, name, args, datafh=None):
1029 def __init__(self, requestid, name, args, datafh=None):
969 self.requestid = requestid
1030 self.requestid = requestid
970 self.name = name
1031 self.name = name
971 self.args = args
1032 self.args = args
972 self.datafh = datafh
1033 self.datafh = datafh
973 self.state = 'pending'
1034 self.state = 'pending'
974
1035
975 class clientreactor(object):
1036 class clientreactor(object):
976 """Holds state of a client issuing frame-based protocol requests.
1037 """Holds state of a client issuing frame-based protocol requests.
977
1038
978 This is like ``serverreactor`` but for client-side state.
1039 This is like ``serverreactor`` but for client-side state.
979
1040
980 Each instance is bound to the lifetime of a connection. For persistent
1041 Each instance is bound to the lifetime of a connection. For persistent
981 connection transports using e.g. TCP sockets and speaking the raw
1042 connection transports using e.g. TCP sockets and speaking the raw
982 framing protocol, there will be a single instance for the lifetime of
1043 framing protocol, there will be a single instance for the lifetime of
983 the TCP socket. For transports where there are multiple discrete
1044 the TCP socket. For transports where there are multiple discrete
984 interactions (say tunneled within in HTTP request), there will be a
1045 interactions (say tunneled within in HTTP request), there will be a
985 separate instance for each distinct interaction.
1046 separate instance for each distinct interaction.
986 """
1047 """
987 def __init__(self, hasmultiplesend=False, buffersends=True):
1048 def __init__(self, hasmultiplesend=False, buffersends=True):
988 """Create a new instance.
1049 """Create a new instance.
989
1050
990 ``hasmultiplesend`` indicates whether multiple sends are supported
1051 ``hasmultiplesend`` indicates whether multiple sends are supported
991 by the transport. When True, it is possible to send commands immediately
1052 by the transport. When True, it is possible to send commands immediately
992 instead of buffering until the caller signals an intent to finish a
1053 instead of buffering until the caller signals an intent to finish a
993 send operation.
1054 send operation.
994
1055
995 ``buffercommands`` indicates whether sends should be buffered until the
1056 ``buffercommands`` indicates whether sends should be buffered until the
996 last request has been issued.
1057 last request has been issued.
997 """
1058 """
998 self._hasmultiplesend = hasmultiplesend
1059 self._hasmultiplesend = hasmultiplesend
999 self._buffersends = buffersends
1060 self._buffersends = buffersends
1000
1061
1001 self._canissuecommands = True
1062 self._canissuecommands = True
1002 self._cansend = True
1063 self._cansend = True
1003
1064
1004 self._nextrequestid = 1
1065 self._nextrequestid = 1
1005 # We only support a single outgoing stream for now.
1066 # We only support a single outgoing stream for now.
1006 self._outgoingstream = stream(1)
1067 self._outgoingstream = stream(1)
1007 self._pendingrequests = collections.deque()
1068 self._pendingrequests = collections.deque()
1008 self._activerequests = {}
1069 self._activerequests = {}
1009 self._incomingstreams = {}
1070 self._incomingstreams = {}
1010
1071
1011 def callcommand(self, name, args, datafh=None):
1072 def callcommand(self, name, args, datafh=None):
1012 """Request that a command be executed.
1073 """Request that a command be executed.
1013
1074
1014 Receives the command name, a dict of arguments to pass to the command,
1075 Receives the command name, a dict of arguments to pass to the command,
1015 and an optional file object containing the raw data for the command.
1076 and an optional file object containing the raw data for the command.
1016
1077
1017 Returns a 3-tuple of (request, action, action data).
1078 Returns a 3-tuple of (request, action, action data).
1018 """
1079 """
1019 if not self._canissuecommands:
1080 if not self._canissuecommands:
1020 raise error.ProgrammingError('cannot issue new commands')
1081 raise error.ProgrammingError('cannot issue new commands')
1021
1082
1022 requestid = self._nextrequestid
1083 requestid = self._nextrequestid
1023 self._nextrequestid += 2
1084 self._nextrequestid += 2
1024
1085
1025 request = commandrequest(requestid, name, args, datafh=datafh)
1086 request = commandrequest(requestid, name, args, datafh=datafh)
1026
1087
1027 if self._buffersends:
1088 if self._buffersends:
1028 self._pendingrequests.append(request)
1089 self._pendingrequests.append(request)
1029 return request, 'noop', {}
1090 return request, 'noop', {}
1030 else:
1091 else:
1031 if not self._cansend:
1092 if not self._cansend:
1032 raise error.ProgrammingError('sends cannot be performed on '
1093 raise error.ProgrammingError('sends cannot be performed on '
1033 'this instance')
1094 'this instance')
1034
1095
1035 if not self._hasmultiplesend:
1096 if not self._hasmultiplesend:
1036 self._cansend = False
1097 self._cansend = False
1037 self._canissuecommands = False
1098 self._canissuecommands = False
1038
1099
1039 return request, 'sendframes', {
1100 return request, 'sendframes', {
1040 'framegen': self._makecommandframes(request),
1101 'framegen': self._makecommandframes(request),
1041 }
1102 }
1042
1103
1043 def flushcommands(self):
1104 def flushcommands(self):
1044 """Request that all queued commands be sent.
1105 """Request that all queued commands be sent.
1045
1106
1046 If any commands are buffered, this will instruct the caller to send
1107 If any commands are buffered, this will instruct the caller to send
1047 them over the wire. If no commands are buffered it instructs the client
1108 them over the wire. If no commands are buffered it instructs the client
1048 to no-op.
1109 to no-op.
1049
1110
1050 If instances aren't configured for multiple sends, no new command
1111 If instances aren't configured for multiple sends, no new command
1051 requests are allowed after this is called.
1112 requests are allowed after this is called.
1052 """
1113 """
1053 if not self._pendingrequests:
1114 if not self._pendingrequests:
1054 return 'noop', {}
1115 return 'noop', {}
1055
1116
1056 if not self._cansend:
1117 if not self._cansend:
1057 raise error.ProgrammingError('sends cannot be performed on this '
1118 raise error.ProgrammingError('sends cannot be performed on this '
1058 'instance')
1119 'instance')
1059
1120
1060 # If the instance only allows sending once, mark that we have fired
1121 # If the instance only allows sending once, mark that we have fired
1061 # our one shot.
1122 # our one shot.
1062 if not self._hasmultiplesend:
1123 if not self._hasmultiplesend:
1063 self._canissuecommands = False
1124 self._canissuecommands = False
1064 self._cansend = False
1125 self._cansend = False
1065
1126
1066 def makeframes():
1127 def makeframes():
1067 while self._pendingrequests:
1128 while self._pendingrequests:
1068 request = self._pendingrequests.popleft()
1129 request = self._pendingrequests.popleft()
1069 for frame in self._makecommandframes(request):
1130 for frame in self._makecommandframes(request):
1070 yield frame
1131 yield frame
1071
1132
1072 return 'sendframes', {
1133 return 'sendframes', {
1073 'framegen': makeframes(),
1134 'framegen': makeframes(),
1074 }
1135 }
1075
1136
1076 def _makecommandframes(self, request):
1137 def _makecommandframes(self, request):
1077 """Emit frames to issue a command request.
1138 """Emit frames to issue a command request.
1078
1139
1079 As a side-effect, update request accounting to reflect its changed
1140 As a side-effect, update request accounting to reflect its changed
1080 state.
1141 state.
1081 """
1142 """
1082 self._activerequests[request.requestid] = request
1143 self._activerequests[request.requestid] = request
1083 request.state = 'sending'
1144 request.state = 'sending'
1084
1145
1085 res = createcommandframes(self._outgoingstream,
1146 res = createcommandframes(self._outgoingstream,
1086 request.requestid,
1147 request.requestid,
1087 request.name,
1148 request.name,
1088 request.args,
1149 request.args,
1089 request.datafh)
1150 request.datafh)
1090
1151
1091 for frame in res:
1152 for frame in res:
1092 yield frame
1153 yield frame
1093
1154
1094 request.state = 'sent'
1155 request.state = 'sent'
1095
1156
1096 def onframerecv(self, frame):
1157 def onframerecv(self, frame):
1097 """Process a frame that has been received off the wire.
1158 """Process a frame that has been received off the wire.
1098
1159
1099 Returns a 2-tuple of (action, meta) describing further action the
1160 Returns a 2-tuple of (action, meta) describing further action the
1100 caller needs to take as a result of receiving this frame.
1161 caller needs to take as a result of receiving this frame.
1101 """
1162 """
1102 if frame.streamid % 2:
1163 if frame.streamid % 2:
1103 return 'error', {
1164 return 'error', {
1104 'message': (
1165 'message': (
1105 _('received frame with odd numbered stream ID: %d') %
1166 _('received frame with odd numbered stream ID: %d') %
1106 frame.streamid),
1167 frame.streamid),
1107 }
1168 }
1108
1169
1109 if frame.streamid not in self._incomingstreams:
1170 if frame.streamid not in self._incomingstreams:
1110 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1171 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1111 return 'error', {
1172 return 'error', {
1112 'message': _('received frame on unknown stream '
1173 'message': _('received frame on unknown stream '
1113 'without beginning of stream flag set'),
1174 'without beginning of stream flag set'),
1114 }
1175 }
1115
1176
1116 self._incomingstreams[frame.streamid] = stream(frame.streamid)
1177 self._incomingstreams[frame.streamid] = stream(frame.streamid)
1117
1178
1118 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1179 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1119 raise error.ProgrammingError('support for decoding stream '
1180 raise error.ProgrammingError('support for decoding stream '
1120 'payloads not yet implemneted')
1181 'payloads not yet implemneted')
1121
1182
1122 if frame.streamflags & STREAM_FLAG_END_STREAM:
1183 if frame.streamflags & STREAM_FLAG_END_STREAM:
1123 del self._incomingstreams[frame.streamid]
1184 del self._incomingstreams[frame.streamid]
1124
1185
1125 if frame.requestid not in self._activerequests:
1186 if frame.requestid not in self._activerequests:
1126 return 'error', {
1187 return 'error', {
1127 'message': (_('received frame for inactive request ID: %d') %
1188 'message': (_('received frame for inactive request ID: %d') %
1128 frame.requestid),
1189 frame.requestid),
1129 }
1190 }
1130
1191
1131 request = self._activerequests[frame.requestid]
1192 request = self._activerequests[frame.requestid]
1132 request.state = 'receiving'
1193 request.state = 'receiving'
1133
1194
1134 handlers = {
1195 handlers = {
1135 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1196 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1136 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1197 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1137 }
1198 }
1138
1199
1139 meth = handlers.get(frame.typeid)
1200 meth = handlers.get(frame.typeid)
1140 if not meth:
1201 if not meth:
1141 raise error.ProgrammingError('unhandled frame type: %d' %
1202 raise error.ProgrammingError('unhandled frame type: %d' %
1142 frame.typeid)
1203 frame.typeid)
1143
1204
1144 return meth(request, frame)
1205 return meth(request, frame)
1145
1206
1146 def _oncommandresponseframe(self, request, frame):
1207 def _oncommandresponseframe(self, request, frame):
1147 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1208 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1148 request.state = 'received'
1209 request.state = 'received'
1149 del self._activerequests[request.requestid]
1210 del self._activerequests[request.requestid]
1150
1211
1151 return 'responsedata', {
1212 return 'responsedata', {
1152 'request': request,
1213 'request': request,
1153 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1214 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1154 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1215 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1155 'data': frame.payload,
1216 'data': frame.payload,
1156 }
1217 }
1157
1218
1158 def _onerrorresponseframe(self, request, frame):
1219 def _onerrorresponseframe(self, request, frame):
1159 request.state = 'errored'
1220 request.state = 'errored'
1160 del self._activerequests[request.requestid]
1221 del self._activerequests[request.requestid]
1161
1222
1162 # The payload should be a CBOR map.
1223 # The payload should be a CBOR map.
1163 m = cborutil.decodeall(frame.payload)[0]
1224 m = cborutil.decodeall(frame.payload)[0]
1164
1225
1165 return 'error', {
1226 return 'error', {
1166 'request': request,
1227 'request': request,
1167 'type': m['type'],
1228 'type': m['type'],
1168 'message': m['message'],
1229 'message': m['message'],
1169 }
1230 }
@@ -1,375 +1,354 b''
1 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
1 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 from .node import (
8 from .node import (
9 bin,
9 bin,
10 hex,
10 hex,
11 )
11 )
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 util,
15 util,
16 )
16 )
17 from .utils import (
17 from .utils import (
18 interfaceutil,
18 interfaceutil,
19 )
19 )
20
20
21 # Names of the SSH protocol implementations.
21 # Names of the SSH protocol implementations.
22 SSHV1 = 'ssh-v1'
22 SSHV1 = 'ssh-v1'
23 # These are advertised over the wire. Increment the counters at the end
23 # These are advertised over the wire. Increment the counters at the end
24 # to reflect BC breakages.
24 # to reflect BC breakages.
25 SSHV2 = 'exp-ssh-v2-0001'
25 SSHV2 = 'exp-ssh-v2-0001'
26 HTTP_WIREPROTO_V2 = 'exp-http-v2-0001'
26 HTTP_WIREPROTO_V2 = 'exp-http-v2-0001'
27
27
28 # All available wire protocol transports.
28 # All available wire protocol transports.
29 TRANSPORTS = {
29 TRANSPORTS = {
30 SSHV1: {
30 SSHV1: {
31 'transport': 'ssh',
31 'transport': 'ssh',
32 'version': 1,
32 'version': 1,
33 },
33 },
34 SSHV2: {
34 SSHV2: {
35 'transport': 'ssh',
35 'transport': 'ssh',
36 # TODO mark as version 2 once all commands are implemented.
36 # TODO mark as version 2 once all commands are implemented.
37 'version': 1,
37 'version': 1,
38 },
38 },
39 'http-v1': {
39 'http-v1': {
40 'transport': 'http',
40 'transport': 'http',
41 'version': 1,
41 'version': 1,
42 },
42 },
43 HTTP_WIREPROTO_V2: {
43 HTTP_WIREPROTO_V2: {
44 'transport': 'http',
44 'transport': 'http',
45 'version': 2,
45 'version': 2,
46 }
46 }
47 }
47 }
48
48
49 class bytesresponse(object):
49 class bytesresponse(object):
50 """A wire protocol response consisting of raw bytes."""
50 """A wire protocol response consisting of raw bytes."""
51 def __init__(self, data):
51 def __init__(self, data):
52 self.data = data
52 self.data = data
53
53
54 class ooberror(object):
54 class ooberror(object):
55 """wireproto reply: failure of a batch of operation
55 """wireproto reply: failure of a batch of operation
56
56
57 Something failed during a batch call. The error message is stored in
57 Something failed during a batch call. The error message is stored in
58 `self.message`.
58 `self.message`.
59 """
59 """
60 def __init__(self, message):
60 def __init__(self, message):
61 self.message = message
61 self.message = message
62
62
63 class pushres(object):
63 class pushres(object):
64 """wireproto reply: success with simple integer return
64 """wireproto reply: success with simple integer return
65
65
66 The call was successful and returned an integer contained in `self.res`.
66 The call was successful and returned an integer contained in `self.res`.
67 """
67 """
68 def __init__(self, res, output):
68 def __init__(self, res, output):
69 self.res = res
69 self.res = res
70 self.output = output
70 self.output = output
71
71
72 class pusherr(object):
72 class pusherr(object):
73 """wireproto reply: failure
73 """wireproto reply: failure
74
74
75 The call failed. The `self.res` attribute contains the error message.
75 The call failed. The `self.res` attribute contains the error message.
76 """
76 """
77 def __init__(self, res, output):
77 def __init__(self, res, output):
78 self.res = res
78 self.res = res
79 self.output = output
79 self.output = output
80
80
81 class streamres(object):
81 class streamres(object):
82 """wireproto reply: binary stream
82 """wireproto reply: binary stream
83
83
84 The call was successful and the result is a stream.
84 The call was successful and the result is a stream.
85
85
86 Accepts a generator containing chunks of data to be sent to the client.
86 Accepts a generator containing chunks of data to be sent to the client.
87
87
88 ``prefer_uncompressed`` indicates that the data is expected to be
88 ``prefer_uncompressed`` indicates that the data is expected to be
89 uncompressable and that the stream should therefore use the ``none``
89 uncompressable and that the stream should therefore use the ``none``
90 engine.
90 engine.
91 """
91 """
92 def __init__(self, gen=None, prefer_uncompressed=False):
92 def __init__(self, gen=None, prefer_uncompressed=False):
93 self.gen = gen
93 self.gen = gen
94 self.prefer_uncompressed = prefer_uncompressed
94 self.prefer_uncompressed = prefer_uncompressed
95
95
96 class streamreslegacy(object):
96 class streamreslegacy(object):
97 """wireproto reply: uncompressed binary stream
97 """wireproto reply: uncompressed binary stream
98
98
99 The call was successful and the result is a stream.
99 The call was successful and the result is a stream.
100
100
101 Accepts a generator containing chunks of data to be sent to the client.
101 Accepts a generator containing chunks of data to be sent to the client.
102
102
103 Like ``streamres``, but sends an uncompressed data for "version 1" clients
103 Like ``streamres``, but sends an uncompressed data for "version 1" clients
104 using the application/mercurial-0.1 media type.
104 using the application/mercurial-0.1 media type.
105 """
105 """
106 def __init__(self, gen=None):
106 def __init__(self, gen=None):
107 self.gen = gen
107 self.gen = gen
108
108
109 class cborresponse(object):
110 """Encode the response value as CBOR."""
111 def __init__(self, v):
112 self.value = v
113
114 class v2errorresponse(object):
115 """Represents a command error for version 2 transports."""
116 def __init__(self, message, args=None):
117 self.message = message
118 self.args = args
119
120 class v2streamingresponse(object):
121 """A response whose data is supplied by a generator.
122
123 The generator can either consist of data structures to CBOR
124 encode or a stream of already-encoded bytes.
125 """
126 def __init__(self, gen, compressible=True):
127 self.gen = gen
128 self.compressible = compressible
129
130 # list of nodes encoding / decoding
109 # list of nodes encoding / decoding
131 def decodelist(l, sep=' '):
110 def decodelist(l, sep=' '):
132 if l:
111 if l:
133 return [bin(v) for v in l.split(sep)]
112 return [bin(v) for v in l.split(sep)]
134 return []
113 return []
135
114
136 def encodelist(l, sep=' '):
115 def encodelist(l, sep=' '):
137 try:
116 try:
138 return sep.join(map(hex, l))
117 return sep.join(map(hex, l))
139 except TypeError:
118 except TypeError:
140 raise
119 raise
141
120
142 # batched call argument encoding
121 # batched call argument encoding
143
122
144 def escapebatcharg(plain):
123 def escapebatcharg(plain):
145 return (plain
124 return (plain
146 .replace(':', ':c')
125 .replace(':', ':c')
147 .replace(',', ':o')
126 .replace(',', ':o')
148 .replace(';', ':s')
127 .replace(';', ':s')
149 .replace('=', ':e'))
128 .replace('=', ':e'))
150
129
151 def unescapebatcharg(escaped):
130 def unescapebatcharg(escaped):
152 return (escaped
131 return (escaped
153 .replace(':e', '=')
132 .replace(':e', '=')
154 .replace(':s', ';')
133 .replace(':s', ';')
155 .replace(':o', ',')
134 .replace(':o', ',')
156 .replace(':c', ':'))
135 .replace(':c', ':'))
157
136
158 # mapping of options accepted by getbundle and their types
137 # mapping of options accepted by getbundle and their types
159 #
138 #
160 # Meant to be extended by extensions. It is extensions responsibility to ensure
139 # Meant to be extended by extensions. It is extensions responsibility to ensure
161 # such options are properly processed in exchange.getbundle.
140 # such options are properly processed in exchange.getbundle.
162 #
141 #
163 # supported types are:
142 # supported types are:
164 #
143 #
165 # :nodes: list of binary nodes
144 # :nodes: list of binary nodes
166 # :csv: list of comma-separated values
145 # :csv: list of comma-separated values
167 # :scsv: list of comma-separated values return as set
146 # :scsv: list of comma-separated values return as set
168 # :plain: string with no transformation needed.
147 # :plain: string with no transformation needed.
169 GETBUNDLE_ARGUMENTS = {
148 GETBUNDLE_ARGUMENTS = {
170 'heads': 'nodes',
149 'heads': 'nodes',
171 'bookmarks': 'boolean',
150 'bookmarks': 'boolean',
172 'common': 'nodes',
151 'common': 'nodes',
173 'obsmarkers': 'boolean',
152 'obsmarkers': 'boolean',
174 'phases': 'boolean',
153 'phases': 'boolean',
175 'bundlecaps': 'scsv',
154 'bundlecaps': 'scsv',
176 'listkeys': 'csv',
155 'listkeys': 'csv',
177 'cg': 'boolean',
156 'cg': 'boolean',
178 'cbattempted': 'boolean',
157 'cbattempted': 'boolean',
179 'stream': 'boolean',
158 'stream': 'boolean',
180 }
159 }
181
160
182 class baseprotocolhandler(interfaceutil.Interface):
161 class baseprotocolhandler(interfaceutil.Interface):
183 """Abstract base class for wire protocol handlers.
162 """Abstract base class for wire protocol handlers.
184
163
185 A wire protocol handler serves as an interface between protocol command
164 A wire protocol handler serves as an interface between protocol command
186 handlers and the wire protocol transport layer. Protocol handlers provide
165 handlers and the wire protocol transport layer. Protocol handlers provide
187 methods to read command arguments, redirect stdio for the duration of
166 methods to read command arguments, redirect stdio for the duration of
188 the request, handle response types, etc.
167 the request, handle response types, etc.
189 """
168 """
190
169
191 name = interfaceutil.Attribute(
170 name = interfaceutil.Attribute(
192 """The name of the protocol implementation.
171 """The name of the protocol implementation.
193
172
194 Used for uniquely identifying the transport type.
173 Used for uniquely identifying the transport type.
195 """)
174 """)
196
175
197 def getargs(args):
176 def getargs(args):
198 """return the value for arguments in <args>
177 """return the value for arguments in <args>
199
178
200 For version 1 transports, returns a list of values in the same
179 For version 1 transports, returns a list of values in the same
201 order they appear in ``args``. For version 2 transports, returns
180 order they appear in ``args``. For version 2 transports, returns
202 a dict mapping argument name to value.
181 a dict mapping argument name to value.
203 """
182 """
204
183
205 def getprotocaps():
184 def getprotocaps():
206 """Returns the list of protocol-level capabilities of client
185 """Returns the list of protocol-level capabilities of client
207
186
208 Returns a list of capabilities as declared by the client for
187 Returns a list of capabilities as declared by the client for
209 the current request (or connection for stateful protocol handlers)."""
188 the current request (or connection for stateful protocol handlers)."""
210
189
211 def getpayload():
190 def getpayload():
212 """Provide a generator for the raw payload.
191 """Provide a generator for the raw payload.
213
192
214 The caller is responsible for ensuring that the full payload is
193 The caller is responsible for ensuring that the full payload is
215 processed.
194 processed.
216 """
195 """
217
196
218 def mayberedirectstdio():
197 def mayberedirectstdio():
219 """Context manager to possibly redirect stdio.
198 """Context manager to possibly redirect stdio.
220
199
221 The context manager yields a file-object like object that receives
200 The context manager yields a file-object like object that receives
222 stdout and stderr output when the context manager is active. Or it
201 stdout and stderr output when the context manager is active. Or it
223 yields ``None`` if no I/O redirection occurs.
202 yields ``None`` if no I/O redirection occurs.
224
203
225 The intent of this context manager is to capture stdio output
204 The intent of this context manager is to capture stdio output
226 so it may be sent in the response. Some transports support streaming
205 so it may be sent in the response. Some transports support streaming
227 stdio to the client in real time. For these transports, stdio output
206 stdio to the client in real time. For these transports, stdio output
228 won't be captured.
207 won't be captured.
229 """
208 """
230
209
231 def client():
210 def client():
232 """Returns a string representation of this client (as bytes)."""
211 """Returns a string representation of this client (as bytes)."""
233
212
234 def addcapabilities(repo, caps):
213 def addcapabilities(repo, caps):
235 """Adds advertised capabilities specific to this protocol.
214 """Adds advertised capabilities specific to this protocol.
236
215
237 Receives the list of capabilities collected so far.
216 Receives the list of capabilities collected so far.
238
217
239 Returns a list of capabilities. The passed in argument can be returned.
218 Returns a list of capabilities. The passed in argument can be returned.
240 """
219 """
241
220
242 def checkperm(perm):
221 def checkperm(perm):
243 """Validate that the client has permissions to perform a request.
222 """Validate that the client has permissions to perform a request.
244
223
245 The argument is the permission required to proceed. If the client
224 The argument is the permission required to proceed. If the client
246 doesn't have that permission, the exception should raise or abort
225 doesn't have that permission, the exception should raise or abort
247 in a protocol specific manner.
226 in a protocol specific manner.
248 """
227 """
249
228
250 class commandentry(object):
229 class commandentry(object):
251 """Represents a declared wire protocol command."""
230 """Represents a declared wire protocol command."""
252 def __init__(self, func, args='', transports=None,
231 def __init__(self, func, args='', transports=None,
253 permission='push'):
232 permission='push'):
254 self.func = func
233 self.func = func
255 self.args = args
234 self.args = args
256 self.transports = transports or set()
235 self.transports = transports or set()
257 self.permission = permission
236 self.permission = permission
258
237
259 def _merge(self, func, args):
238 def _merge(self, func, args):
260 """Merge this instance with an incoming 2-tuple.
239 """Merge this instance with an incoming 2-tuple.
261
240
262 This is called when a caller using the old 2-tuple API attempts
241 This is called when a caller using the old 2-tuple API attempts
263 to replace an instance. The incoming values are merged with
242 to replace an instance. The incoming values are merged with
264 data not captured by the 2-tuple and a new instance containing
243 data not captured by the 2-tuple and a new instance containing
265 the union of the two objects is returned.
244 the union of the two objects is returned.
266 """
245 """
267 return commandentry(func, args=args, transports=set(self.transports),
246 return commandentry(func, args=args, transports=set(self.transports),
268 permission=self.permission)
247 permission=self.permission)
269
248
270 # Old code treats instances as 2-tuples. So expose that interface.
249 # Old code treats instances as 2-tuples. So expose that interface.
271 def __iter__(self):
250 def __iter__(self):
272 yield self.func
251 yield self.func
273 yield self.args
252 yield self.args
274
253
275 def __getitem__(self, i):
254 def __getitem__(self, i):
276 if i == 0:
255 if i == 0:
277 return self.func
256 return self.func
278 elif i == 1:
257 elif i == 1:
279 return self.args
258 return self.args
280 else:
259 else:
281 raise IndexError('can only access elements 0 and 1')
260 raise IndexError('can only access elements 0 and 1')
282
261
283 class commanddict(dict):
262 class commanddict(dict):
284 """Container for registered wire protocol commands.
263 """Container for registered wire protocol commands.
285
264
286 It behaves like a dict. But __setitem__ is overwritten to allow silent
265 It behaves like a dict. But __setitem__ is overwritten to allow silent
287 coercion of values from 2-tuples for API compatibility.
266 coercion of values from 2-tuples for API compatibility.
288 """
267 """
289 def __setitem__(self, k, v):
268 def __setitem__(self, k, v):
290 if isinstance(v, commandentry):
269 if isinstance(v, commandentry):
291 pass
270 pass
292 # Cast 2-tuples to commandentry instances.
271 # Cast 2-tuples to commandentry instances.
293 elif isinstance(v, tuple):
272 elif isinstance(v, tuple):
294 if len(v) != 2:
273 if len(v) != 2:
295 raise ValueError('command tuples must have exactly 2 elements')
274 raise ValueError('command tuples must have exactly 2 elements')
296
275
297 # It is common for extensions to wrap wire protocol commands via
276 # It is common for extensions to wrap wire protocol commands via
298 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
277 # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers
299 # doing this aren't aware of the new API that uses objects to store
278 # doing this aren't aware of the new API that uses objects to store
300 # command entries, we automatically merge old state with new.
279 # command entries, we automatically merge old state with new.
301 if k in self:
280 if k in self:
302 v = self[k]._merge(v[0], v[1])
281 v = self[k]._merge(v[0], v[1])
303 else:
282 else:
304 # Use default values from @wireprotocommand.
283 # Use default values from @wireprotocommand.
305 v = commandentry(v[0], args=v[1],
284 v = commandentry(v[0], args=v[1],
306 transports=set(TRANSPORTS),
285 transports=set(TRANSPORTS),
307 permission='push')
286 permission='push')
308 else:
287 else:
309 raise ValueError('command entries must be commandentry instances '
288 raise ValueError('command entries must be commandentry instances '
310 'or 2-tuples')
289 'or 2-tuples')
311
290
312 return super(commanddict, self).__setitem__(k, v)
291 return super(commanddict, self).__setitem__(k, v)
313
292
314 def commandavailable(self, command, proto):
293 def commandavailable(self, command, proto):
315 """Determine if a command is available for the requested protocol."""
294 """Determine if a command is available for the requested protocol."""
316 assert proto.name in TRANSPORTS
295 assert proto.name in TRANSPORTS
317
296
318 entry = self.get(command)
297 entry = self.get(command)
319
298
320 if not entry:
299 if not entry:
321 return False
300 return False
322
301
323 if proto.name not in entry.transports:
302 if proto.name not in entry.transports:
324 return False
303 return False
325
304
326 return True
305 return True
327
306
328 def supportedcompengines(ui, role):
307 def supportedcompengines(ui, role):
329 """Obtain the list of supported compression engines for a request."""
308 """Obtain the list of supported compression engines for a request."""
330 assert role in (util.CLIENTROLE, util.SERVERROLE)
309 assert role in (util.CLIENTROLE, util.SERVERROLE)
331
310
332 compengines = util.compengines.supportedwireengines(role)
311 compengines = util.compengines.supportedwireengines(role)
333
312
334 # Allow config to override default list and ordering.
313 # Allow config to override default list and ordering.
335 if role == util.SERVERROLE:
314 if role == util.SERVERROLE:
336 configengines = ui.configlist('server', 'compressionengines')
315 configengines = ui.configlist('server', 'compressionengines')
337 config = 'server.compressionengines'
316 config = 'server.compressionengines'
338 else:
317 else:
339 # This is currently implemented mainly to facilitate testing. In most
318 # This is currently implemented mainly to facilitate testing. In most
340 # cases, the server should be in charge of choosing a compression engine
319 # cases, the server should be in charge of choosing a compression engine
341 # because a server has the most to lose from a sub-optimal choice. (e.g.
320 # because a server has the most to lose from a sub-optimal choice. (e.g.
342 # CPU DoS due to an expensive engine or a network DoS due to poor
321 # CPU DoS due to an expensive engine or a network DoS due to poor
343 # compression ratio).
322 # compression ratio).
344 configengines = ui.configlist('experimental',
323 configengines = ui.configlist('experimental',
345 'clientcompressionengines')
324 'clientcompressionengines')
346 config = 'experimental.clientcompressionengines'
325 config = 'experimental.clientcompressionengines'
347
326
348 # No explicit config. Filter out the ones that aren't supposed to be
327 # No explicit config. Filter out the ones that aren't supposed to be
349 # advertised and return default ordering.
328 # advertised and return default ordering.
350 if not configengines:
329 if not configengines:
351 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
330 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
352 return [e for e in compengines
331 return [e for e in compengines
353 if getattr(e.wireprotosupport(), attr) > 0]
332 if getattr(e.wireprotosupport(), attr) > 0]
354
333
355 # If compression engines are listed in the config, assume there is a good
334 # If compression engines are listed in the config, assume there is a good
356 # reason for it (like server operators wanting to achieve specific
335 # reason for it (like server operators wanting to achieve specific
357 # performance characteristics). So fail fast if the config references
336 # performance characteristics). So fail fast if the config references
358 # unusable compression engines.
337 # unusable compression engines.
359 validnames = set(e.name() for e in compengines)
338 validnames = set(e.name() for e in compengines)
360 invalidnames = set(e for e in configengines if e not in validnames)
339 invalidnames = set(e for e in configengines if e not in validnames)
361 if invalidnames:
340 if invalidnames:
362 raise error.Abort(_('invalid compression engine defined in %s: %s') %
341 raise error.Abort(_('invalid compression engine defined in %s: %s') %
363 (config, ', '.join(sorted(invalidnames))))
342 (config, ', '.join(sorted(invalidnames))))
364
343
365 compengines = [e for e in compengines if e.name() in configengines]
344 compengines = [e for e in compengines if e.name() in configengines]
366 compengines = sorted(compengines,
345 compengines = sorted(compengines,
367 key=lambda e: configengines.index(e.name()))
346 key=lambda e: configengines.index(e.name()))
368
347
369 if not compengines:
348 if not compengines:
370 raise error.Abort(_('%s config option does not specify any known '
349 raise error.Abort(_('%s config option does not specify any known '
371 'compression engines') % config,
350 'compression engines') % config,
372 hint=_('usable compression engines: %s') %
351 hint=_('usable compression engines: %s') %
373 ', '.sorted(validnames))
352 ', '.sorted(validnames))
374
353
375 return compengines
354 return compengines
@@ -1,210 +1,212 b''
1 # wireprotov2peer.py - client side code for wire protocol version 2
1 # wireprotov2peer.py - client side code for wire protocol version 2
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11 from . import (
11 from . import (
12 encoding,
12 encoding,
13 error,
13 error,
14 util,
14 util,
15 wireprotoframing,
15 wireprotoframing,
16 )
16 )
17 from .utils import (
17 from .utils import (
18 cborutil,
18 cborutil,
19 )
19 )
20
20
21 def formatrichmessage(atoms):
21 def formatrichmessage(atoms):
22 """Format an encoded message from the framing protocol."""
22 """Format an encoded message from the framing protocol."""
23
23
24 chunks = []
24 chunks = []
25
25
26 for atom in atoms:
26 for atom in atoms:
27 msg = _(atom[b'msg'])
27 msg = _(atom[b'msg'])
28
28
29 if b'args' in atom:
29 if b'args' in atom:
30 msg = msg % tuple(atom[b'args'])
30 msg = msg % tuple(atom[b'args'])
31
31
32 chunks.append(msg)
32 chunks.append(msg)
33
33
34 return b''.join(chunks)
34 return b''.join(chunks)
35
35
36 class commandresponse(object):
36 class commandresponse(object):
37 """Represents the response to a command request."""
37 """Represents the response to a command request."""
38
38
39 def __init__(self, requestid, command):
39 def __init__(self, requestid, command):
40 self.requestid = requestid
40 self.requestid = requestid
41 self.command = command
41 self.command = command
42
42
43 self.b = util.bytesio()
43 self.b = util.bytesio()
44
44
45 def cborobjects(self):
45 def cborobjects(self):
46 """Obtain decoded CBOR objects from this response."""
46 """Obtain decoded CBOR objects from this response."""
47 self.b.seek(0)
47 self.b.seek(0)
48
48
49 for v in cborutil.decodeall(self.b.getvalue()):
49 for v in cborutil.decodeall(self.b.getvalue()):
50 yield v
50 yield v
51
51
52 class clienthandler(object):
52 class clienthandler(object):
53 """Object to handle higher-level client activities.
53 """Object to handle higher-level client activities.
54
54
55 The ``clientreactor`` is used to hold low-level state about the frame-based
55 The ``clientreactor`` is used to hold low-level state about the frame-based
56 protocol, such as which requests and streams are active. This type is used
56 protocol, such as which requests and streams are active. This type is used
57 for higher-level operations, such as reading frames from a socket, exposing
57 for higher-level operations, such as reading frames from a socket, exposing
58 and managing a higher-level primitive for representing command responses,
58 and managing a higher-level primitive for representing command responses,
59 etc. This class is what peers should probably use to bridge wire activity
59 etc. This class is what peers should probably use to bridge wire activity
60 with the higher-level peer API.
60 with the higher-level peer API.
61 """
61 """
62
62
63 def __init__(self, ui, clientreactor):
63 def __init__(self, ui, clientreactor):
64 self._ui = ui
64 self._ui = ui
65 self._reactor = clientreactor
65 self._reactor = clientreactor
66 self._requests = {}
66 self._requests = {}
67 self._futures = {}
67 self._futures = {}
68 self._responses = {}
68 self._responses = {}
69
69
70 def callcommand(self, command, args, f):
70 def callcommand(self, command, args, f):
71 """Register a request to call a command.
71 """Register a request to call a command.
72
72
73 Returns an iterable of frames that should be sent over the wire.
73 Returns an iterable of frames that should be sent over the wire.
74 """
74 """
75 request, action, meta = self._reactor.callcommand(command, args)
75 request, action, meta = self._reactor.callcommand(command, args)
76
76
77 if action != 'noop':
77 if action != 'noop':
78 raise error.ProgrammingError('%s not yet supported' % action)
78 raise error.ProgrammingError('%s not yet supported' % action)
79
79
80 rid = request.requestid
80 rid = request.requestid
81 self._requests[rid] = request
81 self._requests[rid] = request
82 self._futures[rid] = f
82 self._futures[rid] = f
83 self._responses[rid] = commandresponse(rid, command)
83 self._responses[rid] = commandresponse(rid, command)
84
84
85 return iter(())
85 return iter(())
86
86
87 def flushcommands(self):
87 def flushcommands(self):
88 """Flush all queued commands.
88 """Flush all queued commands.
89
89
90 Returns an iterable of frames that should be sent over the wire.
90 Returns an iterable of frames that should be sent over the wire.
91 """
91 """
92 action, meta = self._reactor.flushcommands()
92 action, meta = self._reactor.flushcommands()
93
93
94 if action != 'sendframes':
94 if action != 'sendframes':
95 raise error.ProgrammingError('%s not yet supported' % action)
95 raise error.ProgrammingError('%s not yet supported' % action)
96
96
97 return meta['framegen']
97 return meta['framegen']
98
98
99 def readframe(self, fh):
99 def readframe(self, fh):
100 """Attempt to read and process a frame.
100 """Attempt to read and process a frame.
101
101
102 Returns None if no frame was read. Presumably this means EOF.
102 Returns None if no frame was read. Presumably this means EOF.
103 """
103 """
104 frame = wireprotoframing.readframe(fh)
104 frame = wireprotoframing.readframe(fh)
105 if frame is None:
105 if frame is None:
106 # TODO tell reactor?
106 # TODO tell reactor?
107 return
107 return
108
108
109 self._ui.note(_('received %r\n') % frame)
109 self._ui.note(_('received %r\n') % frame)
110 self._processframe(frame)
110 self._processframe(frame)
111
111
112 return True
112 return True
113
113
114 def _processframe(self, frame):
114 def _processframe(self, frame):
115 """Process a single read frame."""
115 """Process a single read frame."""
116
116
117 action, meta = self._reactor.onframerecv(frame)
117 action, meta = self._reactor.onframerecv(frame)
118
118
119 if action == 'error':
119 if action == 'error':
120 e = error.RepoError(meta['message'])
120 e = error.RepoError(meta['message'])
121
121
122 if frame.requestid in self._futures:
122 if frame.requestid in self._futures:
123 self._futures[frame.requestid].set_exception(e)
123 self._futures[frame.requestid].set_exception(e)
124 else:
124 else:
125 raise e
125 raise e
126
126
127 return
128
127 if frame.requestid not in self._requests:
129 if frame.requestid not in self._requests:
128 raise error.ProgrammingError(
130 raise error.ProgrammingError(
129 'received frame for unknown request; this is either a bug in '
131 'received frame for unknown request; this is either a bug in '
130 'the clientreactor not screening for this or this instance was '
132 'the clientreactor not screening for this or this instance was '
131 'never told about this request: %r' % frame)
133 'never told about this request: %r' % frame)
132
134
133 response = self._responses[frame.requestid]
135 response = self._responses[frame.requestid]
134
136
135 if action == 'responsedata':
137 if action == 'responsedata':
136 # Any failures processing this frame should bubble up to the
138 # Any failures processing this frame should bubble up to the
137 # future tracking the request.
139 # future tracking the request.
138 try:
140 try:
139 self._processresponsedata(frame, meta, response)
141 self._processresponsedata(frame, meta, response)
140 except BaseException as e:
142 except BaseException as e:
141 self._futures[frame.requestid].set_exception(e)
143 self._futures[frame.requestid].set_exception(e)
142 else:
144 else:
143 raise error.ProgrammingError(
145 raise error.ProgrammingError(
144 'unhandled action from clientreactor: %s' % action)
146 'unhandled action from clientreactor: %s' % action)
145
147
146 def _processresponsedata(self, frame, meta, response):
148 def _processresponsedata(self, frame, meta, response):
147 # This buffers all data until end of stream is received. This
149 # This buffers all data until end of stream is received. This
148 # is bad for performance.
150 # is bad for performance.
149 # TODO make response data streamable
151 # TODO make response data streamable
150 response.b.write(meta['data'])
152 response.b.write(meta['data'])
151
153
152 if meta['eos']:
154 if meta['eos']:
153 # If the command has a decoder, resolve the future to the
155 # If the command has a decoder, resolve the future to the
154 # decoded value. Otherwise resolve to the rich response object.
156 # decoded value. Otherwise resolve to the rich response object.
155 decoder = COMMAND_DECODERS.get(response.command)
157 decoder = COMMAND_DECODERS.get(response.command)
156
158
157 # TODO consider always resolving the overall status map.
159 # TODO consider always resolving the overall status map.
158 if decoder:
160 if decoder:
159 objs = response.cborobjects()
161 objs = response.cborobjects()
160
162
161 overall = next(objs)
163 overall = next(objs)
162
164
163 if overall['status'] == 'ok':
165 if overall['status'] == 'ok':
164 self._futures[frame.requestid].set_result(decoder(objs))
166 self._futures[frame.requestid].set_result(decoder(objs))
165 else:
167 else:
166 atoms = [{'msg': overall['error']['message']}]
168 atoms = [{'msg': overall['error']['message']}]
167 if 'args' in overall['error']:
169 if 'args' in overall['error']:
168 atoms[0]['args'] = overall['error']['args']
170 atoms[0]['args'] = overall['error']['args']
169 e = error.RepoError(formatrichmessage(atoms))
171 e = error.RepoError(formatrichmessage(atoms))
170 self._futures[frame.requestid].set_exception(e)
172 self._futures[frame.requestid].set_exception(e)
171 else:
173 else:
172 self._futures[frame.requestid].set_result(response)
174 self._futures[frame.requestid].set_result(response)
173
175
174 del self._requests[frame.requestid]
176 del self._requests[frame.requestid]
175 del self._futures[frame.requestid]
177 del self._futures[frame.requestid]
176
178
177 def decodebranchmap(objs):
179 def decodebranchmap(objs):
178 # Response should be a single CBOR map of branch name to array of nodes.
180 # Response should be a single CBOR map of branch name to array of nodes.
179 bm = next(objs)
181 bm = next(objs)
180
182
181 return {encoding.tolocal(k): v for k, v in bm.items()}
183 return {encoding.tolocal(k): v for k, v in bm.items()}
182
184
183 def decodeheads(objs):
185 def decodeheads(objs):
184 # Array of node bytestrings.
186 # Array of node bytestrings.
185 return next(objs)
187 return next(objs)
186
188
187 def decodeknown(objs):
189 def decodeknown(objs):
188 # Bytestring where each byte is a 0 or 1.
190 # Bytestring where each byte is a 0 or 1.
189 raw = next(objs)
191 raw = next(objs)
190
192
191 return [True if c == '1' else False for c in raw]
193 return [True if c == '1' else False for c in raw]
192
194
193 def decodelistkeys(objs):
195 def decodelistkeys(objs):
194 # Map with bytestring keys and values.
196 # Map with bytestring keys and values.
195 return next(objs)
197 return next(objs)
196
198
197 def decodelookup(objs):
199 def decodelookup(objs):
198 return next(objs)
200 return next(objs)
199
201
200 def decodepushkey(objs):
202 def decodepushkey(objs):
201 return next(objs)
203 return next(objs)
202
204
203 COMMAND_DECODERS = {
205 COMMAND_DECODERS = {
204 'branchmap': decodebranchmap,
206 'branchmap': decodebranchmap,
205 'heads': decodeheads,
207 'heads': decodeheads,
206 'known': decodeknown,
208 'known': decodeknown,
207 'listkeys': decodelistkeys,
209 'listkeys': decodelistkeys,
208 'lookup': decodelookup,
210 'lookup': decodelookup,
209 'pushkey': decodepushkey,
211 'pushkey': decodepushkey,
210 }
212 }
@@ -1,535 +1,522 b''
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10
10
11 from .i18n import _
11 from .i18n import _
12 from . import (
12 from . import (
13 encoding,
13 encoding,
14 error,
14 error,
15 pycompat,
15 pycompat,
16 streamclone,
16 streamclone,
17 util,
17 util,
18 wireprotoframing,
18 wireprotoframing,
19 wireprototypes,
19 wireprototypes,
20 )
20 )
21 from .utils import (
21 from .utils import (
22 cborutil,
23 interfaceutil,
22 interfaceutil,
24 )
23 )
25
24
26 FRAMINGTYPE = b'application/mercurial-exp-framing-0005'
25 FRAMINGTYPE = b'application/mercurial-exp-framing-0005'
27
26
28 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
27 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
29
28
30 COMMANDS = wireprototypes.commanddict()
29 COMMANDS = wireprototypes.commanddict()
31
30
32 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
31 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
33 from .hgweb import common as hgwebcommon
32 from .hgweb import common as hgwebcommon
34
33
35 # URL space looks like: <permissions>/<command>, where <permission> can
34 # URL space looks like: <permissions>/<command>, where <permission> can
36 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
35 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
37
36
38 # Root URL does nothing meaningful... yet.
37 # Root URL does nothing meaningful... yet.
39 if not urlparts:
38 if not urlparts:
40 res.status = b'200 OK'
39 res.status = b'200 OK'
41 res.headers[b'Content-Type'] = b'text/plain'
40 res.headers[b'Content-Type'] = b'text/plain'
42 res.setbodybytes(_('HTTP version 2 API handler'))
41 res.setbodybytes(_('HTTP version 2 API handler'))
43 return
42 return
44
43
45 if len(urlparts) == 1:
44 if len(urlparts) == 1:
46 res.status = b'404 Not Found'
45 res.status = b'404 Not Found'
47 res.headers[b'Content-Type'] = b'text/plain'
46 res.headers[b'Content-Type'] = b'text/plain'
48 res.setbodybytes(_('do not know how to process %s\n') %
47 res.setbodybytes(_('do not know how to process %s\n') %
49 req.dispatchpath)
48 req.dispatchpath)
50 return
49 return
51
50
52 permission, command = urlparts[0:2]
51 permission, command = urlparts[0:2]
53
52
54 if permission not in (b'ro', b'rw'):
53 if permission not in (b'ro', b'rw'):
55 res.status = b'404 Not Found'
54 res.status = b'404 Not Found'
56 res.headers[b'Content-Type'] = b'text/plain'
55 res.headers[b'Content-Type'] = b'text/plain'
57 res.setbodybytes(_('unknown permission: %s') % permission)
56 res.setbodybytes(_('unknown permission: %s') % permission)
58 return
57 return
59
58
60 if req.method != 'POST':
59 if req.method != 'POST':
61 res.status = b'405 Method Not Allowed'
60 res.status = b'405 Method Not Allowed'
62 res.headers[b'Allow'] = b'POST'
61 res.headers[b'Allow'] = b'POST'
63 res.setbodybytes(_('commands require POST requests'))
62 res.setbodybytes(_('commands require POST requests'))
64 return
63 return
65
64
66 # At some point we'll want to use our own API instead of recycling the
65 # At some point we'll want to use our own API instead of recycling the
67 # behavior of version 1 of the wire protocol...
66 # behavior of version 1 of the wire protocol...
68 # TODO return reasonable responses - not responses that overload the
67 # TODO return reasonable responses - not responses that overload the
69 # HTTP status line message for error reporting.
68 # HTTP status line message for error reporting.
70 try:
69 try:
71 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
70 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
72 except hgwebcommon.ErrorResponse as e:
71 except hgwebcommon.ErrorResponse as e:
73 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
72 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
74 for k, v in e.headers:
73 for k, v in e.headers:
75 res.headers[k] = v
74 res.headers[k] = v
76 res.setbodybytes('permission denied')
75 res.setbodybytes('permission denied')
77 return
76 return
78
77
79 # We have a special endpoint to reflect the request back at the client.
78 # We have a special endpoint to reflect the request back at the client.
80 if command == b'debugreflect':
79 if command == b'debugreflect':
81 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
80 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
82 return
81 return
83
82
84 # Extra commands that we handle that aren't really wire protocol
83 # Extra commands that we handle that aren't really wire protocol
85 # commands. Think extra hard before making this hackery available to
84 # commands. Think extra hard before making this hackery available to
86 # extension.
85 # extension.
87 extracommands = {'multirequest'}
86 extracommands = {'multirequest'}
88
87
89 if command not in COMMANDS and command not in extracommands:
88 if command not in COMMANDS and command not in extracommands:
90 res.status = b'404 Not Found'
89 res.status = b'404 Not Found'
91 res.headers[b'Content-Type'] = b'text/plain'
90 res.headers[b'Content-Type'] = b'text/plain'
92 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
91 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
93 return
92 return
94
93
95 repo = rctx.repo
94 repo = rctx.repo
96 ui = repo.ui
95 ui = repo.ui
97
96
98 proto = httpv2protocolhandler(req, ui)
97 proto = httpv2protocolhandler(req, ui)
99
98
100 if (not COMMANDS.commandavailable(command, proto)
99 if (not COMMANDS.commandavailable(command, proto)
101 and command not in extracommands):
100 and command not in extracommands):
102 res.status = b'404 Not Found'
101 res.status = b'404 Not Found'
103 res.headers[b'Content-Type'] = b'text/plain'
102 res.headers[b'Content-Type'] = b'text/plain'
104 res.setbodybytes(_('invalid wire protocol command: %s') % command)
103 res.setbodybytes(_('invalid wire protocol command: %s') % command)
105 return
104 return
106
105
107 # TODO consider cases where proxies may add additional Accept headers.
106 # TODO consider cases where proxies may add additional Accept headers.
108 if req.headers.get(b'Accept') != FRAMINGTYPE:
107 if req.headers.get(b'Accept') != FRAMINGTYPE:
109 res.status = b'406 Not Acceptable'
108 res.status = b'406 Not Acceptable'
110 res.headers[b'Content-Type'] = b'text/plain'
109 res.headers[b'Content-Type'] = b'text/plain'
111 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
110 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
112 % FRAMINGTYPE)
111 % FRAMINGTYPE)
113 return
112 return
114
113
115 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
114 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
116 res.status = b'415 Unsupported Media Type'
115 res.status = b'415 Unsupported Media Type'
117 # TODO we should send a response with appropriate media type,
116 # TODO we should send a response with appropriate media type,
118 # since client does Accept it.
117 # since client does Accept it.
119 res.headers[b'Content-Type'] = b'text/plain'
118 res.headers[b'Content-Type'] = b'text/plain'
120 res.setbodybytes(_('client MUST send Content-Type header with '
119 res.setbodybytes(_('client MUST send Content-Type header with '
121 'value: %s\n') % FRAMINGTYPE)
120 'value: %s\n') % FRAMINGTYPE)
122 return
121 return
123
122
124 _processhttpv2request(ui, repo, req, res, permission, command, proto)
123 _processhttpv2request(ui, repo, req, res, permission, command, proto)
125
124
126 def _processhttpv2reflectrequest(ui, repo, req, res):
125 def _processhttpv2reflectrequest(ui, repo, req, res):
127 """Reads unified frame protocol request and dumps out state to client.
126 """Reads unified frame protocol request and dumps out state to client.
128
127
129 This special endpoint can be used to help debug the wire protocol.
128 This special endpoint can be used to help debug the wire protocol.
130
129
131 Instead of routing the request through the normal dispatch mechanism,
130 Instead of routing the request through the normal dispatch mechanism,
132 we instead read all frames, decode them, and feed them into our state
131 we instead read all frames, decode them, and feed them into our state
133 tracker. We then dump the log of all that activity back out to the
132 tracker. We then dump the log of all that activity back out to the
134 client.
133 client.
135 """
134 """
136 import json
135 import json
137
136
138 # Reflection APIs have a history of being abused, accidentally disclosing
137 # Reflection APIs have a history of being abused, accidentally disclosing
139 # sensitive data, etc. So we have a config knob.
138 # sensitive data, etc. So we have a config knob.
140 if not ui.configbool('experimental', 'web.api.debugreflect'):
139 if not ui.configbool('experimental', 'web.api.debugreflect'):
141 res.status = b'404 Not Found'
140 res.status = b'404 Not Found'
142 res.headers[b'Content-Type'] = b'text/plain'
141 res.headers[b'Content-Type'] = b'text/plain'
143 res.setbodybytes(_('debugreflect service not available'))
142 res.setbodybytes(_('debugreflect service not available'))
144 return
143 return
145
144
146 # We assume we have a unified framing protocol request body.
145 # We assume we have a unified framing protocol request body.
147
146
148 reactor = wireprotoframing.serverreactor()
147 reactor = wireprotoframing.serverreactor()
149 states = []
148 states = []
150
149
151 while True:
150 while True:
152 frame = wireprotoframing.readframe(req.bodyfh)
151 frame = wireprotoframing.readframe(req.bodyfh)
153
152
154 if not frame:
153 if not frame:
155 states.append(b'received: <no frame>')
154 states.append(b'received: <no frame>')
156 break
155 break
157
156
158 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
157 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
159 frame.requestid,
158 frame.requestid,
160 frame.payload))
159 frame.payload))
161
160
162 action, meta = reactor.onframerecv(frame)
161 action, meta = reactor.onframerecv(frame)
163 states.append(json.dumps((action, meta), sort_keys=True,
162 states.append(json.dumps((action, meta), sort_keys=True,
164 separators=(', ', ': ')))
163 separators=(', ', ': ')))
165
164
166 action, meta = reactor.oninputeof()
165 action, meta = reactor.oninputeof()
167 meta['action'] = action
166 meta['action'] = action
168 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
167 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
169
168
170 res.status = b'200 OK'
169 res.status = b'200 OK'
171 res.headers[b'Content-Type'] = b'text/plain'
170 res.headers[b'Content-Type'] = b'text/plain'
172 res.setbodybytes(b'\n'.join(states))
171 res.setbodybytes(b'\n'.join(states))
173
172
174 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
173 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
175 """Post-validation handler for HTTPv2 requests.
174 """Post-validation handler for HTTPv2 requests.
176
175
177 Called when the HTTP request contains unified frame-based protocol
176 Called when the HTTP request contains unified frame-based protocol
178 frames for evaluation.
177 frames for evaluation.
179 """
178 """
180 # TODO Some HTTP clients are full duplex and can receive data before
179 # TODO Some HTTP clients are full duplex and can receive data before
181 # the entire request is transmitted. Figure out a way to indicate support
180 # the entire request is transmitted. Figure out a way to indicate support
182 # for that so we can opt into full duplex mode.
181 # for that so we can opt into full duplex mode.
183 reactor = wireprotoframing.serverreactor(deferoutput=True)
182 reactor = wireprotoframing.serverreactor(deferoutput=True)
184 seencommand = False
183 seencommand = False
185
184
186 outstream = reactor.makeoutputstream()
185 outstream = reactor.makeoutputstream()
187
186
188 while True:
187 while True:
189 frame = wireprotoframing.readframe(req.bodyfh)
188 frame = wireprotoframing.readframe(req.bodyfh)
190 if not frame:
189 if not frame:
191 break
190 break
192
191
193 action, meta = reactor.onframerecv(frame)
192 action, meta = reactor.onframerecv(frame)
194
193
195 if action == 'wantframe':
194 if action == 'wantframe':
196 # Need more data before we can do anything.
195 # Need more data before we can do anything.
197 continue
196 continue
198 elif action == 'runcommand':
197 elif action == 'runcommand':
199 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
198 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
200 reqcommand, reactor, outstream,
199 reqcommand, reactor, outstream,
201 meta, issubsequent=seencommand)
200 meta, issubsequent=seencommand)
202
201
203 if sentoutput:
202 if sentoutput:
204 return
203 return
205
204
206 seencommand = True
205 seencommand = True
207
206
208 elif action == 'error':
207 elif action == 'error':
209 # TODO define proper error mechanism.
208 # TODO define proper error mechanism.
210 res.status = b'200 OK'
209 res.status = b'200 OK'
211 res.headers[b'Content-Type'] = b'text/plain'
210 res.headers[b'Content-Type'] = b'text/plain'
212 res.setbodybytes(meta['message'] + b'\n')
211 res.setbodybytes(meta['message'] + b'\n')
213 return
212 return
214 else:
213 else:
215 raise error.ProgrammingError(
214 raise error.ProgrammingError(
216 'unhandled action from frame processor: %s' % action)
215 'unhandled action from frame processor: %s' % action)
217
216
218 action, meta = reactor.oninputeof()
217 action, meta = reactor.oninputeof()
219 if action == 'sendframes':
218 if action == 'sendframes':
220 # We assume we haven't started sending the response yet. If we're
219 # We assume we haven't started sending the response yet. If we're
221 # wrong, the response type will raise an exception.
220 # wrong, the response type will raise an exception.
222 res.status = b'200 OK'
221 res.status = b'200 OK'
223 res.headers[b'Content-Type'] = FRAMINGTYPE
222 res.headers[b'Content-Type'] = FRAMINGTYPE
224 res.setbodygen(meta['framegen'])
223 res.setbodygen(meta['framegen'])
225 elif action == 'noop':
224 elif action == 'noop':
226 pass
225 pass
227 else:
226 else:
228 raise error.ProgrammingError('unhandled action from frame processor: %s'
227 raise error.ProgrammingError('unhandled action from frame processor: %s'
229 % action)
228 % action)
230
229
231 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
230 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
232 outstream, command, issubsequent):
231 outstream, command, issubsequent):
233 """Dispatch a wire protocol command made from HTTPv2 requests.
232 """Dispatch a wire protocol command made from HTTPv2 requests.
234
233
235 The authenticated permission (``authedperm``) along with the original
234 The authenticated permission (``authedperm``) along with the original
236 command from the URL (``reqcommand``) are passed in.
235 command from the URL (``reqcommand``) are passed in.
237 """
236 """
238 # We already validated that the session has permissions to perform the
237 # We already validated that the session has permissions to perform the
239 # actions in ``authedperm``. In the unified frame protocol, the canonical
238 # actions in ``authedperm``. In the unified frame protocol, the canonical
240 # command to run is expressed in a frame. However, the URL also requested
239 # command to run is expressed in a frame. However, the URL also requested
241 # to run a specific command. We need to be careful that the command we
240 # to run a specific command. We need to be careful that the command we
242 # run doesn't have permissions requirements greater than what was granted
241 # run doesn't have permissions requirements greater than what was granted
243 # by ``authedperm``.
242 # by ``authedperm``.
244 #
243 #
245 # Our rule for this is we only allow one command per HTTP request and
244 # Our rule for this is we only allow one command per HTTP request and
246 # that command must match the command in the URL. However, we make
245 # that command must match the command in the URL. However, we make
247 # an exception for the ``multirequest`` URL. This URL is allowed to
246 # an exception for the ``multirequest`` URL. This URL is allowed to
248 # execute multiple commands. We double check permissions of each command
247 # execute multiple commands. We double check permissions of each command
249 # as it is invoked to ensure there is no privilege escalation.
248 # as it is invoked to ensure there is no privilege escalation.
250 # TODO consider allowing multiple commands to regular command URLs
249 # TODO consider allowing multiple commands to regular command URLs
251 # iff each command is the same.
250 # iff each command is the same.
252
251
253 proto = httpv2protocolhandler(req, ui, args=command['args'])
252 proto = httpv2protocolhandler(req, ui, args=command['args'])
254
253
255 if reqcommand == b'multirequest':
254 if reqcommand == b'multirequest':
256 if not COMMANDS.commandavailable(command['command'], proto):
255 if not COMMANDS.commandavailable(command['command'], proto):
257 # TODO proper error mechanism
256 # TODO proper error mechanism
258 res.status = b'200 OK'
257 res.status = b'200 OK'
259 res.headers[b'Content-Type'] = b'text/plain'
258 res.headers[b'Content-Type'] = b'text/plain'
260 res.setbodybytes(_('wire protocol command not available: %s') %
259 res.setbodybytes(_('wire protocol command not available: %s') %
261 command['command'])
260 command['command'])
262 return True
261 return True
263
262
264 # TODO don't use assert here, since it may be elided by -O.
263 # TODO don't use assert here, since it may be elided by -O.
265 assert authedperm in (b'ro', b'rw')
264 assert authedperm in (b'ro', b'rw')
266 wirecommand = COMMANDS[command['command']]
265 wirecommand = COMMANDS[command['command']]
267 assert wirecommand.permission in ('push', 'pull')
266 assert wirecommand.permission in ('push', 'pull')
268
267
269 if authedperm == b'ro' and wirecommand.permission != 'pull':
268 if authedperm == b'ro' and wirecommand.permission != 'pull':
270 # TODO proper error mechanism
269 # TODO proper error mechanism
271 res.status = b'403 Forbidden'
270 res.status = b'403 Forbidden'
272 res.headers[b'Content-Type'] = b'text/plain'
271 res.headers[b'Content-Type'] = b'text/plain'
273 res.setbodybytes(_('insufficient permissions to execute '
272 res.setbodybytes(_('insufficient permissions to execute '
274 'command: %s') % command['command'])
273 'command: %s') % command['command'])
275 return True
274 return True
276
275
277 # TODO should we also call checkperm() here? Maybe not if we're going
276 # TODO should we also call checkperm() here? Maybe not if we're going
278 # to overhaul that API. The granted scope from the URL check should
277 # to overhaul that API. The granted scope from the URL check should
279 # be good enough.
278 # be good enough.
280
279
281 else:
280 else:
282 # Don't allow multiple commands outside of ``multirequest`` URL.
281 # Don't allow multiple commands outside of ``multirequest`` URL.
283 if issubsequent:
282 if issubsequent:
284 # TODO proper error mechanism
283 # TODO proper error mechanism
285 res.status = b'200 OK'
284 res.status = b'200 OK'
286 res.headers[b'Content-Type'] = b'text/plain'
285 res.headers[b'Content-Type'] = b'text/plain'
287 res.setbodybytes(_('multiple commands cannot be issued to this '
286 res.setbodybytes(_('multiple commands cannot be issued to this '
288 'URL'))
287 'URL'))
289 return True
288 return True
290
289
291 if reqcommand != command['command']:
290 if reqcommand != command['command']:
292 # TODO define proper error mechanism
291 # TODO define proper error mechanism
293 res.status = b'200 OK'
292 res.status = b'200 OK'
294 res.headers[b'Content-Type'] = b'text/plain'
293 res.headers[b'Content-Type'] = b'text/plain'
295 res.setbodybytes(_('command in frame must match command in URL'))
294 res.setbodybytes(_('command in frame must match command in URL'))
296 return True
295 return True
297
296
298 rsp = dispatch(repo, proto, command['command'])
299
300 res.status = b'200 OK'
297 res.status = b'200 OK'
301 res.headers[b'Content-Type'] = FRAMINGTYPE
298 res.headers[b'Content-Type'] = FRAMINGTYPE
302
299
303 # TODO consider adding a type to represent an iterable of values to
300 try:
304 # be CBOR encoded.
301 objs = dispatch(repo, proto, command['command'])
305 if isinstance(rsp, wireprototypes.cborresponse):
302
306 # TODO consider calling oncommandresponsereadygen().
303 action, meta = reactor.oncommandresponsereadyobjects(
307 encoded = b''.join(cborutil.streamencode(rsp.value))
304 outstream, command['requestid'], objs)
308 action, meta = reactor.oncommandresponseready(outstream,
305
309 command['requestid'],
306 except Exception as e:
310 encoded)
311 elif isinstance(rsp, wireprototypes.v2streamingresponse):
312 action, meta = reactor.oncommandresponsereadygen(outstream,
313 command['requestid'],
314 rsp.gen)
315 elif isinstance(rsp, wireprototypes.v2errorresponse):
316 action, meta = reactor.oncommanderror(outstream,
317 command['requestid'],
318 rsp.message,
319 rsp.args)
320 else:
321 action, meta = reactor.onservererror(
307 action, meta = reactor.onservererror(
322 _('unhandled response type from wire proto command'))
308 outstream, command['requestid'],
309 _('exception when invoking command: %s') % e)
323
310
324 if action == 'sendframes':
311 if action == 'sendframes':
325 res.setbodygen(meta['framegen'])
312 res.setbodygen(meta['framegen'])
326 return True
313 return True
327 elif action == 'noop':
314 elif action == 'noop':
328 return False
315 return False
329 else:
316 else:
330 raise error.ProgrammingError('unhandled event from reactor: %s' %
317 raise error.ProgrammingError('unhandled event from reactor: %s' %
331 action)
318 action)
332
319
333 def getdispatchrepo(repo, proto, command):
320 def getdispatchrepo(repo, proto, command):
334 return repo.filtered('served')
321 return repo.filtered('served')
335
322
336 def dispatch(repo, proto, command):
323 def dispatch(repo, proto, command):
337 repo = getdispatchrepo(repo, proto, command)
324 repo = getdispatchrepo(repo, proto, command)
338
325
339 func, spec = COMMANDS[command]
326 func, spec = COMMANDS[command]
340 args = proto.getargs(spec)
327 args = proto.getargs(spec)
341
328
342 return func(repo, proto, **args)
329 return func(repo, proto, **args)
343
330
344 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
331 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
345 class httpv2protocolhandler(object):
332 class httpv2protocolhandler(object):
346 def __init__(self, req, ui, args=None):
333 def __init__(self, req, ui, args=None):
347 self._req = req
334 self._req = req
348 self._ui = ui
335 self._ui = ui
349 self._args = args
336 self._args = args
350
337
351 @property
338 @property
352 def name(self):
339 def name(self):
353 return HTTP_WIREPROTO_V2
340 return HTTP_WIREPROTO_V2
354
341
355 def getargs(self, args):
342 def getargs(self, args):
356 data = {}
343 data = {}
357 for k, typ in args.items():
344 for k, typ in args.items():
358 if k == '*':
345 if k == '*':
359 raise NotImplementedError('do not support * args')
346 raise NotImplementedError('do not support * args')
360 elif k in self._args:
347 elif k in self._args:
361 # TODO consider validating value types.
348 # TODO consider validating value types.
362 data[k] = self._args[k]
349 data[k] = self._args[k]
363
350
364 return data
351 return data
365
352
366 def getprotocaps(self):
353 def getprotocaps(self):
367 # Protocol capabilities are currently not implemented for HTTP V2.
354 # Protocol capabilities are currently not implemented for HTTP V2.
368 return set()
355 return set()
369
356
370 def getpayload(self):
357 def getpayload(self):
371 raise NotImplementedError
358 raise NotImplementedError
372
359
373 @contextlib.contextmanager
360 @contextlib.contextmanager
374 def mayberedirectstdio(self):
361 def mayberedirectstdio(self):
375 raise NotImplementedError
362 raise NotImplementedError
376
363
377 def client(self):
364 def client(self):
378 raise NotImplementedError
365 raise NotImplementedError
379
366
380 def addcapabilities(self, repo, caps):
367 def addcapabilities(self, repo, caps):
381 return caps
368 return caps
382
369
383 def checkperm(self, perm):
370 def checkperm(self, perm):
384 raise NotImplementedError
371 raise NotImplementedError
385
372
386 def httpv2apidescriptor(req, repo):
373 def httpv2apidescriptor(req, repo):
387 proto = httpv2protocolhandler(req, repo.ui)
374 proto = httpv2protocolhandler(req, repo.ui)
388
375
389 return _capabilitiesv2(repo, proto)
376 return _capabilitiesv2(repo, proto)
390
377
391 def _capabilitiesv2(repo, proto):
378 def _capabilitiesv2(repo, proto):
392 """Obtain the set of capabilities for version 2 transports.
379 """Obtain the set of capabilities for version 2 transports.
393
380
394 These capabilities are distinct from the capabilities for version 1
381 These capabilities are distinct from the capabilities for version 1
395 transports.
382 transports.
396 """
383 """
397 compression = []
384 compression = []
398 for engine in wireprototypes.supportedcompengines(repo.ui, util.SERVERROLE):
385 for engine in wireprototypes.supportedcompengines(repo.ui, util.SERVERROLE):
399 compression.append({
386 compression.append({
400 b'name': engine.wireprotosupport().name,
387 b'name': engine.wireprotosupport().name,
401 })
388 })
402
389
403 caps = {
390 caps = {
404 'commands': {},
391 'commands': {},
405 'compression': compression,
392 'compression': compression,
406 'framingmediatypes': [FRAMINGTYPE],
393 'framingmediatypes': [FRAMINGTYPE],
407 }
394 }
408
395
409 for command, entry in COMMANDS.items():
396 for command, entry in COMMANDS.items():
410 caps['commands'][command] = {
397 caps['commands'][command] = {
411 'args': entry.args,
398 'args': entry.args,
412 'permissions': [entry.permission],
399 'permissions': [entry.permission],
413 }
400 }
414
401
415 if streamclone.allowservergeneration(repo):
402 if streamclone.allowservergeneration(repo):
416 caps['rawrepoformats'] = sorted(repo.requirements &
403 caps['rawrepoformats'] = sorted(repo.requirements &
417 repo.supportedformats)
404 repo.supportedformats)
418
405
419 return proto.addcapabilities(repo, caps)
406 return proto.addcapabilities(repo, caps)
420
407
421 def wireprotocommand(name, args=None, permission='push'):
408 def wireprotocommand(name, args=None, permission='push'):
422 """Decorator to declare a wire protocol command.
409 """Decorator to declare a wire protocol command.
423
410
424 ``name`` is the name of the wire protocol command being provided.
411 ``name`` is the name of the wire protocol command being provided.
425
412
426 ``args`` is a dict of argument names to example values.
413 ``args`` is a dict of argument names to example values.
427
414
428 ``permission`` defines the permission type needed to run this command.
415 ``permission`` defines the permission type needed to run this command.
429 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
416 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
430 respectively. Default is to assume command requires ``push`` permissions
417 respectively. Default is to assume command requires ``push`` permissions
431 because otherwise commands not declaring their permissions could modify
418 because otherwise commands not declaring their permissions could modify
432 a repository that is supposed to be read-only.
419 a repository that is supposed to be read-only.
420
421 Wire protocol commands are generators of objects to be serialized and
422 sent to the client.
423
424 If a command raises an uncaught exception, this will be translated into
425 a command error.
433 """
426 """
434 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
427 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
435 if v['version'] == 2}
428 if v['version'] == 2}
436
429
437 if permission not in ('push', 'pull'):
430 if permission not in ('push', 'pull'):
438 raise error.ProgrammingError('invalid wire protocol permission; '
431 raise error.ProgrammingError('invalid wire protocol permission; '
439 'got %s; expected "push" or "pull"' %
432 'got %s; expected "push" or "pull"' %
440 permission)
433 permission)
441
434
442 if args is None:
435 if args is None:
443 args = {}
436 args = {}
444
437
445 if not isinstance(args, dict):
438 if not isinstance(args, dict):
446 raise error.ProgrammingError('arguments for version 2 commands '
439 raise error.ProgrammingError('arguments for version 2 commands '
447 'must be declared as dicts')
440 'must be declared as dicts')
448
441
449 def register(func):
442 def register(func):
450 if name in COMMANDS:
443 if name in COMMANDS:
451 raise error.ProgrammingError('%s command already registered '
444 raise error.ProgrammingError('%s command already registered '
452 'for version 2' % name)
445 'for version 2' % name)
453
446
454 COMMANDS[name] = wireprototypes.commandentry(
447 COMMANDS[name] = wireprototypes.commandentry(
455 func, args=args, transports=transports, permission=permission)
448 func, args=args, transports=transports, permission=permission)
456
449
457 return func
450 return func
458
451
459 return register
452 return register
460
453
461 @wireprotocommand('branchmap', permission='pull')
454 @wireprotocommand('branchmap', permission='pull')
462 def branchmapv2(repo, proto):
455 def branchmapv2(repo, proto):
463 branchmap = {encoding.fromlocal(k): v
456 yield {encoding.fromlocal(k): v
464 for k, v in repo.branchmap().iteritems()}
457 for k, v in repo.branchmap().iteritems()}
465
466 return wireprototypes.cborresponse(branchmap)
467
458
468 @wireprotocommand('capabilities', permission='pull')
459 @wireprotocommand('capabilities', permission='pull')
469 def capabilitiesv2(repo, proto):
460 def capabilitiesv2(repo, proto):
470 caps = _capabilitiesv2(repo, proto)
461 yield _capabilitiesv2(repo, proto)
471
472 return wireprototypes.cborresponse(caps)
473
462
474 @wireprotocommand('heads',
463 @wireprotocommand('heads',
475 args={
464 args={
476 'publiconly': False,
465 'publiconly': False,
477 },
466 },
478 permission='pull')
467 permission='pull')
479 def headsv2(repo, proto, publiconly=False):
468 def headsv2(repo, proto, publiconly=False):
480 if publiconly:
469 if publiconly:
481 repo = repo.filtered('immutable')
470 repo = repo.filtered('immutable')
482
471
483 return wireprototypes.cborresponse(repo.heads())
472 yield repo.heads()
484
473
485 @wireprotocommand('known',
474 @wireprotocommand('known',
486 args={
475 args={
487 'nodes': [b'deadbeef'],
476 'nodes': [b'deadbeef'],
488 },
477 },
489 permission='pull')
478 permission='pull')
490 def knownv2(repo, proto, nodes=None):
479 def knownv2(repo, proto, nodes=None):
491 nodes = nodes or []
480 nodes = nodes or []
492 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
481 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
493 return wireprototypes.cborresponse(result)
482 yield result
494
483
495 @wireprotocommand('listkeys',
484 @wireprotocommand('listkeys',
496 args={
485 args={
497 'namespace': b'ns',
486 'namespace': b'ns',
498 },
487 },
499 permission='pull')
488 permission='pull')
500 def listkeysv2(repo, proto, namespace=None):
489 def listkeysv2(repo, proto, namespace=None):
501 keys = repo.listkeys(encoding.tolocal(namespace))
490 keys = repo.listkeys(encoding.tolocal(namespace))
502 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
491 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
503 for k, v in keys.iteritems()}
492 for k, v in keys.iteritems()}
504
493
505 return wireprototypes.cborresponse(keys)
494 yield keys
506
495
507 @wireprotocommand('lookup',
496 @wireprotocommand('lookup',
508 args={
497 args={
509 'key': b'foo',
498 'key': b'foo',
510 },
499 },
511 permission='pull')
500 permission='pull')
512 def lookupv2(repo, proto, key):
501 def lookupv2(repo, proto, key):
513 key = encoding.tolocal(key)
502 key = encoding.tolocal(key)
514
503
515 # TODO handle exception.
504 # TODO handle exception.
516 node = repo.lookup(key)
505 node = repo.lookup(key)
517
506
518 return wireprototypes.cborresponse(node)
507 yield node
519
508
520 @wireprotocommand('pushkey',
509 @wireprotocommand('pushkey',
521 args={
510 args={
522 'namespace': b'ns',
511 'namespace': b'ns',
523 'key': b'key',
512 'key': b'key',
524 'old': b'old',
513 'old': b'old',
525 'new': b'new',
514 'new': b'new',
526 },
515 },
527 permission='push')
516 permission='push')
528 def pushkeyv2(repo, proto, namespace, key, old, new):
517 def pushkeyv2(repo, proto, namespace, key, old, new):
529 # TODO handle ui output redirection
518 # TODO handle ui output redirection
530 r = repo.pushkey(encoding.tolocal(namespace),
519 yield repo.pushkey(encoding.tolocal(namespace),
531 encoding.tolocal(key),
520 encoding.tolocal(key),
532 encoding.tolocal(old),
521 encoding.tolocal(old),
533 encoding.tolocal(new))
522 encoding.tolocal(new))
534
535 return wireprototypes.cborresponse(r)
@@ -1,571 +1,622 b''
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4 $ enabledummycommands
4 $ enabledummycommands
5
5
6 $ hg init server
6 $ hg init server
7 $ cat > server/.hg/hgrc << EOF
7 $ cat > server/.hg/hgrc << EOF
8 > [experimental]
8 > [experimental]
9 > web.apiserver = true
9 > web.apiserver = true
10 > EOF
10 > EOF
11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
11 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
12 $ cat hg.pid > $DAEMON_PIDS
12 $ cat hg.pid > $DAEMON_PIDS
13
13
14 HTTP v2 protocol not enabled by default
14 HTTP v2 protocol not enabled by default
15
15
16 $ sendhttpraw << EOF
16 $ sendhttpraw << EOF
17 > httprequest GET api/$HTTPV2
17 > httprequest GET api/$HTTPV2
18 > user-agent: test
18 > user-agent: test
19 > EOF
19 > EOF
20 using raw connection to peer
20 using raw connection to peer
21 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
21 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
22 s> Accept-Encoding: identity\r\n
22 s> Accept-Encoding: identity\r\n
23 s> user-agent: test\r\n
23 s> user-agent: test\r\n
24 s> host: $LOCALIP:$HGPORT\r\n (glob)
24 s> host: $LOCALIP:$HGPORT\r\n (glob)
25 s> \r\n
25 s> \r\n
26 s> makefile('rb', None)
26 s> makefile('rb', None)
27 s> HTTP/1.1 404 Not Found\r\n
27 s> HTTP/1.1 404 Not Found\r\n
28 s> Server: testing stub value\r\n
28 s> Server: testing stub value\r\n
29 s> Date: $HTTP_DATE$\r\n
29 s> Date: $HTTP_DATE$\r\n
30 s> Content-Type: text/plain\r\n
30 s> Content-Type: text/plain\r\n
31 s> Content-Length: 33\r\n
31 s> Content-Length: 33\r\n
32 s> \r\n
32 s> \r\n
33 s> API exp-http-v2-0001 not enabled\n
33 s> API exp-http-v2-0001 not enabled\n
34
34
35 Restart server with support for HTTP v2 API
35 Restart server with support for HTTP v2 API
36
36
37 $ killdaemons.py
37 $ killdaemons.py
38 $ enablehttpv2 server
38 $ enablehttpv2 server
39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
39 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
40 $ cat hg.pid > $DAEMON_PIDS
40 $ cat hg.pid > $DAEMON_PIDS
41
41
42 Request to unknown command yields 404
42 Request to unknown command yields 404
43
43
44 $ sendhttpraw << EOF
44 $ sendhttpraw << EOF
45 > httprequest POST api/$HTTPV2/ro/badcommand
45 > httprequest POST api/$HTTPV2/ro/badcommand
46 > user-agent: test
46 > user-agent: test
47 > EOF
47 > EOF
48 using raw connection to peer
48 using raw connection to peer
49 s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
49 s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
50 s> Accept-Encoding: identity\r\n
50 s> Accept-Encoding: identity\r\n
51 s> user-agent: test\r\n
51 s> user-agent: test\r\n
52 s> host: $LOCALIP:$HGPORT\r\n (glob)
52 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 s> \r\n
53 s> \r\n
54 s> makefile('rb', None)
54 s> makefile('rb', None)
55 s> HTTP/1.1 404 Not Found\r\n
55 s> HTTP/1.1 404 Not Found\r\n
56 s> Server: testing stub value\r\n
56 s> Server: testing stub value\r\n
57 s> Date: $HTTP_DATE$\r\n
57 s> Date: $HTTP_DATE$\r\n
58 s> Content-Type: text/plain\r\n
58 s> Content-Type: text/plain\r\n
59 s> Content-Length: 42\r\n
59 s> Content-Length: 42\r\n
60 s> \r\n
60 s> \r\n
61 s> unknown wire protocol command: badcommand\n
61 s> unknown wire protocol command: badcommand\n
62
62
63 GET to read-only command yields a 405
63 GET to read-only command yields a 405
64
64
65 $ sendhttpraw << EOF
65 $ sendhttpraw << EOF
66 > httprequest GET api/$HTTPV2/ro/customreadonly
66 > httprequest GET api/$HTTPV2/ro/customreadonly
67 > user-agent: test
67 > user-agent: test
68 > EOF
68 > EOF
69 using raw connection to peer
69 using raw connection to peer
70 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
70 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
71 s> Accept-Encoding: identity\r\n
71 s> Accept-Encoding: identity\r\n
72 s> user-agent: test\r\n
72 s> user-agent: test\r\n
73 s> host: $LOCALIP:$HGPORT\r\n (glob)
73 s> host: $LOCALIP:$HGPORT\r\n (glob)
74 s> \r\n
74 s> \r\n
75 s> makefile('rb', None)
75 s> makefile('rb', None)
76 s> HTTP/1.1 405 Method Not Allowed\r\n
76 s> HTTP/1.1 405 Method Not Allowed\r\n
77 s> Server: testing stub value\r\n
77 s> Server: testing stub value\r\n
78 s> Date: $HTTP_DATE$\r\n
78 s> Date: $HTTP_DATE$\r\n
79 s> Allow: POST\r\n
79 s> Allow: POST\r\n
80 s> Content-Length: 30\r\n
80 s> Content-Length: 30\r\n
81 s> \r\n
81 s> \r\n
82 s> commands require POST requests
82 s> commands require POST requests
83
83
84 Missing Accept header results in 406
84 Missing Accept header results in 406
85
85
86 $ sendhttpraw << EOF
86 $ sendhttpraw << EOF
87 > httprequest POST api/$HTTPV2/ro/customreadonly
87 > httprequest POST api/$HTTPV2/ro/customreadonly
88 > user-agent: test
88 > user-agent: test
89 > EOF
89 > EOF
90 using raw connection to peer
90 using raw connection to peer
91 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
91 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
92 s> Accept-Encoding: identity\r\n
92 s> Accept-Encoding: identity\r\n
93 s> user-agent: test\r\n
93 s> user-agent: test\r\n
94 s> host: $LOCALIP:$HGPORT\r\n (glob)
94 s> host: $LOCALIP:$HGPORT\r\n (glob)
95 s> \r\n
95 s> \r\n
96 s> makefile('rb', None)
96 s> makefile('rb', None)
97 s> HTTP/1.1 406 Not Acceptable\r\n
97 s> HTTP/1.1 406 Not Acceptable\r\n
98 s> Server: testing stub value\r\n
98 s> Server: testing stub value\r\n
99 s> Date: $HTTP_DATE$\r\n
99 s> Date: $HTTP_DATE$\r\n
100 s> Content-Type: text/plain\r\n
100 s> Content-Type: text/plain\r\n
101 s> Content-Length: 85\r\n
101 s> Content-Length: 85\r\n
102 s> \r\n
102 s> \r\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
103 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
104
104
105 Bad Accept header results in 406
105 Bad Accept header results in 406
106
106
107 $ sendhttpraw << EOF
107 $ sendhttpraw << EOF
108 > httprequest POST api/$HTTPV2/ro/customreadonly
108 > httprequest POST api/$HTTPV2/ro/customreadonly
109 > accept: invalid
109 > accept: invalid
110 > user-agent: test
110 > user-agent: test
111 > EOF
111 > EOF
112 using raw connection to peer
112 using raw connection to peer
113 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
113 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
114 s> Accept-Encoding: identity\r\n
114 s> Accept-Encoding: identity\r\n
115 s> accept: invalid\r\n
115 s> accept: invalid\r\n
116 s> user-agent: test\r\n
116 s> user-agent: test\r\n
117 s> host: $LOCALIP:$HGPORT\r\n (glob)
117 s> host: $LOCALIP:$HGPORT\r\n (glob)
118 s> \r\n
118 s> \r\n
119 s> makefile('rb', None)
119 s> makefile('rb', None)
120 s> HTTP/1.1 406 Not Acceptable\r\n
120 s> HTTP/1.1 406 Not Acceptable\r\n
121 s> Server: testing stub value\r\n
121 s> Server: testing stub value\r\n
122 s> Date: $HTTP_DATE$\r\n
122 s> Date: $HTTP_DATE$\r\n
123 s> Content-Type: text/plain\r\n
123 s> Content-Type: text/plain\r\n
124 s> Content-Length: 85\r\n
124 s> Content-Length: 85\r\n
125 s> \r\n
125 s> \r\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
126 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
127
127
128 Bad Content-Type header results in 415
128 Bad Content-Type header results in 415
129
129
130 $ sendhttpraw << EOF
130 $ sendhttpraw << EOF
131 > httprequest POST api/$HTTPV2/ro/customreadonly
131 > httprequest POST api/$HTTPV2/ro/customreadonly
132 > accept: $MEDIATYPE
132 > accept: $MEDIATYPE
133 > user-agent: test
133 > user-agent: test
134 > content-type: badmedia
134 > content-type: badmedia
135 > EOF
135 > EOF
136 using raw connection to peer
136 using raw connection to peer
137 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
137 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
138 s> Accept-Encoding: identity\r\n
138 s> Accept-Encoding: identity\r\n
139 s> accept: application/mercurial-exp-framing-0005\r\n
139 s> accept: application/mercurial-exp-framing-0005\r\n
140 s> content-type: badmedia\r\n
140 s> content-type: badmedia\r\n
141 s> user-agent: test\r\n
141 s> user-agent: test\r\n
142 s> host: $LOCALIP:$HGPORT\r\n (glob)
142 s> host: $LOCALIP:$HGPORT\r\n (glob)
143 s> \r\n
143 s> \r\n
144 s> makefile('rb', None)
144 s> makefile('rb', None)
145 s> HTTP/1.1 415 Unsupported Media Type\r\n
145 s> HTTP/1.1 415 Unsupported Media Type\r\n
146 s> Server: testing stub value\r\n
146 s> Server: testing stub value\r\n
147 s> Date: $HTTP_DATE$\r\n
147 s> Date: $HTTP_DATE$\r\n
148 s> Content-Type: text/plain\r\n
148 s> Content-Type: text/plain\r\n
149 s> Content-Length: 88\r\n
149 s> Content-Length: 88\r\n
150 s> \r\n
150 s> \r\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0005\n
151 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0005\n
152
152
153 Request to read-only command works out of the box
153 Request to read-only command works out of the box
154
154
155 $ sendhttpraw << EOF
155 $ sendhttpraw << EOF
156 > httprequest POST api/$HTTPV2/ro/customreadonly
156 > httprequest POST api/$HTTPV2/ro/customreadonly
157 > accept: $MEDIATYPE
157 > accept: $MEDIATYPE
158 > content-type: $MEDIATYPE
158 > content-type: $MEDIATYPE
159 > user-agent: test
159 > user-agent: test
160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
160 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
161 > EOF
161 > EOF
162 using raw connection to peer
162 using raw connection to peer
163 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
163 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
164 s> Accept-Encoding: identity\r\n
164 s> Accept-Encoding: identity\r\n
165 s> *\r\n (glob)
165 s> *\r\n (glob)
166 s> content-type: application/mercurial-exp-framing-0005\r\n
166 s> content-type: application/mercurial-exp-framing-0005\r\n
167 s> user-agent: test\r\n
167 s> user-agent: test\r\n
168 s> content-length: 29\r\n
168 s> content-length: 29\r\n
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 s> \r\n
170 s> \r\n
171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
171 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
172 s> makefile('rb', None)
172 s> makefile('rb', None)
173 s> HTTP/1.1 200 OK\r\n
173 s> HTTP/1.1 200 OK\r\n
174 s> Server: testing stub value\r\n
174 s> Server: testing stub value\r\n
175 s> Date: $HTTP_DATE$\r\n
175 s> Date: $HTTP_DATE$\r\n
176 s> Content-Type: application/mercurial-exp-framing-0005\r\n
176 s> Content-Type: application/mercurial-exp-framing-0005\r\n
177 s> Transfer-Encoding: chunked\r\n
177 s> Transfer-Encoding: chunked\r\n
178 s> \r\n
178 s> \r\n
179 s> 32\r\n
179 s> 13\r\n
180 s> *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
180 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
181 s> \r\n
182 s> 27\r\n
183 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
184 s> \r\n
185 s> 8\r\n
186 s> \x00\x00\x00\x01\x00\x02\x002
181 s> \r\n
187 s> \r\n
182 s> 0\r\n
188 s> 0\r\n
183 s> \r\n
189 s> \r\n
184
190
185 $ sendhttpv2peer << EOF
191 $ sendhttpv2peer << EOF
186 > command customreadonly
192 > command customreadonly
187 > EOF
193 > EOF
188 creating http peer for wire protocol version 2
194 creating http peer for wire protocol version 2
189 sending customreadonly command
195 sending customreadonly command
190 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
196 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
191 s> Accept-Encoding: identity\r\n
197 s> Accept-Encoding: identity\r\n
192 s> accept: application/mercurial-exp-framing-0005\r\n
198 s> accept: application/mercurial-exp-framing-0005\r\n
193 s> content-type: application/mercurial-exp-framing-0005\r\n
199 s> content-type: application/mercurial-exp-framing-0005\r\n
194 s> content-length: 29\r\n
200 s> content-length: 29\r\n
195 s> host: $LOCALIP:$HGPORT\r\n (glob)
201 s> host: $LOCALIP:$HGPORT\r\n (glob)
196 s> user-agent: Mercurial debugwireproto\r\n
202 s> user-agent: Mercurial debugwireproto\r\n
197 s> \r\n
203 s> \r\n
198 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
204 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
199 s> makefile('rb', None)
205 s> makefile('rb', None)
200 s> HTTP/1.1 200 OK\r\n
206 s> HTTP/1.1 200 OK\r\n
201 s> Server: testing stub value\r\n
207 s> Server: testing stub value\r\n
202 s> Date: $HTTP_DATE$\r\n
208 s> Date: $HTTP_DATE$\r\n
203 s> Content-Type: application/mercurial-exp-framing-0005\r\n
209 s> Content-Type: application/mercurial-exp-framing-0005\r\n
204 s> Transfer-Encoding: chunked\r\n
210 s> Transfer-Encoding: chunked\r\n
205 s> \r\n
211 s> \r\n
206 s> 32\r\n
212 s> 13\r\n
207 s> *\x00\x00\x01\x00\x02\x012
213 s> \x0b\x00\x00\x01\x00\x02\x011
208 s> \xa1FstatusBokX\x1dcustomreadonly bytes response
214 s> \xa1FstatusBok
209 s> \r\n
215 s> \r\n
210 received frame(size=42; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
216 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
217 s> 27\r\n
218 s> \x1f\x00\x00\x01\x00\x02\x001
219 s> X\x1dcustomreadonly bytes response
220 s> \r\n
221 received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
222 s> 8\r\n
223 s> \x00\x00\x00\x01\x00\x02\x002
224 s> \r\n
211 s> 0\r\n
225 s> 0\r\n
212 s> \r\n
226 s> \r\n
227 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
213 response: [
228 response: [
214 {
229 {
215 b'status': b'ok'
230 b'status': b'ok'
216 },
231 },
217 b'customreadonly bytes response'
232 b'customreadonly bytes response'
218 ]
233 ]
219
234
220 Request to read-write command fails because server is read-only by default
235 Request to read-write command fails because server is read-only by default
221
236
222 GET to read-write request yields 405
237 GET to read-write request yields 405
223
238
224 $ sendhttpraw << EOF
239 $ sendhttpraw << EOF
225 > httprequest GET api/$HTTPV2/rw/customreadonly
240 > httprequest GET api/$HTTPV2/rw/customreadonly
226 > user-agent: test
241 > user-agent: test
227 > EOF
242 > EOF
228 using raw connection to peer
243 using raw connection to peer
229 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
244 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
230 s> Accept-Encoding: identity\r\n
245 s> Accept-Encoding: identity\r\n
231 s> user-agent: test\r\n
246 s> user-agent: test\r\n
232 s> host: $LOCALIP:$HGPORT\r\n (glob)
247 s> host: $LOCALIP:$HGPORT\r\n (glob)
233 s> \r\n
248 s> \r\n
234 s> makefile('rb', None)
249 s> makefile('rb', None)
235 s> HTTP/1.1 405 Method Not Allowed\r\n
250 s> HTTP/1.1 405 Method Not Allowed\r\n
236 s> Server: testing stub value\r\n
251 s> Server: testing stub value\r\n
237 s> Date: $HTTP_DATE$\r\n
252 s> Date: $HTTP_DATE$\r\n
238 s> Allow: POST\r\n
253 s> Allow: POST\r\n
239 s> Content-Length: 30\r\n
254 s> Content-Length: 30\r\n
240 s> \r\n
255 s> \r\n
241 s> commands require POST requests
256 s> commands require POST requests
242
257
243 Even for unknown commands
258 Even for unknown commands
244
259
245 $ sendhttpraw << EOF
260 $ sendhttpraw << EOF
246 > httprequest GET api/$HTTPV2/rw/badcommand
261 > httprequest GET api/$HTTPV2/rw/badcommand
247 > user-agent: test
262 > user-agent: test
248 > EOF
263 > EOF
249 using raw connection to peer
264 using raw connection to peer
250 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
265 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
251 s> Accept-Encoding: identity\r\n
266 s> Accept-Encoding: identity\r\n
252 s> user-agent: test\r\n
267 s> user-agent: test\r\n
253 s> host: $LOCALIP:$HGPORT\r\n (glob)
268 s> host: $LOCALIP:$HGPORT\r\n (glob)
254 s> \r\n
269 s> \r\n
255 s> makefile('rb', None)
270 s> makefile('rb', None)
256 s> HTTP/1.1 405 Method Not Allowed\r\n
271 s> HTTP/1.1 405 Method Not Allowed\r\n
257 s> Server: testing stub value\r\n
272 s> Server: testing stub value\r\n
258 s> Date: $HTTP_DATE$\r\n
273 s> Date: $HTTP_DATE$\r\n
259 s> Allow: POST\r\n
274 s> Allow: POST\r\n
260 s> Content-Length: 30\r\n
275 s> Content-Length: 30\r\n
261 s> \r\n
276 s> \r\n
262 s> commands require POST requests
277 s> commands require POST requests
263
278
264 SSL required by default
279 SSL required by default
265
280
266 $ sendhttpraw << EOF
281 $ sendhttpraw << EOF
267 > httprequest POST api/$HTTPV2/rw/customreadonly
282 > httprequest POST api/$HTTPV2/rw/customreadonly
268 > user-agent: test
283 > user-agent: test
269 > EOF
284 > EOF
270 using raw connection to peer
285 using raw connection to peer
271 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
286 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
272 s> Accept-Encoding: identity\r\n
287 s> Accept-Encoding: identity\r\n
273 s> user-agent: test\r\n
288 s> user-agent: test\r\n
274 s> host: $LOCALIP:$HGPORT\r\n (glob)
289 s> host: $LOCALIP:$HGPORT\r\n (glob)
275 s> \r\n
290 s> \r\n
276 s> makefile('rb', None)
291 s> makefile('rb', None)
277 s> HTTP/1.1 403 ssl required\r\n
292 s> HTTP/1.1 403 ssl required\r\n
278 s> Server: testing stub value\r\n
293 s> Server: testing stub value\r\n
279 s> Date: $HTTP_DATE$\r\n
294 s> Date: $HTTP_DATE$\r\n
280 s> Content-Length: 17\r\n
295 s> Content-Length: 17\r\n
281 s> \r\n
296 s> \r\n
282 s> permission denied
297 s> permission denied
283
298
284 Restart server to allow non-ssl read-write operations
299 Restart server to allow non-ssl read-write operations
285
300
286 $ killdaemons.py
301 $ killdaemons.py
287 $ cat > server/.hg/hgrc << EOF
302 $ cat > server/.hg/hgrc << EOF
288 > [experimental]
303 > [experimental]
289 > web.apiserver = true
304 > web.apiserver = true
290 > web.api.http-v2 = true
305 > web.api.http-v2 = true
291 > [web]
306 > [web]
292 > push_ssl = false
307 > push_ssl = false
293 > allow-push = *
308 > allow-push = *
294 > EOF
309 > EOF
295
310
296 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
311 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
297 $ cat hg.pid > $DAEMON_PIDS
312 $ cat hg.pid > $DAEMON_PIDS
298
313
299 Authorized request for valid read-write command works
314 Authorized request for valid read-write command works
300
315
301 $ sendhttpraw << EOF
316 $ sendhttpraw << EOF
302 > httprequest POST api/$HTTPV2/rw/customreadonly
317 > httprequest POST api/$HTTPV2/rw/customreadonly
303 > user-agent: test
318 > user-agent: test
304 > accept: $MEDIATYPE
319 > accept: $MEDIATYPE
305 > content-type: $MEDIATYPE
320 > content-type: $MEDIATYPE
306 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
321 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
307 > EOF
322 > EOF
308 using raw connection to peer
323 using raw connection to peer
309 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
324 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
310 s> Accept-Encoding: identity\r\n
325 s> Accept-Encoding: identity\r\n
311 s> accept: application/mercurial-exp-framing-0005\r\n
326 s> accept: application/mercurial-exp-framing-0005\r\n
312 s> content-type: application/mercurial-exp-framing-0005\r\n
327 s> content-type: application/mercurial-exp-framing-0005\r\n
313 s> user-agent: test\r\n
328 s> user-agent: test\r\n
314 s> content-length: 29\r\n
329 s> content-length: 29\r\n
315 s> host: $LOCALIP:$HGPORT\r\n (glob)
330 s> host: $LOCALIP:$HGPORT\r\n (glob)
316 s> \r\n
331 s> \r\n
317 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
332 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
318 s> makefile('rb', None)
333 s> makefile('rb', None)
319 s> HTTP/1.1 200 OK\r\n
334 s> HTTP/1.1 200 OK\r\n
320 s> Server: testing stub value\r\n
335 s> Server: testing stub value\r\n
321 s> Date: $HTTP_DATE$\r\n
336 s> Date: $HTTP_DATE$\r\n
322 s> Content-Type: application/mercurial-exp-framing-0005\r\n
337 s> Content-Type: application/mercurial-exp-framing-0005\r\n
323 s> Transfer-Encoding: chunked\r\n
338 s> Transfer-Encoding: chunked\r\n
324 s> \r\n
339 s> \r\n
325 s> 32\r\n
340 s> 13\r\n
326 s> *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
341 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
342 s> \r\n
343 s> 27\r\n
344 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
345 s> \r\n
346 s> 8\r\n
347 s> \x00\x00\x00\x01\x00\x02\x002
327 s> \r\n
348 s> \r\n
328 s> 0\r\n
349 s> 0\r\n
329 s> \r\n
350 s> \r\n
330
351
331 Authorized request for unknown command is rejected
352 Authorized request for unknown command is rejected
332
353
333 $ sendhttpraw << EOF
354 $ sendhttpraw << EOF
334 > httprequest POST api/$HTTPV2/rw/badcommand
355 > httprequest POST api/$HTTPV2/rw/badcommand
335 > user-agent: test
356 > user-agent: test
336 > accept: $MEDIATYPE
357 > accept: $MEDIATYPE
337 > EOF
358 > EOF
338 using raw connection to peer
359 using raw connection to peer
339 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
360 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
340 s> Accept-Encoding: identity\r\n
361 s> Accept-Encoding: identity\r\n
341 s> accept: application/mercurial-exp-framing-0005\r\n
362 s> accept: application/mercurial-exp-framing-0005\r\n
342 s> user-agent: test\r\n
363 s> user-agent: test\r\n
343 s> host: $LOCALIP:$HGPORT\r\n (glob)
364 s> host: $LOCALIP:$HGPORT\r\n (glob)
344 s> \r\n
365 s> \r\n
345 s> makefile('rb', None)
366 s> makefile('rb', None)
346 s> HTTP/1.1 404 Not Found\r\n
367 s> HTTP/1.1 404 Not Found\r\n
347 s> Server: testing stub value\r\n
368 s> Server: testing stub value\r\n
348 s> Date: $HTTP_DATE$\r\n
369 s> Date: $HTTP_DATE$\r\n
349 s> Content-Type: text/plain\r\n
370 s> Content-Type: text/plain\r\n
350 s> Content-Length: 42\r\n
371 s> Content-Length: 42\r\n
351 s> \r\n
372 s> \r\n
352 s> unknown wire protocol command: badcommand\n
373 s> unknown wire protocol command: badcommand\n
353
374
354 debugreflect isn't enabled by default
375 debugreflect isn't enabled by default
355
376
356 $ sendhttpraw << EOF
377 $ sendhttpraw << EOF
357 > httprequest POST api/$HTTPV2/ro/debugreflect
378 > httprequest POST api/$HTTPV2/ro/debugreflect
358 > user-agent: test
379 > user-agent: test
359 > EOF
380 > EOF
360 using raw connection to peer
381 using raw connection to peer
361 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
382 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
362 s> Accept-Encoding: identity\r\n
383 s> Accept-Encoding: identity\r\n
363 s> user-agent: test\r\n
384 s> user-agent: test\r\n
364 s> host: $LOCALIP:$HGPORT\r\n (glob)
385 s> host: $LOCALIP:$HGPORT\r\n (glob)
365 s> \r\n
386 s> \r\n
366 s> makefile('rb', None)
387 s> makefile('rb', None)
367 s> HTTP/1.1 404 Not Found\r\n
388 s> HTTP/1.1 404 Not Found\r\n
368 s> Server: testing stub value\r\n
389 s> Server: testing stub value\r\n
369 s> Date: $HTTP_DATE$\r\n
390 s> Date: $HTTP_DATE$\r\n
370 s> Content-Type: text/plain\r\n
391 s> Content-Type: text/plain\r\n
371 s> Content-Length: 34\r\n
392 s> Content-Length: 34\r\n
372 s> \r\n
393 s> \r\n
373 s> debugreflect service not available
394 s> debugreflect service not available
374
395
375 Restart server to get debugreflect endpoint
396 Restart server to get debugreflect endpoint
376
397
377 $ killdaemons.py
398 $ killdaemons.py
378 $ cat > server/.hg/hgrc << EOF
399 $ cat > server/.hg/hgrc << EOF
379 > [experimental]
400 > [experimental]
380 > web.apiserver = true
401 > web.apiserver = true
381 > web.api.debugreflect = true
402 > web.api.debugreflect = true
382 > web.api.http-v2 = true
403 > web.api.http-v2 = true
383 > [web]
404 > [web]
384 > push_ssl = false
405 > push_ssl = false
385 > allow-push = *
406 > allow-push = *
386 > EOF
407 > EOF
387
408
388 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
409 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
389 $ cat hg.pid > $DAEMON_PIDS
410 $ cat hg.pid > $DAEMON_PIDS
390
411
391 Command frames can be reflected via debugreflect
412 Command frames can be reflected via debugreflect
392
413
393 $ sendhttpraw << EOF
414 $ sendhttpraw << EOF
394 > httprequest POST api/$HTTPV2/ro/debugreflect
415 > httprequest POST api/$HTTPV2/ro/debugreflect
395 > accept: $MEDIATYPE
416 > accept: $MEDIATYPE
396 > content-type: $MEDIATYPE
417 > content-type: $MEDIATYPE
397 > user-agent: test
418 > user-agent: test
398 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
419 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
399 > EOF
420 > EOF
400 using raw connection to peer
421 using raw connection to peer
401 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
422 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
402 s> Accept-Encoding: identity\r\n
423 s> Accept-Encoding: identity\r\n
403 s> accept: application/mercurial-exp-framing-0005\r\n
424 s> accept: application/mercurial-exp-framing-0005\r\n
404 s> content-type: application/mercurial-exp-framing-0005\r\n
425 s> content-type: application/mercurial-exp-framing-0005\r\n
405 s> user-agent: test\r\n
426 s> user-agent: test\r\n
406 s> content-length: 47\r\n
427 s> content-length: 47\r\n
407 s> host: $LOCALIP:$HGPORT\r\n (glob)
428 s> host: $LOCALIP:$HGPORT\r\n (glob)
408 s> \r\n
429 s> \r\n
409 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
430 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
410 s> makefile('rb', None)
431 s> makefile('rb', None)
411 s> HTTP/1.1 200 OK\r\n
432 s> HTTP/1.1 200 OK\r\n
412 s> Server: testing stub value\r\n
433 s> Server: testing stub value\r\n
413 s> Date: $HTTP_DATE$\r\n
434 s> Date: $HTTP_DATE$\r\n
414 s> Content-Type: text/plain\r\n
435 s> Content-Type: text/plain\r\n
415 s> Content-Length: 205\r\n
436 s> Content-Length: 205\r\n
416 s> \r\n
437 s> \r\n
417 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
438 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
418 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
439 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
419 s> received: <no frame>\n
440 s> received: <no frame>\n
420 s> {"action": "noop"}
441 s> {"action": "noop"}
421
442
422 Multiple requests to regular command URL are not allowed
443 Multiple requests to regular command URL are not allowed
423
444
424 $ sendhttpraw << EOF
445 $ sendhttpraw << EOF
425 > httprequest POST api/$HTTPV2/ro/customreadonly
446 > httprequest POST api/$HTTPV2/ro/customreadonly
426 > accept: $MEDIATYPE
447 > accept: $MEDIATYPE
427 > content-type: $MEDIATYPE
448 > content-type: $MEDIATYPE
428 > user-agent: test
449 > user-agent: test
429 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
450 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
430 > EOF
451 > EOF
431 using raw connection to peer
452 using raw connection to peer
432 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
453 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
433 s> Accept-Encoding: identity\r\n
454 s> Accept-Encoding: identity\r\n
434 s> accept: application/mercurial-exp-framing-0005\r\n
455 s> accept: application/mercurial-exp-framing-0005\r\n
435 s> content-type: application/mercurial-exp-framing-0005\r\n
456 s> content-type: application/mercurial-exp-framing-0005\r\n
436 s> user-agent: test\r\n
457 s> user-agent: test\r\n
437 s> content-length: 29\r\n
458 s> content-length: 29\r\n
438 s> host: $LOCALIP:$HGPORT\r\n (glob)
459 s> host: $LOCALIP:$HGPORT\r\n (glob)
439 s> \r\n
460 s> \r\n
440 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
461 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
441 s> makefile('rb', None)
462 s> makefile('rb', None)
442 s> HTTP/1.1 200 OK\r\n
463 s> HTTP/1.1 200 OK\r\n
443 s> Server: testing stub value\r\n
464 s> Server: testing stub value\r\n
444 s> Date: $HTTP_DATE$\r\n
465 s> Date: $HTTP_DATE$\r\n
445 s> Content-Type: application/mercurial-exp-framing-0005\r\n
466 s> Content-Type: application/mercurial-exp-framing-0005\r\n
446 s> Transfer-Encoding: chunked\r\n
467 s> Transfer-Encoding: chunked\r\n
447 s> \r\n
468 s> \r\n
448 s> 32\r\n
469 s> 13\r\n
449 s> *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
470 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
471 s> \r\n
472 s> 27\r\n
473 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
474 s> \r\n
475 s> 8\r\n
476 s> \x00\x00\x00\x01\x00\x02\x002
450 s> \r\n
477 s> \r\n
451 s> 0\r\n
478 s> 0\r\n
452 s> \r\n
479 s> \r\n
453
480
454 Multiple requests to "multirequest" URL are allowed
481 Multiple requests to "multirequest" URL are allowed
455
482
456 $ sendhttpraw << EOF
483 $ sendhttpraw << EOF
457 > httprequest POST api/$HTTPV2/ro/multirequest
484 > httprequest POST api/$HTTPV2/ro/multirequest
458 > accept: $MEDIATYPE
485 > accept: $MEDIATYPE
459 > content-type: $MEDIATYPE
486 > content-type: $MEDIATYPE
460 > user-agent: test
487 > user-agent: test
461 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
488 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
462 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
489 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
463 > EOF
490 > EOF
464 using raw connection to peer
491 using raw connection to peer
465 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
492 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
466 s> Accept-Encoding: identity\r\n
493 s> Accept-Encoding: identity\r\n
467 s> *\r\n (glob)
494 s> *\r\n (glob)
468 s> *\r\n (glob)
495 s> *\r\n (glob)
469 s> user-agent: test\r\n
496 s> user-agent: test\r\n
470 s> content-length: 58\r\n
497 s> content-length: 58\r\n
471 s> host: $LOCALIP:$HGPORT\r\n (glob)
498 s> host: $LOCALIP:$HGPORT\r\n (glob)
472 s> \r\n
499 s> \r\n
473 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
500 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
474 s> makefile('rb', None)
501 s> makefile('rb', None)
475 s> HTTP/1.1 200 OK\r\n
502 s> HTTP/1.1 200 OK\r\n
476 s> Server: testing stub value\r\n
503 s> Server: testing stub value\r\n
477 s> Date: $HTTP_DATE$\r\n
504 s> Date: $HTTP_DATE$\r\n
478 s> Content-Type: application/mercurial-exp-framing-0005\r\n
505 s> Content-Type: application/mercurial-exp-framing-0005\r\n
479 s> Transfer-Encoding: chunked\r\n
506 s> Transfer-Encoding: chunked\r\n
480 s> \r\n
507 s> \r\n
481 s> 32\r\n
508 s> 13\r\n
482 s> *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
509 s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
510 s> \r\n
511 s> 27\r\n
512 s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
513 s> \r\n
514 s> 8\r\n
515 s> \x00\x00\x00\x01\x00\x02\x002
483 s> \r\n
516 s> \r\n
484 s> 32\r\n
517 s> 13\r\n
485 s> *\x00\x00\x03\x00\x02\x002\xa1FstatusBokX\x1dcustomreadonly bytes response
518 s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
519 s> \r\n
520 s> 27\r\n
521 s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
522 s> \r\n
523 s> 8\r\n
524 s> \x00\x00\x00\x03\x00\x02\x002
486 s> \r\n
525 s> \r\n
487 s> 0\r\n
526 s> 0\r\n
488 s> \r\n
527 s> \r\n
489
528
490 Interleaved requests to "multirequest" are processed
529 Interleaved requests to "multirequest" are processed
491
530
492 $ sendhttpraw << EOF
531 $ sendhttpraw << EOF
493 > httprequest POST api/$HTTPV2/ro/multirequest
532 > httprequest POST api/$HTTPV2/ro/multirequest
494 > accept: $MEDIATYPE
533 > accept: $MEDIATYPE
495 > content-type: $MEDIATYPE
534 > content-type: $MEDIATYPE
496 > user-agent: test
535 > user-agent: test
497 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
536 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
498 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
537 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
499 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
538 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
500 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
539 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
501 > EOF
540 > EOF
502 using raw connection to peer
541 using raw connection to peer
503 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
542 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
504 s> Accept-Encoding: identity\r\n
543 s> Accept-Encoding: identity\r\n
505 s> accept: application/mercurial-exp-framing-0005\r\n
544 s> accept: application/mercurial-exp-framing-0005\r\n
506 s> content-type: application/mercurial-exp-framing-0005\r\n
545 s> content-type: application/mercurial-exp-framing-0005\r\n
507 s> user-agent: test\r\n
546 s> user-agent: test\r\n
508 s> content-length: 115\r\n
547 s> content-length: 115\r\n
509 s> host: $LOCALIP:$HGPORT\r\n (glob)
548 s> host: $LOCALIP:$HGPORT\r\n (glob)
510 s> \r\n
549 s> \r\n
511 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
550 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
512 s> makefile('rb', None)
551 s> makefile('rb', None)
513 s> HTTP/1.1 200 OK\r\n
552 s> HTTP/1.1 200 OK\r\n
514 s> Server: testing stub value\r\n
553 s> Server: testing stub value\r\n
515 s> Date: $HTTP_DATE$\r\n
554 s> Date: $HTTP_DATE$\r\n
516 s> Content-Type: application/mercurial-exp-framing-0005\r\n
555 s> Content-Type: application/mercurial-exp-framing-0005\r\n
517 s> Transfer-Encoding: chunked\r\n
556 s> Transfer-Encoding: chunked\r\n
518 s> \r\n
557 s> \r\n
519 s> 33\r\n
558 s> 13\r\n
520 s> +\x00\x00\x03\x00\x02\x012\xa1FstatusBok\xa3Ibookmarks@Jnamespaces@Fphases@
559 s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
560 s> \r\n
561 s> 28\r\n
562 s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
563 s> \r\n
564 s> 8\r\n
565 s> \x00\x00\x00\x03\x00\x02\x002
521 s> \r\n
566 s> \r\n
522 s> 14\r\n
567 s> 13\r\n
523 s> \x0c\x00\x00\x01\x00\x02\x002\xa1FstatusBok\xa0
568 s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
569 s> \r\n
570 s> 9\r\n
571 s> \x01\x00\x00\x01\x00\x02\x001\xa0
572 s> \r\n
573 s> 8\r\n
574 s> \x00\x00\x00\x01\x00\x02\x002
524 s> \r\n
575 s> \r\n
525 s> 0\r\n
576 s> 0\r\n
526 s> \r\n
577 s> \r\n
527
578
528 Restart server to disable read-write access
579 Restart server to disable read-write access
529
580
530 $ killdaemons.py
581 $ killdaemons.py
531 $ cat > server/.hg/hgrc << EOF
582 $ cat > server/.hg/hgrc << EOF
532 > [experimental]
583 > [experimental]
533 > web.apiserver = true
584 > web.apiserver = true
534 > web.api.debugreflect = true
585 > web.api.debugreflect = true
535 > web.api.http-v2 = true
586 > web.api.http-v2 = true
536 > [web]
587 > [web]
537 > push_ssl = false
588 > push_ssl = false
538 > EOF
589 > EOF
539
590
540 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
591 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
541 $ cat hg.pid > $DAEMON_PIDS
592 $ cat hg.pid > $DAEMON_PIDS
542
593
543 Attempting to run a read-write command via multirequest on read-only URL is not allowed
594 Attempting to run a read-write command via multirequest on read-only URL is not allowed
544
595
545 $ sendhttpraw << EOF
596 $ sendhttpraw << EOF
546 > httprequest POST api/$HTTPV2/ro/multirequest
597 > httprequest POST api/$HTTPV2/ro/multirequest
547 > accept: $MEDIATYPE
598 > accept: $MEDIATYPE
548 > content-type: $MEDIATYPE
599 > content-type: $MEDIATYPE
549 > user-agent: test
600 > user-agent: test
550 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
601 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
551 > EOF
602 > EOF
552 using raw connection to peer
603 using raw connection to peer
553 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
604 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
554 s> Accept-Encoding: identity\r\n
605 s> Accept-Encoding: identity\r\n
555 s> accept: application/mercurial-exp-framing-0005\r\n
606 s> accept: application/mercurial-exp-framing-0005\r\n
556 s> content-type: application/mercurial-exp-framing-0005\r\n
607 s> content-type: application/mercurial-exp-framing-0005\r\n
557 s> user-agent: test\r\n
608 s> user-agent: test\r\n
558 s> content-length: 22\r\n
609 s> content-length: 22\r\n
559 s> host: $LOCALIP:$HGPORT\r\n (glob)
610 s> host: $LOCALIP:$HGPORT\r\n (glob)
560 s> \r\n
611 s> \r\n
561 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
612 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
562 s> makefile('rb', None)
613 s> makefile('rb', None)
563 s> HTTP/1.1 403 Forbidden\r\n
614 s> HTTP/1.1 403 Forbidden\r\n
564 s> Server: testing stub value\r\n
615 s> Server: testing stub value\r\n
565 s> Date: $HTTP_DATE$\r\n
616 s> Date: $HTTP_DATE$\r\n
566 s> Content-Type: text/plain\r\n
617 s> Content-Type: text/plain\r\n
567 s> Content-Length: 52\r\n
618 s> Content-Length: 52\r\n
568 s> \r\n
619 s> \r\n
569 s> insufficient permissions to execute command: pushkey
620 s> insufficient permissions to execute command: pushkey
570
621
571 $ cat error.log
622 $ cat error.log
@@ -1,740 +1,749 b''
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4
4
5 $ cat >> $HGRCPATH << EOF
5 $ cat >> $HGRCPATH << EOF
6 > [web]
6 > [web]
7 > push_ssl = false
7 > push_ssl = false
8 > allow_push = *
8 > allow_push = *
9 > EOF
9 > EOF
10
10
11 $ hg init server
11 $ hg init server
12 $ cd server
12 $ cd server
13 $ touch a
13 $ touch a
14 $ hg -q commit -A -m initial
14 $ hg -q commit -A -m initial
15 $ cd ..
15 $ cd ..
16
16
17 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
17 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
18 $ cat hg.pid >> $DAEMON_PIDS
18 $ cat hg.pid >> $DAEMON_PIDS
19
19
20 compression formats are advertised in compression capability
20 compression formats are advertised in compression capability
21
21
22 #if zstd
22 #if zstd
23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
24 #else
24 #else
25 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
25 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
26 #endif
26 #endif
27
27
28 $ killdaemons.py
28 $ killdaemons.py
29
29
30 server.compressionengines can replace engines list wholesale
30 server.compressionengines can replace engines list wholesale
31
31
32 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
32 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
33 $ cat hg.pid > $DAEMON_PIDS
33 $ cat hg.pid > $DAEMON_PIDS
34 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
34 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
35
35
36 $ killdaemons.py
36 $ killdaemons.py
37
37
38 Order of engines can also change
38 Order of engines can also change
39
39
40 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
40 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
41 $ cat hg.pid > $DAEMON_PIDS
41 $ cat hg.pid > $DAEMON_PIDS
42 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
42 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
43
43
44 $ killdaemons.py
44 $ killdaemons.py
45
45
46 Start a default server again
46 Start a default server again
47
47
48 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
48 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
49 $ cat hg.pid > $DAEMON_PIDS
49 $ cat hg.pid > $DAEMON_PIDS
50
50
51 Server should send application/mercurial-0.1 to clients if no Accept is used
51 Server should send application/mercurial-0.1 to clients if no Accept is used
52
52
53 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
53 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
54 200 Script output follows
54 200 Script output follows
55 content-type: application/mercurial-0.1
55 content-type: application/mercurial-0.1
56 date: $HTTP_DATE$
56 date: $HTTP_DATE$
57 server: testing stub value
57 server: testing stub value
58 transfer-encoding: chunked
58 transfer-encoding: chunked
59
59
60 Server should send application/mercurial-0.1 when client says it wants it
60 Server should send application/mercurial-0.1 when client says it wants it
61
61
62 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
62 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
63 200 Script output follows
63 200 Script output follows
64 content-type: application/mercurial-0.1
64 content-type: application/mercurial-0.1
65 date: $HTTP_DATE$
65 date: $HTTP_DATE$
66 server: testing stub value
66 server: testing stub value
67 transfer-encoding: chunked
67 transfer-encoding: chunked
68
68
69 Server should send application/mercurial-0.2 when client says it wants it
69 Server should send application/mercurial-0.2 when client says it wants it
70
70
71 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
71 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
72 200 Script output follows
72 200 Script output follows
73 content-type: application/mercurial-0.2
73 content-type: application/mercurial-0.2
74 date: $HTTP_DATE$
74 date: $HTTP_DATE$
75 server: testing stub value
75 server: testing stub value
76 transfer-encoding: chunked
76 transfer-encoding: chunked
77
77
78 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
78 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
79 200 Script output follows
79 200 Script output follows
80 content-type: application/mercurial-0.2
80 content-type: application/mercurial-0.2
81 date: $HTTP_DATE$
81 date: $HTTP_DATE$
82 server: testing stub value
82 server: testing stub value
83 transfer-encoding: chunked
83 transfer-encoding: chunked
84
84
85 Requesting a compression format that server doesn't support results will fall back to 0.1
85 Requesting a compression format that server doesn't support results will fall back to 0.1
86
86
87 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
87 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
88 200 Script output follows
88 200 Script output follows
89 content-type: application/mercurial-0.1
89 content-type: application/mercurial-0.1
90 date: $HTTP_DATE$
90 date: $HTTP_DATE$
91 server: testing stub value
91 server: testing stub value
92 transfer-encoding: chunked
92 transfer-encoding: chunked
93
93
94 #if zstd
94 #if zstd
95 zstd is used if available
95 zstd is used if available
96
96
97 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
97 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
98 $ f --size --hexdump --bytes 36 --sha1 resp
98 $ f --size --hexdump --bytes 36 --sha1 resp
99 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
99 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
100 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
100 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
101 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
101 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
102 0020: 28 b5 2f fd |(./.|
102 0020: 28 b5 2f fd |(./.|
103
103
104 #endif
104 #endif
105
105
106 application/mercurial-0.2 is not yet used on non-streaming responses
106 application/mercurial-0.2 is not yet used on non-streaming responses
107
107
108 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
108 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
109 200 Script output follows
109 200 Script output follows
110 content-length: 41
110 content-length: 41
111 content-type: application/mercurial-0.1
111 content-type: application/mercurial-0.1
112 date: $HTTP_DATE$
112 date: $HTTP_DATE$
113 server: testing stub value
113 server: testing stub value
114
114
115 e93700bd72895c5addab234c56d4024b487a362f
115 e93700bd72895c5addab234c56d4024b487a362f
116
116
117 Now test protocol preference usage
117 Now test protocol preference usage
118
118
119 $ killdaemons.py
119 $ killdaemons.py
120 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
120 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
121 $ cat hg.pid > $DAEMON_PIDS
121 $ cat hg.pid > $DAEMON_PIDS
122
122
123 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
123 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
124
124
125 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
125 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
126 200 Script output follows
126 200 Script output follows
127 content-type: application/mercurial-0.1
127 content-type: application/mercurial-0.1
128
128
129 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
129 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
130 $ f --size --hexdump --bytes 28 --sha1 resp
130 $ f --size --hexdump --bytes 28 --sha1 resp
131 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
131 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
132 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
132 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
133 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
133 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
134
134
135 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
135 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
136
136
137 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
137 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
138 $ f --size --hexdump --bytes 28 --sha1 resp
138 $ f --size --hexdump --bytes 28 --sha1 resp
139 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
139 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
140 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
140 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
141 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
141 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
142
142
143 0.2 with no compression will get "none" because that is server's preference
143 0.2 with no compression will get "none" because that is server's preference
144 (spec says ZL and UN are implicitly supported)
144 (spec says ZL and UN are implicitly supported)
145
145
146 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
146 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
147 $ f --size --hexdump --bytes 32 --sha1 resp
147 $ f --size --hexdump --bytes 32 --sha1 resp
148 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
148 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
149 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
149 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
150 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
150 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
151
151
152 Client receives server preference even if local order doesn't match
152 Client receives server preference even if local order doesn't match
153
153
154 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
154 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
155 $ f --size --hexdump --bytes 32 --sha1 resp
155 $ f --size --hexdump --bytes 32 --sha1 resp
156 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
156 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
157 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
157 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
158 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
158 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
159
159
160 Client receives only supported format even if not server preferred format
160 Client receives only supported format even if not server preferred format
161
161
162 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
162 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
163 $ f --size --hexdump --bytes 33 --sha1 resp
163 $ f --size --hexdump --bytes 33 --sha1 resp
164 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
164 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
165 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
165 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
166 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
166 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
167 0020: 78 |x|
167 0020: 78 |x|
168
168
169 $ killdaemons.py
169 $ killdaemons.py
170 $ cd ..
170 $ cd ..
171
171
172 Test listkeys for listing namespaces
172 Test listkeys for listing namespaces
173
173
174 $ hg init empty
174 $ hg init empty
175 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
175 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
176 $ cat hg.pid > $DAEMON_PIDS
176 $ cat hg.pid > $DAEMON_PIDS
177
177
178 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
178 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
179 > command listkeys
179 > command listkeys
180 > namespace namespaces
180 > namespace namespaces
181 > EOF
181 > EOF
182 s> GET /?cmd=capabilities HTTP/1.1\r\n
182 s> GET /?cmd=capabilities HTTP/1.1\r\n
183 s> Accept-Encoding: identity\r\n
183 s> Accept-Encoding: identity\r\n
184 s> accept: application/mercurial-0.1\r\n
184 s> accept: application/mercurial-0.1\r\n
185 s> host: $LOCALIP:$HGPORT\r\n (glob)
185 s> host: $LOCALIP:$HGPORT\r\n (glob)
186 s> user-agent: Mercurial debugwireproto\r\n
186 s> user-agent: Mercurial debugwireproto\r\n
187 s> \r\n
187 s> \r\n
188 s> makefile('rb', None)
188 s> makefile('rb', None)
189 s> HTTP/1.1 200 Script output follows\r\n
189 s> HTTP/1.1 200 Script output follows\r\n
190 s> Server: testing stub value\r\n
190 s> Server: testing stub value\r\n
191 s> Date: $HTTP_DATE$\r\n
191 s> Date: $HTTP_DATE$\r\n
192 s> Content-Type: application/mercurial-0.1\r\n
192 s> Content-Type: application/mercurial-0.1\r\n
193 s> Content-Length: *\r\n (glob)
193 s> Content-Length: *\r\n (glob)
194 s> \r\n
194 s> \r\n
195 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
195 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
196 sending listkeys command
196 sending listkeys command
197 s> GET /?cmd=listkeys HTTP/1.1\r\n
197 s> GET /?cmd=listkeys HTTP/1.1\r\n
198 s> Accept-Encoding: identity\r\n
198 s> Accept-Encoding: identity\r\n
199 s> vary: X-HgArg-1,X-HgProto-1\r\n
199 s> vary: X-HgArg-1,X-HgProto-1\r\n
200 s> x-hgarg-1: namespace=namespaces\r\n
200 s> x-hgarg-1: namespace=namespaces\r\n
201 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
201 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
202 s> accept: application/mercurial-0.1\r\n
202 s> accept: application/mercurial-0.1\r\n
203 s> host: $LOCALIP:$HGPORT\r\n (glob)
203 s> host: $LOCALIP:$HGPORT\r\n (glob)
204 s> user-agent: Mercurial debugwireproto\r\n
204 s> user-agent: Mercurial debugwireproto\r\n
205 s> \r\n
205 s> \r\n
206 s> makefile('rb', None)
206 s> makefile('rb', None)
207 s> HTTP/1.1 200 Script output follows\r\n
207 s> HTTP/1.1 200 Script output follows\r\n
208 s> Server: testing stub value\r\n
208 s> Server: testing stub value\r\n
209 s> Date: $HTTP_DATE$\r\n
209 s> Date: $HTTP_DATE$\r\n
210 s> Content-Type: application/mercurial-0.1\r\n
210 s> Content-Type: application/mercurial-0.1\r\n
211 s> Content-Length: 30\r\n
211 s> Content-Length: 30\r\n
212 s> \r\n
212 s> \r\n
213 s> bookmarks\t\n
213 s> bookmarks\t\n
214 s> namespaces\t\n
214 s> namespaces\t\n
215 s> phases\t
215 s> phases\t
216 response: {
216 response: {
217 b'bookmarks': b'',
217 b'bookmarks': b'',
218 b'namespaces': b'',
218 b'namespaces': b'',
219 b'phases': b''
219 b'phases': b''
220 }
220 }
221
221
222 Same thing, but with "httprequest" command
222 Same thing, but with "httprequest" command
223
223
224 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
224 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
225 > httprequest GET ?cmd=listkeys
225 > httprequest GET ?cmd=listkeys
226 > user-agent: test
226 > user-agent: test
227 > x-hgarg-1: namespace=namespaces
227 > x-hgarg-1: namespace=namespaces
228 > EOF
228 > EOF
229 using raw connection to peer
229 using raw connection to peer
230 s> GET /?cmd=listkeys HTTP/1.1\r\n
230 s> GET /?cmd=listkeys HTTP/1.1\r\n
231 s> Accept-Encoding: identity\r\n
231 s> Accept-Encoding: identity\r\n
232 s> user-agent: test\r\n
232 s> user-agent: test\r\n
233 s> x-hgarg-1: namespace=namespaces\r\n
233 s> x-hgarg-1: namespace=namespaces\r\n
234 s> host: $LOCALIP:$HGPORT\r\n (glob)
234 s> host: $LOCALIP:$HGPORT\r\n (glob)
235 s> \r\n
235 s> \r\n
236 s> makefile('rb', None)
236 s> makefile('rb', None)
237 s> HTTP/1.1 200 Script output follows\r\n
237 s> HTTP/1.1 200 Script output follows\r\n
238 s> Server: testing stub value\r\n
238 s> Server: testing stub value\r\n
239 s> Date: $HTTP_DATE$\r\n
239 s> Date: $HTTP_DATE$\r\n
240 s> Content-Type: application/mercurial-0.1\r\n
240 s> Content-Type: application/mercurial-0.1\r\n
241 s> Content-Length: 30\r\n
241 s> Content-Length: 30\r\n
242 s> \r\n
242 s> \r\n
243 s> bookmarks\t\n
243 s> bookmarks\t\n
244 s> namespaces\t\n
244 s> namespaces\t\n
245 s> phases\t
245 s> phases\t
246
246
247 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
247 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
248
248
249 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
249 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
250 > command heads
250 > command heads
251 > EOF
251 > EOF
252 s> GET /?cmd=capabilities HTTP/1.1\r\n
252 s> GET /?cmd=capabilities HTTP/1.1\r\n
253 s> Accept-Encoding: identity\r\n
253 s> Accept-Encoding: identity\r\n
254 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
254 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
255 s> x-hgproto-1: cbor\r\n
255 s> x-hgproto-1: cbor\r\n
256 s> x-hgupgrade-1: exp-http-v2-0001\r\n
256 s> x-hgupgrade-1: exp-http-v2-0001\r\n
257 s> accept: application/mercurial-0.1\r\n
257 s> accept: application/mercurial-0.1\r\n
258 s> host: $LOCALIP:$HGPORT\r\n (glob)
258 s> host: $LOCALIP:$HGPORT\r\n (glob)
259 s> user-agent: Mercurial debugwireproto\r\n
259 s> user-agent: Mercurial debugwireproto\r\n
260 s> \r\n
260 s> \r\n
261 s> makefile('rb', None)
261 s> makefile('rb', None)
262 s> HTTP/1.1 200 Script output follows\r\n
262 s> HTTP/1.1 200 Script output follows\r\n
263 s> Server: testing stub value\r\n
263 s> Server: testing stub value\r\n
264 s> Date: $HTTP_DATE$\r\n
264 s> Date: $HTTP_DATE$\r\n
265 s> Content-Type: application/mercurial-0.1\r\n
265 s> Content-Type: application/mercurial-0.1\r\n
266 s> Content-Length: *\r\n (glob)
266 s> Content-Length: *\r\n (glob)
267 s> \r\n
267 s> \r\n
268 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
268 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
269 sending heads command
269 sending heads command
270 s> GET /?cmd=heads HTTP/1.1\r\n
270 s> GET /?cmd=heads HTTP/1.1\r\n
271 s> Accept-Encoding: identity\r\n
271 s> Accept-Encoding: identity\r\n
272 s> vary: X-HgProto-1\r\n
272 s> vary: X-HgProto-1\r\n
273 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
273 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
274 s> accept: application/mercurial-0.1\r\n
274 s> accept: application/mercurial-0.1\r\n
275 s> host: $LOCALIP:$HGPORT\r\n (glob)
275 s> host: $LOCALIP:$HGPORT\r\n (glob)
276 s> user-agent: Mercurial debugwireproto\r\n
276 s> user-agent: Mercurial debugwireproto\r\n
277 s> \r\n
277 s> \r\n
278 s> makefile('rb', None)
278 s> makefile('rb', None)
279 s> HTTP/1.1 200 Script output follows\r\n
279 s> HTTP/1.1 200 Script output follows\r\n
280 s> Server: testing stub value\r\n
280 s> Server: testing stub value\r\n
281 s> Date: $HTTP_DATE$\r\n
281 s> Date: $HTTP_DATE$\r\n
282 s> Content-Type: application/mercurial-0.1\r\n
282 s> Content-Type: application/mercurial-0.1\r\n
283 s> Content-Length: 41\r\n
283 s> Content-Length: 41\r\n
284 s> \r\n
284 s> \r\n
285 s> 0000000000000000000000000000000000000000\n
285 s> 0000000000000000000000000000000000000000\n
286 response: [
286 response: [
287 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
287 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
288 ]
288 ]
289
289
290 $ killdaemons.py
290 $ killdaemons.py
291 $ enablehttpv2 empty
291 $ enablehttpv2 empty
292 $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid
292 $ hg --config server.compressionengines=zlib -R empty serve -p $HGPORT -d --pid-file hg.pid
293 $ cat hg.pid > $DAEMON_PIDS
293 $ cat hg.pid > $DAEMON_PIDS
294
294
295 Client with HTTPv2 enabled automatically upgrades if the server supports it
295 Client with HTTPv2 enabled automatically upgrades if the server supports it
296
296
297 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
297 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
298 > command heads
298 > command heads
299 > EOF
299 > EOF
300 s> GET /?cmd=capabilities HTTP/1.1\r\n
300 s> GET /?cmd=capabilities HTTP/1.1\r\n
301 s> Accept-Encoding: identity\r\n
301 s> Accept-Encoding: identity\r\n
302 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
302 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
303 s> x-hgproto-1: cbor\r\n
303 s> x-hgproto-1: cbor\r\n
304 s> x-hgupgrade-1: exp-http-v2-0001\r\n
304 s> x-hgupgrade-1: exp-http-v2-0001\r\n
305 s> accept: application/mercurial-0.1\r\n
305 s> accept: application/mercurial-0.1\r\n
306 s> host: $LOCALIP:$HGPORT\r\n (glob)
306 s> host: $LOCALIP:$HGPORT\r\n (glob)
307 s> user-agent: Mercurial debugwireproto\r\n
307 s> user-agent: Mercurial debugwireproto\r\n
308 s> \r\n
308 s> \r\n
309 s> makefile('rb', None)
309 s> makefile('rb', None)
310 s> HTTP/1.1 200 OK\r\n
310 s> HTTP/1.1 200 OK\r\n
311 s> Server: testing stub value\r\n
311 s> Server: testing stub value\r\n
312 s> Date: $HTTP_DATE$\r\n
312 s> Date: $HTTP_DATE$\r\n
313 s> Content-Type: application/mercurial-cbor\r\n
313 s> Content-Type: application/mercurial-cbor\r\n
314 s> Content-Length: *\r\n (glob)
314 s> Content-Length: *\r\n (glob)
315 s> \r\n
315 s> \r\n
316 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
316 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
317 sending heads command
317 sending heads command
318 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
318 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
319 s> Accept-Encoding: identity\r\n
319 s> Accept-Encoding: identity\r\n
320 s> accept: application/mercurial-exp-framing-0005\r\n
320 s> accept: application/mercurial-exp-framing-0005\r\n
321 s> content-type: application/mercurial-exp-framing-0005\r\n
321 s> content-type: application/mercurial-exp-framing-0005\r\n
322 s> content-length: 20\r\n
322 s> content-length: 20\r\n
323 s> host: $LOCALIP:$HGPORT\r\n (glob)
323 s> host: $LOCALIP:$HGPORT\r\n (glob)
324 s> user-agent: Mercurial debugwireproto\r\n
324 s> user-agent: Mercurial debugwireproto\r\n
325 s> \r\n
325 s> \r\n
326 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
326 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
327 s> makefile('rb', None)
327 s> makefile('rb', None)
328 s> HTTP/1.1 200 OK\r\n
328 s> HTTP/1.1 200 OK\r\n
329 s> Server: testing stub value\r\n
329 s> Server: testing stub value\r\n
330 s> Date: $HTTP_DATE$\r\n
330 s> Date: $HTTP_DATE$\r\n
331 s> Content-Type: application/mercurial-exp-framing-0005\r\n
331 s> Content-Type: application/mercurial-exp-framing-0005\r\n
332 s> Transfer-Encoding: chunked\r\n
332 s> Transfer-Encoding: chunked\r\n
333 s> \r\n
333 s> \r\n
334 s> 29\r\n
334 s> 13\r\n
335 s> !\x00\x00\x01\x00\x02\x012
335 s> \x0b\x00\x00\x01\x00\x02\x011
336 s> \xa1FstatusBok\x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
336 s> \xa1FstatusBok
337 s> \r\n
337 s> \r\n
338 received frame(size=33; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
338 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
339 s> 1e\r\n
340 s> \x16\x00\x00\x01\x00\x02\x001
341 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
342 s> \r\n
343 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
344 s> 8\r\n
345 s> \x00\x00\x00\x01\x00\x02\x002
346 s> \r\n
339 s> 0\r\n
347 s> 0\r\n
340 s> \r\n
348 s> \r\n
349 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
341 response: [
350 response: [
342 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
351 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
343 ]
352 ]
344
353
345 $ killdaemons.py
354 $ killdaemons.py
346
355
347 HTTP client follows HTTP redirect on handshake to new repo
356 HTTP client follows HTTP redirect on handshake to new repo
348
357
349 $ cd $TESTTMP
358 $ cd $TESTTMP
350
359
351 $ hg init redirector
360 $ hg init redirector
352 $ hg init redirected
361 $ hg init redirected
353 $ cd redirected
362 $ cd redirected
354 $ touch foo
363 $ touch foo
355 $ hg -q commit -A -m initial
364 $ hg -q commit -A -m initial
356 $ cd ..
365 $ cd ..
357
366
358 $ cat > paths.conf << EOF
367 $ cat > paths.conf << EOF
359 > [paths]
368 > [paths]
360 > / = $TESTTMP/*
369 > / = $TESTTMP/*
361 > EOF
370 > EOF
362
371
363 $ cat > redirectext.py << EOF
372 $ cat > redirectext.py << EOF
364 > from mercurial import extensions, wireprotoserver
373 > from mercurial import extensions, wireprotoserver
365 > def wrappedcallhttp(orig, repo, req, res, proto, cmd):
374 > def wrappedcallhttp(orig, repo, req, res, proto, cmd):
366 > path = req.advertisedurl[len(req.advertisedbaseurl):]
375 > path = req.advertisedurl[len(req.advertisedbaseurl):]
367 > if not path.startswith(b'/redirector'):
376 > if not path.startswith(b'/redirector'):
368 > return orig(repo, req, res, proto, cmd)
377 > return orig(repo, req, res, proto, cmd)
369 > relpath = path[len(b'/redirector'):]
378 > relpath = path[len(b'/redirector'):]
370 > res.status = b'301 Redirect'
379 > res.status = b'301 Redirect'
371 > newurl = b'%s/redirected%s' % (req.baseurl, relpath)
380 > newurl = b'%s/redirected%s' % (req.baseurl, relpath)
372 > if not repo.ui.configbool('testing', 'redirectqs', True) and b'?' in newurl:
381 > if not repo.ui.configbool('testing', 'redirectqs', True) and b'?' in newurl:
373 > newurl = newurl[0:newurl.index(b'?')]
382 > newurl = newurl[0:newurl.index(b'?')]
374 > res.headers[b'Location'] = newurl
383 > res.headers[b'Location'] = newurl
375 > res.headers[b'Content-Type'] = b'text/plain'
384 > res.headers[b'Content-Type'] = b'text/plain'
376 > res.setbodybytes(b'redirected')
385 > res.setbodybytes(b'redirected')
377 > return True
386 > return True
378 >
387 >
379 > extensions.wrapfunction(wireprotoserver, '_callhttp', wrappedcallhttp)
388 > extensions.wrapfunction(wireprotoserver, '_callhttp', wrappedcallhttp)
380 > EOF
389 > EOF
381
390
382 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
391 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
383 > --config server.compressionengines=zlib \
392 > --config server.compressionengines=zlib \
384 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
393 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
385 $ cat hg.pid > $DAEMON_PIDS
394 $ cat hg.pid > $DAEMON_PIDS
386
395
387 Verify our HTTP 301 is served properly
396 Verify our HTTP 301 is served properly
388
397
389 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
398 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
390 > httprequest GET /redirector?cmd=capabilities
399 > httprequest GET /redirector?cmd=capabilities
391 > user-agent: test
400 > user-agent: test
392 > EOF
401 > EOF
393 using raw connection to peer
402 using raw connection to peer
394 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
403 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
395 s> Accept-Encoding: identity\r\n
404 s> Accept-Encoding: identity\r\n
396 s> user-agent: test\r\n
405 s> user-agent: test\r\n
397 s> host: $LOCALIP:$HGPORT\r\n (glob)
406 s> host: $LOCALIP:$HGPORT\r\n (glob)
398 s> \r\n
407 s> \r\n
399 s> makefile('rb', None)
408 s> makefile('rb', None)
400 s> HTTP/1.1 301 Redirect\r\n
409 s> HTTP/1.1 301 Redirect\r\n
401 s> Server: testing stub value\r\n
410 s> Server: testing stub value\r\n
402 s> Date: $HTTP_DATE$\r\n
411 s> Date: $HTTP_DATE$\r\n
403 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
412 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
404 s> Content-Type: text/plain\r\n
413 s> Content-Type: text/plain\r\n
405 s> Content-Length: 10\r\n
414 s> Content-Length: 10\r\n
406 s> \r\n
415 s> \r\n
407 s> redirected
416 s> redirected
408 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
417 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
409 s> Accept-Encoding: identity\r\n
418 s> Accept-Encoding: identity\r\n
410 s> user-agent: test\r\n
419 s> user-agent: test\r\n
411 s> host: $LOCALIP:$HGPORT\r\n (glob)
420 s> host: $LOCALIP:$HGPORT\r\n (glob)
412 s> \r\n
421 s> \r\n
413 s> makefile('rb', None)
422 s> makefile('rb', None)
414 s> HTTP/1.1 200 Script output follows\r\n
423 s> HTTP/1.1 200 Script output follows\r\n
415 s> Server: testing stub value\r\n
424 s> Server: testing stub value\r\n
416 s> Date: $HTTP_DATE$\r\n
425 s> Date: $HTTP_DATE$\r\n
417 s> Content-Type: application/mercurial-0.1\r\n
426 s> Content-Type: application/mercurial-0.1\r\n
418 s> Content-Length: 453\r\n
427 s> Content-Length: 453\r\n
419 s> \r\n
428 s> \r\n
420 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
429 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
421
430
422 Test with the HTTP peer
431 Test with the HTTP peer
423
432
424 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
433 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
425 > command heads
434 > command heads
426 > EOF
435 > EOF
427 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
436 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
428 s> Accept-Encoding: identity\r\n
437 s> Accept-Encoding: identity\r\n
429 s> accept: application/mercurial-0.1\r\n
438 s> accept: application/mercurial-0.1\r\n
430 s> host: $LOCALIP:$HGPORT\r\n (glob)
439 s> host: $LOCALIP:$HGPORT\r\n (glob)
431 s> user-agent: Mercurial debugwireproto\r\n
440 s> user-agent: Mercurial debugwireproto\r\n
432 s> \r\n
441 s> \r\n
433 s> makefile('rb', None)
442 s> makefile('rb', None)
434 s> HTTP/1.1 301 Redirect\r\n
443 s> HTTP/1.1 301 Redirect\r\n
435 s> Server: testing stub value\r\n
444 s> Server: testing stub value\r\n
436 s> Date: $HTTP_DATE$\r\n
445 s> Date: $HTTP_DATE$\r\n
437 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
446 s> Location: http://$LOCALIP:$HGPORT/redirected?cmd=capabilities\r\n (glob)
438 s> Content-Type: text/plain\r\n
447 s> Content-Type: text/plain\r\n
439 s> Content-Length: 10\r\n
448 s> Content-Length: 10\r\n
440 s> \r\n
449 s> \r\n
441 s> redirected
450 s> redirected
442 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
451 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
443 s> Accept-Encoding: identity\r\n
452 s> Accept-Encoding: identity\r\n
444 s> accept: application/mercurial-0.1\r\n
453 s> accept: application/mercurial-0.1\r\n
445 s> host: $LOCALIP:$HGPORT\r\n (glob)
454 s> host: $LOCALIP:$HGPORT\r\n (glob)
446 s> user-agent: Mercurial debugwireproto\r\n
455 s> user-agent: Mercurial debugwireproto\r\n
447 s> \r\n
456 s> \r\n
448 s> makefile('rb', None)
457 s> makefile('rb', None)
449 s> HTTP/1.1 200 Script output follows\r\n
458 s> HTTP/1.1 200 Script output follows\r\n
450 s> Server: testing stub value\r\n
459 s> Server: testing stub value\r\n
451 s> Date: $HTTP_DATE$\r\n
460 s> Date: $HTTP_DATE$\r\n
452 s> Content-Type: application/mercurial-0.1\r\n
461 s> Content-Type: application/mercurial-0.1\r\n
453 s> Content-Length: 453\r\n
462 s> Content-Length: 453\r\n
454 s> \r\n
463 s> \r\n
455 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
464 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
456 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
465 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
457 sending heads command
466 sending heads command
458 s> GET /redirected?cmd=heads HTTP/1.1\r\n
467 s> GET /redirected?cmd=heads HTTP/1.1\r\n
459 s> Accept-Encoding: identity\r\n
468 s> Accept-Encoding: identity\r\n
460 s> vary: X-HgProto-1\r\n
469 s> vary: X-HgProto-1\r\n
461 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
470 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
462 s> accept: application/mercurial-0.1\r\n
471 s> accept: application/mercurial-0.1\r\n
463 s> host: $LOCALIP:$HGPORT\r\n (glob)
472 s> host: $LOCALIP:$HGPORT\r\n (glob)
464 s> user-agent: Mercurial debugwireproto\r\n
473 s> user-agent: Mercurial debugwireproto\r\n
465 s> \r\n
474 s> \r\n
466 s> makefile('rb', None)
475 s> makefile('rb', None)
467 s> HTTP/1.1 200 Script output follows\r\n
476 s> HTTP/1.1 200 Script output follows\r\n
468 s> Server: testing stub value\r\n
477 s> Server: testing stub value\r\n
469 s> Date: $HTTP_DATE$\r\n
478 s> Date: $HTTP_DATE$\r\n
470 s> Content-Type: application/mercurial-0.1\r\n
479 s> Content-Type: application/mercurial-0.1\r\n
471 s> Content-Length: 41\r\n
480 s> Content-Length: 41\r\n
472 s> \r\n
481 s> \r\n
473 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
482 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
474 response: [
483 response: [
475 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
484 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
476 ]
485 ]
477
486
478 $ killdaemons.py
487 $ killdaemons.py
479
488
480 Now test a variation where we strip the query string from the redirect URL.
489 Now test a variation where we strip the query string from the redirect URL.
481 (SCM Manager apparently did this and clients would recover from it)
490 (SCM Manager apparently did this and clients would recover from it)
482
491
483 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
492 $ hg --config extensions.redirect=$TESTTMP/redirectext.py \
484 > --config server.compressionengines=zlib \
493 > --config server.compressionengines=zlib \
485 > --config testing.redirectqs=false \
494 > --config testing.redirectqs=false \
486 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
495 > serve --web-conf paths.conf --pid-file hg.pid -p $HGPORT -d
487 $ cat hg.pid > $DAEMON_PIDS
496 $ cat hg.pid > $DAEMON_PIDS
488
497
489 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
498 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
490 > httprequest GET /redirector?cmd=capabilities
499 > httprequest GET /redirector?cmd=capabilities
491 > user-agent: test
500 > user-agent: test
492 > EOF
501 > EOF
493 using raw connection to peer
502 using raw connection to peer
494 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
503 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
495 s> Accept-Encoding: identity\r\n
504 s> Accept-Encoding: identity\r\n
496 s> user-agent: test\r\n
505 s> user-agent: test\r\n
497 s> host: $LOCALIP:$HGPORT\r\n (glob)
506 s> host: $LOCALIP:$HGPORT\r\n (glob)
498 s> \r\n
507 s> \r\n
499 s> makefile('rb', None)
508 s> makefile('rb', None)
500 s> HTTP/1.1 301 Redirect\r\n
509 s> HTTP/1.1 301 Redirect\r\n
501 s> Server: testing stub value\r\n
510 s> Server: testing stub value\r\n
502 s> Date: $HTTP_DATE$\r\n
511 s> Date: $HTTP_DATE$\r\n
503 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
512 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
504 s> Content-Type: text/plain\r\n
513 s> Content-Type: text/plain\r\n
505 s> Content-Length: 10\r\n
514 s> Content-Length: 10\r\n
506 s> \r\n
515 s> \r\n
507 s> redirected
516 s> redirected
508 s> GET /redirected HTTP/1.1\r\n
517 s> GET /redirected HTTP/1.1\r\n
509 s> Accept-Encoding: identity\r\n
518 s> Accept-Encoding: identity\r\n
510 s> user-agent: test\r\n
519 s> user-agent: test\r\n
511 s> host: $LOCALIP:$HGPORT\r\n (glob)
520 s> host: $LOCALIP:$HGPORT\r\n (glob)
512 s> \r\n
521 s> \r\n
513 s> makefile('rb', None)
522 s> makefile('rb', None)
514 s> HTTP/1.1 200 Script output follows\r\n
523 s> HTTP/1.1 200 Script output follows\r\n
515 s> Server: testing stub value\r\n
524 s> Server: testing stub value\r\n
516 s> Date: $HTTP_DATE$\r\n
525 s> Date: $HTTP_DATE$\r\n
517 s> ETag: W/"*"\r\n (glob)
526 s> ETag: W/"*"\r\n (glob)
518 s> Content-Type: text/html; charset=ascii\r\n
527 s> Content-Type: text/html; charset=ascii\r\n
519 s> Transfer-Encoding: chunked\r\n
528 s> Transfer-Encoding: chunked\r\n
520 s> \r\n
529 s> \r\n
521 s> 414\r\n
530 s> 414\r\n
522 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
531 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
523 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
532 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
524 s> <head>\n
533 s> <head>\n
525 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
534 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
526 s> <meta name="robots" content="index, nofollow" />\n
535 s> <meta name="robots" content="index, nofollow" />\n
527 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
536 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
528 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
537 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
529 s> \n
538 s> \n
530 s> <title>redirected: log</title>\n
539 s> <title>redirected: log</title>\n
531 s> <link rel="alternate" type="application/atom+xml"\n
540 s> <link rel="alternate" type="application/atom+xml"\n
532 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
541 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
533 s> <link rel="alternate" type="application/rss+xml"\n
542 s> <link rel="alternate" type="application/rss+xml"\n
534 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
543 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
535 s> </head>\n
544 s> </head>\n
536 s> <body>\n
545 s> <body>\n
537 s> \n
546 s> \n
538 s> <div class="container">\n
547 s> <div class="container">\n
539 s> <div class="menu">\n
548 s> <div class="menu">\n
540 s> <div class="logo">\n
549 s> <div class="logo">\n
541 s> <a href="https://mercurial-scm.org/">\n
550 s> <a href="https://mercurial-scm.org/">\n
542 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
551 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
543 s> </div>\n
552 s> </div>\n
544 s> <ul>\n
553 s> <ul>\n
545 s> <li class="active">log</li>\n
554 s> <li class="active">log</li>\n
546 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
555 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
547 s> <li><a href="/redirected/tags">tags</a></li>\n
556 s> <li><a href="/redirected/tags">tags</a></li>\n
548 s> <li><a href="
557 s> <li><a href="
549 s> \r\n
558 s> \r\n
550 s> 810\r\n
559 s> 810\r\n
551 s> /redirected/bookmarks">bookmarks</a></li>\n
560 s> /redirected/bookmarks">bookmarks</a></li>\n
552 s> <li><a href="/redirected/branches">branches</a></li>\n
561 s> <li><a href="/redirected/branches">branches</a></li>\n
553 s> </ul>\n
562 s> </ul>\n
554 s> <ul>\n
563 s> <ul>\n
555 s> <li><a href="/redirected/rev/tip">changeset</a></li>\n
564 s> <li><a href="/redirected/rev/tip">changeset</a></li>\n
556 s> <li><a href="/redirected/file/tip">browse</a></li>\n
565 s> <li><a href="/redirected/file/tip">browse</a></li>\n
557 s> </ul>\n
566 s> </ul>\n
558 s> <ul>\n
567 s> <ul>\n
559 s> \n
568 s> \n
560 s> </ul>\n
569 s> </ul>\n
561 s> <ul>\n
570 s> <ul>\n
562 s> <li><a href="/redirected/help">help</a></li>\n
571 s> <li><a href="/redirected/help">help</a></li>\n
563 s> </ul>\n
572 s> </ul>\n
564 s> <div class="atom-logo">\n
573 s> <div class="atom-logo">\n
565 s> <a href="/redirected/atom-log" title="subscribe to atom feed">\n
574 s> <a href="/redirected/atom-log" title="subscribe to atom feed">\n
566 s> <img class="atom-logo" src="/redirected/static/feed-icon-14x14.png" alt="atom feed" />\n
575 s> <img class="atom-logo" src="/redirected/static/feed-icon-14x14.png" alt="atom feed" />\n
567 s> </a>\n
576 s> </a>\n
568 s> </div>\n
577 s> </div>\n
569 s> </div>\n
578 s> </div>\n
570 s> \n
579 s> \n
571 s> <div class="main">\n
580 s> <div class="main">\n
572 s> <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/redirected">redirected</a> </h2>\n
581 s> <h2 class="breadcrumb"><a href="/">Mercurial</a> &gt; <a href="/redirected">redirected</a> </h2>\n
573 s> <h3>log</h3>\n
582 s> <h3>log</h3>\n
574 s> \n
583 s> \n
575 s> \n
584 s> \n
576 s> <form class="search" action="/redirected/log">\n
585 s> <form class="search" action="/redirected/log">\n
577 s> \n
586 s> \n
578 s> <p><input name="rev" id="search1" type="text" size="30" value="" /></p>\n
587 s> <p><input name="rev" id="search1" type="text" size="30" value="" /></p>\n
579 s> <div id="hint">Find changesets by keywords (author, files, the commit message), revision\n
588 s> <div id="hint">Find changesets by keywords (author, files, the commit message), revision\n
580 s> number or hash, or <a href="/redirected/help/revsets">revset expression</a>.</div>\n
589 s> number or hash, or <a href="/redirected/help/revsets">revset expression</a>.</div>\n
581 s> </form>\n
590 s> </form>\n
582 s> \n
591 s> \n
583 s> <div class="navigate">\n
592 s> <div class="navigate">\n
584 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
593 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
585 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
594 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
586 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
595 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
587 s> </div>\n
596 s> </div>\n
588 s> \n
597 s> \n
589 s> <table class="bigtable">\n
598 s> <table class="bigtable">\n
590 s> <thead>\n
599 s> <thead>\n
591 s> <tr>\n
600 s> <tr>\n
592 s> <th class="age">age</th>\n
601 s> <th class="age">age</th>\n
593 s> <th class="author">author</th>\n
602 s> <th class="author">author</th>\n
594 s> <th class="description">description</th>\n
603 s> <th class="description">description</th>\n
595 s> </tr>\n
604 s> </tr>\n
596 s> </thead>\n
605 s> </thead>\n
597 s> <tbody class="stripes2">\n
606 s> <tbody class="stripes2">\n
598 s> <tr>\n
607 s> <tr>\n
599 s> <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>\n
608 s> <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>\n
600 s> <td class="author">test</td>\n
609 s> <td class="author">test</td>\n
601 s> <td class="description">\n
610 s> <td class="description">\n
602 s> <a href="/redirected/rev/96ee1d7354c4">initial</a>\n
611 s> <a href="/redirected/rev/96ee1d7354c4">initial</a>\n
603 s> <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> \n
612 s> <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> \n
604 s> </td>\n
613 s> </td>\n
605 s> </tr>\n
614 s> </tr>\n
606 s> \n
615 s> \n
607 s> </tbody>\n
616 s> </tbody>\n
608 s> </table>\n
617 s> </table>\n
609 s> \n
618 s> \n
610 s> <div class="navigate">\n
619 s> <div class="navigate">\n
611 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
620 s> <a href="/redirected/shortlog/tip?revcount=30">less</a>\n
612 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
621 s> <a href="/redirected/shortlog/tip?revcount=120">more</a>\n
613 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
622 s> | rev 0: <a href="/redirected/shortlog/96ee1d7354c4">(0)</a> <a href="/redirected/shortlog/tip">tip</a> \n
614 s> </div>\n
623 s> </div>\n
615 s> \n
624 s> \n
616 s> <script type="text/javascript">\n
625 s> <script type="text/javascript">\n
617 s> ajaxScrollInit(\n
626 s> ajaxScrollInit(\n
618 s> \'/redirected/shortlog/%next%\',\n
627 s> \'/redirected/shortlog/%next%\',\n
619 s> \'\', <!-- NEXTHASH\n
628 s> \'\', <!-- NEXTHASH\n
620 s> function (htmlText) {
629 s> function (htmlText) {
621 s> \r\n
630 s> \r\n
622 s> 14a\r\n
631 s> 14a\r\n
623 s> \n
632 s> \n
624 s> var m = htmlText.match(/\'(\\w+)\', <!-- NEXTHASH/);\n
633 s> var m = htmlText.match(/\'(\\w+)\', <!-- NEXTHASH/);\n
625 s> return m ? m[1] : null;\n
634 s> return m ? m[1] : null;\n
626 s> },\n
635 s> },\n
627 s> \'.bigtable > tbody\',\n
636 s> \'.bigtable > tbody\',\n
628 s> \'<tr class="%class%">\\\n
637 s> \'<tr class="%class%">\\\n
629 s> <td colspan="3" style="text-align: center;">%text%</td>\\\n
638 s> <td colspan="3" style="text-align: center;">%text%</td>\\\n
630 s> </tr>\'\n
639 s> </tr>\'\n
631 s> );\n
640 s> );\n
632 s> </script>\n
641 s> </script>\n
633 s> \n
642 s> \n
634 s> </div>\n
643 s> </div>\n
635 s> </div>\n
644 s> </div>\n
636 s> \n
645 s> \n
637 s> \n
646 s> \n
638 s> \n
647 s> \n
639 s> </body>\n
648 s> </body>\n
640 s> </html>\n
649 s> </html>\n
641 s> \n
650 s> \n
642 s> \r\n
651 s> \r\n
643 s> 0\r\n
652 s> 0\r\n
644 s> \r\n
653 s> \r\n
645
654
646 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
655 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT/redirector << EOF
647 > command heads
656 > command heads
648 > EOF
657 > EOF
649 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
658 s> GET /redirector?cmd=capabilities HTTP/1.1\r\n
650 s> Accept-Encoding: identity\r\n
659 s> Accept-Encoding: identity\r\n
651 s> accept: application/mercurial-0.1\r\n
660 s> accept: application/mercurial-0.1\r\n
652 s> host: $LOCALIP:$HGPORT\r\n (glob)
661 s> host: $LOCALIP:$HGPORT\r\n (glob)
653 s> user-agent: Mercurial debugwireproto\r\n
662 s> user-agent: Mercurial debugwireproto\r\n
654 s> \r\n
663 s> \r\n
655 s> makefile('rb', None)
664 s> makefile('rb', None)
656 s> HTTP/1.1 301 Redirect\r\n
665 s> HTTP/1.1 301 Redirect\r\n
657 s> Server: testing stub value\r\n
666 s> Server: testing stub value\r\n
658 s> Date: $HTTP_DATE$\r\n
667 s> Date: $HTTP_DATE$\r\n
659 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
668 s> Location: http://$LOCALIP:$HGPORT/redirected\r\n (glob)
660 s> Content-Type: text/plain\r\n
669 s> Content-Type: text/plain\r\n
661 s> Content-Length: 10\r\n
670 s> Content-Length: 10\r\n
662 s> \r\n
671 s> \r\n
663 s> redirected
672 s> redirected
664 s> GET /redirected HTTP/1.1\r\n
673 s> GET /redirected HTTP/1.1\r\n
665 s> Accept-Encoding: identity\r\n
674 s> Accept-Encoding: identity\r\n
666 s> accept: application/mercurial-0.1\r\n
675 s> accept: application/mercurial-0.1\r\n
667 s> host: $LOCALIP:$HGPORT\r\n (glob)
676 s> host: $LOCALIP:$HGPORT\r\n (glob)
668 s> user-agent: Mercurial debugwireproto\r\n
677 s> user-agent: Mercurial debugwireproto\r\n
669 s> \r\n
678 s> \r\n
670 s> makefile('rb', None)
679 s> makefile('rb', None)
671 s> HTTP/1.1 200 Script output follows\r\n
680 s> HTTP/1.1 200 Script output follows\r\n
672 s> Server: testing stub value\r\n
681 s> Server: testing stub value\r\n
673 s> Date: $HTTP_DATE$\r\n
682 s> Date: $HTTP_DATE$\r\n
674 s> ETag: W/"*"\r\n (glob)
683 s> ETag: W/"*"\r\n (glob)
675 s> Content-Type: text/html; charset=ascii\r\n
684 s> Content-Type: text/html; charset=ascii\r\n
676 s> Transfer-Encoding: chunked\r\n
685 s> Transfer-Encoding: chunked\r\n
677 s> \r\n
686 s> \r\n
678 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
687 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
679 s> 414\r\n
688 s> 414\r\n
680 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
689 s> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n
681 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
690 s> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">\n
682 s> <head>\n
691 s> <head>\n
683 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
692 s> <link rel="icon" href="/redirected/static/hgicon.png" type="image/png" />\n
684 s> <meta name="robots" content="index, nofollow" />\n
693 s> <meta name="robots" content="index, nofollow" />\n
685 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
694 s> <link rel="stylesheet" href="/redirected/static/style-paper.css" type="text/css" />\n
686 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
695 s> <script type="text/javascript" src="/redirected/static/mercurial.js"></script>\n
687 s> \n
696 s> \n
688 s> <title>redirected: log</title>\n
697 s> <title>redirected: log</title>\n
689 s> <link rel="alternate" type="application/atom+xml"\n
698 s> <link rel="alternate" type="application/atom+xml"\n
690 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
699 s> href="/redirected/atom-log" title="Atom feed for redirected" />\n
691 s> <link rel="alternate" type="application/rss+xml"\n
700 s> <link rel="alternate" type="application/rss+xml"\n
692 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
701 s> href="/redirected/rss-log" title="RSS feed for redirected" />\n
693 s> </head>\n
702 s> </head>\n
694 s> <body>\n
703 s> <body>\n
695 s> \n
704 s> \n
696 s> <div class="container">\n
705 s> <div class="container">\n
697 s> <div class="menu">\n
706 s> <div class="menu">\n
698 s> <div class="logo">\n
707 s> <div class="logo">\n
699 s> <a href="https://mercurial-scm.org/">\n
708 s> <a href="https://mercurial-scm.org/">\n
700 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
709 s> <img src="/redirected/static/hglogo.png" alt="mercurial" /></a>\n
701 s> </div>\n
710 s> </div>\n
702 s> <ul>\n
711 s> <ul>\n
703 s> <li class="active">log</li>\n
712 s> <li class="active">log</li>\n
704 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
713 s> <li><a href="/redirected/graph/tip">graph</a></li>\n
705 s> <li><a href="/redirected/tags">tags</a
714 s> <li><a href="/redirected/tags">tags</a
706 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
715 s> GET /redirected?cmd=capabilities HTTP/1.1\r\n
707 s> Accept-Encoding: identity\r\n
716 s> Accept-Encoding: identity\r\n
708 s> accept: application/mercurial-0.1\r\n
717 s> accept: application/mercurial-0.1\r\n
709 s> host: $LOCALIP:$HGPORT\r\n (glob)
718 s> host: $LOCALIP:$HGPORT\r\n (glob)
710 s> user-agent: Mercurial debugwireproto\r\n
719 s> user-agent: Mercurial debugwireproto\r\n
711 s> \r\n
720 s> \r\n
712 s> makefile('rb', None)
721 s> makefile('rb', None)
713 s> HTTP/1.1 200 Script output follows\r\n
722 s> HTTP/1.1 200 Script output follows\r\n
714 s> Server: testing stub value\r\n
723 s> Server: testing stub value\r\n
715 s> Date: $HTTP_DATE$\r\n
724 s> Date: $HTTP_DATE$\r\n
716 s> Content-Type: application/mercurial-0.1\r\n
725 s> Content-Type: application/mercurial-0.1\r\n
717 s> Content-Length: 453\r\n
726 s> Content-Length: 453\r\n
718 s> \r\n
727 s> \r\n
719 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
728 real URL is http://$LOCALIP:$HGPORT/redirected (glob)
720 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
729 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
721 sending heads command
730 sending heads command
722 s> GET /redirected?cmd=heads HTTP/1.1\r\n
731 s> GET /redirected?cmd=heads HTTP/1.1\r\n
723 s> Accept-Encoding: identity\r\n
732 s> Accept-Encoding: identity\r\n
724 s> vary: X-HgProto-1\r\n
733 s> vary: X-HgProto-1\r\n
725 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
734 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
726 s> accept: application/mercurial-0.1\r\n
735 s> accept: application/mercurial-0.1\r\n
727 s> host: $LOCALIP:$HGPORT\r\n (glob)
736 s> host: $LOCALIP:$HGPORT\r\n (glob)
728 s> user-agent: Mercurial debugwireproto\r\n
737 s> user-agent: Mercurial debugwireproto\r\n
729 s> \r\n
738 s> \r\n
730 s> makefile('rb', None)
739 s> makefile('rb', None)
731 s> HTTP/1.1 200 Script output follows\r\n
740 s> HTTP/1.1 200 Script output follows\r\n
732 s> Server: testing stub value\r\n
741 s> Server: testing stub value\r\n
733 s> Date: $HTTP_DATE$\r\n
742 s> Date: $HTTP_DATE$\r\n
734 s> Content-Type: application/mercurial-0.1\r\n
743 s> Content-Type: application/mercurial-0.1\r\n
735 s> Content-Length: 41\r\n
744 s> Content-Length: 41\r\n
736 s> \r\n
745 s> \r\n
737 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
746 s> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
738 response: [
747 response: [
739 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
748 b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
740 ]
749 ]
@@ -1,83 +1,92 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ hg debugdrawdag << EOF
6 $ hg debugdrawdag << EOF
7 > C D
7 > C D
8 > |/
8 > |/
9 > B
9 > B
10 > |
10 > |
11 > A
11 > A
12 > EOF
12 > EOF
13
13
14 $ hg up B
14 $ hg up B
15 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
15 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 $ hg branch branch1
16 $ hg branch branch1
17 marked working directory as branch branch1
17 marked working directory as branch branch1
18 (branches are permanent and global, did you want a bookmark?)
18 (branches are permanent and global, did you want a bookmark?)
19 $ echo b1 > foo
19 $ echo b1 > foo
20 $ hg -q commit -A -m 'branch 1'
20 $ hg -q commit -A -m 'branch 1'
21 $ hg up B
21 $ hg up B
22 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
22 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
23 $ hg branch branch2
23 $ hg branch branch2
24 marked working directory as branch branch2
24 marked working directory as branch branch2
25 $ echo b2 > foo
25 $ echo b2 > foo
26 $ hg -q commit -A -m 'branch 2'
26 $ hg -q commit -A -m 'branch 2'
27
27
28 $ hg log -T '{rev}:{node} {branch} {desc}\n'
28 $ hg log -T '{rev}:{node} {branch} {desc}\n'
29 5:224161c7589aa48fa83a48feff5e95b56ae327fc branch2 branch 2
29 5:224161c7589aa48fa83a48feff5e95b56ae327fc branch2 branch 2
30 4:b5faacdfd2633768cb3152336cc0953381266688 branch1 branch 1
30 4:b5faacdfd2633768cb3152336cc0953381266688 branch1 branch 1
31 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 default D
31 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 default D
32 2:26805aba1e600a82e93661149f2313866a221a7b default C
32 2:26805aba1e600a82e93661149f2313866a221a7b default C
33 1:112478962961147124edd43549aedd1a335e44bf default B
33 1:112478962961147124edd43549aedd1a335e44bf default B
34 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 default A
34 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 default A
35
35
36 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
36 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
37 $ cat hg.pid > $DAEMON_PIDS
37 $ cat hg.pid > $DAEMON_PIDS
38
38
39 No arguments returns something reasonable
39 No arguments returns something reasonable
40
40
41 $ sendhttpv2peer << EOF
41 $ sendhttpv2peer << EOF
42 > command branchmap
42 > command branchmap
43 > EOF
43 > EOF
44 creating http peer for wire protocol version 2
44 creating http peer for wire protocol version 2
45 sending branchmap command
45 sending branchmap command
46 s> POST /api/exp-http-v2-0001/ro/branchmap HTTP/1.1\r\n
46 s> POST /api/exp-http-v2-0001/ro/branchmap HTTP/1.1\r\n
47 s> Accept-Encoding: identity\r\n
47 s> Accept-Encoding: identity\r\n
48 s> accept: application/mercurial-exp-framing-0005\r\n
48 s> accept: application/mercurial-exp-framing-0005\r\n
49 s> content-type: application/mercurial-exp-framing-0005\r\n
49 s> content-type: application/mercurial-exp-framing-0005\r\n
50 s> content-length: 24\r\n
50 s> content-length: 24\r\n
51 s> host: $LOCALIP:$HGPORT\r\n (glob)
51 s> host: $LOCALIP:$HGPORT\r\n (glob)
52 s> user-agent: Mercurial debugwireproto\r\n
52 s> user-agent: Mercurial debugwireproto\r\n
53 s> \r\n
53 s> \r\n
54 s> \x10\x00\x00\x01\x00\x01\x01\x11\xa1DnameIbranchmap
54 s> \x10\x00\x00\x01\x00\x01\x01\x11\xa1DnameIbranchmap
55 s> makefile('rb', None)
55 s> makefile('rb', None)
56 s> HTTP/1.1 200 OK\r\n
56 s> HTTP/1.1 200 OK\r\n
57 s> Server: testing stub value\r\n
57 s> Server: testing stub value\r\n
58 s> Date: $HTTP_DATE$\r\n
58 s> Date: $HTTP_DATE$\r\n
59 s> Content-Type: application/mercurial-exp-framing-0005\r\n
59 s> Content-Type: application/mercurial-exp-framing-0005\r\n
60 s> Transfer-Encoding: chunked\r\n
60 s> Transfer-Encoding: chunked\r\n
61 s> \r\n
61 s> \r\n
62 s> 83\r\n
62 s> 13\r\n
63 s> {\x00\x00\x01\x00\x02\x012
63 s> \x0b\x00\x00\x01\x00\x02\x011
64 s> \xa1FstatusBok\xa3Gbranch1\x81T\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88Gbranch2\x81T"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfcGdefault\x82T&\x80Z\xba\x1e`\n
64 s> \xa1FstatusBok
65 s> \r\n
66 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
67 s> 78\r\n
68 s> p\x00\x00\x01\x00\x02\x001
69 s> \xa3Gbranch1\x81T\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88Gbranch2\x81T"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfcGdefault\x82T&\x80Z\xba\x1e`\n
65 s> \x82\xe96a\x14\x9f#\x13\x86j"\x1a{T\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82
70 s> \x82\xe96a\x14\x9f#\x13\x86j"\x1a{T\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82
66 s> \r\n
71 s> \r\n
67 received frame(size=123; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
72 received frame(size=112; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
73 s> 8\r\n
74 s> \x00\x00\x00\x01\x00\x02\x002
75 s> \r\n
68 s> 0\r\n
76 s> 0\r\n
69 s> \r\n
77 s> \r\n
78 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
70 response: {
79 response: {
71 b'branch1': [
80 b'branch1': [
72 b'\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88'
81 b'\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88'
73 ],
82 ],
74 b'branch2': [
83 b'branch2': [
75 b'"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfc'
84 b'"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfc'
76 ],
85 ],
77 b'default': [
86 b'default': [
78 b'&\x80Z\xba\x1e`\n\x82\xe96a\x14\x9f#\x13\x86j"\x1a{',
87 b'&\x80Z\xba\x1e`\n\x82\xe96a\x14\x9f#\x13\x86j"\x1a{',
79 b'\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82'
88 b'\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82'
80 ]
89 ]
81 }
90 }
82
91
83 $ cat error.log
92 $ cat error.log
@@ -1,422 +1,431 b''
1 #require no-chg
1 #require no-chg
2
2
3 $ . $TESTDIR/wireprotohelpers.sh
3 $ . $TESTDIR/wireprotohelpers.sh
4
4
5 $ hg init server
5 $ hg init server
6
6
7 zstd isn't present in plain builds. Make tests easier by removing
7 zstd isn't present in plain builds. Make tests easier by removing
8 zstd from the equation.
8 zstd from the equation.
9
9
10 $ cat >> server/.hg/hgrc << EOF
10 $ cat >> server/.hg/hgrc << EOF
11 > [server]
11 > [server]
12 > compressionengines = zlib
12 > compressionengines = zlib
13 > EOF
13 > EOF
14
14
15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
15 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
16 $ cat hg.pid > $DAEMON_PIDS
16 $ cat hg.pid > $DAEMON_PIDS
17
17
18 A normal capabilities request is serviced for version 1
18 A normal capabilities request is serviced for version 1
19
19
20 $ sendhttpraw << EOF
20 $ sendhttpraw << EOF
21 > httprequest GET ?cmd=capabilities
21 > httprequest GET ?cmd=capabilities
22 > user-agent: test
22 > user-agent: test
23 > EOF
23 > EOF
24 using raw connection to peer
24 using raw connection to peer
25 s> GET /?cmd=capabilities HTTP/1.1\r\n
25 s> GET /?cmd=capabilities HTTP/1.1\r\n
26 s> Accept-Encoding: identity\r\n
26 s> Accept-Encoding: identity\r\n
27 s> user-agent: test\r\n
27 s> user-agent: test\r\n
28 s> host: $LOCALIP:$HGPORT\r\n (glob)
28 s> host: $LOCALIP:$HGPORT\r\n (glob)
29 s> \r\n
29 s> \r\n
30 s> makefile('rb', None)
30 s> makefile('rb', None)
31 s> HTTP/1.1 200 Script output follows\r\n
31 s> HTTP/1.1 200 Script output follows\r\n
32 s> Server: testing stub value\r\n
32 s> Server: testing stub value\r\n
33 s> Date: $HTTP_DATE$\r\n
33 s> Date: $HTTP_DATE$\r\n
34 s> Content-Type: application/mercurial-0.1\r\n
34 s> Content-Type: application/mercurial-0.1\r\n
35 s> Content-Length: *\r\n (glob)
35 s> Content-Length: *\r\n (glob)
36 s> \r\n
36 s> \r\n
37 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
37 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
38
38
39 A proper request without the API server enabled returns the legacy response
39 A proper request without the API server enabled returns the legacy response
40
40
41 $ sendhttpraw << EOF
41 $ sendhttpraw << EOF
42 > httprequest GET ?cmd=capabilities
42 > httprequest GET ?cmd=capabilities
43 > user-agent: test
43 > user-agent: test
44 > x-hgupgrade-1: foo
44 > x-hgupgrade-1: foo
45 > x-hgproto-1: cbor
45 > x-hgproto-1: cbor
46 > EOF
46 > EOF
47 using raw connection to peer
47 using raw connection to peer
48 s> GET /?cmd=capabilities HTTP/1.1\r\n
48 s> GET /?cmd=capabilities HTTP/1.1\r\n
49 s> Accept-Encoding: identity\r\n
49 s> Accept-Encoding: identity\r\n
50 s> user-agent: test\r\n
50 s> user-agent: test\r\n
51 s> x-hgproto-1: cbor\r\n
51 s> x-hgproto-1: cbor\r\n
52 s> x-hgupgrade-1: foo\r\n
52 s> x-hgupgrade-1: foo\r\n
53 s> host: $LOCALIP:$HGPORT\r\n (glob)
53 s> host: $LOCALIP:$HGPORT\r\n (glob)
54 s> \r\n
54 s> \r\n
55 s> makefile('rb', None)
55 s> makefile('rb', None)
56 s> HTTP/1.1 200 Script output follows\r\n
56 s> HTTP/1.1 200 Script output follows\r\n
57 s> Server: testing stub value\r\n
57 s> Server: testing stub value\r\n
58 s> Date: $HTTP_DATE$\r\n
58 s> Date: $HTTP_DATE$\r\n
59 s> Content-Type: application/mercurial-0.1\r\n
59 s> Content-Type: application/mercurial-0.1\r\n
60 s> Content-Length: *\r\n (glob)
60 s> Content-Length: *\r\n (glob)
61 s> \r\n
61 s> \r\n
62 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
62 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
63
63
64 Restart with just API server enabled. This enables serving the new format.
64 Restart with just API server enabled. This enables serving the new format.
65
65
66 $ killdaemons.py
66 $ killdaemons.py
67 $ cat error.log
67 $ cat error.log
68
68
69 $ cat >> server/.hg/hgrc << EOF
69 $ cat >> server/.hg/hgrc << EOF
70 > [experimental]
70 > [experimental]
71 > web.apiserver = true
71 > web.apiserver = true
72 > EOF
72 > EOF
73
73
74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
74 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
75 $ cat hg.pid > $DAEMON_PIDS
75 $ cat hg.pid > $DAEMON_PIDS
76
76
77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
77 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
78
78
79 $ sendhttpraw << EOF
79 $ sendhttpraw << EOF
80 > httprequest GET ?cmd=capabilities
80 > httprequest GET ?cmd=capabilities
81 > user-agent: test
81 > user-agent: test
82 > x-hgupgrade-1: foo bar
82 > x-hgupgrade-1: foo bar
83 > EOF
83 > EOF
84 using raw connection to peer
84 using raw connection to peer
85 s> GET /?cmd=capabilities HTTP/1.1\r\n
85 s> GET /?cmd=capabilities HTTP/1.1\r\n
86 s> Accept-Encoding: identity\r\n
86 s> Accept-Encoding: identity\r\n
87 s> user-agent: test\r\n
87 s> user-agent: test\r\n
88 s> x-hgupgrade-1: foo bar\r\n
88 s> x-hgupgrade-1: foo bar\r\n
89 s> host: $LOCALIP:$HGPORT\r\n (glob)
89 s> host: $LOCALIP:$HGPORT\r\n (glob)
90 s> \r\n
90 s> \r\n
91 s> makefile('rb', None)
91 s> makefile('rb', None)
92 s> HTTP/1.1 200 Script output follows\r\n
92 s> HTTP/1.1 200 Script output follows\r\n
93 s> Server: testing stub value\r\n
93 s> Server: testing stub value\r\n
94 s> Date: $HTTP_DATE$\r\n
94 s> Date: $HTTP_DATE$\r\n
95 s> Content-Type: application/mercurial-0.1\r\n
95 s> Content-Type: application/mercurial-0.1\r\n
96 s> Content-Length: *\r\n (glob)
96 s> Content-Length: *\r\n (glob)
97 s> \r\n
97 s> \r\n
98 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
98 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
99
99
100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
100 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
101
101
102 $ sendhttpraw << EOF
102 $ sendhttpraw << EOF
103 > httprequest GET ?cmd=capabilities
103 > httprequest GET ?cmd=capabilities
104 > user-agent: test
104 > user-agent: test
105 > x-hgupgrade-1: foo bar
105 > x-hgupgrade-1: foo bar
106 > x-hgproto-1: some value
106 > x-hgproto-1: some value
107 > EOF
107 > EOF
108 using raw connection to peer
108 using raw connection to peer
109 s> GET /?cmd=capabilities HTTP/1.1\r\n
109 s> GET /?cmd=capabilities HTTP/1.1\r\n
110 s> Accept-Encoding: identity\r\n
110 s> Accept-Encoding: identity\r\n
111 s> user-agent: test\r\n
111 s> user-agent: test\r\n
112 s> x-hgproto-1: some value\r\n
112 s> x-hgproto-1: some value\r\n
113 s> x-hgupgrade-1: foo bar\r\n
113 s> x-hgupgrade-1: foo bar\r\n
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 s> \r\n
115 s> \r\n
116 s> makefile('rb', None)
116 s> makefile('rb', None)
117 s> HTTP/1.1 200 Script output follows\r\n
117 s> HTTP/1.1 200 Script output follows\r\n
118 s> Server: testing stub value\r\n
118 s> Server: testing stub value\r\n
119 s> Date: $HTTP_DATE$\r\n
119 s> Date: $HTTP_DATE$\r\n
120 s> Content-Type: application/mercurial-0.1\r\n
120 s> Content-Type: application/mercurial-0.1\r\n
121 s> Content-Length: *\r\n (glob)
121 s> Content-Length: *\r\n (glob)
122 s> \r\n
122 s> \r\n
123 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
123 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
124
124
125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
125 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
126
126
127 $ sendhttpraw << EOF
127 $ sendhttpraw << EOF
128 > httprequest GET ?cmd=capabilities
128 > httprequest GET ?cmd=capabilities
129 > user-agent: test
129 > user-agent: test
130 > x-hgupgrade-1: foo bar
130 > x-hgupgrade-1: foo bar
131 > x-hgproto-1: cbor
131 > x-hgproto-1: cbor
132 > EOF
132 > EOF
133 using raw connection to peer
133 using raw connection to peer
134 s> GET /?cmd=capabilities HTTP/1.1\r\n
134 s> GET /?cmd=capabilities HTTP/1.1\r\n
135 s> Accept-Encoding: identity\r\n
135 s> Accept-Encoding: identity\r\n
136 s> user-agent: test\r\n
136 s> user-agent: test\r\n
137 s> x-hgproto-1: cbor\r\n
137 s> x-hgproto-1: cbor\r\n
138 s> x-hgupgrade-1: foo bar\r\n
138 s> x-hgupgrade-1: foo bar\r\n
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
139 s> host: $LOCALIP:$HGPORT\r\n (glob)
140 s> \r\n
140 s> \r\n
141 s> makefile('rb', None)
141 s> makefile('rb', None)
142 s> HTTP/1.1 200 OK\r\n
142 s> HTTP/1.1 200 OK\r\n
143 s> Server: testing stub value\r\n
143 s> Server: testing stub value\r\n
144 s> Date: $HTTP_DATE$\r\n
144 s> Date: $HTTP_DATE$\r\n
145 s> Content-Type: application/mercurial-cbor\r\n
145 s> Content-Type: application/mercurial-cbor\r\n
146 s> Content-Length: *\r\n (glob)
146 s> Content-Length: *\r\n (glob)
147 s> \r\n
147 s> \r\n
148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
148 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
149 cbor> {
149 cbor> {
150 b'apibase': b'api/',
150 b'apibase': b'api/',
151 b'apis': {},
151 b'apis': {},
152 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
152 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
153 }
153 }
154
154
155 Restart server to enable HTTPv2
155 Restart server to enable HTTPv2
156
156
157 $ killdaemons.py
157 $ killdaemons.py
158 $ enablehttpv2 server
158 $ enablehttpv2 server
159 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
159 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
160 $ cat hg.pid > $DAEMON_PIDS
160 $ cat hg.pid > $DAEMON_PIDS
161
161
162 Only requested API services are returned
162 Only requested API services are returned
163
163
164 $ sendhttpraw << EOF
164 $ sendhttpraw << EOF
165 > httprequest GET ?cmd=capabilities
165 > httprequest GET ?cmd=capabilities
166 > user-agent: test
166 > user-agent: test
167 > x-hgupgrade-1: foo bar
167 > x-hgupgrade-1: foo bar
168 > x-hgproto-1: cbor
168 > x-hgproto-1: cbor
169 > EOF
169 > EOF
170 using raw connection to peer
170 using raw connection to peer
171 s> GET /?cmd=capabilities HTTP/1.1\r\n
171 s> GET /?cmd=capabilities HTTP/1.1\r\n
172 s> Accept-Encoding: identity\r\n
172 s> Accept-Encoding: identity\r\n
173 s> user-agent: test\r\n
173 s> user-agent: test\r\n
174 s> x-hgproto-1: cbor\r\n
174 s> x-hgproto-1: cbor\r\n
175 s> x-hgupgrade-1: foo bar\r\n
175 s> x-hgupgrade-1: foo bar\r\n
176 s> host: $LOCALIP:$HGPORT\r\n (glob)
176 s> host: $LOCALIP:$HGPORT\r\n (glob)
177 s> \r\n
177 s> \r\n
178 s> makefile('rb', None)
178 s> makefile('rb', None)
179 s> HTTP/1.1 200 OK\r\n
179 s> HTTP/1.1 200 OK\r\n
180 s> Server: testing stub value\r\n
180 s> Server: testing stub value\r\n
181 s> Date: $HTTP_DATE$\r\n
181 s> Date: $HTTP_DATE$\r\n
182 s> Content-Type: application/mercurial-cbor\r\n
182 s> Content-Type: application/mercurial-cbor\r\n
183 s> Content-Length: *\r\n (glob)
183 s> Content-Length: *\r\n (glob)
184 s> \r\n
184 s> \r\n
185 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
185 s> \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
186 cbor> {
186 cbor> {
187 b'apibase': b'api/',
187 b'apibase': b'api/',
188 b'apis': {},
188 b'apis': {},
189 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
189 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
190 }
190 }
191
191
192 Request for HTTPv2 service returns information about it
192 Request for HTTPv2 service returns information about it
193
193
194 $ sendhttpraw << EOF
194 $ sendhttpraw << EOF
195 > httprequest GET ?cmd=capabilities
195 > httprequest GET ?cmd=capabilities
196 > user-agent: test
196 > user-agent: test
197 > x-hgupgrade-1: exp-http-v2-0001 foo bar
197 > x-hgupgrade-1: exp-http-v2-0001 foo bar
198 > x-hgproto-1: cbor
198 > x-hgproto-1: cbor
199 > EOF
199 > EOF
200 using raw connection to peer
200 using raw connection to peer
201 s> GET /?cmd=capabilities HTTP/1.1\r\n
201 s> GET /?cmd=capabilities HTTP/1.1\r\n
202 s> Accept-Encoding: identity\r\n
202 s> Accept-Encoding: identity\r\n
203 s> user-agent: test\r\n
203 s> user-agent: test\r\n
204 s> x-hgproto-1: cbor\r\n
204 s> x-hgproto-1: cbor\r\n
205 s> x-hgupgrade-1: exp-http-v2-0001 foo bar\r\n
205 s> x-hgupgrade-1: exp-http-v2-0001 foo bar\r\n
206 s> host: $LOCALIP:$HGPORT\r\n (glob)
206 s> host: $LOCALIP:$HGPORT\r\n (glob)
207 s> \r\n
207 s> \r\n
208 s> makefile('rb', None)
208 s> makefile('rb', None)
209 s> HTTP/1.1 200 OK\r\n
209 s> HTTP/1.1 200 OK\r\n
210 s> Server: testing stub value\r\n
210 s> Server: testing stub value\r\n
211 s> Date: $HTTP_DATE$\r\n
211 s> Date: $HTTP_DATE$\r\n
212 s> Content-Type: application/mercurial-cbor\r\n
212 s> Content-Type: application/mercurial-cbor\r\n
213 s> Content-Length: *\r\n (glob)
213 s> Content-Length: *\r\n (glob)
214 s> \r\n
214 s> \r\n
215 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
215 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
216 cbor> {
216 cbor> {
217 b'apibase': b'api/',
217 b'apibase': b'api/',
218 b'apis': {
218 b'apis': {
219 b'exp-http-v2-0001': {
219 b'exp-http-v2-0001': {
220 b'commands': {
220 b'commands': {
221 b'branchmap': {
221 b'branchmap': {
222 b'args': {},
222 b'args': {},
223 b'permissions': [
223 b'permissions': [
224 b'pull'
224 b'pull'
225 ]
225 ]
226 },
226 },
227 b'capabilities': {
227 b'capabilities': {
228 b'args': {},
228 b'args': {},
229 b'permissions': [
229 b'permissions': [
230 b'pull'
230 b'pull'
231 ]
231 ]
232 },
232 },
233 b'heads': {
233 b'heads': {
234 b'args': {
234 b'args': {
235 b'publiconly': False
235 b'publiconly': False
236 },
236 },
237 b'permissions': [
237 b'permissions': [
238 b'pull'
238 b'pull'
239 ]
239 ]
240 },
240 },
241 b'known': {
241 b'known': {
242 b'args': {
242 b'args': {
243 b'nodes': [
243 b'nodes': [
244 b'deadbeef'
244 b'deadbeef'
245 ]
245 ]
246 },
246 },
247 b'permissions': [
247 b'permissions': [
248 b'pull'
248 b'pull'
249 ]
249 ]
250 },
250 },
251 b'listkeys': {
251 b'listkeys': {
252 b'args': {
252 b'args': {
253 b'namespace': b'ns'
253 b'namespace': b'ns'
254 },
254 },
255 b'permissions': [
255 b'permissions': [
256 b'pull'
256 b'pull'
257 ]
257 ]
258 },
258 },
259 b'lookup': {
259 b'lookup': {
260 b'args': {
260 b'args': {
261 b'key': b'foo'
261 b'key': b'foo'
262 },
262 },
263 b'permissions': [
263 b'permissions': [
264 b'pull'
264 b'pull'
265 ]
265 ]
266 },
266 },
267 b'pushkey': {
267 b'pushkey': {
268 b'args': {
268 b'args': {
269 b'key': b'key',
269 b'key': b'key',
270 b'namespace': b'ns',
270 b'namespace': b'ns',
271 b'new': b'new',
271 b'new': b'new',
272 b'old': b'old'
272 b'old': b'old'
273 },
273 },
274 b'permissions': [
274 b'permissions': [
275 b'push'
275 b'push'
276 ]
276 ]
277 }
277 }
278 },
278 },
279 b'compression': [
279 b'compression': [
280 {
280 {
281 b'name': b'zlib'
281 b'name': b'zlib'
282 }
282 }
283 ],
283 ],
284 b'framingmediatypes': [
284 b'framingmediatypes': [
285 b'application/mercurial-exp-framing-0005'
285 b'application/mercurial-exp-framing-0005'
286 ],
286 ],
287 b'rawrepoformats': [
287 b'rawrepoformats': [
288 b'generaldelta',
288 b'generaldelta',
289 b'revlogv1'
289 b'revlogv1'
290 ]
290 ]
291 }
291 }
292 },
292 },
293 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
293 b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
294 }
294 }
295
295
296 capabilities command returns expected info
296 capabilities command returns expected info
297
297
298 $ sendhttpv2peerhandshake << EOF
298 $ sendhttpv2peerhandshake << EOF
299 > command capabilities
299 > command capabilities
300 > EOF
300 > EOF
301 creating http peer for wire protocol version 2
301 creating http peer for wire protocol version 2
302 s> GET /?cmd=capabilities HTTP/1.1\r\n
302 s> GET /?cmd=capabilities HTTP/1.1\r\n
303 s> Accept-Encoding: identity\r\n
303 s> Accept-Encoding: identity\r\n
304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
304 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
305 s> x-hgproto-1: cbor\r\n
305 s> x-hgproto-1: cbor\r\n
306 s> x-hgupgrade-1: exp-http-v2-0001\r\n
306 s> x-hgupgrade-1: exp-http-v2-0001\r\n
307 s> accept: application/mercurial-0.1\r\n
307 s> accept: application/mercurial-0.1\r\n
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
309 s> user-agent: Mercurial debugwireproto\r\n
309 s> user-agent: Mercurial debugwireproto\r\n
310 s> \r\n
310 s> \r\n
311 s> makefile('rb', None)
311 s> makefile('rb', None)
312 s> HTTP/1.1 200 OK\r\n
312 s> HTTP/1.1 200 OK\r\n
313 s> Server: testing stub value\r\n
313 s> Server: testing stub value\r\n
314 s> Date: $HTTP_DATE$\r\n
314 s> Date: $HTTP_DATE$\r\n
315 s> Content-Type: application/mercurial-cbor\r\n
315 s> Content-Type: application/mercurial-cbor\r\n
316 s> Content-Length: *\r\n (glob)
316 s> Content-Length: *\r\n (glob)
317 s> \r\n
317 s> \r\n
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
318 s> \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
319 sending capabilities command
319 sending capabilities command
320 s> POST /api/exp-http-v2-0001/ro/capabilities HTTP/1.1\r\n
320 s> POST /api/exp-http-v2-0001/ro/capabilities HTTP/1.1\r\n
321 s> Accept-Encoding: identity\r\n
321 s> Accept-Encoding: identity\r\n
322 s> *\r\n (glob)
322 s> *\r\n (glob)
323 s> content-type: application/mercurial-exp-framing-0005\r\n
323 s> content-type: application/mercurial-exp-framing-0005\r\n
324 s> content-length: 27\r\n
324 s> content-length: 27\r\n
325 s> host: $LOCALIP:$HGPORT\r\n (glob)
325 s> host: $LOCALIP:$HGPORT\r\n (glob)
326 s> user-agent: Mercurial debugwireproto\r\n
326 s> user-agent: Mercurial debugwireproto\r\n
327 s> \r\n
327 s> \r\n
328 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
328 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
329 s> makefile('rb', None)
329 s> makefile('rb', None)
330 s> HTTP/1.1 200 OK\r\n
330 s> HTTP/1.1 200 OK\r\n
331 s> Server: testing stub value\r\n
331 s> Server: testing stub value\r\n
332 s> Date: $HTTP_DATE$\r\n
332 s> Date: $HTTP_DATE$\r\n
333 s> Content-Type: application/mercurial-exp-framing-0005\r\n
333 s> Content-Type: application/mercurial-exp-framing-0005\r\n
334 s> Transfer-Encoding: chunked\r\n
334 s> Transfer-Encoding: chunked\r\n
335 s> \r\n
335 s> \r\n
336 s> 1d7\r\n
336 s> 13\r\n
337 s> \xcf\x01\x00\x01\x00\x02\x012
337 s> \x0b\x00\x00\x01\x00\x02\x011
338 s> \xa1FstatusBok\xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1
338 s> \xa1FstatusBok
339 s> \r\n
339 s> \r\n
340 received frame(size=463; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
340 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
341 s> 1cc\r\n
342 s> \xc4\x01\x00\x01\x00\x02\x001
343 s> \xa4Hcommands\xa7Ibranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyInamespaceBnsCnewCnewColdColdKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Nrawrepoformats\x82LgeneraldeltaHrevlogv1
344 s> \r\n
345 received frame(size=452; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
346 s> 8\r\n
347 s> \x00\x00\x00\x01\x00\x02\x002
348 s> \r\n
341 s> 0\r\n
349 s> 0\r\n
342 s> \r\n
350 s> \r\n
351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
343 response: [
352 response: [
344 {
353 {
345 b'status': b'ok'
354 b'status': b'ok'
346 },
355 },
347 {
356 {
348 b'commands': {
357 b'commands': {
349 b'branchmap': {
358 b'branchmap': {
350 b'args': {},
359 b'args': {},
351 b'permissions': [
360 b'permissions': [
352 b'pull'
361 b'pull'
353 ]
362 ]
354 },
363 },
355 b'capabilities': {
364 b'capabilities': {
356 b'args': {},
365 b'args': {},
357 b'permissions': [
366 b'permissions': [
358 b'pull'
367 b'pull'
359 ]
368 ]
360 },
369 },
361 b'heads': {
370 b'heads': {
362 b'args': {
371 b'args': {
363 b'publiconly': False
372 b'publiconly': False
364 },
373 },
365 b'permissions': [
374 b'permissions': [
366 b'pull'
375 b'pull'
367 ]
376 ]
368 },
377 },
369 b'known': {
378 b'known': {
370 b'args': {
379 b'args': {
371 b'nodes': [
380 b'nodes': [
372 b'deadbeef'
381 b'deadbeef'
373 ]
382 ]
374 },
383 },
375 b'permissions': [
384 b'permissions': [
376 b'pull'
385 b'pull'
377 ]
386 ]
378 },
387 },
379 b'listkeys': {
388 b'listkeys': {
380 b'args': {
389 b'args': {
381 b'namespace': b'ns'
390 b'namespace': b'ns'
382 },
391 },
383 b'permissions': [
392 b'permissions': [
384 b'pull'
393 b'pull'
385 ]
394 ]
386 },
395 },
387 b'lookup': {
396 b'lookup': {
388 b'args': {
397 b'args': {
389 b'key': b'foo'
398 b'key': b'foo'
390 },
399 },
391 b'permissions': [
400 b'permissions': [
392 b'pull'
401 b'pull'
393 ]
402 ]
394 },
403 },
395 b'pushkey': {
404 b'pushkey': {
396 b'args': {
405 b'args': {
397 b'key': b'key',
406 b'key': b'key',
398 b'namespace': b'ns',
407 b'namespace': b'ns',
399 b'new': b'new',
408 b'new': b'new',
400 b'old': b'old'
409 b'old': b'old'
401 },
410 },
402 b'permissions': [
411 b'permissions': [
403 b'push'
412 b'push'
404 ]
413 ]
405 }
414 }
406 },
415 },
407 b'compression': [
416 b'compression': [
408 {
417 {
409 b'name': b'zlib'
418 b'name': b'zlib'
410 }
419 }
411 ],
420 ],
412 b'framingmediatypes': [
421 b'framingmediatypes': [
413 b'application/mercurial-exp-framing-0005'
422 b'application/mercurial-exp-framing-0005'
414 ],
423 ],
415 b'rawrepoformats': [
424 b'rawrepoformats': [
416 b'generaldelta',
425 b'generaldelta',
417 b'revlogv1'
426 b'revlogv1'
418 ]
427 ]
419 }
428 }
420 ]
429 ]
421
430
422 $ cat error.log
431 $ cat error.log
@@ -1,102 +1,120 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ hg debugdrawdag << EOF
6 $ hg debugdrawdag << EOF
7 > H I J
7 > H I J
8 > | | |
8 > | | |
9 > E F G
9 > E F G
10 > | |/
10 > | |/
11 > C D
11 > C D
12 > |/
12 > |/
13 > B
13 > B
14 > |
14 > |
15 > A
15 > A
16 > EOF
16 > EOF
17
17
18 $ hg phase --force --secret J
18 $ hg phase --force --secret J
19 $ hg phase --public E
19 $ hg phase --public E
20
20
21 $ hg log -r 'E + H + I + G + J' -T '{rev}:{node} {desc} {phase}\n'
21 $ hg log -r 'E + H + I + G + J' -T '{rev}:{node} {desc} {phase}\n'
22 4:78d2dca436b2f5b188ac267e29b81e07266d38fc E public
22 4:78d2dca436b2f5b188ac267e29b81e07266d38fc E public
23 7:ae492e36b0c8339ffaf328d00b85b4525de1165e H draft
23 7:ae492e36b0c8339ffaf328d00b85b4525de1165e H draft
24 8:1d6f6b91d44aaba6d5e580bc30a9948530dbe00b I draft
24 8:1d6f6b91d44aaba6d5e580bc30a9948530dbe00b I draft
25 6:29446d2dc5419c5f97447a8bc062e4cc328bf241 G draft
25 6:29446d2dc5419c5f97447a8bc062e4cc328bf241 G draft
26 9:dec04b246d7cbb670c6689806c05ad17c835284e J secret
26 9:dec04b246d7cbb670c6689806c05ad17c835284e J secret
27
27
28 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
28 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
29 $ cat hg.pid > $DAEMON_PIDS
29 $ cat hg.pid > $DAEMON_PIDS
30
30
31 All non-secret heads returned by default
31 All non-secret heads returned by default
32
32
33 $ sendhttpv2peer << EOF
33 $ sendhttpv2peer << EOF
34 > command heads
34 > command heads
35 > EOF
35 > EOF
36 creating http peer for wire protocol version 2
36 creating http peer for wire protocol version 2
37 sending heads command
37 sending heads command
38 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
38 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
39 s> Accept-Encoding: identity\r\n
39 s> Accept-Encoding: identity\r\n
40 s> accept: application/mercurial-exp-framing-0005\r\n
40 s> accept: application/mercurial-exp-framing-0005\r\n
41 s> content-type: application/mercurial-exp-framing-0005\r\n
41 s> content-type: application/mercurial-exp-framing-0005\r\n
42 s> content-length: 20\r\n
42 s> content-length: 20\r\n
43 s> host: $LOCALIP:$HGPORT\r\n (glob)
43 s> host: $LOCALIP:$HGPORT\r\n (glob)
44 s> user-agent: Mercurial debugwireproto\r\n
44 s> user-agent: Mercurial debugwireproto\r\n
45 s> \r\n
45 s> \r\n
46 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
46 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
47 s> makefile('rb', None)
47 s> makefile('rb', None)
48 s> HTTP/1.1 200 OK\r\n
48 s> HTTP/1.1 200 OK\r\n
49 s> Server: testing stub value\r\n
49 s> Server: testing stub value\r\n
50 s> Date: $HTTP_DATE$\r\n
50 s> Date: $HTTP_DATE$\r\n
51 s> Content-Type: application/mercurial-exp-framing-0005\r\n
51 s> Content-Type: application/mercurial-exp-framing-0005\r\n
52 s> Transfer-Encoding: chunked\r\n
52 s> Transfer-Encoding: chunked\r\n
53 s> \r\n
53 s> \r\n
54 s> 53\r\n
54 s> 13\r\n
55 s> K\x00\x00\x01\x00\x02\x012
55 s> \x0b\x00\x00\x01\x00\x02\x011
56 s> \xa1FstatusBok\x83T\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0bT\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^T)Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A
56 s> \xa1FstatusBok
57 s> \r\n
57 s> \r\n
58 received frame(size=75; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
58 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
59 s> 48\r\n
60 s> @\x00\x00\x01\x00\x02\x001
61 s> \x83T\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0bT\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^T)Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A
62 s> \r\n
63 received frame(size=64; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
64 s> 8\r\n
65 s> \x00\x00\x00\x01\x00\x02\x002
66 s> \r\n
59 s> 0\r\n
67 s> 0\r\n
60 s> \r\n
68 s> \r\n
69 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
61 response: [
70 response: [
62 b'\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0b',
71 b'\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0b',
63 b'\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^',
72 b'\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^',
64 b')Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A'
73 b')Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A'
65 ]
74 ]
66
75
67 Requesting just the public heads works
76 Requesting just the public heads works
68
77
69 $ sendhttpv2peer << EOF
78 $ sendhttpv2peer << EOF
70 > command heads
79 > command heads
71 > publiconly 1
80 > publiconly 1
72 > EOF
81 > EOF
73 creating http peer for wire protocol version 2
82 creating http peer for wire protocol version 2
74 sending heads command
83 sending heads command
75 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
84 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
76 s> Accept-Encoding: identity\r\n
85 s> Accept-Encoding: identity\r\n
77 s> accept: application/mercurial-exp-framing-0005\r\n
86 s> accept: application/mercurial-exp-framing-0005\r\n
78 s> content-type: application/mercurial-exp-framing-0005\r\n
87 s> content-type: application/mercurial-exp-framing-0005\r\n
79 s> content-length: 39\r\n
88 s> content-length: 39\r\n
80 s> host: $LOCALIP:$HGPORT\r\n (glob)
89 s> host: $LOCALIP:$HGPORT\r\n (glob)
81 s> user-agent: Mercurial debugwireproto\r\n
90 s> user-agent: Mercurial debugwireproto\r\n
82 s> \r\n
91 s> \r\n
83 s> \x1f\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1JpubliconlyA1DnameEheads
92 s> \x1f\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1JpubliconlyA1DnameEheads
84 s> makefile('rb', None)
93 s> makefile('rb', None)
85 s> HTTP/1.1 200 OK\r\n
94 s> HTTP/1.1 200 OK\r\n
86 s> Server: testing stub value\r\n
95 s> Server: testing stub value\r\n
87 s> Date: $HTTP_DATE$\r\n
96 s> Date: $HTTP_DATE$\r\n
88 s> Content-Type: application/mercurial-exp-framing-0005\r\n
97 s> Content-Type: application/mercurial-exp-framing-0005\r\n
89 s> Transfer-Encoding: chunked\r\n
98 s> Transfer-Encoding: chunked\r\n
90 s> \r\n
99 s> \r\n
91 s> 29\r\n
100 s> 13\r\n
92 s> !\x00\x00\x01\x00\x02\x012
101 s> \x0b\x00\x00\x01\x00\x02\x011
93 s> \xa1FstatusBok\x81Tx\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc
102 s> \xa1FstatusBok
94 s> \r\n
103 s> \r\n
95 received frame(size=33; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
104 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
105 s> 1e\r\n
106 s> \x16\x00\x00\x01\x00\x02\x001
107 s> \x81Tx\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc
108 s> \r\n
109 received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
110 s> 8\r\n
111 s> \x00\x00\x00\x01\x00\x02\x002
112 s> \r\n
96 s> 0\r\n
113 s> 0\r\n
97 s> \r\n
114 s> \r\n
115 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
98 response: [
116 response: [
99 b'x\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc'
117 b'x\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc'
100 ]
118 ]
101
119
102 $ cat error.log
120 $ cat error.log
@@ -1,127 +1,154 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ hg debugdrawdag << EOF
6 $ hg debugdrawdag << EOF
7 > C D
7 > C D
8 > |/
8 > |/
9 > B
9 > B
10 > |
10 > |
11 > A
11 > A
12 > EOF
12 > EOF
13
13
14 $ hg log -T '{rev}:{node} {desc}\n'
14 $ hg log -T '{rev}:{node} {desc}\n'
15 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D
15 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D
16 2:26805aba1e600a82e93661149f2313866a221a7b C
16 2:26805aba1e600a82e93661149f2313866a221a7b C
17 1:112478962961147124edd43549aedd1a335e44bf B
17 1:112478962961147124edd43549aedd1a335e44bf B
18 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A
18 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A
19
19
20 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
20 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
21 $ cat hg.pid > $DAEMON_PIDS
21 $ cat hg.pid > $DAEMON_PIDS
22
22
23 No arguments returns something reasonable
23 No arguments returns something reasonable
24
24
25 $ sendhttpv2peer << EOF
25 $ sendhttpv2peer << EOF
26 > command known
26 > command known
27 > EOF
27 > EOF
28 creating http peer for wire protocol version 2
28 creating http peer for wire protocol version 2
29 sending known command
29 sending known command
30 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
30 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
31 s> Accept-Encoding: identity\r\n
31 s> Accept-Encoding: identity\r\n
32 s> accept: application/mercurial-exp-framing-0005\r\n
32 s> accept: application/mercurial-exp-framing-0005\r\n
33 s> content-type: application/mercurial-exp-framing-0005\r\n
33 s> content-type: application/mercurial-exp-framing-0005\r\n
34 s> content-length: 20\r\n
34 s> content-length: 20\r\n
35 s> host: $LOCALIP:$HGPORT\r\n (glob)
35 s> host: $LOCALIP:$HGPORT\r\n (glob)
36 s> user-agent: Mercurial debugwireproto\r\n
36 s> user-agent: Mercurial debugwireproto\r\n
37 s> \r\n
37 s> \r\n
38 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEknown
38 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEknown
39 s> makefile('rb', None)
39 s> makefile('rb', None)
40 s> HTTP/1.1 200 OK\r\n
40 s> HTTP/1.1 200 OK\r\n
41 s> Server: testing stub value\r\n
41 s> Server: testing stub value\r\n
42 s> Date: $HTTP_DATE$\r\n
42 s> Date: $HTTP_DATE$\r\n
43 s> Content-Type: application/mercurial-exp-framing-0005\r\n
43 s> Content-Type: application/mercurial-exp-framing-0005\r\n
44 s> Transfer-Encoding: chunked\r\n
44 s> Transfer-Encoding: chunked\r\n
45 s> \r\n
45 s> \r\n
46 s> 14\r\n
46 s> 13\r\n
47 s> \x0c\x00\x00\x01\x00\x02\x012
47 s> \x0b\x00\x00\x01\x00\x02\x011
48 s> \xa1FstatusBok@
48 s> \xa1FstatusBok
49 s> \r\n
49 s> \r\n
50 received frame(size=12; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
50 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
51 s> 9\r\n
52 s> \x01\x00\x00\x01\x00\x02\x001
53 s> @
54 s> \r\n
55 received frame(size=1; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
56 s> 8\r\n
57 s> \x00\x00\x00\x01\x00\x02\x002
58 s> \r\n
51 s> 0\r\n
59 s> 0\r\n
52 s> \r\n
60 s> \r\n
61 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
53 response: []
62 response: []
54
63
55 Single known node works
64 Single known node works
56
65
57 $ sendhttpv2peer << EOF
66 $ sendhttpv2peer << EOF
58 > command known
67 > command known
59 > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0']
68 > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0']
60 > EOF
69 > EOF
61 creating http peer for wire protocol version 2
70 creating http peer for wire protocol version 2
62 sending known command
71 sending known command
63 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
72 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
64 s> Accept-Encoding: identity\r\n
73 s> Accept-Encoding: identity\r\n
65 s> accept: application/mercurial-exp-framing-0005\r\n
74 s> accept: application/mercurial-exp-framing-0005\r\n
66 s> content-type: application/mercurial-exp-framing-0005\r\n
75 s> content-type: application/mercurial-exp-framing-0005\r\n
67 s> content-length: 54\r\n
76 s> content-length: 54\r\n
68 s> host: $LOCALIP:$HGPORT\r\n (glob)
77 s> host: $LOCALIP:$HGPORT\r\n (glob)
69 s> user-agent: Mercurial debugwireproto\r\n
78 s> user-agent: Mercurial debugwireproto\r\n
70 s> \r\n
79 s> \r\n
71 s> .\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x81TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0DnameEknown
80 s> .\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x81TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0DnameEknown
72 s> makefile('rb', None)
81 s> makefile('rb', None)
73 s> HTTP/1.1 200 OK\r\n
82 s> HTTP/1.1 200 OK\r\n
74 s> Server: testing stub value\r\n
83 s> Server: testing stub value\r\n
75 s> Date: $HTTP_DATE$\r\n
84 s> Date: $HTTP_DATE$\r\n
76 s> Content-Type: application/mercurial-exp-framing-0005\r\n
85 s> Content-Type: application/mercurial-exp-framing-0005\r\n
77 s> Transfer-Encoding: chunked\r\n
86 s> Transfer-Encoding: chunked\r\n
78 s> \r\n
87 s> \r\n
79 s> 15\r\n
88 s> 13\r\n
80 s> \r\x00\x00\x01\x00\x02\x012
89 s> \x0b\x00\x00\x01\x00\x02\x011
81 s> \xa1FstatusBokA1
90 s> \xa1FstatusBok
82 s> \r\n
91 s> \r\n
83 received frame(size=13; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
92 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
93 s> a\r\n
94 s> \x02\x00\x00\x01\x00\x02\x001
95 s> A1
96 s> \r\n
97 received frame(size=2; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
98 s> 8\r\n
99 s> \x00\x00\x00\x01\x00\x02\x002
100 s> \r\n
84 s> 0\r\n
101 s> 0\r\n
85 s> \r\n
102 s> \r\n
103 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
86 response: [
104 response: [
87 True
105 True
88 ]
106 ]
89
107
90 Multiple nodes works
108 Multiple nodes works
91
109
92 $ sendhttpv2peer << EOF
110 $ sendhttpv2peer << EOF
93 > command known
111 > command known
94 > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0', b'00000000000000000000', b'\x11\x24\x78\x96\x29\x61\x14\x71\x24\xed\xd4\x35\x49\xae\xdd\x1a\x33\x5e\x44\xbf']
112 > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0', b'00000000000000000000', b'\x11\x24\x78\x96\x29\x61\x14\x71\x24\xed\xd4\x35\x49\xae\xdd\x1a\x33\x5e\x44\xbf']
95 > EOF
113 > EOF
96 creating http peer for wire protocol version 2
114 creating http peer for wire protocol version 2
97 sending known command
115 sending known command
98 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
116 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
99 s> Accept-Encoding: identity\r\n
117 s> Accept-Encoding: identity\r\n
100 s> accept: application/mercurial-exp-framing-0005\r\n
118 s> accept: application/mercurial-exp-framing-0005\r\n
101 s> content-type: application/mercurial-exp-framing-0005\r\n
119 s> content-type: application/mercurial-exp-framing-0005\r\n
102 s> content-length: 96\r\n
120 s> content-length: 96\r\n
103 s> host: $LOCALIP:$HGPORT\r\n (glob)
121 s> host: $LOCALIP:$HGPORT\r\n (glob)
104 s> user-agent: Mercurial debugwireproto\r\n
122 s> user-agent: Mercurial debugwireproto\r\n
105 s> \r\n
123 s> \r\n
106 s> X\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x83TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0T00000000000000000000T\x11$x\x96)a\x14q$\xed\xd45I\xae\xdd\x1a3^D\xbfDnameEknown
124 s> X\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x83TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0T00000000000000000000T\x11$x\x96)a\x14q$\xed\xd45I\xae\xdd\x1a3^D\xbfDnameEknown
107 s> makefile('rb', None)
125 s> makefile('rb', None)
108 s> HTTP/1.1 200 OK\r\n
126 s> HTTP/1.1 200 OK\r\n
109 s> Server: testing stub value\r\n
127 s> Server: testing stub value\r\n
110 s> Date: $HTTP_DATE$\r\n
128 s> Date: $HTTP_DATE$\r\n
111 s> Content-Type: application/mercurial-exp-framing-0005\r\n
129 s> Content-Type: application/mercurial-exp-framing-0005\r\n
112 s> Transfer-Encoding: chunked\r\n
130 s> Transfer-Encoding: chunked\r\n
113 s> \r\n
131 s> \r\n
114 s> 17\r\n
132 s> 13\r\n
115 s> \x0f\x00\x00\x01\x00\x02\x012
133 s> \x0b\x00\x00\x01\x00\x02\x011
116 s> \xa1FstatusBokC101
134 s> \xa1FstatusBok
117 s> \r\n
135 s> \r\n
118 received frame(size=15; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
136 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
137 s> c\r\n
138 s> \x04\x00\x00\x01\x00\x02\x001
139 s> C101
140 s> \r\n
141 received frame(size=4; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
142 s> 8\r\n
143 s> \x00\x00\x00\x01\x00\x02\x002
144 s> \r\n
119 s> 0\r\n
145 s> 0\r\n
120 s> \r\n
146 s> \r\n
147 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
121 response: [
148 response: [
122 True,
149 True,
123 False,
150 False,
124 True
151 True
125 ]
152 ]
126
153
127 $ cat error.log
154 $ cat error.log
@@ -1,134 +1,161 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ hg debugdrawdag << EOF
6 $ hg debugdrawdag << EOF
7 > C D
7 > C D
8 > |/
8 > |/
9 > B
9 > B
10 > |
10 > |
11 > A
11 > A
12 > EOF
12 > EOF
13
13
14 $ hg phase --public -r C
14 $ hg phase --public -r C
15 $ hg book -r C @
15 $ hg book -r C @
16
16
17 $ hg log -T '{rev}:{node} {desc}\n'
17 $ hg log -T '{rev}:{node} {desc}\n'
18 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D
18 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D
19 2:26805aba1e600a82e93661149f2313866a221a7b C
19 2:26805aba1e600a82e93661149f2313866a221a7b C
20 1:112478962961147124edd43549aedd1a335e44bf B
20 1:112478962961147124edd43549aedd1a335e44bf B
21 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A
21 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A
22
22
23 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
23 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
24 $ cat hg.pid > $DAEMON_PIDS
24 $ cat hg.pid > $DAEMON_PIDS
25
25
26 Request for namespaces works
26 Request for namespaces works
27
27
28 $ sendhttpv2peer << EOF
28 $ sendhttpv2peer << EOF
29 > command listkeys
29 > command listkeys
30 > namespace namespaces
30 > namespace namespaces
31 > EOF
31 > EOF
32 creating http peer for wire protocol version 2
32 creating http peer for wire protocol version 2
33 sending listkeys command
33 sending listkeys command
34 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
34 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
35 s> Accept-Encoding: identity\r\n
35 s> Accept-Encoding: identity\r\n
36 s> accept: application/mercurial-exp-framing-0005\r\n
36 s> accept: application/mercurial-exp-framing-0005\r\n
37 s> content-type: application/mercurial-exp-framing-0005\r\n
37 s> content-type: application/mercurial-exp-framing-0005\r\n
38 s> content-length: 50\r\n
38 s> content-length: 50\r\n
39 s> host: $LOCALIP:$HGPORT\r\n (glob)
39 s> host: $LOCALIP:$HGPORT\r\n (glob)
40 s> user-agent: Mercurial debugwireproto\r\n
40 s> user-agent: Mercurial debugwireproto\r\n
41 s> \r\n
41 s> \r\n
42 s> *\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceJnamespacesDnameHlistkeys
42 s> *\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceJnamespacesDnameHlistkeys
43 s> makefile('rb', None)
43 s> makefile('rb', None)
44 s> HTTP/1.1 200 OK\r\n
44 s> HTTP/1.1 200 OK\r\n
45 s> Server: testing stub value\r\n
45 s> Server: testing stub value\r\n
46 s> Date: $HTTP_DATE$\r\n
46 s> Date: $HTTP_DATE$\r\n
47 s> Content-Type: application/mercurial-exp-framing-0005\r\n
47 s> Content-Type: application/mercurial-exp-framing-0005\r\n
48 s> Transfer-Encoding: chunked\r\n
48 s> Transfer-Encoding: chunked\r\n
49 s> \r\n
49 s> \r\n
50 s> 33\r\n
50 s> 13\r\n
51 s> +\x00\x00\x01\x00\x02\x012
51 s> \x0b\x00\x00\x01\x00\x02\x011
52 s> \xa1FstatusBok\xa3Ibookmarks@Jnamespaces@Fphases@
52 s> \xa1FstatusBok
53 s> \r\n
53 s> \r\n
54 received frame(size=43; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
54 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
55 s> 28\r\n
56 s> \x00\x00\x01\x00\x02\x001
57 s> \xa3Ibookmarks@Jnamespaces@Fphases@
58 s> \r\n
59 received frame(size=32; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
60 s> 8\r\n
61 s> \x00\x00\x00\x01\x00\x02\x002
62 s> \r\n
55 s> 0\r\n
63 s> 0\r\n
56 s> \r\n
64 s> \r\n
65 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
57 response: {
66 response: {
58 b'bookmarks': b'',
67 b'bookmarks': b'',
59 b'namespaces': b'',
68 b'namespaces': b'',
60 b'phases': b''
69 b'phases': b''
61 }
70 }
62
71
63 Request for phases works
72 Request for phases works
64
73
65 $ sendhttpv2peer << EOF
74 $ sendhttpv2peer << EOF
66 > command listkeys
75 > command listkeys
67 > namespace phases
76 > namespace phases
68 > EOF
77 > EOF
69 creating http peer for wire protocol version 2
78 creating http peer for wire protocol version 2
70 sending listkeys command
79 sending listkeys command
71 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
80 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
72 s> Accept-Encoding: identity\r\n
81 s> Accept-Encoding: identity\r\n
73 s> accept: application/mercurial-exp-framing-0005\r\n
82 s> accept: application/mercurial-exp-framing-0005\r\n
74 s> content-type: application/mercurial-exp-framing-0005\r\n
83 s> content-type: application/mercurial-exp-framing-0005\r\n
75 s> content-length: 46\r\n
84 s> content-length: 46\r\n
76 s> host: $LOCALIP:$HGPORT\r\n (glob)
85 s> host: $LOCALIP:$HGPORT\r\n (glob)
77 s> user-agent: Mercurial debugwireproto\r\n
86 s> user-agent: Mercurial debugwireproto\r\n
78 s> \r\n
87 s> \r\n
79 s> &\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceFphasesDnameHlistkeys
88 s> &\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceFphasesDnameHlistkeys
80 s> makefile('rb', None)
89 s> makefile('rb', None)
81 s> HTTP/1.1 200 OK\r\n
90 s> HTTP/1.1 200 OK\r\n
82 s> Server: testing stub value\r\n
91 s> Server: testing stub value\r\n
83 s> Date: $HTTP_DATE$\r\n
92 s> Date: $HTTP_DATE$\r\n
84 s> Content-Type: application/mercurial-exp-framing-0005\r\n
93 s> Content-Type: application/mercurial-exp-framing-0005\r\n
85 s> Transfer-Encoding: chunked\r\n
94 s> Transfer-Encoding: chunked\r\n
86 s> \r\n
95 s> \r\n
87 s> 50\r\n
96 s> 13\r\n
88 s> H\x00\x00\x01\x00\x02\x012
97 s> \x0b\x00\x00\x01\x00\x02\x011
89 s> \xa1FstatusBok\xa2X(be0ef73c17ade3fc89dc41701eb9fc3a91b58282A1JpublishingDTrue
98 s> \xa1FstatusBok
90 s> \r\n
99 s> \r\n
91 received frame(size=72; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
100 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
101 s> 45\r\n
102 s> =\x00\x00\x01\x00\x02\x001
103 s> \xa2X(be0ef73c17ade3fc89dc41701eb9fc3a91b58282A1JpublishingDTrue
104 s> \r\n
105 received frame(size=61; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
106 s> 8\r\n
107 s> \x00\x00\x00\x01\x00\x02\x002
108 s> \r\n
92 s> 0\r\n
109 s> 0\r\n
93 s> \r\n
110 s> \r\n
111 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
94 response: {
112 response: {
95 b'be0ef73c17ade3fc89dc41701eb9fc3a91b58282': b'1',
113 b'be0ef73c17ade3fc89dc41701eb9fc3a91b58282': b'1',
96 b'publishing': b'True'
114 b'publishing': b'True'
97 }
115 }
98
116
99 Request for bookmarks works
117 Request for bookmarks works
100
118
101 $ sendhttpv2peer << EOF
119 $ sendhttpv2peer << EOF
102 > command listkeys
120 > command listkeys
103 > namespace bookmarks
121 > namespace bookmarks
104 > EOF
122 > EOF
105 creating http peer for wire protocol version 2
123 creating http peer for wire protocol version 2
106 sending listkeys command
124 sending listkeys command
107 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
125 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
108 s> Accept-Encoding: identity\r\n
126 s> Accept-Encoding: identity\r\n
109 s> accept: application/mercurial-exp-framing-0005\r\n
127 s> accept: application/mercurial-exp-framing-0005\r\n
110 s> content-type: application/mercurial-exp-framing-0005\r\n
128 s> content-type: application/mercurial-exp-framing-0005\r\n
111 s> content-length: 49\r\n
129 s> content-length: 49\r\n
112 s> host: $LOCALIP:$HGPORT\r\n (glob)
130 s> host: $LOCALIP:$HGPORT\r\n (glob)
113 s> user-agent: Mercurial debugwireproto\r\n
131 s> user-agent: Mercurial debugwireproto\r\n
114 s> \r\n
132 s> \r\n
115 s> )\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceIbookmarksDnameHlistkeys
133 s> )\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceIbookmarksDnameHlistkeys
116 s> makefile('rb', None)
134 s> makefile('rb', None)
117 s> HTTP/1.1 200 OK\r\n
135 s> HTTP/1.1 200 OK\r\n
118 s> Server: testing stub value\r\n
136 s> Server: testing stub value\r\n
119 s> Date: $HTTP_DATE$\r\n
137 s> Date: $HTTP_DATE$\r\n
120 s> Content-Type: application/mercurial-exp-framing-0005\r\n
138 s> Content-Type: application/mercurial-exp-framing-0005\r\n
121 s> Transfer-Encoding: chunked\r\n
139 s> Transfer-Encoding: chunked\r\n
122 s> \r\n
140 s> \r\n
123 s> 40\r\n
141 s> 13\r\n
124 s> 8\x00\x00\x01\x00\x02\x012
142 s> \x0b\x00\x00\x01\x00\x02\x011
125 s> \xa1FstatusBok\xa1A@X(26805aba1e600a82e93661149f2313866a221a7b
143 s> \xa1FstatusBok
126 s> \r\n
144 s> \r\n
127 received frame(size=56; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
145 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
146 s> 35\r\n
147 s> -\x00\x00\x01\x00\x02\x001
148 s> \xa1A@X(26805aba1e600a82e93661149f2313866a221a7b
149 s> \r\n
150 received frame(size=45; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
151 s> 8\r\n
152 s> \x00\x00\x00\x01\x00\x02\x002
153 s> \r\n
128 s> 0\r\n
154 s> 0\r\n
129 s> \r\n
155 s> \r\n
156 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
130 response: {
157 response: {
131 b'@': b'26805aba1e600a82e93661149f2313866a221a7b'
158 b'@': b'26805aba1e600a82e93661149f2313866a221a7b'
132 }
159 }
133
160
134 $ cat error.log
161 $ cat error.log
@@ -1,55 +1,64 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ cat >> .hg/hgrc << EOF
6 $ cat >> .hg/hgrc << EOF
7 > [web]
7 > [web]
8 > push_ssl = false
8 > push_ssl = false
9 > allow-push = *
9 > allow-push = *
10 > EOF
10 > EOF
11 $ hg debugdrawdag << EOF
11 $ hg debugdrawdag << EOF
12 > C D
12 > C D
13 > |/
13 > |/
14 > B
14 > B
15 > |
15 > |
16 > A
16 > A
17 > EOF
17 > EOF
18
18
19 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
19 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
20 $ cat hg.pid > $DAEMON_PIDS
20 $ cat hg.pid > $DAEMON_PIDS
21
21
22 lookup for known node works
22 lookup for known node works
23
23
24 $ sendhttpv2peer << EOF
24 $ sendhttpv2peer << EOF
25 > command lookup
25 > command lookup
26 > key 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
26 > key 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
27 > EOF
27 > EOF
28 creating http peer for wire protocol version 2
28 creating http peer for wire protocol version 2
29 sending lookup command
29 sending lookup command
30 s> *\r\n (glob)
30 s> *\r\n (glob)
31 s> Accept-Encoding: identity\r\n
31 s> Accept-Encoding: identity\r\n
32 s> accept: application/mercurial-exp-framing-0005\r\n
32 s> accept: application/mercurial-exp-framing-0005\r\n
33 s> content-type: application/mercurial-exp-framing-0005\r\n
33 s> content-type: application/mercurial-exp-framing-0005\r\n
34 s> content-length: 73\r\n
34 s> content-length: 73\r\n
35 s> host: $LOCALIP:$HGPORT\r\n (glob)
35 s> host: $LOCALIP:$HGPORT\r\n (glob)
36 s> user-agent: Mercurial debugwireproto\r\n
36 s> user-agent: Mercurial debugwireproto\r\n
37 s> \r\n
37 s> \r\n
38 s> A\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1CkeyX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0DnameFlookup
38 s> A\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1CkeyX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0DnameFlookup
39 s> makefile('rb', None)
39 s> makefile('rb', None)
40 s> HTTP/1.1 200 OK\r\n
40 s> HTTP/1.1 200 OK\r\n
41 s> Server: testing stub value\r\n
41 s> Server: testing stub value\r\n
42 s> Date: $HTTP_DATE$\r\n
42 s> Date: $HTTP_DATE$\r\n
43 s> Content-Type: application/mercurial-exp-framing-0005\r\n
43 s> Content-Type: application/mercurial-exp-framing-0005\r\n
44 s> Transfer-Encoding: chunked\r\n
44 s> Transfer-Encoding: chunked\r\n
45 s> \r\n
45 s> \r\n
46 s> 28\r\n
46 s> 13\r\n
47 s> \x00\x00\x01\x00\x02\x012
47 s> \x0b\x00\x00\x01\x00\x02\x011
48 s> \xa1FstatusBokTBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0
48 s> \xa1FstatusBok
49 s> \r\n
49 s> \r\n
50 received frame(size=32; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
50 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
51 s> 1d\r\n
52 s> \x15\x00\x00\x01\x00\x02\x001
53 s> TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0
54 s> \r\n
55 received frame(size=21; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
56 s> 8\r\n
57 s> \x00\x00\x00\x01\x00\x02\x002
58 s> \r\n
51 s> 0\r\n
59 s> 0\r\n
52 s> \r\n
60 s> \r\n
61 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
53 response: b'Bk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0'
62 response: b'Bk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0'
54
63
55 $ cat error.log
64 $ cat error.log
@@ -1,91 +1,109 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ cat >> .hg/hgrc << EOF
6 $ cat >> .hg/hgrc << EOF
7 > [web]
7 > [web]
8 > push_ssl = false
8 > push_ssl = false
9 > allow-push = *
9 > allow-push = *
10 > EOF
10 > EOF
11 $ hg debugdrawdag << EOF
11 $ hg debugdrawdag << EOF
12 > C D
12 > C D
13 > |/
13 > |/
14 > B
14 > B
15 > |
15 > |
16 > A
16 > A
17 > EOF
17 > EOF
18
18
19 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
19 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
20 $ cat hg.pid > $DAEMON_PIDS
20 $ cat hg.pid > $DAEMON_PIDS
21
21
22 pushkey for a bookmark works
22 pushkey for a bookmark works
23
23
24 $ sendhttpv2peer << EOF
24 $ sendhttpv2peer << EOF
25 > command pushkey
25 > command pushkey
26 > namespace bookmarks
26 > namespace bookmarks
27 > key @
27 > key @
28 > old
28 > old
29 > new 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
29 > new 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
30 > EOF
30 > EOF
31 creating http peer for wire protocol version 2
31 creating http peer for wire protocol version 2
32 sending pushkey command
32 sending pushkey command
33 s> *\r\n (glob)
33 s> *\r\n (glob)
34 s> Accept-Encoding: identity\r\n
34 s> Accept-Encoding: identity\r\n
35 s> accept: application/mercurial-exp-framing-0005\r\n
35 s> accept: application/mercurial-exp-framing-0005\r\n
36 s> content-type: application/mercurial-exp-framing-0005\r\n
36 s> content-type: application/mercurial-exp-framing-0005\r\n
37 s> content-length: 105\r\n
37 s> content-length: 105\r\n
38 s> host: $LOCALIP:$HGPORT\r\n (glob)
38 s> host: $LOCALIP:$HGPORT\r\n (glob)
39 s> user-agent: Mercurial debugwireproto\r\n
39 s> user-agent: Mercurial debugwireproto\r\n
40 s> \r\n
40 s> \r\n
41 s> a\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4CkeyA@InamespaceIbookmarksCnewX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0Cold@DnameGpushkey
41 s> a\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4CkeyA@InamespaceIbookmarksCnewX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0Cold@DnameGpushkey
42 s> makefile('rb', None)
42 s> makefile('rb', None)
43 s> HTTP/1.1 200 OK\r\n
43 s> HTTP/1.1 200 OK\r\n
44 s> Server: testing stub value\r\n
44 s> Server: testing stub value\r\n
45 s> Date: $HTTP_DATE$\r\n
45 s> Date: $HTTP_DATE$\r\n
46 s> Content-Type: application/mercurial-exp-framing-0005\r\n
46 s> Content-Type: application/mercurial-exp-framing-0005\r\n
47 s> Transfer-Encoding: chunked\r\n
47 s> Transfer-Encoding: chunked\r\n
48 s> \r\n
48 s> \r\n
49 s> 14\r\n
49 s> 13\r\n
50 s> \x0c\x00\x00\x01\x00\x02\x012
50 s> \x0b\x00\x00\x01\x00\x02\x011
51 s> \xa1FstatusBok\xf5
51 s> \xa1FstatusBok
52 s> \r\n
52 s> \r\n
53 received frame(size=12; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
53 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
54 s> 9\r\n
55 s> \x01\x00\x00\x01\x00\x02\x001
56 s> \xf5
57 s> \r\n
58 received frame(size=1; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
59 s> 8\r\n
60 s> \x00\x00\x00\x01\x00\x02\x002
61 s> \r\n
54 s> 0\r\n
62 s> 0\r\n
55 s> \r\n
63 s> \r\n
64 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
56 response: True
65 response: True
57
66
58 $ sendhttpv2peer << EOF
67 $ sendhttpv2peer << EOF
59 > command listkeys
68 > command listkeys
60 > namespace bookmarks
69 > namespace bookmarks
61 > EOF
70 > EOF
62 creating http peer for wire protocol version 2
71 creating http peer for wire protocol version 2
63 sending listkeys command
72 sending listkeys command
64 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
73 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
65 s> Accept-Encoding: identity\r\n
74 s> Accept-Encoding: identity\r\n
66 s> accept: application/mercurial-exp-framing-0005\r\n
75 s> accept: application/mercurial-exp-framing-0005\r\n
67 s> content-type: application/mercurial-exp-framing-0005\r\n
76 s> content-type: application/mercurial-exp-framing-0005\r\n
68 s> content-length: 49\r\n
77 s> content-length: 49\r\n
69 s> host: $LOCALIP:$HGPORT\r\n (glob)
78 s> host: $LOCALIP:$HGPORT\r\n (glob)
70 s> user-agent: Mercurial debugwireproto\r\n
79 s> user-agent: Mercurial debugwireproto\r\n
71 s> \r\n
80 s> \r\n
72 s> )\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceIbookmarksDnameHlistkeys
81 s> )\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceIbookmarksDnameHlistkeys
73 s> makefile('rb', None)
82 s> makefile('rb', None)
74 s> HTTP/1.1 200 OK\r\n
83 s> HTTP/1.1 200 OK\r\n
75 s> Server: testing stub value\r\n
84 s> Server: testing stub value\r\n
76 s> Date: $HTTP_DATE$\r\n
85 s> Date: $HTTP_DATE$\r\n
77 s> Content-Type: application/mercurial-exp-framing-0005\r\n
86 s> Content-Type: application/mercurial-exp-framing-0005\r\n
78 s> Transfer-Encoding: chunked\r\n
87 s> Transfer-Encoding: chunked\r\n
79 s> \r\n
88 s> \r\n
80 s> 40\r\n
89 s> 13\r\n
81 s> 8\x00\x00\x01\x00\x02\x012
90 s> \x0b\x00\x00\x01\x00\x02\x011
82 s> \xa1FstatusBok\xa1A@X(426bada5c67598ca65036d57d9e4b64b0c1ce7a0
91 s> \xa1FstatusBok
83 s> \r\n
92 s> \r\n
84 received frame(size=56; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
93 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
94 s> 35\r\n
95 s> -\x00\x00\x01\x00\x02\x001
96 s> \xa1A@X(426bada5c67598ca65036d57d9e4b64b0c1ce7a0
97 s> \r\n
98 received frame(size=45; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
99 s> 8\r\n
100 s> \x00\x00\x00\x01\x00\x02\x002
101 s> \r\n
85 s> 0\r\n
102 s> 0\r\n
86 s> \r\n
103 s> \r\n
104 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
87 response: {
105 response: {
88 b'@': b'426bada5c67598ca65036d57d9e4b64b0c1ce7a0'
106 b'@': b'426bada5c67598ca65036d57d9e4b64b0c1ce7a0'
89 }
107 }
90
108
91 $ cat error.log
109 $ cat error.log
@@ -1,58 +1,58 b''
1 HTTPV2=exp-http-v2-0001
1 HTTPV2=exp-http-v2-0001
2 MEDIATYPE=application/mercurial-exp-framing-0005
2 MEDIATYPE=application/mercurial-exp-framing-0005
3
3
4 sendhttpraw() {
4 sendhttpraw() {
5 hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
5 hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
6 }
6 }
7
7
8 sendhttpv2peer() {
8 sendhttpv2peer() {
9 hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
9 hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
10 }
10 }
11
11
12 sendhttpv2peerhandshake() {
12 sendhttpv2peerhandshake() {
13 hg --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/
13 hg --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/
14 }
14 }
15
15
16 cat > dummycommands.py << EOF
16 cat > dummycommands.py << EOF
17 from mercurial import (
17 from mercurial import (
18 wireprototypes,
18 wireprototypes,
19 wireprotov1server,
19 wireprotov1server,
20 wireprotov2server,
20 wireprotov2server,
21 )
21 )
22
22
23 @wireprotov1server.wireprotocommand(b'customreadonly', permission=b'pull')
23 @wireprotov1server.wireprotocommand(b'customreadonly', permission=b'pull')
24 def customreadonlyv1(repo, proto):
24 def customreadonlyv1(repo, proto):
25 return wireprototypes.bytesresponse(b'customreadonly bytes response')
25 return wireprototypes.bytesresponse(b'customreadonly bytes response')
26
26
27 @wireprotov2server.wireprotocommand(b'customreadonly', permission=b'pull')
27 @wireprotov2server.wireprotocommand(b'customreadonly', permission=b'pull')
28 def customreadonlyv2(repo, proto):
28 def customreadonlyv2(repo, proto):
29 return wireprototypes.cborresponse(b'customreadonly bytes response')
29 yield b'customreadonly bytes response'
30
30
31 @wireprotov1server.wireprotocommand(b'customreadwrite', permission=b'push')
31 @wireprotov1server.wireprotocommand(b'customreadwrite', permission=b'push')
32 def customreadwrite(repo, proto):
32 def customreadwrite(repo, proto):
33 return wireprototypes.bytesresponse(b'customreadwrite bytes response')
33 return wireprototypes.bytesresponse(b'customreadwrite bytes response')
34
34
35 @wireprotov2server.wireprotocommand(b'customreadwrite', permission=b'push')
35 @wireprotov2server.wireprotocommand(b'customreadwrite', permission=b'push')
36 def customreadwritev2(repo, proto):
36 def customreadwritev2(repo, proto):
37 return wireprototypes.cborresponse(b'customreadwrite bytes response')
37 yield b'customreadwrite bytes response'
38 EOF
38 EOF
39
39
40 cat >> $HGRCPATH << EOF
40 cat >> $HGRCPATH << EOF
41 [extensions]
41 [extensions]
42 drawdag = $TESTDIR/drawdag.py
42 drawdag = $TESTDIR/drawdag.py
43 EOF
43 EOF
44
44
45 enabledummycommands() {
45 enabledummycommands() {
46 cat >> $HGRCPATH << EOF
46 cat >> $HGRCPATH << EOF
47 [extensions]
47 [extensions]
48 dummycommands = $TESTTMP/dummycommands.py
48 dummycommands = $TESTTMP/dummycommands.py
49 EOF
49 EOF
50 }
50 }
51
51
52 enablehttpv2() {
52 enablehttpv2() {
53 cat >> $1/.hg/hgrc << EOF
53 cat >> $1/.hg/hgrc << EOF
54 [experimental]
54 [experimental]
55 web.apiserver = true
55 web.apiserver = true
56 web.api.http-v2 = true
56 web.api.http-v2 = true
57 EOF
57 EOF
58 }
58 }
General Comments 0
You need to be logged in to leave comments. Login now