##// END OF EJS Templates
wireprotov2: server support for sending content redirects...
Gregory Szorc -
r40061:b099e603 default
parent child Browse files
Show More
@@ -21,6 +21,7 b' from .thirdparty import ('
21 21 from . import (
22 22 encoding,
23 23 error,
24 pycompat,
24 25 util,
25 26 wireprototypes,
26 27 )
@@ -429,6 +430,26 b' def createcommandresponseeosframe(stream'
429 430 flags=FLAG_COMMAND_RESPONSE_EOS,
430 431 payload=b'')
431 432
433 def createalternatelocationresponseframe(stream, requestid, location):
434 data = {
435 b'status': b'redirect',
436 b'location': {
437 b'url': location.url,
438 b'mediatype': location.mediatype,
439 }
440 }
441
442 for a in (r'size', r'fullhashes', r'fullhashseed', r'serverdercerts',
443 r'servercadercerts'):
444 value = getattr(location, a)
445 if value is not None:
446 data[b'location'][pycompat.bytestr(a)] = value
447
448 return stream.makeframe(requestid=requestid,
449 typeid=FRAME_TYPE_COMMAND_RESPONSE,
450 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
451 payload=b''.join(cborutil.streamencode(data)))
452
432 453 def createcommanderrorresponse(stream, requestid, message, args=None):
433 454 # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
434 455 # formatting works consistently?
@@ -813,6 +834,7 b' class serverreactor(object):'
813 834
814 835 def sendframes():
815 836 emitted = False
837 alternatelocationsent = False
816 838 emitter = bufferingcommandresponseemitter(stream, requestid)
817 839 while True:
818 840 try:
@@ -841,6 +863,25 b' class serverreactor(object):'
841 863 break
842 864
843 865 try:
866 # Alternate location responses can only be the first and
867 # only object in the output stream.
868 if isinstance(o, wireprototypes.alternatelocationresponse):
869 if emitted:
870 raise error.ProgrammingError(
871 'alternatelocationresponse seen after initial '
872 'output object')
873
874 yield createalternatelocationresponseframe(
875 stream, requestid, o)
876
877 alternatelocationsent = True
878 emitted = True
879 continue
880
881 if alternatelocationsent:
882 raise error.ProgrammingError(
883 'object follows alternatelocationresponse')
884
844 885 if not emitted:
845 886 yield createcommandresponseokframe(stream, requestid)
846 887 emitted = True
@@ -977,6 +1018,7 b' class serverreactor(object):'
977 1018 'requestid': requestid,
978 1019 'command': request[b'name'],
979 1020 'args': request[b'args'],
1021 'redirect': request.get(b'redirect'),
980 1022 'data': entry['data'].getvalue() if entry['data'] else None,
981 1023 }
982 1024
@@ -368,3 +368,20 b' class encodedresponse(object):'
368 368 and the content from this object is used instead.
369 369 """
370 370 data = attr.ib()
371
372 @attr.s
373 class alternatelocationresponse(object):
374 """Represents a response available at an alternate location.
375
376 Instances are sent in place of actual response objects when the server
377 is sending a "content redirect" response.
378
379 Only compatible with wire protocol version 2.
380 """
381 url = attr.ib()
382 mediatype = attr.ib()
383 size = attr.ib(default=None)
384 fullhashes = attr.ib(default=None)
385 fullhashseed = attr.ib(default=None)
386 serverdercerts = attr.ib(default=None)
387 servercadercerts = attr.ib(default=None)
@@ -312,7 +312,7 b' def _httpv2runcommand(ui, repo, req, res'
312 312 res.headers[b'Content-Type'] = FRAMINGTYPE
313 313
314 314 try:
315 objs = dispatch(repo, proto, command['command'])
315 objs = dispatch(repo, proto, command['command'], command['redirect'])
316 316
317 317 action, meta = reactor.oncommandresponsereadyobjects(
318 318 outstream, command['requestid'], objs)
@@ -339,7 +339,7 b' def _httpv2runcommand(ui, repo, req, res'
339 339 def getdispatchrepo(repo, proto, command):
340 340 return repo.filtered('served')
341 341
342 def dispatch(repo, proto, command):
342 def dispatch(repo, proto, command, redirect):
343 343 """Run a wire protocol command.
344 344
345 345 Returns an iterable of objects that will be sent to the client.
@@ -364,8 +364,17 b' def dispatch(repo, proto, command):'
364 364 yield o
365 365 return
366 366
367 if redirect:
368 redirecttargets = redirect[b'targets']
369 redirecthashes = redirect[b'hashes']
370 else:
371 redirecttargets = []
372 redirecthashes = []
373
367 374 cacher = makeresponsecacher(repo, proto, command, args,
368 cborutil.streamencode)
375 cborutil.streamencode,
376 redirecttargets=redirecttargets,
377 redirecthashes=redirecthashes)
369 378
370 379 # But we have no cacher. Do default handling.
371 380 if not cacher:
@@ -751,7 +760,8 b' def makecommandcachekeyfn(command, local'
751 760
752 761 return cachekeyfn
753 762
754 def makeresponsecacher(repo, proto, command, args, objencoderfn):
763 def makeresponsecacher(repo, proto, command, args, objencoderfn,
764 redirecttargets, redirecthashes):
755 765 """Construct a cacher for a cacheable command.
756 766
757 767 Returns an ``iwireprotocolcommandcacher`` instance.
@@ -430,10 +430,10 b' Command frames can be reflected via debu'
430 430 s> Server: testing stub value\r\n
431 431 s> Date: $HTTP_DATE$\r\n
432 432 s> Content-Type: text/plain\r\n
433 s> Content-Length: 205\r\n
433 s> Content-Length: 223\r\n
434 434 s> \r\n
435 435 s> received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
436 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
436 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "redirect": null, "requestid": 1}]\n
437 437 s> received: <no frame>\n
438 438 s> {"action": "noop"}
439 439
@@ -1,11 +1,20 b''
1 1 $ . $TESTDIR/wireprotohelpers.sh
2 2
3 $ cat >> $HGRCPATH << EOF
4 > [extensions]
5 > blackbox =
6 > [blackbox]
7 > track = simplecache
8 > EOF
9
3 10 $ hg init server
4 11 $ enablehttpv2 server
5 12 $ cd server
6 13 $ cat >> .hg/hgrc << EOF
7 14 > [extensions]
8 15 > simplecache = $TESTDIR/wireprotosimplecache.py
16 > [simplecache]
17 > cacheapi = true
9 18 > EOF
10 19
11 20 $ echo a0 > a
@@ -1183,5 +1192,178 b' Unknown tls value is filtered from compa'
1183 1192 }
1184 1193 ]
1185 1194
1195 Set up the server to issue content redirects to its built-in API server.
1196
1197 $ cat > redirects.py << EOF
1198 > [
1199 > {
1200 > b'name': b'local',
1201 > b'protocol': b'http',
1202 > b'uris': [b'http://example.com/'],
1203 > },
1204 > ]
1205 > EOF
1206
1207 Request to eventual cache URL should return 404 (validating the cache server works)
1208
1209 $ sendhttpraw << EOF
1210 > httprequest GET api/simplecache/missingkey
1211 > user-agent: test
1212 > EOF
1213 using raw connection to peer
1214 s> GET /api/simplecache/missingkey HTTP/1.1\r\n
1215 s> Accept-Encoding: identity\r\n
1216 s> user-agent: test\r\n
1217 s> host: $LOCALIP:$HGPORT\r\n (glob)
1218 s> \r\n
1219 s> makefile('rb', None)
1220 s> HTTP/1.1 404 Not Found\r\n
1221 s> Server: testing stub value\r\n
1222 s> Date: $HTTP_DATE$\r\n
1223 s> Content-Type: text/plain\r\n
1224 s> Content-Length: 22\r\n
1225 s> \r\n
1226 s> key not found in cache
1227
1228 Send a cacheable request
1229
1230 $ sendhttpv2peer << EOF
1231 > command manifestdata
1232 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1233 > tree eval:b''
1234 > fields eval:[b'parents']
1235 > EOF
1236 creating http peer for wire protocol version 2
1237 sending manifestdata command
1238 s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
1239 s> Accept-Encoding: identity\r\n
1240 s> accept: application/mercurial-exp-framing-0005\r\n
1241 s> content-type: application/mercurial-exp-framing-0005\r\n
1242 s> content-length: 128\r\n
1243 s> host: $LOCALIP:$HGPORT\r\n (glob)
1244 s> user-agent: Mercurial debugwireproto\r\n
1245 s> \r\n
1246 s> x\x00\x00\x01\x00\x01\x01\x11\xa3Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdataHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Elocal
1247 s> makefile('rb', None)
1248 s> HTTP/1.1 200 OK\r\n
1249 s> Server: testing stub value\r\n
1250 s> Date: $HTTP_DATE$\r\n
1251 s> Content-Type: application/mercurial-exp-framing-0005\r\n
1252 s> Transfer-Encoding: chunked\r\n
1253 s> \r\n
1254 s> 13\r\n
1255 s> \x0b\x00\x00\x01\x00\x02\x011
1256 s> \xa1FstatusBok
1257 s> \r\n
1258 received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
1259 s> 63\r\n
1260 s> [\x00\x00\x01\x00\x02\x001
1261 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1262 s> \r\n
1263 received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
1264 s> 8\r\n
1265 s> \x00\x00\x00\x01\x00\x02\x002
1266 s> \r\n
1267 s> 0\r\n
1268 s> \r\n
1269 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
1270 response: gen[
1271 {
1272 b'totalitems': 1
1273 },
1274 {
1275 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1276 b'parents': [
1277 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1278 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1279 ]
1280 }
1281 ]
1282
1283 Cached entry should be available on server
1284
1285 $ sendhttpraw << EOF
1286 > httprequest GET api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0
1287 > user-agent: test
1288 > EOF
1289 using raw connection to peer
1290 s> GET /api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 HTTP/1.1\r\n
1291 s> Accept-Encoding: identity\r\n
1292 s> user-agent: test\r\n
1293 s> host: $LOCALIP:$HGPORT\r\n (glob)
1294 s> \r\n
1295 s> makefile('rb', None)
1296 s> HTTP/1.1 200 OK\r\n
1297 s> Server: testing stub value\r\n
1298 s> Date: $HTTP_DATE$\r\n
1299 s> Content-Type: application/mercurial-cbor\r\n
1300 s> Content-Length: 91\r\n
1301 s> \r\n
1302 s> \xa1Jtotalitems\x01\xa2DnodeT\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&AGparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
1303 cbor> [
1304 {
1305 b'totalitems': 1
1306 },
1307 {
1308 b'node': b'\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
1309 b'parents': [
1310 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
1311 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
1312 ]
1313 }
1314 ]
1315
1316 2nd request should result in content redirect response
1317
1318 $ sendhttpv2peer << EOF
1319 > command manifestdata
1320 > nodes eval:[b'\x99\x2f\x47\x79\x02\x9a\x3d\xf8\xd0\x66\x6d\x00\xbb\x92\x4f\x69\x63\x4e\x26\x41']
1321 > tree eval:b''
1322 > fields eval:[b'parents']
1323 > EOF
1324 creating http peer for wire protocol version 2
1325 sending manifestdata command
1326 s> POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
1327 s> Accept-Encoding: identity\r\n
1328 s> accept: application/mercurial-exp-framing-0005\r\n
1329 s> content-type: application/mercurial-exp-framing-0005\r\n
1330 s> content-length: 128\r\n
1331 s> host: $LOCALIP:$HGPORT\r\n (glob)
1332 s> user-agent: Mercurial debugwireproto\r\n
1333 s> \r\n
1334 s> x\x00\x00\x01\x00\x01\x01\x11\xa3Dargs\xa3Ffields\x81GparentsEnodes\x81T\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&ADtree@DnameLmanifestdataHredirect\xa2Fhashes\x82Fsha256Dsha1Gtargets\x81Elocal
1335 s> makefile('rb', None)
1336 s> HTTP/1.1 200 OK\r\n
1337 s> Server: testing stub value\r\n
1338 s> Date: $HTTP_DATE$\r\n
1339 s> Content-Type: application/mercurial-exp-framing-0005\r\n
1340 s> Transfer-Encoding: chunked\r\n
1341 s> \r\n
1342 s> *\r\n (glob)
1343 s> \x*\x00\x00\x01\x00\x02\x011 (glob)
1344 s> \xa2Hlocation\xa2ImediatypeX\x1aapplication/mercurial-cborCurl*http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0FstatusHredirect (glob)
1345 s> \r\n
1346 received frame(size=*; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation) (glob)
1347 s> 8\r\n
1348 s> \x00\x00\x00\x01\x00\x02\x001
1349 s> \r\n
1350 s> 8\r\n
1351 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
1352 s> \x00\x00\x00\x01\x00\x02\x002
1353 s> \r\n
1354 s> 0\r\n
1355 s> \r\n
1356 received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
1357 abort: redirect responses not yet supported
1358 [255]
1359
1186 1360 $ cat error.log
1187 1361 $ killdaemons.py
1362
1363 $ cat .hg/blackbox.log
1364 *> cacher constructed for manifestdata (glob)
1365 *> cache miss for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1366 *> storing cache entry for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1367 *> cacher constructed for manifestdata (glob)
1368 *> cache hit for c045a581599d58608efd3d93d8129841f2af04a0 (glob)
1369 *> sending content redirect for c045a581599d58608efd3d93d8129841f2af04a0 to http://*:$HGPORT/api/simplecache/c045a581599d58608efd3d93d8129841f2af04a0 (glob)
@@ -69,6 +69,7 b' class ServerReactorTests(unittest.TestCa'
69 69 b'requestid': 1,
70 70 b'command': b'mycommand',
71 71 b'args': {},
72 b'redirect': None,
72 73 b'data': None,
73 74 })
74 75
@@ -86,6 +87,7 b' class ServerReactorTests(unittest.TestCa'
86 87 b'requestid': 41,
87 88 b'command': b'mycommand',
88 89 b'args': {b'foo': b'bar'},
90 b'redirect': None,
89 91 b'data': None,
90 92 })
91 93
@@ -100,6 +102,7 b' class ServerReactorTests(unittest.TestCa'
100 102 b'requestid': 1,
101 103 b'command': b'mycommand',
102 104 b'args': {b'foo': b'bar', b'biz': b'baz'},
105 b'redirect': None,
103 106 b'data': None,
104 107 })
105 108
@@ -115,6 +118,7 b' class ServerReactorTests(unittest.TestCa'
115 118 b'requestid': 1,
116 119 b'command': b'mycommand',
117 120 b'args': {},
121 b'redirect': None,
118 122 b'data': b'data!',
119 123 })
120 124
@@ -137,6 +141,7 b' class ServerReactorTests(unittest.TestCa'
137 141 b'requestid': 1,
138 142 b'command': b'mycommand',
139 143 b'args': {},
144 b'redirect': None,
140 145 b'data': b'data1data2data3',
141 146 })
142 147
@@ -160,6 +165,7 b' class ServerReactorTests(unittest.TestCa'
160 165 b'key': b'val',
161 166 b'foo': b'bar',
162 167 },
168 b'redirect': None,
163 169 b'data': b'value1value2',
164 170 })
165 171
@@ -235,6 +241,7 b' class ServerReactorTests(unittest.TestCa'
235 241 b'requestid': 1,
236 242 b'command': b'command',
237 243 b'args': {},
244 b'redirect': None,
238 245 b'data': None,
239 246 })
240 247
@@ -291,12 +298,14 b' class ServerReactorTests(unittest.TestCa'
291 298 b'requestid': 3,
292 299 b'command': b'command3',
293 300 b'args': {b'biz': b'baz', b'key': b'val'},
301 b'redirect': None,
294 302 b'data': None,
295 303 })
296 304 self.assertEqual(results[5][1], {
297 305 b'requestid': 1,
298 306 b'command': b'command1',
299 307 b'args': {b'foo': b'bar', b'key1': b'val'},
308 b'redirect': None,
300 309 b'data': None,
301 310 })
302 311
@@ -12,6 +12,7 b' from mercurial import ('
12 12 registrar,
13 13 repository,
14 14 util,
15 wireprotoserver,
15 16 wireprototypes,
16 17 wireprotov2server,
17 18 )
@@ -25,18 +26,59 b' CACHE = None'
25 26 configtable = {}
26 27 configitem = registrar.configitem(configtable)
27 28
29 configitem('simplecache', 'cacheapi',
30 default=False)
28 31 configitem('simplecache', 'cacheobjects',
29 32 default=False)
30 33 configitem('simplecache', 'redirectsfile',
31 34 default=None)
32 35
36 # API handler that makes cached keys available.
37 def handlecacherequest(rctx, req, res, checkperm, urlparts):
38 if rctx.repo.ui.configbool('simplecache', 'cacheobjects'):
39 res.status = b'500 Internal Server Error'
40 res.setbodybytes(b'cacheobjects not supported for api server')
41 return
42
43 if not urlparts:
44 res.status = b'200 OK'
45 res.headers[b'Content-Type'] = b'text/plain'
46 res.setbodybytes(b'simple cache server')
47 return
48
49 key = b'/'.join(urlparts)
50
51 if key not in CACHE:
52 res.status = b'404 Not Found'
53 res.headers[b'Content-Type'] = b'text/plain'
54 res.setbodybytes(b'key not found in cache')
55 return
56
57 res.status = b'200 OK'
58 res.headers[b'Content-Type'] = b'application/mercurial-cbor'
59 res.setbodybytes(CACHE[key])
60
61 def cachedescriptor(req, repo):
62 return {}
63
64 wireprotoserver.API_HANDLERS[b'simplecache'] = {
65 'config': (b'simplecache', b'cacheapi'),
66 'handler': handlecacherequest,
67 'apidescriptor': cachedescriptor,
68 }
69
33 70 @interfaceutil.implementer(repository.iwireprotocolcommandcacher)
34 71 class memorycacher(object):
35 def __init__(self, ui, command, encodefn):
72 def __init__(self, ui, command, encodefn, redirecttargets, redirecthashes,
73 req):
36 74 self.ui = ui
37 75 self.encodefn = encodefn
76 self.redirecttargets = redirecttargets
77 self.redirecthashes = redirecthashes
78 self.req = req
38 79 self.key = None
39 80 self.cacheobjects = ui.configbool('simplecache', 'cacheobjects')
81 self.cacheapi = ui.configbool('simplecache', 'cacheapi')
40 82 self.buffered = []
41 83
42 84 ui.log('simplecache', 'cacher constructed for %s\n', command)
@@ -65,6 +107,37 b' class memorycacher(object):'
65 107 entry = CACHE[self.key]
66 108 self.ui.log('simplecache', 'cache hit for %s\n', self.key)
67 109
110 redirectable = True
111
112 if not self.cacheapi:
113 redirectable = False
114 elif not self.redirecttargets:
115 redirectable = False
116 else:
117 clienttargets = set(self.redirecttargets)
118 ourtargets = set(t[b'name'] for t in loadredirecttargets(self.ui))
119
120 # We only ever redirect to a single target (for now). So we don't
121 # need to store which target matched.
122 if not clienttargets & ourtargets:
123 redirectable = False
124
125 if redirectable:
126 paths = self.req.dispatchparts[:-3]
127 paths.append(b'simplecache')
128 paths.append(self.key)
129
130 url = b'%s/%s' % (self.req.advertisedbaseurl, b'/'.join(paths))
131
132 #url = b'http://example.com/%s' % self.key
133 self.ui.log('simplecache', 'sending content redirect for %s to '
134 '%s\n', self.key, url)
135 response = wireprototypes.alternatelocationresponse(
136 url=url,
137 mediatype=b'application/mercurial-cbor')
138
139 return {'objs': [response]}
140
68 141 if self.cacheobjects:
69 142 return {
70 143 'objs': entry,
@@ -91,8 +164,10 b' class memorycacher(object):'
91 164
92 165 return []
93 166
94 def makeresponsecacher(orig, repo, proto, command, args, objencoderfn):
95 return memorycacher(repo.ui, command, objencoderfn)
167 def makeresponsecacher(orig, repo, proto, command, args, objencoderfn,
168 redirecttargets, redirecthashes):
169 return memorycacher(repo.ui, command, objencoderfn, redirecttargets,
170 redirecthashes, proto._req)
96 171
97 172 def loadredirecttargets(ui):
98 173 path = ui.config('simplecache', 'redirectsfile')
General Comments 0
You need to be logged in to leave comments. Login now