##// END OF EJS Templates
hgweb: don't responsd to api requests unless feature is enabled...
Gregory Szorc -
r37111:db114320 default
parent child Browse files
Show More
@@ -1,461 +1,464
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import contextlib
12 12 import os
13 13
14 14 from .common import (
15 15 ErrorResponse,
16 16 HTTP_BAD_REQUEST,
17 17 cspvalues,
18 18 permhooks,
19 19 statusmessage,
20 20 )
21 21
22 22 from .. import (
23 23 encoding,
24 24 error,
25 25 formatter,
26 26 hg,
27 27 hook,
28 28 profiling,
29 29 pycompat,
30 30 repoview,
31 31 templatefilters,
32 32 templater,
33 33 ui as uimod,
34 34 util,
35 35 wireprotoserver,
36 36 )
37 37
38 38 from . import (
39 39 request as requestmod,
40 40 webcommands,
41 41 webutil,
42 42 wsgicgi,
43 43 )
44 44
45 45 archivespecs = util.sortdict((
46 46 ('zip', ('application/zip', 'zip', '.zip', None)),
47 47 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
48 48 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
49 49 ))
50 50
51 51 def getstyle(req, configfn, templatepath):
52 52 styles = (
53 53 req.qsparams.get('style', None),
54 54 configfn('web', 'style'),
55 55 'paper',
56 56 )
57 57 return styles, templater.stylemap(styles, templatepath)
58 58
59 59 def makebreadcrumb(url, prefix=''):
60 60 '''Return a 'URL breadcrumb' list
61 61
62 62 A 'URL breadcrumb' is a list of URL-name pairs,
63 63 corresponding to each of the path items on a URL.
64 64 This can be used to create path navigation entries.
65 65 '''
66 66 if url.endswith('/'):
67 67 url = url[:-1]
68 68 if prefix:
69 69 url = '/' + prefix + url
70 70 relpath = url
71 71 if relpath.startswith('/'):
72 72 relpath = relpath[1:]
73 73
74 74 breadcrumb = []
75 75 urlel = url
76 76 pathitems = [''] + relpath.split('/')
77 77 for pathel in reversed(pathitems):
78 78 if not pathel or not urlel:
79 79 break
80 80 breadcrumb.append({'url': urlel, 'name': pathel})
81 81 urlel = os.path.dirname(urlel)
82 82 return reversed(breadcrumb)
83 83
84 84 class requestcontext(object):
85 85 """Holds state/context for an individual request.
86 86
87 87 Servers can be multi-threaded. Holding state on the WSGI application
88 88 is prone to race conditions. Instances of this class exist to hold
89 89 mutable and race-free state for requests.
90 90 """
91 91 def __init__(self, app, repo, req, res):
92 92 self.repo = repo
93 93 self.reponame = app.reponame
94 94 self.req = req
95 95 self.res = res
96 96
97 97 self.archivespecs = archivespecs
98 98
99 99 self.maxchanges = self.configint('web', 'maxchanges')
100 100 self.stripecount = self.configint('web', 'stripes')
101 101 self.maxshortchanges = self.configint('web', 'maxshortchanges')
102 102 self.maxfiles = self.configint('web', 'maxfiles')
103 103 self.allowpull = self.configbool('web', 'allow-pull')
104 104
105 105 # we use untrusted=False to prevent a repo owner from using
106 106 # web.templates in .hg/hgrc to get access to any file readable
107 107 # by the user running the CGI script
108 108 self.templatepath = self.config('web', 'templates', untrusted=False)
109 109
110 110 # This object is more expensive to build than simple config values.
111 111 # It is shared across requests. The app will replace the object
112 112 # if it is updated. Since this is a reference and nothing should
113 113 # modify the underlying object, it should be constant for the lifetime
114 114 # of the request.
115 115 self.websubtable = app.websubtable
116 116
117 117 self.csp, self.nonce = cspvalues(self.repo.ui)
118 118
119 119 # Trust the settings from the .hg/hgrc files by default.
120 120 def config(self, section, name, default=uimod._unset, untrusted=True):
121 121 return self.repo.ui.config(section, name, default,
122 122 untrusted=untrusted)
123 123
124 124 def configbool(self, section, name, default=uimod._unset, untrusted=True):
125 125 return self.repo.ui.configbool(section, name, default,
126 126 untrusted=untrusted)
127 127
128 128 def configint(self, section, name, default=uimod._unset, untrusted=True):
129 129 return self.repo.ui.configint(section, name, default,
130 130 untrusted=untrusted)
131 131
132 132 def configlist(self, section, name, default=uimod._unset, untrusted=True):
133 133 return self.repo.ui.configlist(section, name, default,
134 134 untrusted=untrusted)
135 135
136 136 def archivelist(self, nodeid):
137 137 allowed = self.configlist('web', 'allow_archive')
138 138 for typ, spec in self.archivespecs.iteritems():
139 139 if typ in allowed or self.configbool('web', 'allow%s' % typ):
140 140 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
141 141
142 142 def templater(self, req):
143 143 # determine scheme, port and server name
144 144 # this is needed to create absolute urls
145 145 logourl = self.config('web', 'logourl')
146 146 logoimg = self.config('web', 'logoimg')
147 147 staticurl = (self.config('web', 'staticurl')
148 148 or req.apppath + '/static/')
149 149 if not staticurl.endswith('/'):
150 150 staticurl += '/'
151 151
152 152 # some functions for the templater
153 153
154 154 def motd(**map):
155 155 yield self.config('web', 'motd')
156 156
157 157 # figure out which style to use
158 158
159 159 vars = {}
160 160 styles, (style, mapfile) = getstyle(req, self.config,
161 161 self.templatepath)
162 162 if style == styles[0]:
163 163 vars['style'] = style
164 164
165 165 sessionvars = webutil.sessionvars(vars, '?')
166 166
167 167 if not self.reponame:
168 168 self.reponame = (self.config('web', 'name', '')
169 169 or req.reponame
170 170 or req.apppath
171 171 or self.repo.root)
172 172
173 173 def websubfilter(text):
174 174 return templatefilters.websub(text, self.websubtable)
175 175
176 176 # create the templater
177 177 # TODO: export all keywords: defaults = templatekw.keywords.copy()
178 178 defaults = {
179 179 'url': req.apppath + '/',
180 180 'logourl': logourl,
181 181 'logoimg': logoimg,
182 182 'staticurl': staticurl,
183 183 'urlbase': req.advertisedbaseurl,
184 184 'repo': self.reponame,
185 185 'encoding': encoding.encoding,
186 186 'motd': motd,
187 187 'sessionvars': sessionvars,
188 188 'pathdef': makebreadcrumb(req.apppath),
189 189 'style': style,
190 190 'nonce': self.nonce,
191 191 }
192 192 tres = formatter.templateresources(self.repo.ui, self.repo)
193 193 tmpl = templater.templater.frommapfile(mapfile,
194 194 filters={'websub': websubfilter},
195 195 defaults=defaults,
196 196 resources=tres)
197 197 return tmpl
198 198
199 199 def sendtemplate(self, name, **kwargs):
200 200 """Helper function to send a response generated from a template."""
201 201 kwargs = pycompat.byteskwargs(kwargs)
202 202 self.res.setbodygen(self.tmpl.generate(name, kwargs))
203 203 return self.res.sendresponse()
204 204
205 205 class hgweb(object):
206 206 """HTTP server for individual repositories.
207 207
208 208 Instances of this class serve HTTP responses for a particular
209 209 repository.
210 210
211 211 Instances are typically used as WSGI applications.
212 212
213 213 Some servers are multi-threaded. On these servers, there may
214 214 be multiple active threads inside __call__.
215 215 """
216 216 def __init__(self, repo, name=None, baseui=None):
217 217 if isinstance(repo, str):
218 218 if baseui:
219 219 u = baseui.copy()
220 220 else:
221 221 u = uimod.ui.load()
222 222 r = hg.repository(u, repo)
223 223 else:
224 224 # we trust caller to give us a private copy
225 225 r = repo
226 226
227 227 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
228 228 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
229 229 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
230 230 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
231 231 # resolve file patterns relative to repo root
232 232 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
233 233 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
234 234 # displaying bundling progress bar while serving feel wrong and may
235 235 # break some wsgi implementation.
236 236 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
237 237 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
238 238 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
239 239 self._lastrepo = self._repos[0]
240 240 hook.redirect(True)
241 241 self.reponame = name
242 242
243 243 def _webifyrepo(self, repo):
244 244 repo = getwebview(repo)
245 245 self.websubtable = webutil.getwebsubs(repo)
246 246 return repo
247 247
248 248 @contextlib.contextmanager
249 249 def _obtainrepo(self):
250 250 """Obtain a repo unique to the caller.
251 251
252 252 Internally we maintain a stack of cachedlocalrepo instances
253 253 to be handed out. If one is available, we pop it and return it,
254 254 ensuring it is up to date in the process. If one is not available,
255 255 we clone the most recently used repo instance and return it.
256 256
257 257 It is currently possible for the stack to grow without bounds
258 258 if the server allows infinite threads. However, servers should
259 259 have a thread limit, thus establishing our limit.
260 260 """
261 261 if self._repos:
262 262 cached = self._repos.pop()
263 263 r, created = cached.fetch()
264 264 else:
265 265 cached = self._lastrepo.copy()
266 266 r, created = cached.fetch()
267 267 if created:
268 268 r = self._webifyrepo(r)
269 269
270 270 self._lastrepo = cached
271 271 self.mtime = cached.mtime
272 272 try:
273 273 yield r
274 274 finally:
275 275 self._repos.append(cached)
276 276
277 277 def run(self):
278 278 """Start a server from CGI environment.
279 279
280 280 Modern servers should be using WSGI and should avoid this
281 281 method, if possible.
282 282 """
283 283 if not encoding.environ.get('GATEWAY_INTERFACE',
284 284 '').startswith("CGI/1."):
285 285 raise RuntimeError("This function is only intended to be "
286 286 "called while running as a CGI script.")
287 287 wsgicgi.launch(self)
288 288
289 289 def __call__(self, env, respond):
290 290 """Run the WSGI application.
291 291
292 292 This may be called by multiple threads.
293 293 """
294 294 req = requestmod.parserequestfromenv(env)
295 295 res = requestmod.wsgiresponse(req, respond)
296 296
297 297 return self.run_wsgi(req, res)
298 298
299 299 def run_wsgi(self, req, res):
300 300 """Internal method to run the WSGI application.
301 301
302 302 This is typically only called by Mercurial. External consumers
303 303 should be using instances of this class as the WSGI application.
304 304 """
305 305 with self._obtainrepo() as repo:
306 306 profile = repo.ui.configbool('profiling', 'enabled')
307 307 with profiling.profile(repo.ui, enabled=profile):
308 308 for r in self._runwsgi(req, res, repo):
309 309 yield r
310 310
311 311 def _runwsgi(self, req, res, repo):
312 312 rctx = requestcontext(self, repo, req, res)
313 313
314 314 # This state is global across all threads.
315 315 encoding.encoding = rctx.config('web', 'encoding')
316 316 rctx.repo.ui.environ = req.rawenv
317 317
318 318 if rctx.csp:
319 319 # hgwebdir may have added CSP header. Since we generate our own,
320 320 # replace it.
321 321 res.headers['Content-Security-Policy'] = rctx.csp
322 322
323 323 # /api/* is reserved for various API implementations. Dispatch
324 # accordingly.
325 if req.dispatchparts and req.dispatchparts[0] == b'api':
324 # accordingly. But URL paths can conflict with subrepos and virtual
325 # repos in hgwebdir. So until we have a workaround for this, only
326 # expose the URLs if the feature is enabled.
327 apienabled = rctx.repo.ui.configbool('experimental', 'web.apiserver')
328 if apienabled and req.dispatchparts and req.dispatchparts[0] == b'api':
326 329 wireprotoserver.handlewsgiapirequest(rctx, req, res,
327 330 self.check_perm)
328 331 return res.sendresponse()
329 332
330 333 handled = wireprotoserver.handlewsgirequest(
331 334 rctx, req, res, self.check_perm)
332 335 if handled:
333 336 return res.sendresponse()
334 337
335 338 # Old implementations of hgweb supported dispatching the request via
336 339 # the initial query string parameter instead of using PATH_INFO.
337 340 # If PATH_INFO is present (signaled by ``req.dispatchpath`` having
338 341 # a value), we use it. Otherwise fall back to the query string.
339 342 if req.dispatchpath is not None:
340 343 query = req.dispatchpath
341 344 else:
342 345 query = req.querystring.partition('&')[0].partition(';')[0]
343 346
344 347 # translate user-visible url structure to internal structure
345 348
346 349 args = query.split('/', 2)
347 350 if 'cmd' not in req.qsparams and args and args[0]:
348 351 cmd = args.pop(0)
349 352 style = cmd.rfind('-')
350 353 if style != -1:
351 354 req.qsparams['style'] = cmd[:style]
352 355 cmd = cmd[style + 1:]
353 356
354 357 # avoid accepting e.g. style parameter as command
355 358 if util.safehasattr(webcommands, cmd):
356 359 req.qsparams['cmd'] = cmd
357 360
358 361 if cmd == 'static':
359 362 req.qsparams['file'] = '/'.join(args)
360 363 else:
361 364 if args and args[0]:
362 365 node = args.pop(0).replace('%2F', '/')
363 366 req.qsparams['node'] = node
364 367 if args:
365 368 if 'file' in req.qsparams:
366 369 del req.qsparams['file']
367 370 for a in args:
368 371 req.qsparams.add('file', a)
369 372
370 373 ua = req.headers.get('User-Agent', '')
371 374 if cmd == 'rev' and 'mercurial' in ua:
372 375 req.qsparams['style'] = 'raw'
373 376
374 377 if cmd == 'archive':
375 378 fn = req.qsparams['node']
376 379 for type_, spec in rctx.archivespecs.iteritems():
377 380 ext = spec[2]
378 381 if fn.endswith(ext):
379 382 req.qsparams['node'] = fn[:-len(ext)]
380 383 req.qsparams['type'] = type_
381 384 else:
382 385 cmd = req.qsparams.get('cmd', '')
383 386
384 387 # process the web interface request
385 388
386 389 try:
387 390 rctx.tmpl = rctx.templater(req)
388 391 ctype = rctx.tmpl.render('mimetype',
389 392 {'encoding': encoding.encoding})
390 393
391 394 # check read permissions non-static content
392 395 if cmd != 'static':
393 396 self.check_perm(rctx, req, None)
394 397
395 398 if cmd == '':
396 399 req.qsparams['cmd'] = rctx.tmpl.render('default', {})
397 400 cmd = req.qsparams['cmd']
398 401
399 402 # Don't enable caching if using a CSP nonce because then it wouldn't
400 403 # be a nonce.
401 404 if rctx.configbool('web', 'cache') and not rctx.nonce:
402 405 tag = 'W/"%d"' % self.mtime
403 406 if req.headers.get('If-None-Match') == tag:
404 407 res.status = '304 Not Modified'
405 408 # Response body not allowed on 304.
406 409 res.setbodybytes('')
407 410 return res.sendresponse()
408 411
409 412 res.headers['ETag'] = tag
410 413
411 414 if cmd not in webcommands.__all__:
412 415 msg = 'no such method: %s' % cmd
413 416 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
414 417 else:
415 418 # Set some globals appropriate for web handlers. Commands can
416 419 # override easily enough.
417 420 res.status = '200 Script output follows'
418 421 res.headers['Content-Type'] = ctype
419 422 return getattr(webcommands, cmd)(rctx)
420 423
421 424 except (error.LookupError, error.RepoLookupError) as err:
422 425 msg = pycompat.bytestr(err)
423 426 if (util.safehasattr(err, 'name') and
424 427 not isinstance(err, error.ManifestLookupError)):
425 428 msg = 'revision not found: %s' % err.name
426 429
427 430 res.status = '404 Not Found'
428 431 res.headers['Content-Type'] = ctype
429 432 return rctx.sendtemplate('error', error=msg)
430 433 except (error.RepoError, error.RevlogError) as e:
431 434 res.status = '500 Internal Server Error'
432 435 res.headers['Content-Type'] = ctype
433 436 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
434 437 except ErrorResponse as e:
435 438 res.status = statusmessage(e.code, pycompat.bytestr(e))
436 439 res.headers['Content-Type'] = ctype
437 440 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
438 441
439 442 def check_perm(self, rctx, req, op):
440 443 for permhook in permhooks:
441 444 permhook(rctx, req, op)
442 445
443 446 def getwebview(repo):
444 447 """The 'web.view' config controls changeset filter to hgweb. Possible
445 448 values are ``served``, ``visible`` and ``all``. Default is ``served``.
446 449 The ``served`` filter only shows changesets that can be pulled from the
447 450 hgweb instance. The``visible`` filter includes secret changesets but
448 451 still excludes "hidden" one.
449 452
450 453 See the repoview module for details.
451 454
452 455 The option has been around undocumented since Mercurial 2.5, but no
453 456 user ever asked about it. So we better keep it undocumented for now."""
454 457 # experimental config: web.view
455 458 viewconfig = repo.ui.config('web', 'view', untrusted=True)
456 459 if viewconfig == 'all':
457 460 return repo.unfiltered()
458 461 elif viewconfig in repoview.filtertable:
459 462 return repo.filtered(viewconfig)
460 463 else:
461 464 return repo.filtered('served')
@@ -1,201 +1,291
1 1 $ send() {
2 2 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
3 3 > }
4 4
5 5 $ hg init server
6 6 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
7 7 $ cat hg.pid > $DAEMON_PIDS
8 8
9 9 Request to /api fails unless web.apiserver is enabled
10 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
11 $ get-with-headers.py $LOCALIP:$HGPORT api
12 400 no such method: api
13
14 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
15 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
16 <head>
17 <link rel="icon" href="/static/hgicon.png" type="image/png" />
18 <meta name="robots" content="index, nofollow" />
19 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
20 <script type="text/javascript" src="/static/mercurial.js"></script>
21
22 <title>$TESTTMP/server: error</title>
23 </head>
24 <body>
25
26 <div class="container">
27 <div class="menu">
28 <div class="logo">
29 <a href="https://mercurial-scm.org/">
30 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
31 </div>
32 <ul>
33 <li><a href="/shortlog">log</a></li>
34 <li><a href="/graph">graph</a></li>
35 <li><a href="/tags">tags</a></li>
36 <li><a href="/bookmarks">bookmarks</a></li>
37 <li><a href="/branches">branches</a></li>
38 </ul>
39 <ul>
40 <li><a href="/help">help</a></li>
41 </ul>
42 </div>
43
44 <div class="main">
45
46 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
47 <h3>error</h3>
48
49
50 <form class="search" action="/log">
51
52 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
53 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
54 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
55 </form>
56
57 <div class="description">
58 <p>
59 An error occurred while processing your request:
60 </p>
61 <p>
62 no such method: api
63 </p>
64 </div>
65 </div>
66 </div>
67
68
69
70 </body>
71 </html>
72
73 [1]
29 74
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
75 $ get-with-headers.py $LOCALIP:$HGPORT api/
76 400 no such method: api
77
78 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
79 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
80 <head>
81 <link rel="icon" href="/static/hgicon.png" type="image/png" />
82 <meta name="robots" content="index, nofollow" />
83 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
84 <script type="text/javascript" src="/static/mercurial.js"></script>
85
86 <title>$TESTTMP/server: error</title>
87 </head>
88 <body>
89
90 <div class="container">
91 <div class="menu">
92 <div class="logo">
93 <a href="https://mercurial-scm.org/">
94 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
95 </div>
96 <ul>
97 <li><a href="/shortlog">log</a></li>
98 <li><a href="/graph">graph</a></li>
99 <li><a href="/tags">tags</a></li>
100 <li><a href="/bookmarks">bookmarks</a></li>
101 <li><a href="/branches">branches</a></li>
102 </ul>
103 <ul>
104 <li><a href="/help">help</a></li>
105 </ul>
106 </div>
107
108 <div class="main">
109
110 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
111 <h3>error</h3>
112
113
114 <form class="search" action="/log">
115
116 <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
117 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
118 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
119 </form>
120
121 <div class="description">
122 <p>
123 An error occurred while processing your request:
124 </p>
125 <p>
126 no such method: api
127 </p>
128 </div>
129 </div>
130 </div>
131
132
133
134 </body>
135 </html>
136
137 [1]
48 138
49 139 Restart server with support for API server
50 140
51 141 $ killdaemons.py
52 142 $ cat > server/.hg/hgrc << EOF
53 143 > [experimental]
54 144 > web.apiserver = true
55 145 > EOF
56 146
57 147 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
58 148 $ cat hg.pid > $DAEMON_PIDS
59 149
60 150 /api lists available APIs (empty since none are available by default)
61 151
62 152 $ send << EOF
63 153 > httprequest GET api
64 154 > user-agent: test
65 155 > EOF
66 156 using raw connection to peer
67 157 s> GET /api HTTP/1.1\r\n
68 158 s> Accept-Encoding: identity\r\n
69 159 s> user-agent: test\r\n
70 160 s> host: $LOCALIP:$HGPORT\r\n (glob)
71 161 s> \r\n
72 162 s> makefile('rb', None)
73 163 s> HTTP/1.1 200 OK\r\n
74 164 s> Server: testing stub value\r\n
75 165 s> Date: $HTTP_DATE$\r\n
76 166 s> Content-Type: text/plain\r\n
77 167 s> Content-Length: 100\r\n
78 168 s> \r\n
79 169 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
80 170 s> \n
81 171 s> (no available APIs)\n
82 172
83 173 $ send << EOF
84 174 > httprequest GET api/
85 175 > user-agent: test
86 176 > EOF
87 177 using raw connection to peer
88 178 s> GET /api/ HTTP/1.1\r\n
89 179 s> Accept-Encoding: identity\r\n
90 180 s> user-agent: test\r\n
91 181 s> host: $LOCALIP:$HGPORT\r\n (glob)
92 182 s> \r\n
93 183 s> makefile('rb', None)
94 184 s> HTTP/1.1 200 OK\r\n
95 185 s> Server: testing stub value\r\n
96 186 s> Date: $HTTP_DATE$\r\n
97 187 s> Content-Type: text/plain\r\n
98 188 s> Content-Length: 100\r\n
99 189 s> \r\n
100 190 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
101 191 s> \n
102 192 s> (no available APIs)\n
103 193
104 194 Accessing an unknown API yields a 404
105 195
106 196 $ send << EOF
107 197 > httprequest GET api/unknown
108 198 > user-agent: test
109 199 > EOF
110 200 using raw connection to peer
111 201 s> GET /api/unknown HTTP/1.1\r\n
112 202 s> Accept-Encoding: identity\r\n
113 203 s> user-agent: test\r\n
114 204 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 205 s> \r\n
116 206 s> makefile('rb', None)
117 207 s> HTTP/1.1 404 Not Found\r\n
118 208 s> Server: testing stub value\r\n
119 209 s> Date: $HTTP_DATE$\r\n
120 210 s> Content-Type: text/plain\r\n
121 211 s> Content-Length: 33\r\n
122 212 s> \r\n
123 213 s> Unknown API: unknown\n
124 214 s> Known APIs:
125 215
126 216 Accessing a known but not enabled API yields a different error
127 217
128 218 $ send << EOF
129 219 > httprequest GET api/exp-http-v2-0001
130 220 > user-agent: test
131 221 > EOF
132 222 using raw connection to peer
133 223 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
134 224 s> Accept-Encoding: identity\r\n
135 225 s> user-agent: test\r\n
136 226 s> host: $LOCALIP:$HGPORT\r\n (glob)
137 227 s> \r\n
138 228 s> makefile('rb', None)
139 229 s> HTTP/1.1 404 Not Found\r\n
140 230 s> Server: testing stub value\r\n
141 231 s> Date: $HTTP_DATE$\r\n
142 232 s> Content-Type: text/plain\r\n
143 233 s> Content-Length: 33\r\n
144 234 s> \r\n
145 235 s> API exp-http-v2-0001 not enabled\n
146 236
147 237 Restart server with support for HTTP v2 API
148 238
149 239 $ killdaemons.py
150 240 $ cat > server/.hg/hgrc << EOF
151 241 > [experimental]
152 242 > web.apiserver = true
153 243 > web.api.http-v2 = true
154 244 > EOF
155 245
156 246 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
157 247 $ cat hg.pid > $DAEMON_PIDS
158 248
159 249 /api lists the HTTP v2 protocol as available
160 250
161 251 $ send << EOF
162 252 > httprequest GET api
163 253 > user-agent: test
164 254 > EOF
165 255 using raw connection to peer
166 256 s> GET /api HTTP/1.1\r\n
167 257 s> Accept-Encoding: identity\r\n
168 258 s> user-agent: test\r\n
169 259 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 260 s> \r\n
171 261 s> makefile('rb', None)
172 262 s> HTTP/1.1 200 OK\r\n
173 263 s> Server: testing stub value\r\n
174 264 s> Date: $HTTP_DATE$\r\n
175 265 s> Content-Type: text/plain\r\n
176 266 s> Content-Length: 96\r\n
177 267 s> \r\n
178 268 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
179 269 s> \n
180 270 s> exp-http-v2-0001
181 271
182 272 $ send << EOF
183 273 > httprequest GET api/
184 274 > user-agent: test
185 275 > EOF
186 276 using raw connection to peer
187 277 s> GET /api/ HTTP/1.1\r\n
188 278 s> Accept-Encoding: identity\r\n
189 279 s> user-agent: test\r\n
190 280 s> host: $LOCALIP:$HGPORT\r\n (glob)
191 281 s> \r\n
192 282 s> makefile('rb', None)
193 283 s> HTTP/1.1 200 OK\r\n
194 284 s> Server: testing stub value\r\n
195 285 s> Date: $HTTP_DATE$\r\n
196 286 s> Content-Type: text/plain\r\n
197 287 s> Content-Length: 96\r\n
198 288 s> \r\n
199 289 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
200 290 s> \n
201 291 s> exp-http-v2-0001
General Comments 0
You need to be logged in to leave comments. Login now