Show More
@@ -2765,12 +2765,14 b' def debugwireproto(ui, repo, path=None, ' | |||||
2765 | syntax. |
|
2765 | syntax. | |
2766 |
|
2766 | |||
2767 | A frame is composed as a type, flags, and payload. These can be parsed |
|
2767 | A frame is composed as a type, flags, and payload. These can be parsed | |
2768 |
from a string of the form ``<type> <flags> <payload>``. That is, |
|
2768 | from a string of the form ``<requestid> <type> <flags> <payload>``. That is, | |
2769 | space-delimited strings. |
|
2769 | 4 space-delimited strings. | |
2770 |
|
2770 | |||
2771 | ``payload`` is the simplest: it is evaluated as a Python byte string |
|
2771 | ``payload`` is the simplest: it is evaluated as a Python byte string | |
2772 | literal. |
|
2772 | literal. | |
2773 |
|
2773 | |||
|
2774 | ``requestid`` is an integer defining the request identifier. | |||
|
2775 | ||||
2774 | ``type`` can be an integer value for the frame type or the string name |
|
2776 | ``type`` can be an integer value for the frame type or the string name | |
2775 | of the type. The strings are defined in ``wireprotoframing.py``. e.g. |
|
2777 | of the type. The strings are defined in ``wireprotoframing.py``. e.g. | |
2776 | ``command-name``. |
|
2778 | ``command-name``. |
@@ -469,22 +469,26 b' for sending data and another for receivi' | |||||
469 |
|
469 | |||
470 | The protocol is request-response based: the client issues requests to |
|
470 | The protocol is request-response based: the client issues requests to | |
471 | the server, which issues replies to those requests. Server-initiated |
|
471 | the server, which issues replies to those requests. Server-initiated | |
472 | messaging is not supported. |
|
472 | messaging is not currently supported, but this specification carves | |
|
473 | out room to implement it. | |||
473 |
|
474 | |||
474 | All data is read and written in atomic units called *frames*. These |
|
475 | All data is read and written in atomic units called *frames*. These | |
475 | are conceptually similar to TCP packets. Higher-level functionality |
|
476 | are conceptually similar to TCP packets. Higher-level functionality | |
476 | is built on the exchange and processing of frames. |
|
477 | is built on the exchange and processing of frames. | |
477 |
|
478 | |||
478 | Frames begin with a 4 octet header followed by a variable length |
|
479 | All frames are associated with a numbered request. Frames can thus | |
|
480 | be logically grouped by their request ID. | |||
|
481 | ||||
|
482 | Frames begin with a 6 octet header followed by a variable length | |||
479 | payload:: |
|
483 | payload:: | |
480 |
|
484 | |||
481 | +-----------------------------------------------+ |
|
485 | +-----------------------------------------------+ | |
482 | | Length (24) | |
|
486 | | Length (24) | | |
483 |
+----------- |
|
487 | +---------------------------------+-------------+ | |
484 | | Type (4) | |
|
488 | | Request ID (16) | | |
485 | +-----------+ |
|
489 | +----------+-----------+----------+ | |
486 | | Flags (4) | |
|
490 | | Type (4) | Flags (4) | | |
487 |
+========== |
|
491 | +==========+===========+========================================| | |
488 | | Frame Payload (0...) ... |
|
492 | | Frame Payload (0...) ... | |
489 | +---------------------------------------------------------------+ |
|
493 | +---------------------------------------------------------------+ | |
490 |
|
494 | |||
@@ -494,6 +498,15 b' given permission by the server as part o' | |||||
494 | during the handshake. The frame header is not part of the advertised |
|
498 | during the handshake. The frame header is not part of the advertised | |
495 | frame length. |
|
499 | frame length. | |
496 |
|
500 | |||
|
501 | The 16-bit ``Request ID`` field denotes the integer request identifier, | |||
|
502 | stored as an unsigned little endian integer. Odd numbered requests are | |||
|
503 | client-initiated. Even numbered requests are server-initiated. This | |||
|
504 | refers to where the *request* was initiated - not where the *frame* was | |||
|
505 | initiated, so servers will send frames with odd ``Request ID`` in | |||
|
506 | response to client-initiated requests. Implementations are advised to | |||
|
507 | start ordering request identifiers at ``1`` and ``0``, increment by | |||
|
508 | ``2``, and wrap around if all available numbers have been exhausted. | |||
|
509 | ||||
497 | The 4-bit ``Type`` field denotes the type of message being sent. |
|
510 | The 4-bit ``Type`` field denotes the type of message being sent. | |
498 |
|
511 | |||
499 | The 4-bit ``Flags`` field defines special, per-type attributes for |
|
512 | The 4-bit ``Flags`` field defines special, per-type attributes for | |
@@ -633,6 +646,28 b' frames defining that command. This logic' | |||||
633 | 1 ``Command Request`` frame, 0 or more ``Command Argument`` frames, |
|
646 | 1 ``Command Request`` frame, 0 or more ``Command Argument`` frames, | |
634 | and 0 or more ``Command Data`` frames. |
|
647 | and 0 or more ``Command Data`` frames. | |
635 |
|
648 | |||
|
649 | All frames composing a single command request MUST be associated with | |||
|
650 | the same ``Request ID``. | |||
|
651 | ||||
|
652 | Clients MAY send additional command requests without waiting on the | |||
|
653 | response to a previous command request. If they do so, they MUST ensure | |||
|
654 | that the ``Request ID`` field of outbound frames does not conflict | |||
|
655 | with that of an active ``Request ID`` whose response has not yet been | |||
|
656 | fully received. | |||
|
657 | ||||
|
658 | Servers MAY respond to commands in a different order than they were | |||
|
659 | sent over the wire. Clients MUST be prepared to deal with this. Servers | |||
|
660 | also MAY start executing commands in a different order than they were | |||
|
661 | received, or MAY execute multiple commands concurrently. | |||
|
662 | ||||
|
663 | If there is a dependency between commands or a race condition between | |||
|
664 | commands executing (e.g. a read-only command that depends on the results | |||
|
665 | of a command that mutates the repository), then clients MUST NOT send | |||
|
666 | frames issuing a command until a response to all dependent commands has | |||
|
667 | been received. | |||
|
668 | TODO think about whether we should express dependencies between commands | |||
|
669 | to avoid roundtrip latency. | |||
|
670 | ||||
636 | Argument frames are the recommended mechanism for transferring fixed |
|
671 | Argument frames are the recommended mechanism for transferring fixed | |
637 | sets of parameters to a command. Data frames are appropriate for |
|
672 | sets of parameters to a command. Data frames are appropriate for | |
638 | transferring variable data. A similar comparison would be to HTTP: |
|
673 | transferring variable data. A similar comparison would be to HTTP: |
@@ -19,7 +19,7 b' from . import (' | |||||
19 | util, |
|
19 | util, | |
20 | ) |
|
20 | ) | |
21 |
|
21 | |||
22 |
FRAME_HEADER_SIZE = |
|
22 | FRAME_HEADER_SIZE = 6 | |
23 | DEFAULT_MAX_FRAME_SIZE = 32768 |
|
23 | DEFAULT_MAX_FRAME_SIZE = 32768 | |
24 |
|
24 | |||
25 | FRAME_TYPE_COMMAND_NAME = 0x01 |
|
25 | FRAME_TYPE_COMMAND_NAME = 0x01 | |
@@ -89,28 +89,43 b' FRAME_TYPE_FLAGS = {' | |||||
89 |
|
89 | |||
90 | ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH') |
|
90 | ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH') | |
91 |
|
91 | |||
92 | def makeframe(frametype, frameflags, payload): |
|
92 | def makeframe(requestid, frametype, frameflags, payload): | |
93 | """Assemble a frame into a byte array.""" |
|
93 | """Assemble a frame into a byte array.""" | |
94 | # TODO assert size of payload. |
|
94 | # TODO assert size of payload. | |
95 | frame = bytearray(FRAME_HEADER_SIZE + len(payload)) |
|
95 | frame = bytearray(FRAME_HEADER_SIZE + len(payload)) | |
96 |
|
96 | |||
|
97 | # 24 bits length | |||
|
98 | # 16 bits request id | |||
|
99 | # 4 bits type | |||
|
100 | # 4 bits flags | |||
|
101 | ||||
97 | l = struct.pack(r'<I', len(payload)) |
|
102 | l = struct.pack(r'<I', len(payload)) | |
98 | frame[0:3] = l[0:3] |
|
103 | frame[0:3] = l[0:3] | |
99 | frame[3] = (frametype << 4) | frameflags |
|
104 | struct.pack_into(r'<H', frame, 3, requestid) | |
100 | frame[4:] = payload |
|
105 | frame[5] = (frametype << 4) | frameflags | |
|
106 | frame[6:] = payload | |||
101 |
|
107 | |||
102 | return frame |
|
108 | return frame | |
103 |
|
109 | |||
104 | def makeframefromhumanstring(s): |
|
110 | def makeframefromhumanstring(s): | |
105 | """Given a string of the form: <type> <flags> <payload>, creates a frame. |
|
111 | """Create a frame from a human readable string | |
|
112 | ||||
|
113 | Strings have the form: | |||
|
114 | ||||
|
115 | <request-id> <type> <flags> <payload> | |||
106 |
|
116 | |||
107 | This can be used by user-facing applications and tests for creating |
|
117 | This can be used by user-facing applications and tests for creating | |
108 | frames easily without having to type out a bunch of constants. |
|
118 | frames easily without having to type out a bunch of constants. | |
109 |
|
119 | |||
|
120 | Request ID is an integer. | |||
|
121 | ||||
110 | Frame type and flags can be specified by integer or named constant. |
|
122 | Frame type and flags can be specified by integer or named constant. | |
|
123 | ||||
111 | Flags can be delimited by `|` to bitwise OR them together. |
|
124 | Flags can be delimited by `|` to bitwise OR them together. | |
112 | """ |
|
125 | """ | |
113 |
frametype, frameflags, payload = s.split(b' ', |
|
126 | requestid, frametype, frameflags, payload = s.split(b' ', 3) | |
|
127 | ||||
|
128 | requestid = int(requestid) | |||
114 |
|
129 | |||
115 | if frametype in FRAME_TYPES: |
|
130 | if frametype in FRAME_TYPES: | |
116 | frametype = FRAME_TYPES[frametype] |
|
131 | frametype = FRAME_TYPES[frametype] | |
@@ -127,7 +142,7 b' def makeframefromhumanstring(s):' | |||||
127 |
|
142 | |||
128 | payload = util.unescapestr(payload) |
|
143 | payload = util.unescapestr(payload) | |
129 |
|
144 | |||
130 | return makeframe(frametype, finalflags, payload) |
|
145 | return makeframe(requestid, frametype, finalflags, payload) | |
131 |
|
146 | |||
132 | def parseheader(data): |
|
147 | def parseheader(data): | |
133 | """Parse a unified framing protocol frame header from a buffer. |
|
148 | """Parse a unified framing protocol frame header from a buffer. | |
@@ -140,12 +155,13 b' def parseheader(data):' | |||||
140 | # 4 bits frame flags |
|
155 | # 4 bits frame flags | |
141 | # ... payload |
|
156 | # ... payload | |
142 | framelength = data[0] + 256 * data[1] + 16384 * data[2] |
|
157 | framelength = data[0] + 256 * data[1] + 16384 * data[2] | |
143 | typeflags = data[3] |
|
158 | requestid = struct.unpack_from(r'<H', data, 3)[0] | |
|
159 | typeflags = data[5] | |||
144 |
|
160 | |||
145 | frametype = (typeflags & 0xf0) >> 4 |
|
161 | frametype = (typeflags & 0xf0) >> 4 | |
146 | frameflags = typeflags & 0x0f |
|
162 | frameflags = typeflags & 0x0f | |
147 |
|
163 | |||
148 | return frametype, frameflags, framelength |
|
164 | return requestid, frametype, frameflags, framelength | |
149 |
|
165 | |||
150 | def readframe(fh): |
|
166 | def readframe(fh): | |
151 | """Read a unified framing protocol frame from a file object. |
|
167 | """Read a unified framing protocol frame from a file object. | |
@@ -165,16 +181,16 b' def readframe(fh):' | |||||
165 | raise error.Abort(_('received incomplete frame: got %d bytes: %s') % |
|
181 | raise error.Abort(_('received incomplete frame: got %d bytes: %s') % | |
166 | (readcount, header)) |
|
182 | (readcount, header)) | |
167 |
|
183 | |||
168 | frametype, frameflags, framelength = parseheader(header) |
|
184 | requestid, frametype, frameflags, framelength = parseheader(header) | |
169 |
|
185 | |||
170 | payload = fh.read(framelength) |
|
186 | payload = fh.read(framelength) | |
171 | if len(payload) != framelength: |
|
187 | if len(payload) != framelength: | |
172 | raise error.Abort(_('frame length error: expected %d; got %d') % |
|
188 | raise error.Abort(_('frame length error: expected %d; got %d') % | |
173 | (framelength, len(payload))) |
|
189 | (framelength, len(payload))) | |
174 |
|
190 | |||
175 | return frametype, frameflags, payload |
|
191 | return requestid, frametype, frameflags, payload | |
176 |
|
192 | |||
177 | def createcommandframes(cmd, args, datafh=None): |
|
193 | def createcommandframes(requestid, cmd, args, datafh=None): | |
178 | """Create frames necessary to transmit a request to run a command. |
|
194 | """Create frames necessary to transmit a request to run a command. | |
179 |
|
195 | |||
180 | This is a generator of bytearrays. Each item represents a frame |
|
196 | This is a generator of bytearrays. Each item represents a frame | |
@@ -189,7 +205,7 b' def createcommandframes(cmd, args, dataf' | |||||
189 | if not flags: |
|
205 | if not flags: | |
190 | flags |= FLAG_COMMAND_NAME_EOS |
|
206 | flags |= FLAG_COMMAND_NAME_EOS | |
191 |
|
207 | |||
192 | yield makeframe(FRAME_TYPE_COMMAND_NAME, flags, cmd) |
|
208 | yield makeframe(requestid, FRAME_TYPE_COMMAND_NAME, flags, cmd) | |
193 |
|
209 | |||
194 | for i, k in enumerate(sorted(args)): |
|
210 | for i, k in enumerate(sorted(args)): | |
195 | v = args[k] |
|
211 | v = args[k] | |
@@ -205,7 +221,7 b' def createcommandframes(cmd, args, dataf' | |||||
205 | payload[offset:offset + len(v)] = v |
|
221 | payload[offset:offset + len(v)] = v | |
206 |
|
222 | |||
207 | flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0 |
|
223 | flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0 | |
208 | yield makeframe(FRAME_TYPE_COMMAND_ARGUMENT, flags, payload) |
|
224 | yield makeframe(requestid, FRAME_TYPE_COMMAND_ARGUMENT, flags, payload) | |
209 |
|
225 | |||
210 | if datafh: |
|
226 | if datafh: | |
211 | while True: |
|
227 | while True: | |
@@ -219,12 +235,12 b' def createcommandframes(cmd, args, dataf' | |||||
219 | assert datafh.read(1) == b'' |
|
235 | assert datafh.read(1) == b'' | |
220 | done = True |
|
236 | done = True | |
221 |
|
237 | |||
222 | yield makeframe(FRAME_TYPE_COMMAND_DATA, flags, data) |
|
238 | yield makeframe(requestid, FRAME_TYPE_COMMAND_DATA, flags, data) | |
223 |
|
239 | |||
224 | if done: |
|
240 | if done: | |
225 | break |
|
241 | break | |
226 |
|
242 | |||
227 | def createbytesresponseframesfrombytes(data, |
|
243 | def createbytesresponseframesfrombytes(requestid, data, | |
228 | maxframesize=DEFAULT_MAX_FRAME_SIZE): |
|
244 | maxframesize=DEFAULT_MAX_FRAME_SIZE): | |
229 | """Create a raw frame to send a bytes response from static bytes input. |
|
245 | """Create a raw frame to send a bytes response from static bytes input. | |
230 |
|
246 | |||
@@ -233,7 +249,7 b' def createbytesresponseframesfrombytes(d' | |||||
233 |
|
249 | |||
234 | # Simple case of a single frame. |
|
250 | # Simple case of a single frame. | |
235 | if len(data) <= maxframesize: |
|
251 | if len(data) <= maxframesize: | |
236 | yield makeframe(FRAME_TYPE_BYTES_RESPONSE, |
|
252 | yield makeframe(requestid, FRAME_TYPE_BYTES_RESPONSE, | |
237 | FLAG_BYTES_RESPONSE_EOS, data) |
|
253 | FLAG_BYTES_RESPONSE_EOS, data) | |
238 | return |
|
254 | return | |
239 |
|
255 | |||
@@ -248,12 +264,12 b' def createbytesresponseframesfrombytes(d' | |||||
248 | else: |
|
264 | else: | |
249 | flags = FLAG_BYTES_RESPONSE_CONTINUATION |
|
265 | flags = FLAG_BYTES_RESPONSE_CONTINUATION | |
250 |
|
266 | |||
251 | yield makeframe(FRAME_TYPE_BYTES_RESPONSE, flags, chunk) |
|
267 | yield makeframe(requestid, FRAME_TYPE_BYTES_RESPONSE, flags, chunk) | |
252 |
|
268 | |||
253 | if done: |
|
269 | if done: | |
254 | break |
|
270 | break | |
255 |
|
271 | |||
256 | def createerrorframe(msg, protocol=False, application=False): |
|
272 | def createerrorframe(requestid, msg, protocol=False, application=False): | |
257 | # TODO properly handle frame size limits. |
|
273 | # TODO properly handle frame size limits. | |
258 | assert len(msg) <= DEFAULT_MAX_FRAME_SIZE |
|
274 | assert len(msg) <= DEFAULT_MAX_FRAME_SIZE | |
259 |
|
275 | |||
@@ -263,7 +279,7 b' def createerrorframe(msg, protocol=False' | |||||
263 | if application: |
|
279 | if application: | |
264 | flags |= FLAG_ERROR_RESPONSE_APPLICATION |
|
280 | flags |= FLAG_ERROR_RESPONSE_APPLICATION | |
265 |
|
281 | |||
266 | yield makeframe(FRAME_TYPE_ERROR_RESPONSE, flags, msg) |
|
282 | yield makeframe(requestid, FRAME_TYPE_ERROR_RESPONSE, flags, msg) | |
267 |
|
283 | |||
268 | class serverreactor(object): |
|
284 | class serverreactor(object): | |
269 | """Holds state of a server handling frame-based protocol requests. |
|
285 | """Holds state of a server handling frame-based protocol requests. | |
@@ -326,6 +342,7 b' class serverreactor(object):' | |||||
326 | self._deferoutput = deferoutput |
|
342 | self._deferoutput = deferoutput | |
327 | self._state = 'idle' |
|
343 | self._state = 'idle' | |
328 | self._bufferedframegens = [] |
|
344 | self._bufferedframegens = [] | |
|
345 | self._activerequestid = None | |||
329 | self._activecommand = None |
|
346 | self._activecommand = None | |
330 | self._activeargs = None |
|
347 | self._activeargs = None | |
331 | self._activedata = None |
|
348 | self._activedata = None | |
@@ -334,7 +351,7 b' class serverreactor(object):' | |||||
334 | self._activeargname = None |
|
351 | self._activeargname = None | |
335 | self._activeargchunks = None |
|
352 | self._activeargchunks = None | |
336 |
|
353 | |||
337 | def onframerecv(self, frametype, frameflags, payload): |
|
354 | def onframerecv(self, requestid, frametype, frameflags, payload): | |
338 | """Process a frame that has been received off the wire. |
|
355 | """Process a frame that has been received off the wire. | |
339 |
|
356 | |||
340 | Returns a dict with an ``action`` key that details what action, |
|
357 | Returns a dict with an ``action`` key that details what action, | |
@@ -351,14 +368,14 b' class serverreactor(object):' | |||||
351 | if not meth: |
|
368 | if not meth: | |
352 | raise error.ProgrammingError('unhandled state: %s' % self._state) |
|
369 | raise error.ProgrammingError('unhandled state: %s' % self._state) | |
353 |
|
370 | |||
354 | return meth(frametype, frameflags, payload) |
|
371 | return meth(requestid, frametype, frameflags, payload) | |
355 |
|
372 | |||
356 | def onbytesresponseready(self, data): |
|
373 | def onbytesresponseready(self, requestid, data): | |
357 | """Signal that a bytes response is ready to be sent to the client. |
|
374 | """Signal that a bytes response is ready to be sent to the client. | |
358 |
|
375 | |||
359 | The raw bytes response is passed as an argument. |
|
376 | The raw bytes response is passed as an argument. | |
360 | """ |
|
377 | """ | |
361 | framegen = createbytesresponseframesfrombytes(data) |
|
378 | framegen = createbytesresponseframesfrombytes(requestid, data) | |
362 |
|
379 | |||
363 | if self._deferoutput: |
|
380 | if self._deferoutput: | |
364 | self._bufferedframegens.append(framegen) |
|
381 | self._bufferedframegens.append(framegen) | |
@@ -387,9 +404,9 b' class serverreactor(object):' | |||||
387 | 'framegen': makegen(), |
|
404 | 'framegen': makegen(), | |
388 | } |
|
405 | } | |
389 |
|
406 | |||
390 | def onapplicationerror(self, msg): |
|
407 | def onapplicationerror(self, requestid, msg): | |
391 | return 'sendframes', { |
|
408 | return 'sendframes', { | |
392 | 'framegen': createerrorframe(msg, application=True), |
|
409 | 'framegen': createerrorframe(requestid, msg, application=True), | |
393 | } |
|
410 | } | |
394 |
|
411 | |||
395 | def _makeerrorresult(self, msg): |
|
412 | def _makeerrorresult(self, msg): | |
@@ -399,6 +416,7 b' class serverreactor(object):' | |||||
399 |
|
416 | |||
400 | def _makeruncommandresult(self): |
|
417 | def _makeruncommandresult(self): | |
401 | return 'runcommand', { |
|
418 | return 'runcommand', { | |
|
419 | 'requestid': self._activerequestid, | |||
402 | 'command': self._activecommand, |
|
420 | 'command': self._activecommand, | |
403 | 'args': self._activeargs, |
|
421 | 'args': self._activeargs, | |
404 | 'data': self._activedata.getvalue() if self._activedata else None, |
|
422 | 'data': self._activedata.getvalue() if self._activedata else None, | |
@@ -409,7 +427,7 b' class serverreactor(object):' | |||||
409 | 'state': self._state, |
|
427 | 'state': self._state, | |
410 | } |
|
428 | } | |
411 |
|
429 | |||
412 | def _onframeidle(self, frametype, frameflags, payload): |
|
430 | def _onframeidle(self, requestid, frametype, frameflags, payload): | |
413 | # The only frame type that should be received in this state is a |
|
431 | # The only frame type that should be received in this state is a | |
414 | # command request. |
|
432 | # command request. | |
415 | if frametype != FRAME_TYPE_COMMAND_NAME: |
|
433 | if frametype != FRAME_TYPE_COMMAND_NAME: | |
@@ -417,6 +435,7 b' class serverreactor(object):' | |||||
417 | return self._makeerrorresult( |
|
435 | return self._makeerrorresult( | |
418 | _('expected command frame; got %d') % frametype) |
|
436 | _('expected command frame; got %d') % frametype) | |
419 |
|
437 | |||
|
438 | self._activerequestid = requestid | |||
420 | self._activecommand = payload |
|
439 | self._activecommand = payload | |
421 | self._activeargs = {} |
|
440 | self._activeargs = {} | |
422 | self._activedata = None |
|
441 | self._activedata = None | |
@@ -439,7 +458,7 b' class serverreactor(object):' | |||||
439 | return self._makeerrorresult(_('missing frame flags on ' |
|
458 | return self._makeerrorresult(_('missing frame flags on ' | |
440 | 'command frame')) |
|
459 | 'command frame')) | |
441 |
|
460 | |||
442 | def _onframereceivingargs(self, frametype, frameflags, payload): |
|
461 | def _onframereceivingargs(self, requestid, frametype, frameflags, payload): | |
443 | if frametype != FRAME_TYPE_COMMAND_ARGUMENT: |
|
462 | if frametype != FRAME_TYPE_COMMAND_ARGUMENT: | |
444 | self._state = 'errored' |
|
463 | self._state = 'errored' | |
445 | return self._makeerrorresult(_('expected command argument ' |
|
464 | return self._makeerrorresult(_('expected command argument ' | |
@@ -492,7 +511,7 b' class serverreactor(object):' | |||||
492 | else: |
|
511 | else: | |
493 | return self._makewantframeresult() |
|
512 | return self._makewantframeresult() | |
494 |
|
513 | |||
495 | def _onframereceivingdata(self, frametype, frameflags, payload): |
|
514 | def _onframereceivingdata(self, requestid, frametype, frameflags, payload): | |
496 | if frametype != FRAME_TYPE_COMMAND_DATA: |
|
515 | if frametype != FRAME_TYPE_COMMAND_DATA: | |
497 | self._state = 'errored' |
|
516 | self._state = 'errored' | |
498 | return self._makeerrorresult(_('expected command data frame; ' |
|
517 | return self._makeerrorresult(_('expected command data frame; ' | |
@@ -512,5 +531,5 b' class serverreactor(object):' | |||||
512 | return self._makeerrorresult(_('command data frame without ' |
|
531 | return self._makeerrorresult(_('command data frame without ' | |
513 | 'flags')) |
|
532 | 'flags')) | |
514 |
|
533 | |||
515 | def _onframeerrored(self, frametype, frameflags, payload): |
|
534 | def _onframeerrored(self, requestid, frametype, frameflags, payload): | |
516 | return self._makeerrorresult(_('server already errored')) |
|
535 | return self._makeerrorresult(_('server already errored')) |
@@ -33,7 +33,7 b' HTTP_OK = 200' | |||||
33 | HGTYPE = 'application/mercurial-0.1' |
|
33 | HGTYPE = 'application/mercurial-0.1' | |
34 | HGTYPE2 = 'application/mercurial-0.2' |
|
34 | HGTYPE2 = 'application/mercurial-0.2' | |
35 | HGERRTYPE = 'application/hg-error' |
|
35 | HGERRTYPE = 'application/hg-error' | |
36 |
FRAMINGTYPE = b'application/mercurial-exp-framing-000 |
|
36 | FRAMINGTYPE = b'application/mercurial-exp-framing-0002' | |
37 |
|
37 | |||
38 | HTTPV2 = wireprototypes.HTTPV2 |
|
38 | HTTPV2 = wireprototypes.HTTPV2 | |
39 | SSHV1 = wireprototypes.SSHV1 |
|
39 | SSHV1 = wireprototypes.SSHV1 | |
@@ -394,10 +394,12 b' def _processhttpv2reflectrequest(ui, rep' | |||||
394 | states.append(b'received: <no frame>') |
|
394 | states.append(b'received: <no frame>') | |
395 | break |
|
395 | break | |
396 |
|
396 | |||
397 | frametype, frameflags, payload = frame |
|
397 | requestid, frametype, frameflags, payload = frame | |
398 |
states.append(b'received: %d %d %s' % (frametype, frameflags, |
|
398 | states.append(b'received: %d %d %d %s' % (frametype, frameflags, | |
|
399 | requestid, payload)) | |||
399 |
|
400 | |||
400 |
action, meta = reactor.onframerecv(frametype, frameflags, |
|
401 | action, meta = reactor.onframerecv(requestid, frametype, frameflags, | |
|
402 | payload) | |||
401 | states.append(json.dumps((action, meta), sort_keys=True, |
|
403 | states.append(json.dumps((action, meta), sort_keys=True, | |
402 | separators=(', ', ': '))) |
|
404 | separators=(', ', ': '))) | |
403 |
|
405 | |||
@@ -517,7 +519,8 b' def _httpv2runcommand(ui, repo, req, res' | |||||
517 | res.headers[b'Content-Type'] = FRAMINGTYPE |
|
519 | res.headers[b'Content-Type'] = FRAMINGTYPE | |
518 |
|
520 | |||
519 | if isinstance(rsp, wireprototypes.bytesresponse): |
|
521 | if isinstance(rsp, wireprototypes.bytesresponse): | |
520 |
action, meta = reactor.onbytesresponseready( |
|
522 | action, meta = reactor.onbytesresponseready(command['requestid'], | |
|
523 | rsp.data) | |||
521 | else: |
|
524 | else: | |
522 | action, meta = reactor.onapplicationerror( |
|
525 | action, meta = reactor.onapplicationerror( | |
523 | _('unhandled response type from wire proto command')) |
|
526 | _('unhandled response type from wire proto command')) |
@@ -1,5 +1,5 b'' | |||||
1 | $ HTTPV2=exp-http-v2-0001 |
|
1 | $ HTTPV2=exp-http-v2-0001 | |
2 |
$ MEDIATYPE=application/mercurial-exp-framing-000 |
|
2 | $ MEDIATYPE=application/mercurial-exp-framing-0002 | |
3 |
|
3 | |||
4 | $ send() { |
|
4 | $ send() { | |
5 | > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ |
|
5 | > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ | |
@@ -122,7 +122,7 b' Missing Accept header results in 406' | |||||
122 | s> Content-Type: text/plain\r\n |
|
122 | s> Content-Type: text/plain\r\n | |
123 | s> Content-Length: 85\r\n |
|
123 | s> Content-Length: 85\r\n | |
124 | s> \r\n |
|
124 | s> \r\n | |
125 |
s> client MUST specify Accept header with value: application/mercurial-exp-framing-000 |
|
125 | s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n | |
126 |
|
126 | |||
127 | Bad Accept header results in 406 |
|
127 | Bad Accept header results in 406 | |
128 |
|
128 | |||
@@ -145,7 +145,7 b' Bad Accept header results in 406' | |||||
145 | s> Content-Type: text/plain\r\n |
|
145 | s> Content-Type: text/plain\r\n | |
146 | s> Content-Length: 85\r\n |
|
146 | s> Content-Length: 85\r\n | |
147 | s> \r\n |
|
147 | s> \r\n | |
148 |
s> client MUST specify Accept header with value: application/mercurial-exp-framing-000 |
|
148 | s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n | |
149 |
|
149 | |||
150 | Bad Content-Type header results in 415 |
|
150 | Bad Content-Type header results in 415 | |
151 |
|
151 | |||
@@ -158,7 +158,7 b' Bad Content-Type header results in 415' | |||||
158 | using raw connection to peer |
|
158 | using raw connection to peer | |
159 | s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n |
|
159 | s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n | |
160 | s> Accept-Encoding: identity\r\n |
|
160 | s> Accept-Encoding: identity\r\n | |
161 |
s> accept: application/mercurial-exp-framing-000 |
|
161 | s> accept: application/mercurial-exp-framing-0002\r\n | |
162 | s> content-type: badmedia\r\n |
|
162 | s> content-type: badmedia\r\n | |
163 | s> user-agent: test\r\n |
|
163 | s> user-agent: test\r\n | |
164 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
164 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
@@ -170,7 +170,7 b' Bad Content-Type header results in 415' | |||||
170 | s> Content-Type: text/plain\r\n |
|
170 | s> Content-Type: text/plain\r\n | |
171 | s> Content-Length: 88\r\n |
|
171 | s> Content-Length: 88\r\n | |
172 | s> \r\n |
|
172 | s> \r\n | |
173 |
s> client MUST send Content-Type header with value: application/mercurial-exp-framing-000 |
|
173 | s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0002\n | |
174 |
|
174 | |||
175 | Request to read-only command works out of the box |
|
175 | Request to read-only command works out of the box | |
176 |
|
176 | |||
@@ -179,27 +179,27 b' Request to read-only command works out o' | |||||
179 | > accept: $MEDIATYPE |
|
179 | > accept: $MEDIATYPE | |
180 | > content-type: $MEDIATYPE |
|
180 | > content-type: $MEDIATYPE | |
181 | > user-agent: test |
|
181 | > user-agent: test | |
182 | > frame command-name eos customreadonly |
|
182 | > frame 1 command-name eos customreadonly | |
183 | > EOF |
|
183 | > EOF | |
184 | using raw connection to peer |
|
184 | using raw connection to peer | |
185 | s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n |
|
185 | s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n | |
186 | s> Accept-Encoding: identity\r\n |
|
186 | s> Accept-Encoding: identity\r\n | |
187 |
s> accept: application/mercurial-exp-framing-000 |
|
187 | s> accept: application/mercurial-exp-framing-0002\r\n | |
188 |
s> content-type: application/mercurial-exp-framing-000 |
|
188 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
189 | s> user-agent: test\r\n |
|
189 | s> user-agent: test\r\n | |
190 | s> content-length: 18\r\n |
|
190 | s> *\r\n (glob) | |
191 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
191 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
192 | s> \r\n |
|
192 | s> \r\n | |
193 | s> \x0e\x00\x00\x11customreadonly |
|
193 | s> \x0e\x00\x00\x01\x00\x11customreadonly | |
194 | s> makefile('rb', None) |
|
194 | s> makefile('rb', None) | |
195 | s> HTTP/1.1 200 OK\r\n |
|
195 | s> HTTP/1.1 200 OK\r\n | |
196 | s> Server: testing stub value\r\n |
|
196 | s> Server: testing stub value\r\n | |
197 | s> Date: $HTTP_DATE$\r\n |
|
197 | s> Date: $HTTP_DATE$\r\n | |
198 |
s> Content-Type: application/mercurial-exp-framing-000 |
|
198 | s> Content-Type: application/mercurial-exp-framing-0002\r\n | |
199 | s> Transfer-Encoding: chunked\r\n |
|
199 | s> Transfer-Encoding: chunked\r\n | |
200 | s> \r\n |
|
200 | s> \r\n | |
201 |
s> 2 |
|
201 | s> 23\r\n | |
202 | s> \x1d\x00\x00Bcustomreadonly bytes response |
|
202 | s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response | |
203 | s> \r\n |
|
203 | s> \r\n | |
204 | s> 0\r\n |
|
204 | s> 0\r\n | |
205 | s> \r\n |
|
205 | s> \r\n | |
@@ -290,27 +290,27 b' Authorized request for valid read-write ' | |||||
290 | > user-agent: test |
|
290 | > user-agent: test | |
291 | > accept: $MEDIATYPE |
|
291 | > accept: $MEDIATYPE | |
292 | > content-type: $MEDIATYPE |
|
292 | > content-type: $MEDIATYPE | |
293 | > frame command-name eos customreadonly |
|
293 | > frame 1 command-name eos customreadonly | |
294 | > EOF |
|
294 | > EOF | |
295 | using raw connection to peer |
|
295 | using raw connection to peer | |
296 | s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n |
|
296 | s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n | |
297 | s> Accept-Encoding: identity\r\n |
|
297 | s> Accept-Encoding: identity\r\n | |
298 |
s> accept: application/mercurial-exp-framing-000 |
|
298 | s> accept: application/mercurial-exp-framing-0002\r\n | |
299 |
s> content-type: application/mercurial-exp-framing-000 |
|
299 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
300 | s> user-agent: test\r\n |
|
300 | s> user-agent: test\r\n | |
301 |
s> content-length: |
|
301 | s> content-length: 20\r\n | |
302 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
302 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
303 | s> \r\n |
|
303 | s> \r\n | |
304 | s> \x0e\x00\x00\x11customreadonly |
|
304 | s> \x0e\x00\x00\x01\x00\x11customreadonly | |
305 | s> makefile('rb', None) |
|
305 | s> makefile('rb', None) | |
306 | s> HTTP/1.1 200 OK\r\n |
|
306 | s> HTTP/1.1 200 OK\r\n | |
307 | s> Server: testing stub value\r\n |
|
307 | s> Server: testing stub value\r\n | |
308 | s> Date: $HTTP_DATE$\r\n |
|
308 | s> Date: $HTTP_DATE$\r\n | |
309 |
s> Content-Type: application/mercurial-exp-framing-000 |
|
309 | s> Content-Type: application/mercurial-exp-framing-0002\r\n | |
310 | s> Transfer-Encoding: chunked\r\n |
|
310 | s> Transfer-Encoding: chunked\r\n | |
311 | s> \r\n |
|
311 | s> \r\n | |
312 |
s> 2 |
|
312 | s> 23\r\n | |
313 | s> \x1d\x00\x00Bcustomreadonly bytes response |
|
313 | s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response | |
314 | s> \r\n |
|
314 | s> \r\n | |
315 | s> 0\r\n |
|
315 | s> 0\r\n | |
316 | s> \r\n |
|
316 | s> \r\n | |
@@ -325,7 +325,7 b' Authorized request for unknown command i' | |||||
325 | using raw connection to peer |
|
325 | using raw connection to peer | |
326 | s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n |
|
326 | s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n | |
327 | s> Accept-Encoding: identity\r\n |
|
327 | s> Accept-Encoding: identity\r\n | |
328 |
s> accept: application/mercurial-exp-framing-000 |
|
328 | s> accept: application/mercurial-exp-framing-0002\r\n | |
329 | s> user-agent: test\r\n |
|
329 | s> user-agent: test\r\n | |
330 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
330 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
331 | s> \r\n |
|
331 | s> \r\n | |
@@ -382,33 +382,33 b' Command frames can be reflected via debu' | |||||
382 | > accept: $MEDIATYPE |
|
382 | > accept: $MEDIATYPE | |
383 | > content-type: $MEDIATYPE |
|
383 | > content-type: $MEDIATYPE | |
384 | > user-agent: test |
|
384 | > user-agent: test | |
385 | > frame command-name have-args command1 |
|
385 | > frame 1 command-name have-args command1 | |
386 | > frame command-argument 0 \x03\x00\x04\x00fooval1 |
|
386 | > frame 1 command-argument 0 \x03\x00\x04\x00fooval1 | |
387 | > frame command-argument eoa \x04\x00\x03\x00bar1val |
|
387 | > frame 1 command-argument eoa \x04\x00\x03\x00bar1val | |
388 | > EOF |
|
388 | > EOF | |
389 | using raw connection to peer |
|
389 | using raw connection to peer | |
390 | s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n |
|
390 | s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n | |
391 | s> Accept-Encoding: identity\r\n |
|
391 | s> Accept-Encoding: identity\r\n | |
392 |
s> accept: application/mercurial-exp-framing-000 |
|
392 | s> accept: application/mercurial-exp-framing-0002\r\n | |
393 |
s> content-type: application/mercurial-exp-framing-000 |
|
393 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
394 | s> user-agent: test\r\n |
|
394 | s> user-agent: test\r\n | |
395 |
s> content-length: 4 |
|
395 | s> content-length: 48\r\n | |
396 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
396 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
397 | s> \r\n |
|
397 | s> \r\n | |
398 | s> \x08\x00\x00\x12command1\x0b\x00\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00"\x04\x00\x03\x00bar1val |
|
398 | s> \x08\x00\x00\x01\x00\x12command1\x0b\x00\x00\x01\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00\x01\x00"\x04\x00\x03\x00bar1val | |
399 | s> makefile('rb', None) |
|
399 | s> makefile('rb', None) | |
400 | s> HTTP/1.1 200 OK\r\n |
|
400 | s> HTTP/1.1 200 OK\r\n | |
401 | s> Server: testing stub value\r\n |
|
401 | s> Server: testing stub value\r\n | |
402 | s> Date: $HTTP_DATE$\r\n |
|
402 | s> Date: $HTTP_DATE$\r\n | |
403 | s> Content-Type: text/plain\r\n |
|
403 | s> Content-Type: text/plain\r\n | |
404 |
s> Content-Length: 3 |
|
404 | s> Content-Length: 332\r\n | |
405 | s> \r\n |
|
405 | s> \r\n | |
406 | s> received: 1 2 command1\n |
|
406 | s> received: 1 2 1 command1\n | |
407 | s> ["wantframe", {"state": "command-receiving-args"}]\n |
|
407 | s> ["wantframe", {"state": "command-receiving-args"}]\n | |
408 | s> received: 2 0 \x03\x00\x04\x00fooval1\n |
|
408 | s> received: 2 0 1 \x03\x00\x04\x00fooval1\n | |
409 | s> ["wantframe", {"state": "command-receiving-args"}]\n |
|
409 | s> ["wantframe", {"state": "command-receiving-args"}]\n | |
410 | s> received: 2 2 \x04\x00\x03\x00bar1val\n |
|
410 | s> received: 2 2 1 \x04\x00\x03\x00bar1val\n | |
411 | s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null}]\n |
|
411 | s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n | |
412 | s> received: <no frame>\n |
|
412 | s> received: <no frame>\n | |
413 | s> {"action": "noop"} |
|
413 | s> {"action": "noop"} | |
414 |
|
414 |
@@ -18,52 +18,53 b' def sendframes(reactor, gen):' | |||||
18 | Emits a generator of results from ``onframerecv()`` calls. |
|
18 | Emits a generator of results from ``onframerecv()`` calls. | |
19 | """ |
|
19 | """ | |
20 | for frame in gen: |
|
20 | for frame in gen: | |
21 | frametype, frameflags, framelength = framing.parseheader(frame) |
|
21 | rid, frametype, frameflags, framelength = framing.parseheader(frame) | |
22 | payload = frame[framing.FRAME_HEADER_SIZE:] |
|
22 | payload = frame[framing.FRAME_HEADER_SIZE:] | |
23 | assert len(payload) == framelength |
|
23 | assert len(payload) == framelength | |
24 |
|
24 | |||
25 | yield reactor.onframerecv(frametype, frameflags, payload) |
|
25 | yield reactor.onframerecv(rid, frametype, frameflags, payload) | |
26 |
|
26 | |||
27 | def sendcommandframes(reactor, cmd, args, datafh=None): |
|
27 | def sendcommandframes(reactor, rid, cmd, args, datafh=None): | |
28 | """Generate frames to run a command and send them to a reactor.""" |
|
28 | """Generate frames to run a command and send them to a reactor.""" | |
29 | return sendframes(reactor, framing.createcommandframes(cmd, args, datafh)) |
|
29 | return sendframes(reactor, | |
|
30 | framing.createcommandframes(rid, cmd, args, datafh)) | |||
30 |
|
31 | |||
31 | class FrameTests(unittest.TestCase): |
|
32 | class FrameTests(unittest.TestCase): | |
32 | def testdataexactframesize(self): |
|
33 | def testdataexactframesize(self): | |
33 | data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE) |
|
34 | data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE) | |
34 |
|
35 | |||
35 | frames = list(framing.createcommandframes(b'command', {}, data)) |
|
36 | frames = list(framing.createcommandframes(1, b'command', {}, data)) | |
36 | self.assertEqual(frames, [ |
|
37 | self.assertEqual(frames, [ | |
37 | ffs(b'command-name have-data command'), |
|
38 | ffs(b'1 command-name have-data command'), | |
38 | ffs(b'command-data continuation %s' % data.getvalue()), |
|
39 | ffs(b'1 command-data continuation %s' % data.getvalue()), | |
39 | ffs(b'command-data eos ') |
|
40 | ffs(b'1 command-data eos ') | |
40 | ]) |
|
41 | ]) | |
41 |
|
42 | |||
42 | def testdatamultipleframes(self): |
|
43 | def testdatamultipleframes(self): | |
43 | data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1)) |
|
44 | data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1)) | |
44 | frames = list(framing.createcommandframes(b'command', {}, data)) |
|
45 | frames = list(framing.createcommandframes(1, b'command', {}, data)) | |
45 | self.assertEqual(frames, [ |
|
46 | self.assertEqual(frames, [ | |
46 | ffs(b'command-name have-data command'), |
|
47 | ffs(b'1 command-name have-data command'), | |
47 | ffs(b'command-data continuation %s' % ( |
|
48 | ffs(b'1 command-data continuation %s' % ( | |
48 | b'x' * framing.DEFAULT_MAX_FRAME_SIZE)), |
|
49 | b'x' * framing.DEFAULT_MAX_FRAME_SIZE)), | |
49 | ffs(b'command-data eos x'), |
|
50 | ffs(b'1 command-data eos x'), | |
50 | ]) |
|
51 | ]) | |
51 |
|
52 | |||
52 | def testargsanddata(self): |
|
53 | def testargsanddata(self): | |
53 | data = util.bytesio(b'x' * 100) |
|
54 | data = util.bytesio(b'x' * 100) | |
54 |
|
55 | |||
55 | frames = list(framing.createcommandframes(b'command', { |
|
56 | frames = list(framing.createcommandframes(1, b'command', { | |
56 | b'key1': b'key1value', |
|
57 | b'key1': b'key1value', | |
57 | b'key2': b'key2value', |
|
58 | b'key2': b'key2value', | |
58 | b'key3': b'key3value', |
|
59 | b'key3': b'key3value', | |
59 | }, data)) |
|
60 | }, data)) | |
60 |
|
61 | |||
61 | self.assertEqual(frames, [ |
|
62 | self.assertEqual(frames, [ | |
62 | ffs(b'command-name have-args|have-data command'), |
|
63 | ffs(b'1 command-name have-args|have-data command'), | |
63 | ffs(br'command-argument 0 \x04\x00\x09\x00key1key1value'), |
|
64 | ffs(br'1 command-argument 0 \x04\x00\x09\x00key1key1value'), | |
64 | ffs(br'command-argument 0 \x04\x00\x09\x00key2key2value'), |
|
65 | ffs(br'1 command-argument 0 \x04\x00\x09\x00key2key2value'), | |
65 | ffs(br'command-argument eoa \x04\x00\x09\x00key3key3value'), |
|
66 | ffs(br'1 command-argument eoa \x04\x00\x09\x00key3key3value'), | |
66 | ffs(b'command-data eos %s' % data.getvalue()), |
|
67 | ffs(b'1 command-data eos %s' % data.getvalue()), | |
67 | ]) |
|
68 | ]) | |
68 |
|
69 | |||
69 | class ServerReactorTests(unittest.TestCase): |
|
70 | class ServerReactorTests(unittest.TestCase): | |
@@ -86,10 +87,11 b' class ServerReactorTests(unittest.TestCa' | |||||
86 | def test1framecommand(self): |
|
87 | def test1framecommand(self): | |
87 | """Receiving a command in a single frame yields request to run it.""" |
|
88 | """Receiving a command in a single frame yields request to run it.""" | |
88 | reactor = makereactor() |
|
89 | reactor = makereactor() | |
89 | results = list(sendcommandframes(reactor, b'mycommand', {})) |
|
90 | results = list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
90 | self.assertEqual(len(results), 1) |
|
91 | self.assertEqual(len(results), 1) | |
91 | self.assertaction(results[0], 'runcommand') |
|
92 | self.assertaction(results[0], 'runcommand') | |
92 | self.assertEqual(results[0][1], { |
|
93 | self.assertEqual(results[0][1], { | |
|
94 | 'requestid': 1, | |||
93 | 'command': b'mycommand', |
|
95 | 'command': b'mycommand', | |
94 | 'args': {}, |
|
96 | 'args': {}, | |
95 | 'data': None, |
|
97 | 'data': None, | |
@@ -100,12 +102,13 b' class ServerReactorTests(unittest.TestCa' | |||||
100 |
|
102 | |||
101 | def test1argument(self): |
|
103 | def test1argument(self): | |
102 | reactor = makereactor() |
|
104 | reactor = makereactor() | |
103 | results = list(sendcommandframes(reactor, b'mycommand', |
|
105 | results = list(sendcommandframes(reactor, 41, b'mycommand', | |
104 | {b'foo': b'bar'})) |
|
106 | {b'foo': b'bar'})) | |
105 | self.assertEqual(len(results), 2) |
|
107 | self.assertEqual(len(results), 2) | |
106 | self.assertaction(results[0], 'wantframe') |
|
108 | self.assertaction(results[0], 'wantframe') | |
107 | self.assertaction(results[1], 'runcommand') |
|
109 | self.assertaction(results[1], 'runcommand') | |
108 | self.assertEqual(results[1][1], { |
|
110 | self.assertEqual(results[1][1], { | |
|
111 | 'requestid': 41, | |||
109 | 'command': b'mycommand', |
|
112 | 'command': b'mycommand', | |
110 | 'args': {b'foo': b'bar'}, |
|
113 | 'args': {b'foo': b'bar'}, | |
111 | 'data': None, |
|
114 | 'data': None, | |
@@ -113,13 +116,14 b' class ServerReactorTests(unittest.TestCa' | |||||
113 |
|
116 | |||
114 | def testmultiarguments(self): |
|
117 | def testmultiarguments(self): | |
115 | reactor = makereactor() |
|
118 | reactor = makereactor() | |
116 | results = list(sendcommandframes(reactor, b'mycommand', |
|
119 | results = list(sendcommandframes(reactor, 1, b'mycommand', | |
117 | {b'foo': b'bar', b'biz': b'baz'})) |
|
120 | {b'foo': b'bar', b'biz': b'baz'})) | |
118 | self.assertEqual(len(results), 3) |
|
121 | self.assertEqual(len(results), 3) | |
119 | self.assertaction(results[0], 'wantframe') |
|
122 | self.assertaction(results[0], 'wantframe') | |
120 | self.assertaction(results[1], 'wantframe') |
|
123 | self.assertaction(results[1], 'wantframe') | |
121 | self.assertaction(results[2], 'runcommand') |
|
124 | self.assertaction(results[2], 'runcommand') | |
122 | self.assertEqual(results[2][1], { |
|
125 | self.assertEqual(results[2][1], { | |
|
126 | 'requestid': 1, | |||
123 | 'command': b'mycommand', |
|
127 | 'command': b'mycommand', | |
124 | 'args': {b'foo': b'bar', b'biz': b'baz'}, |
|
128 | 'args': {b'foo': b'bar', b'biz': b'baz'}, | |
125 | 'data': None, |
|
129 | 'data': None, | |
@@ -127,12 +131,13 b' class ServerReactorTests(unittest.TestCa' | |||||
127 |
|
131 | |||
128 | def testsimplecommanddata(self): |
|
132 | def testsimplecommanddata(self): | |
129 | reactor = makereactor() |
|
133 | reactor = makereactor() | |
130 | results = list(sendcommandframes(reactor, b'mycommand', {}, |
|
134 | results = list(sendcommandframes(reactor, 1, b'mycommand', {}, | |
131 | util.bytesio(b'data!'))) |
|
135 | util.bytesio(b'data!'))) | |
132 | self.assertEqual(len(results), 2) |
|
136 | self.assertEqual(len(results), 2) | |
133 | self.assertaction(results[0], 'wantframe') |
|
137 | self.assertaction(results[0], 'wantframe') | |
134 | self.assertaction(results[1], 'runcommand') |
|
138 | self.assertaction(results[1], 'runcommand') | |
135 | self.assertEqual(results[1][1], { |
|
139 | self.assertEqual(results[1][1], { | |
|
140 | 'requestid': 1, | |||
136 | 'command': b'mycommand', |
|
141 | 'command': b'mycommand', | |
137 | 'args': {}, |
|
142 | 'args': {}, | |
138 | 'data': b'data!', |
|
143 | 'data': b'data!', | |
@@ -140,10 +145,10 b' class ServerReactorTests(unittest.TestCa' | |||||
140 |
|
145 | |||
141 | def testmultipledataframes(self): |
|
146 | def testmultipledataframes(self): | |
142 | frames = [ |
|
147 | frames = [ | |
143 | ffs(b'command-name have-data mycommand'), |
|
148 | ffs(b'1 command-name have-data mycommand'), | |
144 | ffs(b'command-data continuation data1'), |
|
149 | ffs(b'1 command-data continuation data1'), | |
145 | ffs(b'command-data continuation data2'), |
|
150 | ffs(b'1 command-data continuation data2'), | |
146 | ffs(b'command-data eos data3'), |
|
151 | ffs(b'1 command-data eos data3'), | |
147 | ] |
|
152 | ] | |
148 |
|
153 | |||
149 | reactor = makereactor() |
|
154 | reactor = makereactor() | |
@@ -153,6 +158,7 b' class ServerReactorTests(unittest.TestCa' | |||||
153 | self.assertaction(results[i], 'wantframe') |
|
158 | self.assertaction(results[i], 'wantframe') | |
154 | self.assertaction(results[3], 'runcommand') |
|
159 | self.assertaction(results[3], 'runcommand') | |
155 | self.assertEqual(results[3][1], { |
|
160 | self.assertEqual(results[3][1], { | |
|
161 | 'requestid': 1, | |||
156 | 'command': b'mycommand', |
|
162 | 'command': b'mycommand', | |
157 | 'args': {}, |
|
163 | 'args': {}, | |
158 | 'data': b'data1data2data3', |
|
164 | 'data': b'data1data2data3', | |
@@ -160,11 +166,11 b' class ServerReactorTests(unittest.TestCa' | |||||
160 |
|
166 | |||
161 | def testargumentanddata(self): |
|
167 | def testargumentanddata(self): | |
162 | frames = [ |
|
168 | frames = [ | |
163 | ffs(b'command-name have-args|have-data command'), |
|
169 | ffs(b'1 command-name have-args|have-data command'), | |
164 | ffs(br'command-argument 0 \x03\x00\x03\x00keyval'), |
|
170 | ffs(br'1 command-argument 0 \x03\x00\x03\x00keyval'), | |
165 | ffs(br'command-argument eoa \x03\x00\x03\x00foobar'), |
|
171 | ffs(br'1 command-argument eoa \x03\x00\x03\x00foobar'), | |
166 | ffs(b'command-data continuation value1'), |
|
172 | ffs(b'1 command-data continuation value1'), | |
167 | ffs(b'command-data eos value2'), |
|
173 | ffs(b'1 command-data eos value2'), | |
168 | ] |
|
174 | ] | |
169 |
|
175 | |||
170 | reactor = makereactor() |
|
176 | reactor = makereactor() | |
@@ -172,6 +178,7 b' class ServerReactorTests(unittest.TestCa' | |||||
172 |
|
178 | |||
173 | self.assertaction(results[-1], 'runcommand') |
|
179 | self.assertaction(results[-1], 'runcommand') | |
174 | self.assertEqual(results[-1][1], { |
|
180 | self.assertEqual(results[-1][1], { | |
|
181 | 'requestid': 1, | |||
175 | 'command': b'command', |
|
182 | 'command': b'command', | |
176 | 'args': { |
|
183 | 'args': { | |
177 | b'key': b'val', |
|
184 | b'key': b'val', | |
@@ -183,7 +190,7 b' class ServerReactorTests(unittest.TestCa' | |||||
183 | def testunexpectedcommandargument(self): |
|
190 | def testunexpectedcommandargument(self): | |
184 | """Command argument frame when not running a command is an error.""" |
|
191 | """Command argument frame when not running a command is an error.""" | |
185 | result = self._sendsingleframe(makereactor(), |
|
192 | result = self._sendsingleframe(makereactor(), | |
186 | b'command-argument 0 ignored') |
|
193 | b'1 command-argument 0 ignored') | |
187 | self.assertaction(result, 'error') |
|
194 | self.assertaction(result, 'error') | |
188 | self.assertEqual(result[1], { |
|
195 | self.assertEqual(result[1], { | |
189 | 'message': b'expected command frame; got 2', |
|
196 | 'message': b'expected command frame; got 2', | |
@@ -192,7 +199,7 b' class ServerReactorTests(unittest.TestCa' | |||||
192 | def testunexpectedcommanddata(self): |
|
199 | def testunexpectedcommanddata(self): | |
193 | """Command argument frame when not running a command is an error.""" |
|
200 | """Command argument frame when not running a command is an error.""" | |
194 | result = self._sendsingleframe(makereactor(), |
|
201 | result = self._sendsingleframe(makereactor(), | |
195 | b'command-data 0 ignored') |
|
202 | b'1 command-data 0 ignored') | |
196 | self.assertaction(result, 'error') |
|
203 | self.assertaction(result, 'error') | |
197 | self.assertEqual(result[1], { |
|
204 | self.assertEqual(result[1], { | |
198 | 'message': b'expected command frame; got 3', |
|
205 | 'message': b'expected command frame; got 3', | |
@@ -201,7 +208,7 b' class ServerReactorTests(unittest.TestCa' | |||||
201 | def testmissingcommandframeflags(self): |
|
208 | def testmissingcommandframeflags(self): | |
202 | """Command name frame must have flags set.""" |
|
209 | """Command name frame must have flags set.""" | |
203 | result = self._sendsingleframe(makereactor(), |
|
210 | result = self._sendsingleframe(makereactor(), | |
204 | b'command-name 0 command') |
|
211 | b'1 command-name 0 command') | |
205 | self.assertaction(result, 'error') |
|
212 | self.assertaction(result, 'error') | |
206 | self.assertEqual(result[1], { |
|
213 | self.assertEqual(result[1], { | |
207 | 'message': b'missing frame flags on command frame', |
|
214 | 'message': b'missing frame flags on command frame', | |
@@ -209,8 +216,8 b' class ServerReactorTests(unittest.TestCa' | |||||
209 |
|
216 | |||
210 | def testmissingargumentframe(self): |
|
217 | def testmissingargumentframe(self): | |
211 | frames = [ |
|
218 | frames = [ | |
212 | ffs(b'command-name have-args command'), |
|
219 | ffs(b'1 command-name have-args command'), | |
213 | ffs(b'command-name 0 ignored'), |
|
220 | ffs(b'1 command-name 0 ignored'), | |
214 | ] |
|
221 | ] | |
215 |
|
222 | |||
216 | results = list(sendframes(makereactor(), frames)) |
|
223 | results = list(sendframes(makereactor(), frames)) | |
@@ -224,8 +231,8 b' class ServerReactorTests(unittest.TestCa' | |||||
224 | def testincompleteargumentname(self): |
|
231 | def testincompleteargumentname(self): | |
225 | """Argument frame with incomplete name.""" |
|
232 | """Argument frame with incomplete name.""" | |
226 | frames = [ |
|
233 | frames = [ | |
227 | ffs(b'command-name have-args command1'), |
|
234 | ffs(b'1 command-name have-args command1'), | |
228 | ffs(br'command-argument eoa \x04\x00\xde\xadfoo'), |
|
235 | ffs(br'1 command-argument eoa \x04\x00\xde\xadfoo'), | |
229 | ] |
|
236 | ] | |
230 |
|
237 | |||
231 | results = list(sendframes(makereactor(), frames)) |
|
238 | results = list(sendframes(makereactor(), frames)) | |
@@ -239,8 +246,8 b' class ServerReactorTests(unittest.TestCa' | |||||
239 | def testincompleteargumentvalue(self): |
|
246 | def testincompleteargumentvalue(self): | |
240 | """Argument frame with incomplete value.""" |
|
247 | """Argument frame with incomplete value.""" | |
241 | frames = [ |
|
248 | frames = [ | |
242 | ffs(b'command-name have-args command'), |
|
249 | ffs(b'1 command-name have-args command'), | |
243 | ffs(br'command-argument eoa \x03\x00\xaa\xaafoopartialvalue'), |
|
250 | ffs(br'1 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'), | |
244 | ] |
|
251 | ] | |
245 |
|
252 | |||
246 | results = list(sendframes(makereactor(), frames)) |
|
253 | results = list(sendframes(makereactor(), frames)) | |
@@ -253,8 +260,8 b' class ServerReactorTests(unittest.TestCa' | |||||
253 |
|
260 | |||
254 | def testmissingcommanddataframe(self): |
|
261 | def testmissingcommanddataframe(self): | |
255 | frames = [ |
|
262 | frames = [ | |
256 | ffs(b'command-name have-data command1'), |
|
263 | ffs(b'1 command-name have-data command1'), | |
257 | ffs(b'command-name eos command2'), |
|
264 | ffs(b'1 command-name eos command2'), | |
258 | ] |
|
265 | ] | |
259 | results = list(sendframes(makereactor(), frames)) |
|
266 | results = list(sendframes(makereactor(), frames)) | |
260 | self.assertEqual(len(results), 2) |
|
267 | self.assertEqual(len(results), 2) | |
@@ -266,8 +273,8 b' class ServerReactorTests(unittest.TestCa' | |||||
266 |
|
273 | |||
267 | def testmissingcommanddataframeflags(self): |
|
274 | def testmissingcommanddataframeflags(self): | |
268 | frames = [ |
|
275 | frames = [ | |
269 | ffs(b'command-name have-data command1'), |
|
276 | ffs(b'1 command-name have-data command1'), | |
270 | ffs(b'command-data 0 data'), |
|
277 | ffs(b'1 command-data 0 data'), | |
271 | ] |
|
278 | ] | |
272 | results = list(sendframes(makereactor(), frames)) |
|
279 | results = list(sendframes(makereactor(), frames)) | |
273 | self.assertEqual(len(results), 2) |
|
280 | self.assertEqual(len(results), 2) | |
@@ -280,12 +287,12 b' class ServerReactorTests(unittest.TestCa' | |||||
280 | def testsimpleresponse(self): |
|
287 | def testsimpleresponse(self): | |
281 | """Bytes response to command sends result frames.""" |
|
288 | """Bytes response to command sends result frames.""" | |
282 | reactor = makereactor() |
|
289 | reactor = makereactor() | |
283 | list(sendcommandframes(reactor, b'mycommand', {})) |
|
290 | list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
284 |
|
291 | |||
285 | result = reactor.onbytesresponseready(b'response') |
|
292 | result = reactor.onbytesresponseready(1, b'response') | |
286 | self.assertaction(result, 'sendframes') |
|
293 | self.assertaction(result, 'sendframes') | |
287 | self.assertframesequal(result[1]['framegen'], [ |
|
294 | self.assertframesequal(result[1]['framegen'], [ | |
288 | b'bytes-response eos response', |
|
295 | b'1 bytes-response eos response', | |
289 | ]) |
|
296 | ]) | |
290 |
|
297 | |||
291 | def testmultiframeresponse(self): |
|
298 | def testmultiframeresponse(self): | |
@@ -294,54 +301,73 b' class ServerReactorTests(unittest.TestCa' | |||||
294 | second = b'y' * 100 |
|
301 | second = b'y' * 100 | |
295 |
|
302 | |||
296 | reactor = makereactor() |
|
303 | reactor = makereactor() | |
297 | list(sendcommandframes(reactor, b'mycommand', {})) |
|
304 | list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
298 |
|
305 | |||
299 | result = reactor.onbytesresponseready(first + second) |
|
306 | result = reactor.onbytesresponseready(1, first + second) | |
300 | self.assertaction(result, 'sendframes') |
|
307 | self.assertaction(result, 'sendframes') | |
301 | self.assertframesequal(result[1]['framegen'], [ |
|
308 | self.assertframesequal(result[1]['framegen'], [ | |
302 | b'bytes-response continuation %s' % first, |
|
309 | b'1 bytes-response continuation %s' % first, | |
303 | b'bytes-response eos %s' % second, |
|
310 | b'1 bytes-response eos %s' % second, | |
304 | ]) |
|
311 | ]) | |
305 |
|
312 | |||
306 | def testapplicationerror(self): |
|
313 | def testapplicationerror(self): | |
307 | reactor = makereactor() |
|
314 | reactor = makereactor() | |
308 | list(sendcommandframes(reactor, b'mycommand', {})) |
|
315 | list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
309 |
|
316 | |||
310 | result = reactor.onapplicationerror(b'some message') |
|
317 | result = reactor.onapplicationerror(1, b'some message') | |
311 | self.assertaction(result, 'sendframes') |
|
318 | self.assertaction(result, 'sendframes') | |
312 | self.assertframesequal(result[1]['framegen'], [ |
|
319 | self.assertframesequal(result[1]['framegen'], [ | |
313 | b'error-response application some message', |
|
320 | b'1 error-response application some message', | |
314 | ]) |
|
321 | ]) | |
315 |
|
322 | |||
316 | def test1commanddeferresponse(self): |
|
323 | def test1commanddeferresponse(self): | |
317 | """Responses when in deferred output mode are delayed until EOF.""" |
|
324 | """Responses when in deferred output mode are delayed until EOF.""" | |
318 | reactor = makereactor(deferoutput=True) |
|
325 | reactor = makereactor(deferoutput=True) | |
319 | results = list(sendcommandframes(reactor, b'mycommand', {})) |
|
326 | results = list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
320 | self.assertEqual(len(results), 1) |
|
327 | self.assertEqual(len(results), 1) | |
321 | self.assertaction(results[0], 'runcommand') |
|
328 | self.assertaction(results[0], 'runcommand') | |
322 |
|
329 | |||
323 | result = reactor.onbytesresponseready(b'response') |
|
330 | result = reactor.onbytesresponseready(1, b'response') | |
324 | self.assertaction(result, 'noop') |
|
331 | self.assertaction(result, 'noop') | |
325 | result = reactor.oninputeof() |
|
332 | result = reactor.oninputeof() | |
326 | self.assertaction(result, 'sendframes') |
|
333 | self.assertaction(result, 'sendframes') | |
327 | self.assertframesequal(result[1]['framegen'], [ |
|
334 | self.assertframesequal(result[1]['framegen'], [ | |
328 | b'bytes-response eos response', |
|
335 | b'1 bytes-response eos response', | |
329 | ]) |
|
336 | ]) | |
330 |
|
337 | |||
331 | def testmultiplecommanddeferresponse(self): |
|
338 | def testmultiplecommanddeferresponse(self): | |
332 | reactor = makereactor(deferoutput=True) |
|
339 | reactor = makereactor(deferoutput=True) | |
333 | list(sendcommandframes(reactor, b'command1', {})) |
|
340 | list(sendcommandframes(reactor, 1, b'command1', {})) | |
334 | list(sendcommandframes(reactor, b'command2', {})) |
|
341 | list(sendcommandframes(reactor, 3, b'command2', {})) | |
335 |
|
342 | |||
336 | result = reactor.onbytesresponseready(b'response1') |
|
343 | result = reactor.onbytesresponseready(1, b'response1') | |
337 | self.assertaction(result, 'noop') |
|
344 | self.assertaction(result, 'noop') | |
338 | result = reactor.onbytesresponseready(b'response2') |
|
345 | result = reactor.onbytesresponseready(3, b'response2') | |
339 | self.assertaction(result, 'noop') |
|
346 | self.assertaction(result, 'noop') | |
340 | result = reactor.oninputeof() |
|
347 | result = reactor.oninputeof() | |
341 | self.assertaction(result, 'sendframes') |
|
348 | self.assertaction(result, 'sendframes') | |
342 | self.assertframesequal(result[1]['framegen'], [ |
|
349 | self.assertframesequal(result[1]['framegen'], [ | |
343 | b'bytes-response eos response1', |
|
350 | b'1 bytes-response eos response1', | |
344 | b'bytes-response eos response2' |
|
351 | b'3 bytes-response eos response2' | |
|
352 | ]) | |||
|
353 | ||||
|
354 | def testrequestidtracking(self): | |||
|
355 | reactor = makereactor(deferoutput=True) | |||
|
356 | list(sendcommandframes(reactor, 1, b'command1', {})) | |||
|
357 | list(sendcommandframes(reactor, 3, b'command2', {})) | |||
|
358 | list(sendcommandframes(reactor, 5, b'command3', {})) | |||
|
359 | ||||
|
360 | # Register results for commands out of order. | |||
|
361 | reactor.onbytesresponseready(3, b'response3') | |||
|
362 | reactor.onbytesresponseready(1, b'response1') | |||
|
363 | reactor.onbytesresponseready(5, b'response5') | |||
|
364 | ||||
|
365 | result = reactor.oninputeof() | |||
|
366 | self.assertaction(result, 'sendframes') | |||
|
367 | self.assertframesequal(result[1]['framegen'], [ | |||
|
368 | b'3 bytes-response eos response3', | |||
|
369 | b'1 bytes-response eos response1', | |||
|
370 | b'5 bytes-response eos response5', | |||
345 | ]) |
|
371 | ]) | |
346 |
|
372 | |||
347 | if __name__ == '__main__': |
|
373 | if __name__ == '__main__': |
General Comments 0
You need to be logged in to leave comments.
Login now