##// END OF EJS Templates
wireproto: support /api/* URL space for exposing APIs...
Gregory Szorc -
r37064:1cfef569 default
parent child Browse files
Show More
@@ -0,0 +1,65 b''
1 $ send() {
2 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
3 > }
4
5 $ hg init server
6 $ cat > server/.hg/hgrc << EOF
7 > [experimental]
8 > web.apiserver = true
9 > EOF
10 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
11 $ cat hg.pid > $DAEMON_PIDS
12
13 HTTP v2 protocol not enabled by default
14
15 $ send << EOF
16 > httprequest GET api/exp-http-v2-0001
17 > user-agent: test
18 > EOF
19 using raw connection to peer
20 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
21 s> Accept-Encoding: identity\r\n
22 s> user-agent: test\r\n
23 s> host: $LOCALIP:$HGPORT\r\n (glob)
24 s> \r\n
25 s> makefile('rb', None)
26 s> HTTP/1.1 404 Not Found\r\n
27 s> Server: testing stub value\r\n
28 s> Date: $HTTP_DATE$\r\n
29 s> Content-Type: text/plain\r\n
30 s> Content-Length: 33\r\n
31 s> \r\n
32 s> API exp-http-v2-0001 not enabled\n
33
34 Restart server with support for HTTP v2 API
35
36 $ killdaemons.py
37 $ cat > server/.hg/hgrc << EOF
38 > [experimental]
39 > web.apiserver = true
40 > web.api.http-v2 = true
41 > EOF
42
43 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
44 $ cat hg.pid > $DAEMON_PIDS
45
46 Requests simply echo their path (for now)
47
48 $ send << EOF
49 > httprequest GET api/exp-http-v2-0001/path1/path2
50 > user-agent: test
51 > EOF
52 using raw connection to peer
53 s> GET /api/exp-http-v2-0001/path1/path2 HTTP/1.1\r\n
54 s> Accept-Encoding: identity\r\n
55 s> user-agent: test\r\n
56 s> host: $LOCALIP:$HGPORT\r\n (glob)
57 s> \r\n
58 s> makefile('rb', None)
59 s> HTTP/1.1 200 OK\r\n
60 s> Server: testing stub value\r\n
61 s> Date: $HTTP_DATE$\r\n
62 s> Content-Type: text/plain\r\n
63 s> Content-Length: 12\r\n
64 s> \r\n
65 s> path1/path2\n
@@ -0,0 +1,201 b''
1 $ send() {
2 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
3 > }
4
5 $ hg init server
6 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
7 $ cat hg.pid > $DAEMON_PIDS
8
9 Request to /api fails unless web.apiserver is enabled
10
11 $ send << EOF
12 > httprequest GET api
13 > user-agent: test
14 > EOF
15 using raw connection to peer
16 s> GET /api HTTP/1.1\r\n
17 s> Accept-Encoding: identity\r\n
18 s> user-agent: test\r\n
19 s> host: $LOCALIP:$HGPORT\r\n (glob)
20 s> \r\n
21 s> makefile('rb', None)
22 s> HTTP/1.1 404 Not Found\r\n
23 s> Server: testing stub value\r\n
24 s> Date: $HTTP_DATE$\r\n
25 s> Content-Type: text/plain\r\n
26 s> Content-Length: 44\r\n
27 s> \r\n
28 s> Experimental API server endpoint not enabled
29
30 $ send << EOF
31 > httprequest GET api/
32 > user-agent: test
33 > EOF
34 using raw connection to peer
35 s> GET /api/ HTTP/1.1\r\n
36 s> Accept-Encoding: identity\r\n
37 s> user-agent: test\r\n
38 s> host: $LOCALIP:$HGPORT\r\n (glob)
39 s> \r\n
40 s> makefile('rb', None)
41 s> HTTP/1.1 404 Not Found\r\n
42 s> Server: testing stub value\r\n
43 s> Date: $HTTP_DATE$\r\n
44 s> Content-Type: text/plain\r\n
45 s> Content-Length: 44\r\n
46 s> \r\n
47 s> Experimental API server endpoint not enabled
48
49 Restart server with support for API server
50
51 $ killdaemons.py
52 $ cat > server/.hg/hgrc << EOF
53 > [experimental]
54 > web.apiserver = true
55 > EOF
56
57 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
58 $ cat hg.pid > $DAEMON_PIDS
59
60 /api lists available APIs (empty since none are available by default)
61
62 $ send << EOF
63 > httprequest GET api
64 > user-agent: test
65 > EOF
66 using raw connection to peer
67 s> GET /api HTTP/1.1\r\n
68 s> Accept-Encoding: identity\r\n
69 s> user-agent: test\r\n
70 s> host: $LOCALIP:$HGPORT\r\n (glob)
71 s> \r\n
72 s> makefile('rb', None)
73 s> HTTP/1.1 200 OK\r\n
74 s> Server: testing stub value\r\n
75 s> Date: $HTTP_DATE$\r\n
76 s> Content-Type: text/plain\r\n
77 s> Content-Length: 100\r\n
78 s> \r\n
79 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
80 s> \n
81 s> (no available APIs)\n
82
83 $ send << EOF
84 > httprequest GET api/
85 > user-agent: test
86 > EOF
87 using raw connection to peer
88 s> GET /api/ HTTP/1.1\r\n
89 s> Accept-Encoding: identity\r\n
90 s> user-agent: test\r\n
91 s> host: $LOCALIP:$HGPORT\r\n (glob)
92 s> \r\n
93 s> makefile('rb', None)
94 s> HTTP/1.1 200 OK\r\n
95 s> Server: testing stub value\r\n
96 s> Date: $HTTP_DATE$\r\n
97 s> Content-Type: text/plain\r\n
98 s> Content-Length: 100\r\n
99 s> \r\n
100 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
101 s> \n
102 s> (no available APIs)\n
103
104 Accessing an unknown API yields a 404
105
106 $ send << EOF
107 > httprequest GET api/unknown
108 > user-agent: test
109 > EOF
110 using raw connection to peer
111 s> GET /api/unknown HTTP/1.1\r\n
112 s> Accept-Encoding: identity\r\n
113 s> user-agent: test\r\n
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 s> \r\n
116 s> makefile('rb', None)
117 s> HTTP/1.1 404 Not Found\r\n
118 s> Server: testing stub value\r\n
119 s> Date: $HTTP_DATE$\r\n
120 s> Content-Type: text/plain\r\n
121 s> Content-Length: 33\r\n
122 s> \r\n
123 s> Unknown API: unknown\n
124 s> Known APIs:
125
126 Accessing a known but not enabled API yields a different error
127
128 $ send << EOF
129 > httprequest GET api/exp-http-v2-0001
130 > user-agent: test
131 > EOF
132 using raw connection to peer
133 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
134 s> Accept-Encoding: identity\r\n
135 s> user-agent: test\r\n
136 s> host: $LOCALIP:$HGPORT\r\n (glob)
137 s> \r\n
138 s> makefile('rb', None)
139 s> HTTP/1.1 404 Not Found\r\n
140 s> Server: testing stub value\r\n
141 s> Date: $HTTP_DATE$\r\n
142 s> Content-Type: text/plain\r\n
143 s> Content-Length: 33\r\n
144 s> \r\n
145 s> API exp-http-v2-0001 not enabled\n
146
147 Restart server with support for HTTP v2 API
148
149 $ killdaemons.py
150 $ cat > server/.hg/hgrc << EOF
151 > [experimental]
152 > web.apiserver = true
153 > web.api.http-v2 = true
154 > EOF
155
156 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
157 $ cat hg.pid > $DAEMON_PIDS
158
159 /api lists the HTTP v2 protocol as available
160
161 $ send << EOF
162 > httprequest GET api
163 > user-agent: test
164 > EOF
165 using raw connection to peer
166 s> GET /api HTTP/1.1\r\n
167 s> Accept-Encoding: identity\r\n
168 s> user-agent: test\r\n
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 s> \r\n
171 s> makefile('rb', None)
172 s> HTTP/1.1 200 OK\r\n
173 s> Server: testing stub value\r\n
174 s> Date: $HTTP_DATE$\r\n
175 s> Content-Type: text/plain\r\n
176 s> Content-Length: 96\r\n
177 s> \r\n
178 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
179 s> \n
180 s> exp-http-v2-0001
181
182 $ send << EOF
183 > httprequest GET api/
184 > user-agent: test
185 > EOF
186 using raw connection to peer
187 s> GET /api/ HTTP/1.1\r\n
188 s> Accept-Encoding: identity\r\n
189 s> user-agent: test\r\n
190 s> host: $LOCALIP:$HGPORT\r\n (glob)
191 s> \r\n
192 s> makefile('rb', None)
193 s> HTTP/1.1 200 OK\r\n
194 s> Server: testing stub value\r\n
195 s> Date: $HTTP_DATE$\r\n
196 s> Content-Type: text/plain\r\n
197 s> Content-Length: 96\r\n
198 s> \r\n
199 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
200 s> \n
201 s> exp-http-v2-0001
@@ -580,6 +580,12 b" coreconfigitem('experimental', 'update.a"
580 coreconfigitem('experimental', 'sshpeer.advertise-v2',
580 coreconfigitem('experimental', 'sshpeer.advertise-v2',
581 default=False,
581 default=False,
582 )
582 )
583 coreconfigitem('experimental', 'web.apiserver',
584 default=False,
585 )
586 coreconfigitem('experimental', 'web.api.http-v2',
587 default=False,
588 )
583 coreconfigitem('experimental', 'xdiff',
589 coreconfigitem('experimental', 'xdiff',
584 default=False,
590 default=False,
585 )
591 )
@@ -320,6 +320,13 b' class hgweb(object):'
320 # replace it.
320 # replace it.
321 res.headers['Content-Security-Policy'] = rctx.csp
321 res.headers['Content-Security-Policy'] = rctx.csp
322
322
323 # /api/* is reserved for various API implementations. Dispatch
324 # accordingly.
325 if req.dispatchparts and req.dispatchparts[0] == b'api':
326 wireprotoserver.handlewsgiapirequest(rctx, req, res,
327 self.check_perm)
328 return res.sendresponse()
329
323 handled = wireprotoserver.handlewsgirequest(
330 handled = wireprotoserver.handlewsgirequest(
324 rctx, req, res, self.check_perm)
331 rctx, req, res, self.check_perm)
325 if handled:
332 if handled:
@@ -33,6 +33,7 b" 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
36 HTTPV2 = wireprototypes.HTTPV2
36 SSHV1 = wireprototypes.SSHV1
37 SSHV1 = wireprototypes.SSHV1
37 SSHV2 = wireprototypes.SSHV2
38 SSHV2 = wireprototypes.SSHV2
38
39
@@ -214,6 +215,75 b' def handlewsgirequest(rctx, req, res, ch'
214
215
215 return True
216 return True
216
217
218 def handlewsgiapirequest(rctx, req, res, checkperm):
219 """Handle requests to /api/*."""
220 assert req.dispatchparts[0] == b'api'
221
222 repo = rctx.repo
223
224 # This whole URL space is experimental for now. But we want to
225 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
226 if not repo.ui.configbool('experimental', 'web.apiserver'):
227 res.status = b'404 Not Found'
228 res.headers[b'Content-Type'] = b'text/plain'
229 res.setbodybytes(_('Experimental API server endpoint not enabled'))
230 return
231
232 # The URL space is /api/<protocol>/*. The structure of URLs under varies
233 # by <protocol>.
234
235 # Registered APIs are made available via config options of the name of
236 # the protocol.
237 availableapis = set()
238 for k, v in API_HANDLERS.items():
239 section, option = v['config']
240 if repo.ui.configbool(section, option):
241 availableapis.add(k)
242
243 # Requests to /api/ list available APIs.
244 if req.dispatchparts == [b'api']:
245 res.status = b'200 OK'
246 res.headers[b'Content-Type'] = b'text/plain'
247 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
248 'one of the following:\n')]
249 if availableapis:
250 lines.extend(sorted(availableapis))
251 else:
252 lines.append(_('(no available APIs)\n'))
253 res.setbodybytes(b'\n'.join(lines))
254 return
255
256 proto = req.dispatchparts[1]
257
258 if proto not in API_HANDLERS:
259 res.status = b'404 Not Found'
260 res.headers[b'Content-Type'] = b'text/plain'
261 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
262 proto, b', '.join(sorted(availableapis))))
263 return
264
265 if proto not in availableapis:
266 res.status = b'404 Not Found'
267 res.headers[b'Content-Type'] = b'text/plain'
268 res.setbodybytes(_('API %s not enabled\n') % proto)
269 return
270
271 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
272 req.dispatchparts[2:])
273
274 def _handlehttpv2request(rctx, req, res, checkperm, urlparts):
275 res.status = b'200 OK'
276 res.headers[b'Content-Type'] = b'text/plain'
277 res.setbodybytes(b'/'.join(urlparts) + b'\n')
278
279 # Maps API name to metadata so custom API can be registered.
280 API_HANDLERS = {
281 HTTPV2: {
282 'config': ('experimental', 'web.api.http-v2'),
283 'handler': _handlehttpv2request,
284 },
285 }
286
217 def _httpresponsetype(ui, req, prefer_uncompressed):
287 def _httpresponsetype(ui, req, prefer_uncompressed):
218 """Determine the appropriate response type and compression settings.
288 """Determine the appropriate response type and compression settings.
219
289
@@ -9,9 +9,10 b' import abc'
9
9
10 # Names of the SSH protocol implementations.
10 # Names of the SSH protocol implementations.
11 SSHV1 = 'ssh-v1'
11 SSHV1 = 'ssh-v1'
12 # This is advertised over the wire. Incremental the counter at the end
12 # These are advertised over the wire. Increment the counters at the end
13 # to reflect BC breakages.
13 # to reflect BC breakages.
14 SSHV2 = 'exp-ssh-v2-0001'
14 SSHV2 = 'exp-ssh-v2-0001'
15 HTTPV2 = 'exp-http-v2-0001'
15
16
16 # All available wire protocol transports.
17 # All available wire protocol transports.
17 TRANSPORTS = {
18 TRANSPORTS = {
@@ -26,6 +27,10 b' TRANSPORTS = {'
26 'http-v1': {
27 'http-v1': {
27 'transport': 'http',
28 'transport': 'http',
28 'version': 1,
29 'version': 1,
30 },
31 HTTPV2: {
32 'transport': 'http',
33 'version': 2,
29 }
34 }
30 }
35 }
31
36
General Comments 0
You need to be logged in to leave comments. Login now