Show More
@@ -0,0 +1,156 b'' | |||||
|
1 | # wireprotoframing.py - unified framing protocol for wire protocol | |||
|
2 | # | |||
|
3 | # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
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. | |||
|
7 | ||||
|
8 | # This file contains functionality to support the unified frame-based wire | |||
|
9 | # protocol. For details about the protocol, see | |||
|
10 | # `hg help internals.wireprotocol`. | |||
|
11 | ||||
|
12 | from __future__ import absolute_import | |||
|
13 | ||||
|
14 | import struct | |||
|
15 | ||||
|
16 | from . import ( | |||
|
17 | util, | |||
|
18 | ) | |||
|
19 | ||||
|
20 | FRAME_HEADER_SIZE = 4 | |||
|
21 | DEFAULT_MAX_FRAME_SIZE = 32768 | |||
|
22 | ||||
|
23 | FRAME_TYPE_COMMAND_NAME = 0x01 | |||
|
24 | FRAME_TYPE_COMMAND_ARGUMENT = 0x02 | |||
|
25 | FRAME_TYPE_COMMAND_DATA = 0x03 | |||
|
26 | ||||
|
27 | FRAME_TYPES = { | |||
|
28 | b'command-name': FRAME_TYPE_COMMAND_NAME, | |||
|
29 | b'command-argument': FRAME_TYPE_COMMAND_ARGUMENT, | |||
|
30 | b'command-data': FRAME_TYPE_COMMAND_DATA, | |||
|
31 | } | |||
|
32 | ||||
|
33 | FLAG_COMMAND_NAME_EOS = 0x01 | |||
|
34 | FLAG_COMMAND_NAME_HAVE_ARGS = 0x02 | |||
|
35 | FLAG_COMMAND_NAME_HAVE_DATA = 0x04 | |||
|
36 | ||||
|
37 | FLAGS_COMMAND = { | |||
|
38 | b'eos': FLAG_COMMAND_NAME_EOS, | |||
|
39 | b'have-args': FLAG_COMMAND_NAME_HAVE_ARGS, | |||
|
40 | b'have-data': FLAG_COMMAND_NAME_HAVE_DATA, | |||
|
41 | } | |||
|
42 | ||||
|
43 | FLAG_COMMAND_ARGUMENT_CONTINUATION = 0x01 | |||
|
44 | FLAG_COMMAND_ARGUMENT_EOA = 0x02 | |||
|
45 | ||||
|
46 | FLAGS_COMMAND_ARGUMENT = { | |||
|
47 | b'continuation': FLAG_COMMAND_ARGUMENT_CONTINUATION, | |||
|
48 | b'eoa': FLAG_COMMAND_ARGUMENT_EOA, | |||
|
49 | } | |||
|
50 | ||||
|
51 | FLAG_COMMAND_DATA_CONTINUATION = 0x01 | |||
|
52 | FLAG_COMMAND_DATA_EOS = 0x02 | |||
|
53 | ||||
|
54 | FLAGS_COMMAND_DATA = { | |||
|
55 | b'continuation': FLAG_COMMAND_DATA_CONTINUATION, | |||
|
56 | b'eos': FLAG_COMMAND_DATA_EOS, | |||
|
57 | } | |||
|
58 | ||||
|
59 | # Maps frame types to their available flags. | |||
|
60 | FRAME_TYPE_FLAGS = { | |||
|
61 | FRAME_TYPE_COMMAND_NAME: FLAGS_COMMAND, | |||
|
62 | FRAME_TYPE_COMMAND_ARGUMENT: FLAGS_COMMAND_ARGUMENT, | |||
|
63 | FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA, | |||
|
64 | } | |||
|
65 | ||||
|
66 | ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH') | |||
|
67 | ||||
|
68 | def makeframe(frametype, frameflags, payload): | |||
|
69 | """Assemble a frame into a byte array.""" | |||
|
70 | # TODO assert size of payload. | |||
|
71 | frame = bytearray(FRAME_HEADER_SIZE + len(payload)) | |||
|
72 | ||||
|
73 | l = struct.pack(r'<I', len(payload)) | |||
|
74 | frame[0:3] = l[0:3] | |||
|
75 | frame[3] = (frametype << 4) | frameflags | |||
|
76 | frame[4:] = payload | |||
|
77 | ||||
|
78 | return frame | |||
|
79 | ||||
|
80 | def makeframefromhumanstring(s): | |||
|
81 | """Given a string of the form: <type> <flags> <payload>, creates a frame. | |||
|
82 | ||||
|
83 | This can be used by user-facing applications and tests for creating | |||
|
84 | frames easily without having to type out a bunch of constants. | |||
|
85 | ||||
|
86 | Frame type and flags can be specified by integer or named constant. | |||
|
87 | Flags can be delimited by `|` to bitwise OR them together. | |||
|
88 | """ | |||
|
89 | frametype, frameflags, payload = s.split(b' ', 2) | |||
|
90 | ||||
|
91 | if frametype in FRAME_TYPES: | |||
|
92 | frametype = FRAME_TYPES[frametype] | |||
|
93 | else: | |||
|
94 | frametype = int(frametype) | |||
|
95 | ||||
|
96 | finalflags = 0 | |||
|
97 | validflags = FRAME_TYPE_FLAGS[frametype] | |||
|
98 | for flag in frameflags.split(b'|'): | |||
|
99 | if flag in validflags: | |||
|
100 | finalflags |= validflags[flag] | |||
|
101 | else: | |||
|
102 | finalflags |= int(flag) | |||
|
103 | ||||
|
104 | payload = util.unescapestr(payload) | |||
|
105 | ||||
|
106 | return makeframe(frametype, finalflags, payload) | |||
|
107 | ||||
|
108 | def createcommandframes(cmd, args, datafh=None): | |||
|
109 | """Create frames necessary to transmit a request to run a command. | |||
|
110 | ||||
|
111 | This is a generator of bytearrays. Each item represents a frame | |||
|
112 | ready to be sent over the wire to a peer. | |||
|
113 | """ | |||
|
114 | flags = 0 | |||
|
115 | if args: | |||
|
116 | flags |= FLAG_COMMAND_NAME_HAVE_ARGS | |||
|
117 | if datafh: | |||
|
118 | flags |= FLAG_COMMAND_NAME_HAVE_DATA | |||
|
119 | ||||
|
120 | if not flags: | |||
|
121 | flags |= FLAG_COMMAND_NAME_EOS | |||
|
122 | ||||
|
123 | yield makeframe(FRAME_TYPE_COMMAND_NAME, flags, cmd) | |||
|
124 | ||||
|
125 | for i, k in enumerate(sorted(args)): | |||
|
126 | v = args[k] | |||
|
127 | last = i == len(args) - 1 | |||
|
128 | ||||
|
129 | # TODO handle splitting of argument values across frames. | |||
|
130 | payload = bytearray(ARGUMENT_FRAME_HEADER.size + len(k) + len(v)) | |||
|
131 | offset = 0 | |||
|
132 | ARGUMENT_FRAME_HEADER.pack_into(payload, offset, len(k), len(v)) | |||
|
133 | offset += ARGUMENT_FRAME_HEADER.size | |||
|
134 | payload[offset:offset + len(k)] = k | |||
|
135 | offset += len(k) | |||
|
136 | payload[offset:offset + len(v)] = v | |||
|
137 | ||||
|
138 | flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0 | |||
|
139 | yield makeframe(FRAME_TYPE_COMMAND_ARGUMENT, flags, payload) | |||
|
140 | ||||
|
141 | if datafh: | |||
|
142 | while True: | |||
|
143 | data = datafh.read(DEFAULT_MAX_FRAME_SIZE) | |||
|
144 | ||||
|
145 | done = False | |||
|
146 | if len(data) == DEFAULT_MAX_FRAME_SIZE: | |||
|
147 | flags = FLAG_COMMAND_DATA_CONTINUATION | |||
|
148 | else: | |||
|
149 | flags = FLAG_COMMAND_DATA_EOS | |||
|
150 | assert datafh.read(1) == b'' | |||
|
151 | done = True | |||
|
152 | ||||
|
153 | yield makeframe(FRAME_TYPE_COMMAND_DATA, flags, data) | |||
|
154 | ||||
|
155 | if done: | |||
|
156 | break |
@@ -78,6 +78,7 b' from . import (' | |||||
78 | url as urlmod, |
|
78 | url as urlmod, | |
79 | util, |
|
79 | util, | |
80 | vfs as vfsmod, |
|
80 | vfs as vfsmod, | |
|
81 | wireprotoframing, | |||
81 | wireprotoserver, |
|
82 | wireprotoserver, | |
82 | ) |
|
83 | ) | |
83 | from .utils import dateutil |
|
84 | from .utils import dateutil | |
@@ -2711,6 +2712,12 b' def debugwireproto(ui, repo, path=None, ' | |||||
2711 | The content of the file defined as the value to this argument will be |
|
2712 | The content of the file defined as the value to this argument will be | |
2712 | transferred verbatim as the HTTP request body. |
|
2713 | transferred verbatim as the HTTP request body. | |
2713 |
|
2714 | |||
|
2715 | ``frame <type> <flags> <payload>`` | |||
|
2716 | Send a unified protocol frame as part of the request body. | |||
|
2717 | ||||
|
2718 | All frames will be collected and sent as the body to the HTTP | |||
|
2719 | request. | |||
|
2720 | ||||
2714 | close |
|
2721 | close | |
2715 | ----- |
|
2722 | ----- | |
2716 |
|
2723 | |||
@@ -2750,6 +2757,28 b' def debugwireproto(ui, repo, path=None, ' | |||||
2750 | --------- |
|
2757 | --------- | |
2751 |
|
2758 | |||
2752 | ``read()`` N bytes from the server's stderr pipe, if available. |
|
2759 | ``read()`` N bytes from the server's stderr pipe, if available. | |
|
2760 | ||||
|
2761 | Specifying Unified Frame-Based Protocol Frames | |||
|
2762 | ---------------------------------------------- | |||
|
2763 | ||||
|
2764 | It is possible to emit a *Unified Frame-Based Protocol* by using special | |||
|
2765 | syntax. | |||
|
2766 | ||||
|
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, 3 | |||
|
2769 | space-delimited strings. | |||
|
2770 | ||||
|
2771 | ``payload`` is the simplest: it is evaluated as a Python byte string | |||
|
2772 | literal. | |||
|
2773 | ||||
|
2774 | ``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. | |||
|
2776 | ``command-name``. | |||
|
2777 | ||||
|
2778 | ``flags`` is a ``|`` delimited list of flag components. Each component | |||
|
2779 | (and there can be just one) can be an integer or a flag name for the | |||
|
2780 | specified frame type. Values are resolved to integers and then bitwise | |||
|
2781 | OR'd together. | |||
2753 | """ |
|
2782 | """ | |
2754 | opts = pycompat.byteskwargs(opts) |
|
2783 | opts = pycompat.byteskwargs(opts) | |
2755 |
|
2784 | |||
@@ -2953,6 +2982,7 b' def debugwireproto(ui, repo, path=None, ' | |||||
2953 | method, httppath = request[1:] |
|
2982 | method, httppath = request[1:] | |
2954 | headers = {} |
|
2983 | headers = {} | |
2955 | body = None |
|
2984 | body = None | |
|
2985 | frames = [] | |||
2956 | for line in lines: |
|
2986 | for line in lines: | |
2957 | line = line.lstrip() |
|
2987 | line = line.lstrip() | |
2958 | m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line) |
|
2988 | m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line) | |
@@ -2963,11 +2993,20 b' def debugwireproto(ui, repo, path=None, ' | |||||
2963 | if line.startswith(b'BODYFILE '): |
|
2993 | if line.startswith(b'BODYFILE '): | |
2964 | with open(line.split(b' ', 1), 'rb') as fh: |
|
2994 | with open(line.split(b' ', 1), 'rb') as fh: | |
2965 | body = fh.read() |
|
2995 | body = fh.read() | |
|
2996 | elif line.startswith(b'frame '): | |||
|
2997 | frame = wireprotoframing.makeframefromhumanstring( | |||
|
2998 | line[len(b'frame '):]) | |||
|
2999 | ||||
|
3000 | frames.append(frame) | |||
2966 | else: |
|
3001 | else: | |
2967 | raise error.Abort(_('unknown argument to httprequest: %s') % |
|
3002 | raise error.Abort(_('unknown argument to httprequest: %s') % | |
2968 | line) |
|
3003 | line) | |
2969 |
|
3004 | |||
2970 | url = path + httppath |
|
3005 | url = path + httppath | |
|
3006 | ||||
|
3007 | if frames: | |||
|
3008 | body = b''.join(bytes(f) for f in frames) | |||
|
3009 | ||||
2971 | req = urlmod.urlreq.request(pycompat.strurl(url), body, headers) |
|
3010 | req = urlmod.urlreq.request(pycompat.strurl(url), body, headers) | |
2972 |
|
3011 | |||
2973 | # urllib.Request insists on using has_data() as a proxy for |
|
3012 | # urllib.Request insists on using has_data() as a proxy for |
@@ -187,12 +187,15 b' exchange takes place. This provides a be' | |||||
187 | Requests to unknown commands or URLS result in an HTTP 404. |
|
187 | Requests to unknown commands or URLS result in an HTTP 404. | |
188 | TODO formally define response type, how error is communicated, etc. |
|
188 | TODO formally define response type, how error is communicated, etc. | |
189 |
|
189 | |||
190 |
HTTP request and response bodies use the * |
|
190 | HTTP request and response bodies use the *Unified Frame-Based Protocol* | |
|
191 | (defined below) for media exchange. The entirety of the HTTP message | |||
|
192 | body is 0 or more frames as defined by this protocol. | |||
191 |
|
193 | |||
192 | Clients and servers MUST advertise the ``TBD`` media type via the |
|
194 | Clients and servers MUST advertise the ``TBD`` media type via the | |
193 | ``Content-Type`` request and response headers. In addition, clients MUST |
|
195 | ``Content-Type`` request and response headers. In addition, clients MUST | |
194 | advertise this media type value in their ``Accept`` request header in all |
|
196 | advertise this media type value in their ``Accept`` request header in all | |
195 | requests. |
|
197 | requests. | |
|
198 | TODO finalize the media type. For now, it is defined in wireprotoserver.py. | |||
196 |
|
199 | |||
197 | Servers receiving requests without an ``Accept`` header SHOULD respond with |
|
200 | Servers receiving requests without an ``Accept`` header SHOULD respond with | |
198 | an HTTP 406. |
|
201 | an HTTP 406. | |
@@ -429,7 +432,7 b' The server terminates if it receives an ' | |||||
429 | SSH Version 2 Transport |
|
432 | SSH Version 2 Transport | |
430 | ----------------------- |
|
433 | ----------------------- | |
431 |
|
434 | |||
432 | **Experimental** |
|
435 | **Experimental and under development** | |
433 |
|
436 | |||
434 | Version 2 of the SSH transport behaves identically to version 1 of the SSH |
|
437 | Version 2 of the SSH transport behaves identically to version 1 of the SSH | |
435 | transport with the exception of handshake semantics. See above for how |
|
438 | transport with the exception of handshake semantics. See above for how | |
@@ -451,6 +454,164 b' e.g.' | |||||
451 | Following capabilities advertisement, the peers communicate using version |
|
454 | Following capabilities advertisement, the peers communicate using version | |
452 | 1 of the SSH transport. |
|
455 | 1 of the SSH transport. | |
453 |
|
456 | |||
|
457 | Unified Frame-Based Protocol | |||
|
458 | ============================ | |||
|
459 | ||||
|
460 | **Experimental and under development** | |||
|
461 | ||||
|
462 | The *Unified Frame-Based Protocol* is a communications protocol between | |||
|
463 | Mercurial peers. The protocol aims to be mostly transport agnostic | |||
|
464 | (works similarly on HTTP, SSH, etc). | |||
|
465 | ||||
|
466 | To operate the protocol, a bi-directional, half-duplex pipe supporting | |||
|
467 | ordered sends and receives is required. That is, each peer has one pipe | |||
|
468 | for sending data and another for receiving. | |||
|
469 | ||||
|
470 | The protocol is request-response based: the client issues requests to | |||
|
471 | the server, which issues replies to those requests. Server-initiated | |||
|
472 | messaging is not supported. | |||
|
473 | ||||
|
474 | All data is read and written in atomic units called *frames*. These | |||
|
475 | are conceptually similar to TCP packets. Higher-level functionality | |||
|
476 | is built on the exchange and processing of frames. | |||
|
477 | ||||
|
478 | Frames begin with a 4 octet header followed by a variable length | |||
|
479 | payload:: | |||
|
480 | ||||
|
481 | +-----------------------------------------------+ | |||
|
482 | | Length (24) | | |||
|
483 | +-----------+-----------------------------------+ | |||
|
484 | | Type (4) | | |||
|
485 | +-----------+ | |||
|
486 | | Flags (4) | | |||
|
487 | +===========+===================================================| | |||
|
488 | | Frame Payload (0...) ... | |||
|
489 | +---------------------------------------------------------------+ | |||
|
490 | ||||
|
491 | The length of the frame payload is expressed as an unsigned 24 bit | |||
|
492 | little endian integer. Values larger than 65535 MUST NOT be used unless | |||
|
493 | given permission by the server as part of the negotiated capabilities | |||
|
494 | during the handshake. The frame header is not part of the advertised | |||
|
495 | frame length. | |||
|
496 | ||||
|
497 | The 4-bit ``Type`` field denotes the type of message being sent. | |||
|
498 | ||||
|
499 | The 4-bit ``Flags`` field defines special, per-type attributes for | |||
|
500 | the frame. | |||
|
501 | ||||
|
502 | The sections below define the frame types and their behavior. | |||
|
503 | ||||
|
504 | Command Request (``0x01``) | |||
|
505 | -------------------------- | |||
|
506 | ||||
|
507 | This frame contains a request to run a command. | |||
|
508 | ||||
|
509 | The name of the command to run constitutes the entirety of the frame | |||
|
510 | payload. | |||
|
511 | ||||
|
512 | This frame type MUST ONLY be sent from clients to servers: it is illegal | |||
|
513 | for a server to send this frame to a client. | |||
|
514 | ||||
|
515 | The following flag values are defined for this type: | |||
|
516 | ||||
|
517 | 0x01 | |||
|
518 | End of command data. When set, the client will not send any command | |||
|
519 | arguments or additional command data. When set, the command has been | |||
|
520 | fully issued and the server has the full context to process the command. | |||
|
521 | The next frame issued by the client is not part of this command. | |||
|
522 | 0x02 | |||
|
523 | Command argument frames expected. When set, the client will send | |||
|
524 | *Command Argument* frames containing command argument data. | |||
|
525 | 0x04 | |||
|
526 | Command data frames expected. When set, the client will send | |||
|
527 | *Command Data* frames containing a raw stream of data for this | |||
|
528 | command. | |||
|
529 | ||||
|
530 | The ``0x01`` flag is mutually exclusive with both the ``0x02`` and ``0x04`` | |||
|
531 | flags. | |||
|
532 | ||||
|
533 | Command Argument (``0x02``) | |||
|
534 | --------------------------- | |||
|
535 | ||||
|
536 | This frame contains a named argument for a command. | |||
|
537 | ||||
|
538 | The frame type MUST ONLY be sent from clients to servers: it is illegal | |||
|
539 | for a server to send this frame to a client. | |||
|
540 | ||||
|
541 | The payload consists of: | |||
|
542 | ||||
|
543 | * A 16-bit little endian integer denoting the length of the | |||
|
544 | argument name. | |||
|
545 | * A 16-bit little endian integer denoting the length of the | |||
|
546 | argument value. | |||
|
547 | * N bytes of ASCII data containing the argument name. | |||
|
548 | * N bytes of binary data containing the argument value. | |||
|
549 | ||||
|
550 | The payload MUST hold the entirety of the 32-bit header and the | |||
|
551 | argument name. The argument value MAY span multiple frames. If this | |||
|
552 | occurs, the appropriate frame flag should be set to indicate this. | |||
|
553 | ||||
|
554 | The following flag values are defined for this type: | |||
|
555 | ||||
|
556 | 0x01 | |||
|
557 | Argument data continuation. When set, the data for this argument did | |||
|
558 | not fit in a single frame and the next frame will contain additional | |||
|
559 | argument data. | |||
|
560 | ||||
|
561 | 0x02 | |||
|
562 | End of arguments data. When set, the client will not send any more | |||
|
563 | command arguments for the command this frame is associated with. | |||
|
564 | The next frame issued by the client will be command data or | |||
|
565 | belong to a separate request. | |||
|
566 | ||||
|
567 | Command Data (``0x03``) | |||
|
568 | ----------------------- | |||
|
569 | ||||
|
570 | This frame contains raw data for a command. | |||
|
571 | ||||
|
572 | Most commands can be executed by specifying arguments. However, | |||
|
573 | arguments have an upper bound to their length. For commands that | |||
|
574 | accept data that is beyond this length or whose length isn't known | |||
|
575 | when the command is initially sent, they will need to stream | |||
|
576 | arbitrary data to the server. This frame type facilitates the sending | |||
|
577 | of this data. | |||
|
578 | ||||
|
579 | The payload of this frame type consists of a stream of raw data to be | |||
|
580 | consumed by the command handler on the server. The format of the data | |||
|
581 | is command specific. | |||
|
582 | ||||
|
583 | The following flag values are defined for this type: | |||
|
584 | ||||
|
585 | 0x01 | |||
|
586 | Command data continuation. When set, the data for this command | |||
|
587 | continues into a subsequent frame. | |||
|
588 | ||||
|
589 | 0x02 | |||
|
590 | End of data. When set, command data has been fully sent to the | |||
|
591 | server. The command has been fully issued and no new data for this | |||
|
592 | command will be sent. The next frame will belong to a new command. | |||
|
593 | ||||
|
594 | Issuing Commands | |||
|
595 | ---------------- | |||
|
596 | ||||
|
597 | A client can request that a remote run a command by sending it | |||
|
598 | frames defining that command. This logical stream is composed of | |||
|
599 | 1 ``Command Request`` frame, 0 or more ``Command Argument`` frames, | |||
|
600 | and 0 or more ``Command Data`` frames. | |||
|
601 | ||||
|
602 | Argument frames are the recommended mechanism for transferring fixed | |||
|
603 | sets of parameters to a command. Data frames are appropriate for | |||
|
604 | transferring variable data. A similar comparison would be to HTTP: | |||
|
605 | argument frames are headers and the message body is data frames. | |||
|
606 | ||||
|
607 | It is recommended for servers to delay the dispatch of a command | |||
|
608 | until all argument frames for that command have been received. Servers | |||
|
609 | MAY impose limits on the maximum argument size. | |||
|
610 | TODO define failure mechanism. | |||
|
611 | ||||
|
612 | Servers MAY dispatch to commands immediately once argument data | |||
|
613 | is available or delay until command data is received in full. | |||
|
614 | ||||
454 | Capabilities |
|
615 | Capabilities | |
455 | ============ |
|
616 | ============ | |
456 |
|
617 |
@@ -32,7 +32,7 b' HTTP_OK = 200' | |||||
32 | HGTYPE = 'application/mercurial-0.1' |
|
32 | HGTYPE = 'application/mercurial-0.1' | |
33 | HGTYPE2 = 'application/mercurial-0.2' |
|
33 | HGTYPE2 = 'application/mercurial-0.2' | |
34 | HGERRTYPE = 'application/hg-error' |
|
34 | HGERRTYPE = 'application/hg-error' | |
35 |
|
|
35 | FRAMINGTYPE = b'application/mercurial-exp-framing-0001' | |
36 |
|
36 | |||
37 | HTTPV2 = wireprototypes.HTTPV2 |
|
37 | HTTPV2 = wireprototypes.HTTPV2 | |
38 | SSHV1 = wireprototypes.SSHV1 |
|
38 | SSHV1 = wireprototypes.SSHV1 | |
@@ -336,21 +336,21 b' def _handlehttpv2request(rctx, req, res,' | |||||
336 | res.setbodybytes(_('invalid wire protocol command: %s') % command) |
|
336 | res.setbodybytes(_('invalid wire protocol command: %s') % command) | |
337 | return |
|
337 | return | |
338 |
|
338 | |||
339 |
if req.headers.get(b'Accept') != |
|
339 | if req.headers.get(b'Accept') != FRAMINGTYPE: | |
340 | res.status = b'406 Not Acceptable' |
|
340 | res.status = b'406 Not Acceptable' | |
341 | res.headers[b'Content-Type'] = b'text/plain' |
|
341 | res.headers[b'Content-Type'] = b'text/plain' | |
342 | res.setbodybytes(_('client MUST specify Accept header with value: %s\n') |
|
342 | res.setbodybytes(_('client MUST specify Accept header with value: %s\n') | |
343 |
% |
|
343 | % FRAMINGTYPE) | |
344 | return |
|
344 | return | |
345 |
|
345 | |||
346 | if (b'Content-Type' in req.headers |
|
346 | if (b'Content-Type' in req.headers | |
347 |
and req.headers[b'Content-Type'] != |
|
347 | and req.headers[b'Content-Type'] != FRAMINGTYPE): | |
348 | res.status = b'415 Unsupported Media Type' |
|
348 | res.status = b'415 Unsupported Media Type' | |
349 | # TODO we should send a response with appropriate media type, |
|
349 | # TODO we should send a response with appropriate media type, | |
350 | # since client does Accept it. |
|
350 | # since client does Accept it. | |
351 | res.headers[b'Content-Type'] = b'text/plain' |
|
351 | res.headers[b'Content-Type'] = b'text/plain' | |
352 | res.setbodybytes(_('client MUST send Content-Type header with ' |
|
352 | res.setbodybytes(_('client MUST send Content-Type header with ' | |
353 |
'value: %s\n') % |
|
353 | 'value: %s\n') % FRAMINGTYPE) | |
354 | return |
|
354 | return | |
355 |
|
355 | |||
356 | # We don't do anything meaningful yet. |
|
356 | # We don't do anything meaningful yet. |
@@ -1,5 +1,5 b'' | |||||
1 | $ HTTPV2=exp-http-v2-0001 |
|
1 | $ HTTPV2=exp-http-v2-0001 | |
2 |
$ MEDIATYPE=application/mercurial- |
|
2 | $ MEDIATYPE=application/mercurial-exp-framing-0001 | |
3 |
|
3 | |||
4 | $ send() { |
|
4 | $ send() { | |
5 | > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ |
|
5 | > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ | |
@@ -120,9 +120,9 b' Missing Accept header results in 406' | |||||
120 | s> Server: testing stub value\r\n |
|
120 | s> Server: testing stub value\r\n | |
121 | s> Date: $HTTP_DATE$\r\n |
|
121 | s> Date: $HTTP_DATE$\r\n | |
122 | s> Content-Type: text/plain\r\n |
|
122 | s> Content-Type: text/plain\r\n | |
123 |
s> Content-Length: |
|
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- |
|
125 | s> client MUST specify Accept header with value: application/mercurial-exp-framing-0001\n | |
126 |
|
126 | |||
127 | Bad Accept header results in 406 |
|
127 | Bad Accept header results in 406 | |
128 |
|
128 | |||
@@ -143,9 +143,9 b' Bad Accept header results in 406' | |||||
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: text/plain\r\n |
|
145 | s> Content-Type: text/plain\r\n | |
146 |
s> Content-Length: |
|
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- |
|
148 | s> client MUST specify Accept header with value: application/mercurial-exp-framing-0001\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- |
|
161 | s> accept: application/mercurial-exp-framing-0001\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) | |
@@ -168,9 +168,9 b' Bad Content-Type header results in 415' | |||||
168 | s> Server: testing stub value\r\n |
|
168 | s> Server: testing stub value\r\n | |
169 | s> Date: $HTTP_DATE$\r\n |
|
169 | s> Date: $HTTP_DATE$\r\n | |
170 | s> Content-Type: text/plain\r\n |
|
170 | s> Content-Type: text/plain\r\n | |
171 |
s> Content-Length: |
|
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- |
|
173 | s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0001\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,15 +179,18 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 | > EOF |
|
183 | > EOF | |
183 | using raw connection to peer |
|
184 | using raw connection to peer | |
184 | 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 | |
185 | s> Accept-Encoding: identity\r\n |
|
186 | s> Accept-Encoding: identity\r\n | |
186 |
s> accept: application/mercurial- |
|
187 | s> accept: application/mercurial-exp-framing-0001\r\n | |
187 |
s> content-type: application/mercurial- |
|
188 | s> content-type: application/mercurial-exp-framing-0001\r\n | |
188 | s> user-agent: test\r\n |
|
189 | s> user-agent: test\r\n | |
|
190 | s> content-length: 18\r\n | |||
189 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
191 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
190 | s> \r\n |
|
192 | s> \r\n | |
|
193 | s> \x0e\x00\x00\x11customreadonly | |||
191 | s> makefile('rb', None) |
|
194 | s> makefile('rb', None) | |
192 | s> HTTP/1.1 200 OK\r\n |
|
195 | s> HTTP/1.1 200 OK\r\n | |
193 | s> Server: testing stub value\r\n |
|
196 | s> Server: testing stub value\r\n | |
@@ -283,15 +286,18 b' Authorized request for valid read-write ' | |||||
283 | > user-agent: test |
|
286 | > user-agent: test | |
284 | > accept: $MEDIATYPE |
|
287 | > accept: $MEDIATYPE | |
285 | > content-type: $MEDIATYPE |
|
288 | > content-type: $MEDIATYPE | |
|
289 | > frame command-name eos customreadonly | |||
286 | > EOF |
|
290 | > EOF | |
287 | using raw connection to peer |
|
291 | using raw connection to peer | |
288 | s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n |
|
292 | s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n | |
289 | s> Accept-Encoding: identity\r\n |
|
293 | s> Accept-Encoding: identity\r\n | |
290 |
s> accept: application/mercurial- |
|
294 | s> accept: application/mercurial-exp-framing-0001\r\n | |
291 |
s> content-type: application/mercurial- |
|
295 | s> content-type: application/mercurial-exp-framing-0001\r\n | |
292 | s> user-agent: test\r\n |
|
296 | s> user-agent: test\r\n | |
|
297 | s> content-length: 18\r\n | |||
293 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
298 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
294 | s> \r\n |
|
299 | s> \r\n | |
|
300 | s> \x0e\x00\x00\x11customreadonly | |||
295 | s> makefile('rb', None) |
|
301 | s> makefile('rb', None) | |
296 | s> HTTP/1.1 200 OK\r\n |
|
302 | s> HTTP/1.1 200 OK\r\n | |
297 | s> Server: testing stub value\r\n |
|
303 | s> Server: testing stub value\r\n | |
@@ -311,7 +317,7 b' Authorized request for unknown command i' | |||||
311 | using raw connection to peer |
|
317 | using raw connection to peer | |
312 | s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n |
|
318 | s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n | |
313 | s> Accept-Encoding: identity\r\n |
|
319 | s> Accept-Encoding: identity\r\n | |
314 |
s> accept: application/mercurial- |
|
320 | s> accept: application/mercurial-exp-framing-0001\r\n | |
315 | s> user-agent: test\r\n |
|
321 | s> user-agent: test\r\n | |
316 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
322 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
317 | s> \r\n |
|
323 | s> \r\n |
General Comments 0
You need to be logged in to leave comments.
Login now