##// END OF EJS Templates
wireprotov2: define and implement "manifestdata" command...
wireprotov2: define and implement "manifestdata" command The added command can be used for obtaining manifest data. Given a manifest path and set of manifest nodes, data about manifests can be retrieved. Unlike changeset data, we wish to emit deltas to describe manifest revisions. So the command uses the relatively new API for building delta requests and emitting them. The code calls into deltaparent(), which I'm not very keen of. There's still work to be done in delta generation land so implementation details of storage (e.g. exactly one delta is stored/available) don't creep into higher levels. But we can worry about this later (there is already a TODO on imanifestorage tracking this). On the subject of parent deltas, the server assumes parent revisions exist on the receiving end. This is obviously wrong for shallow clone. I've added TODOs to add a mechanism to the command to allow clients to specify desired behavior. This shouldn't be too difficult to implement. Another big change is that the client must explicitly request manifest nodes to retrieve. This is a major departure from "getbundle," where the server derives relevant manifests as it iterates changesets and sends them automatically. As implemented, the client must transmit each requested node to the server. At 20 bytes per node, we're looking at 2 MB per 100,000 nodes. Plus wire encoding overhead. This isn't ideal for clients with limited upload bandwidth. I plan to address this in the future by allowing alternate mechanisms for defining the revisions to retrieve. One idea is to define a range of changeset revisions whose manifest revisions to retrieve (similar to how "changesetdata" works). We almost certainly want an API to look up an individual manifest by node. And that's where I've chosen to start with the implementation. Again, a theme of this early exchangev2 work is I want to start by building primitives for accessing raw repository data first and see how far we can get with those before we need more complexity. Differential Revision: https://phab.mercurial-scm.org/D4488

File last commit:

r39597:d06834e0 default
r39673:c7a7c7e8 default
Show More
test-http-api-httpv2.t
619 lines | 19.5 KiB | text/troff | Tads3Lexer
/ tests / test-http-api-httpv2.t
#require no-chg
$ . $TESTDIR/wireprotohelpers.sh
$ enabledummycommands
$ hg init server
$ cat > server/.hg/hgrc << EOF
> [experimental]
> web.apiserver = true
> EOF
$ hg -R server serve -p $HGPORT -d --pid-file hg.pid
$ cat hg.pid > $DAEMON_PIDS
HTTP v2 protocol not enabled by default
$ sendhttpraw << EOF
> httprequest GET api/$HTTPV2
> user-agent: test
> EOF
using raw connection to peer
s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 404 Not Found\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
s> Content-Length: 33\r\n
s> \r\n
s> API exp-http-v2-0001 not enabled\n
Restart server with support for HTTP v2 API
$ killdaemons.py
$ enablehttpv2 server
$ hg -R server serve -p $HGPORT -d --pid-file hg.pid
$ cat hg.pid > $DAEMON_PIDS
Request to unknown command yields 404
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/badcommand
> user-agent: test
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 404 Not Found\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
s> Content-Length: 42\r\n
s> \r\n
s> unknown wire protocol command: badcommand\n
GET to read-only command yields a 405
$ sendhttpraw << EOF
> httprequest GET api/$HTTPV2/ro/customreadonly
> user-agent: test
> EOF
using raw connection to peer
s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 405 Method Not Allowed\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Allow: POST\r\n
s> Content-Length: 30\r\n
s> \r\n
s> commands require POST requests
Missing Accept header results in 406
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/customreadonly
> user-agent: test
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 406 Not Acceptable\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
s> Content-Length: 85\r\n
s> \r\n
s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
Bad Accept header results in 406
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/customreadonly
> accept: invalid
> user-agent: test
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> accept: invalid\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 406 Not Acceptable\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
s> Content-Length: 85\r\n
s> \r\n
s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
Bad Content-Type header results in 415
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/customreadonly
> accept: $MEDIATYPE
> user-agent: test
> content-type: badmedia
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> accept: application/mercurial-exp-framing-0005\r\n
s> content-type: badmedia\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 415 Unsupported Media Type\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
s> Content-Length: 88\r\n
s> \r\n
s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0005\n
Request to read-only command works out of the box
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/customreadonly
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
> frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> *\r\n (glob)
s> content-type: application/mercurial-exp-framing-0005\r\n
s> user-agent: test\r\n
s> content-length: 29\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: application/mercurial-exp-framing-0005\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
s> 13\r\n
s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
s> \r\n
s> 27\r\n
s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
s> \r\n
s> 8\r\n
s> \x00\x00\x00\x01\x00\x02\x002
s> \r\n
s> 0\r\n
s> \r\n
$ sendhttpv2peer << EOF
> command customreadonly
> EOF
creating http peer for wire protocol version 2
sending customreadonly command
s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> accept: application/mercurial-exp-framing-0005\r\n
s> content-type: application/mercurial-exp-framing-0005\r\n
s> content-length: 29\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> user-agent: Mercurial debugwireproto\r\n
s> \r\n
s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: application/mercurial-exp-framing-0005\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
s> 13\r\n
s> \x0b\x00\x00\x01\x00\x02\x011
s> \xa1FstatusBok
s> \r\n
received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
s> 27\r\n
s> \x1f\x00\x00\x01\x00\x02\x001
s> X\x1dcustomreadonly bytes response
s> \r\n
received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
s> 8\r\n
s> \x00\x00\x00\x01\x00\x02\x002
s> \r\n
s> 0\r\n
s> \r\n
received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
response: gen[
b'customreadonly bytes response'
]
Request to read-write command fails because server is read-only by default
GET to read-write request yields 405
$ sendhttpraw << EOF
> httprequest GET api/$HTTPV2/rw/customreadonly
> user-agent: test
> EOF
using raw connection to peer
s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 405 Method Not Allowed\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Allow: POST\r\n
s> Content-Length: 30\r\n
s> \r\n
s> commands require POST requests
Even for unknown commands
$ sendhttpraw << EOF
> httprequest GET api/$HTTPV2/rw/badcommand
> user-agent: test
> EOF
using raw connection to peer
s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 405 Method Not Allowed\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Allow: POST\r\n
s> Content-Length: 30\r\n
s> \r\n
s> commands require POST requests
SSL required by default
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/rw/customreadonly
> user-agent: test
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 403 ssl required\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Length: 17\r\n
s> \r\n
s> permission denied
Restart server to allow non-ssl read-write operations
$ killdaemons.py
$ cat > server/.hg/hgrc << EOF
> [experimental]
> web.apiserver = true
> web.api.http-v2 = true
> [web]
> push_ssl = false
> allow-push = *
> EOF
$ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
$ cat hg.pid > $DAEMON_PIDS
Authorized request for valid read-write command works
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/rw/customreadonly
> user-agent: test
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> accept: application/mercurial-exp-framing-0005\r\n
s> content-type: application/mercurial-exp-framing-0005\r\n
s> user-agent: test\r\n
s> content-length: 29\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: application/mercurial-exp-framing-0005\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
s> 13\r\n
s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
s> \r\n
s> 27\r\n
s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
s> \r\n
s> 8\r\n
s> \x00\x00\x00\x01\x00\x02\x002
s> \r\n
s> 0\r\n
s> \r\n
Authorized request for unknown command is rejected
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/rw/badcommand
> user-agent: test
> accept: $MEDIATYPE
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> accept: application/mercurial-exp-framing-0005\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 404 Not Found\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
s> Content-Length: 42\r\n
s> \r\n
s> unknown wire protocol command: badcommand\n
debugreflect isn't enabled by default
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/debugreflect
> user-agent: test
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> makefile('rb', None)
s> HTTP/1.1 404 Not Found\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
s> Content-Length: 34\r\n
s> \r\n
s> debugreflect service not available
Restart server to get debugreflect endpoint
$ killdaemons.py
$ cat > server/.hg/hgrc << EOF
> [experimental]
> web.apiserver = true
> web.api.debugreflect = true
> web.api.http-v2 = true
> [web]
> push_ssl = false
> allow-push = *
> EOF
$ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
$ cat hg.pid > $DAEMON_PIDS
Command frames can be reflected via debugreflect
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/debugreflect
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
> frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> accept: application/mercurial-exp-framing-0005\r\n
s> content-type: application/mercurial-exp-framing-0005\r\n
s> user-agent: test\r\n
s> content-length: 47\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
s> Content-Length: 205\r\n
s> \r\n
s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
s> received: <no frame>\n
s> {"action": "noop"}
Multiple requests to regular command URL are not allowed
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/customreadonly
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
> frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> accept: application/mercurial-exp-framing-0005\r\n
s> content-type: application/mercurial-exp-framing-0005\r\n
s> user-agent: test\r\n
s> content-length: 29\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: application/mercurial-exp-framing-0005\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
s> 13\r\n
s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
s> \r\n
s> 27\r\n
s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
s> \r\n
s> 8\r\n
s> \x00\x00\x00\x01\x00\x02\x002
s> \r\n
s> 0\r\n
s> \r\n
Multiple requests to "multirequest" URL are allowed
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/multirequest
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
> frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
> frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> *\r\n (glob)
s> *\r\n (glob)
s> user-agent: test\r\n
s> content-length: 58\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: application/mercurial-exp-framing-0005\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
s> 13\r\n
s> \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
s> \r\n
s> 27\r\n
s> \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
s> \r\n
s> 8\r\n
s> \x00\x00\x00\x01\x00\x02\x002
s> \r\n
s> 13\r\n
s> \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
s> \r\n
s> 27\r\n
s> \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
s> \r\n
s> 8\r\n
s> \x00\x00\x00\x03\x00\x02\x002
s> \r\n
s> 0\r\n
s> \r\n
Interleaved requests to "multirequest" are processed
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/multirequest
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
> frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
> frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
> frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
> frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> accept: application/mercurial-exp-framing-0005\r\n
s> content-type: application/mercurial-exp-framing-0005\r\n
s> user-agent: test\r\n
s> content-length: 115\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
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
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: application/mercurial-exp-framing-0005\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
s> 13\r\n
s> \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
s> \r\n
s> 28\r\n
s> \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
s> \r\n
s> 8\r\n
s> \x00\x00\x00\x03\x00\x02\x002
s> \r\n
s> 13\r\n
s> \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
s> \r\n
s> 9\r\n
s> \x01\x00\x00\x01\x00\x02\x001\xa0
s> \r\n
s> 8\r\n
s> \x00\x00\x00\x01\x00\x02\x002
s> \r\n
s> 0\r\n
s> \r\n
Restart server to disable read-write access
$ killdaemons.py
$ cat > server/.hg/hgrc << EOF
> [experimental]
> web.apiserver = true
> web.api.debugreflect = true
> web.api.http-v2 = true
> [web]
> push_ssl = false
> EOF
$ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
$ cat hg.pid > $DAEMON_PIDS
Attempting to run a read-write command via multirequest on read-only URL is not allowed
$ sendhttpraw << EOF
> httprequest POST api/$HTTPV2/ro/multirequest
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
> frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
s> accept: application/mercurial-exp-framing-0005\r\n
s> content-type: application/mercurial-exp-framing-0005\r\n
s> user-agent: test\r\n
s> content-length: 22\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
s> makefile('rb', None)
s> HTTP/1.1 403 Forbidden\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
s> Content-Length: 52\r\n
s> \r\n
s> insufficient permissions to execute command: pushkey
$ cat error.log