comparison tests/test-http-api.t @ 37046:1cfef5693203

wireproto: support /api/* URL space for exposing APIs I will soon be introducing a new version of the HTTP wire protocol. One of the things I want to change with it is the URL routing. I want to rely on URL paths to define endpoints rather than the "cmd" query string argument. That should be pretty straightforward. I was thinking about what URL space to reserve for the new protocol. We /could/ put everything at a top-level path. e.g. /wireproto/* or /http-v2-wireproto/*. However, these constrain us a bit because they assume there will only be 1 API: version 2 of the HTTP wire protocol. I think there is room to grow multiple APIs. For example, there may someday be a proper JSON API to query or even manipulate the repository. And I don't think we should have to create a new top-level URL space for each API nor should we attempt to shoehorn each future API into the same shared URL space: that would just be too chaotic. This commits reserves the /api/* URL space for all our future API needs. Essentially, all requests to /api/* get routed to a new WSGI handler. By default, it 404's the entire URL space unless the "api server" feature is enabled. When enabled, requests to "/api" list available APIs. URLs of the form /api/<name>/* are reserved for a particular named API. Behavior within each API is left up to that API. So, we can grow new APIs easily without worrying about URL space conflicts. APIs can be registered by adding entries to a global dict. This allows extensions to provide their own APIs should they choose to do so. This is probably a premature feature. But IMO the code is easier to read if we're not dealing with API-specific behavior like config option querying inline. To prove it works, we implement a very basic API for version 2 of the HTTP wire protocol. It does nothing of value except facilitate testing of the /api/* URL space. We currently emit plain text responses for all /api/* endpoints. There's definitely room to look at Accept and other request headers to vary the response format. But we have to start somewhere. Differential Revision: https://phab.mercurial-scm.org/D2834
author Gregory Szorc <gregory.szorc@gmail.com>
date Tue, 13 Mar 2018 16:53:21 -0700
parents
children db114320df7e
comparison
equal deleted inserted replaced
37045:a708e1e4d7a8 37046:1cfef5693203
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