##// END OF EJS Templates
wireprotoserver: document and improve the httplib workaround...
Gregory Szorc -
r36005:6010fe1d default
parent child Browse files
Show More
@@ -1,421 +1,434 b''
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import abc
9 import abc
10 import cgi
10 import cgi
11 import struct
11 import struct
12 import sys
12 import sys
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 encoding,
16 encoding,
17 error,
17 error,
18 hook,
18 hook,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 wireproto,
21 wireproto,
22 )
22 )
23
23
24 stringio = util.stringio
24 stringio = util.stringio
25
25
26 urlerr = util.urlerr
26 urlerr = util.urlerr
27 urlreq = util.urlreq
27 urlreq = util.urlreq
28
28
29 HTTP_OK = 200
29 HTTP_OK = 200
30
30
31 HGTYPE = 'application/mercurial-0.1'
31 HGTYPE = 'application/mercurial-0.1'
32 HGTYPE2 = 'application/mercurial-0.2'
32 HGTYPE2 = 'application/mercurial-0.2'
33 HGERRTYPE = 'application/hg-error'
33 HGERRTYPE = 'application/hg-error'
34
34
35 # Names of the SSH protocol implementations.
35 # Names of the SSH protocol implementations.
36 SSHV1 = 'ssh-v1'
36 SSHV1 = 'ssh-v1'
37 # This is advertised over the wire. Incremental the counter at the end
37 # This is advertised over the wire. Incremental the counter at the end
38 # to reflect BC breakages.
38 # to reflect BC breakages.
39 SSHV2 = 'exp-ssh-v2-0001'
39 SSHV2 = 'exp-ssh-v2-0001'
40
40
41 class abstractserverproto(object):
41 class abstractserverproto(object):
42 """abstract class that summarizes the protocol API
42 """abstract class that summarizes the protocol API
43
43
44 Used as reference and documentation.
44 Used as reference and documentation.
45 """
45 """
46
46
47 __metaclass__ = abc.ABCMeta
47 __metaclass__ = abc.ABCMeta
48
48
49 @abc.abstractproperty
49 @abc.abstractproperty
50 def name(self):
50 def name(self):
51 """The name of the protocol implementation.
51 """The name of the protocol implementation.
52
52
53 Used for uniquely identifying the transport type.
53 Used for uniquely identifying the transport type.
54 """
54 """
55
55
56 @abc.abstractmethod
56 @abc.abstractmethod
57 def getargs(self, args):
57 def getargs(self, args):
58 """return the value for arguments in <args>
58 """return the value for arguments in <args>
59
59
60 returns a list of values (same order as <args>)"""
60 returns a list of values (same order as <args>)"""
61
61
62 @abc.abstractmethod
62 @abc.abstractmethod
63 def getfile(self, fp):
63 def getfile(self, fp):
64 """write the whole content of a file into a file like object
64 """write the whole content of a file into a file like object
65
65
66 The file is in the form::
66 The file is in the form::
67
67
68 (<chunk-size>\n<chunk>)+0\n
68 (<chunk-size>\n<chunk>)+0\n
69
69
70 chunk size is the ascii version of the int.
70 chunk size is the ascii version of the int.
71 """
71 """
72
72
73 @abc.abstractmethod
73 @abc.abstractmethod
74 def redirect(self):
74 def redirect(self):
75 """may setup interception for stdout and stderr
75 """may setup interception for stdout and stderr
76
76
77 See also the `restore` method."""
77 See also the `restore` method."""
78
78
79 # If the `redirect` function does install interception, the `restore`
79 # If the `redirect` function does install interception, the `restore`
80 # function MUST be defined. If interception is not used, this function
80 # function MUST be defined. If interception is not used, this function
81 # MUST NOT be defined.
81 # MUST NOT be defined.
82 #
82 #
83 # left commented here on purpose
83 # left commented here on purpose
84 #
84 #
85 #def restore(self):
85 #def restore(self):
86 # """reinstall previous stdout and stderr and return intercepted stdout
86 # """reinstall previous stdout and stderr and return intercepted stdout
87 # """
87 # """
88 # raise NotImplementedError()
88 # raise NotImplementedError()
89
89
90 def decodevaluefromheaders(req, headerprefix):
90 def decodevaluefromheaders(req, headerprefix):
91 """Decode a long value from multiple HTTP request headers.
91 """Decode a long value from multiple HTTP request headers.
92
92
93 Returns the value as a bytes, not a str.
93 Returns the value as a bytes, not a str.
94 """
94 """
95 chunks = []
95 chunks = []
96 i = 1
96 i = 1
97 prefix = headerprefix.upper().replace(r'-', r'_')
97 prefix = headerprefix.upper().replace(r'-', r'_')
98 while True:
98 while True:
99 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
99 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
100 if v is None:
100 if v is None:
101 break
101 break
102 chunks.append(pycompat.bytesurl(v))
102 chunks.append(pycompat.bytesurl(v))
103 i += 1
103 i += 1
104
104
105 return ''.join(chunks)
105 return ''.join(chunks)
106
106
107 class webproto(abstractserverproto):
107 class webproto(abstractserverproto):
108 def __init__(self, req, ui):
108 def __init__(self, req, ui):
109 self._req = req
109 self._req = req
110 self._ui = ui
110 self._ui = ui
111
111
112 @property
112 @property
113 def name(self):
113 def name(self):
114 return 'http'
114 return 'http'
115
115
116 def getargs(self, args):
116 def getargs(self, args):
117 knownargs = self._args()
117 knownargs = self._args()
118 data = {}
118 data = {}
119 keys = args.split()
119 keys = args.split()
120 for k in keys:
120 for k in keys:
121 if k == '*':
121 if k == '*':
122 star = {}
122 star = {}
123 for key in knownargs.keys():
123 for key in knownargs.keys():
124 if key != 'cmd' and key not in keys:
124 if key != 'cmd' and key not in keys:
125 star[key] = knownargs[key][0]
125 star[key] = knownargs[key][0]
126 data['*'] = star
126 data['*'] = star
127 else:
127 else:
128 data[k] = knownargs[k][0]
128 data[k] = knownargs[k][0]
129 return [data[k] for k in keys]
129 return [data[k] for k in keys]
130
130
131 def _args(self):
131 def _args(self):
132 args = util.rapply(pycompat.bytesurl, self._req.form.copy())
132 args = util.rapply(pycompat.bytesurl, self._req.form.copy())
133 postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
133 postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
134 if postlen:
134 if postlen:
135 args.update(cgi.parse_qs(
135 args.update(cgi.parse_qs(
136 self._req.read(postlen), keep_blank_values=True))
136 self._req.read(postlen), keep_blank_values=True))
137 return args
137 return args
138
138
139 argvalue = decodevaluefromheaders(self._req, r'X-HgArg')
139 argvalue = decodevaluefromheaders(self._req, r'X-HgArg')
140 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
140 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
141 return args
141 return args
142
142
143 def getfile(self, fp):
143 def getfile(self, fp):
144 length = int(self._req.env[r'CONTENT_LENGTH'])
144 length = int(self._req.env[r'CONTENT_LENGTH'])
145 # If httppostargs is used, we need to read Content-Length
145 # If httppostargs is used, we need to read Content-Length
146 # minus the amount that was consumed by args.
146 # minus the amount that was consumed by args.
147 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
147 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
148 for s in util.filechunkiter(self._req, limit=length):
148 for s in util.filechunkiter(self._req, limit=length):
149 fp.write(s)
149 fp.write(s)
150
150
151 def redirect(self):
151 def redirect(self):
152 self._oldio = self._ui.fout, self._ui.ferr
152 self._oldio = self._ui.fout, self._ui.ferr
153 self._ui.ferr = self._ui.fout = stringio()
153 self._ui.ferr = self._ui.fout = stringio()
154
154
155 def restore(self):
155 def restore(self):
156 val = self._ui.fout.getvalue()
156 val = self._ui.fout.getvalue()
157 self._ui.ferr, self._ui.fout = self._oldio
157 self._ui.ferr, self._ui.fout = self._oldio
158 return val
158 return val
159
159
160 def _client(self):
160 def _client(self):
161 return 'remote:%s:%s:%s' % (
161 return 'remote:%s:%s:%s' % (
162 self._req.env.get('wsgi.url_scheme') or 'http',
162 self._req.env.get('wsgi.url_scheme') or 'http',
163 urlreq.quote(self._req.env.get('REMOTE_HOST', '')),
163 urlreq.quote(self._req.env.get('REMOTE_HOST', '')),
164 urlreq.quote(self._req.env.get('REMOTE_USER', '')))
164 urlreq.quote(self._req.env.get('REMOTE_USER', '')))
165
165
166 def responsetype(self, prefer_uncompressed):
166 def responsetype(self, prefer_uncompressed):
167 """Determine the appropriate response type and compression settings.
167 """Determine the appropriate response type and compression settings.
168
168
169 Returns a tuple of (mediatype, compengine, engineopts).
169 Returns a tuple of (mediatype, compengine, engineopts).
170 """
170 """
171 # Determine the response media type and compression engine based
171 # Determine the response media type and compression engine based
172 # on the request parameters.
172 # on the request parameters.
173 protocaps = decodevaluefromheaders(self._req, r'X-HgProto').split(' ')
173 protocaps = decodevaluefromheaders(self._req, r'X-HgProto').split(' ')
174
174
175 if '0.2' in protocaps:
175 if '0.2' in protocaps:
176 # All clients are expected to support uncompressed data.
176 # All clients are expected to support uncompressed data.
177 if prefer_uncompressed:
177 if prefer_uncompressed:
178 return HGTYPE2, util._noopengine(), {}
178 return HGTYPE2, util._noopengine(), {}
179
179
180 # Default as defined by wire protocol spec.
180 # Default as defined by wire protocol spec.
181 compformats = ['zlib', 'none']
181 compformats = ['zlib', 'none']
182 for cap in protocaps:
182 for cap in protocaps:
183 if cap.startswith('comp='):
183 if cap.startswith('comp='):
184 compformats = cap[5:].split(',')
184 compformats = cap[5:].split(',')
185 break
185 break
186
186
187 # Now find an agreed upon compression format.
187 # Now find an agreed upon compression format.
188 for engine in wireproto.supportedcompengines(self._ui, self,
188 for engine in wireproto.supportedcompengines(self._ui, self,
189 util.SERVERROLE):
189 util.SERVERROLE):
190 if engine.wireprotosupport().name in compformats:
190 if engine.wireprotosupport().name in compformats:
191 opts = {}
191 opts = {}
192 level = self._ui.configint('server',
192 level = self._ui.configint('server',
193 '%slevel' % engine.name())
193 '%slevel' % engine.name())
194 if level is not None:
194 if level is not None:
195 opts['level'] = level
195 opts['level'] = level
196
196
197 return HGTYPE2, engine, opts
197 return HGTYPE2, engine, opts
198
198
199 # No mutually supported compression format. Fall back to the
199 # No mutually supported compression format. Fall back to the
200 # legacy protocol.
200 # legacy protocol.
201
201
202 # Don't allow untrusted settings because disabling compression or
202 # Don't allow untrusted settings because disabling compression or
203 # setting a very high compression level could lead to flooding
203 # setting a very high compression level could lead to flooding
204 # the server's network or CPU.
204 # the server's network or CPU.
205 opts = {'level': self._ui.configint('server', 'zliblevel')}
205 opts = {'level': self._ui.configint('server', 'zliblevel')}
206 return HGTYPE, util.compengines['zlib'], opts
206 return HGTYPE, util.compengines['zlib'], opts
207
207
208 def iscmd(cmd):
208 def iscmd(cmd):
209 return cmd in wireproto.commands
209 return cmd in wireproto.commands
210
210
211 def parsehttprequest(repo, req, query):
211 def parsehttprequest(repo, req, query):
212 """Parse the HTTP request for a wire protocol request.
212 """Parse the HTTP request for a wire protocol request.
213
213
214 If the current request appears to be a wire protocol request, this
214 If the current request appears to be a wire protocol request, this
215 function returns a dict with details about that request, including
215 function returns a dict with details about that request, including
216 an ``abstractprotocolserver`` instance suitable for handling the
216 an ``abstractprotocolserver`` instance suitable for handling the
217 request. Otherwise, ``None`` is returned.
217 request. Otherwise, ``None`` is returned.
218
218
219 ``req`` is a ``wsgirequest`` instance.
219 ``req`` is a ``wsgirequest`` instance.
220 """
220 """
221 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
221 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
222 # string parameter. If it isn't present, this isn't a wire protocol
222 # string parameter. If it isn't present, this isn't a wire protocol
223 # request.
223 # request.
224 if r'cmd' not in req.form:
224 if r'cmd' not in req.form:
225 return None
225 return None
226
226
227 cmd = pycompat.sysbytes(req.form[r'cmd'][0])
227 cmd = pycompat.sysbytes(req.form[r'cmd'][0])
228
228
229 # The "cmd" request parameter is used by both the wire protocol and hgweb.
229 # The "cmd" request parameter is used by both the wire protocol and hgweb.
230 # While not all wire protocol commands are available for all transports,
230 # While not all wire protocol commands are available for all transports,
231 # if we see a "cmd" value that resembles a known wire protocol command, we
231 # if we see a "cmd" value that resembles a known wire protocol command, we
232 # route it to a protocol handler. This is better than routing possible
232 # route it to a protocol handler. This is better than routing possible
233 # wire protocol requests to hgweb because it prevents hgweb from using
233 # wire protocol requests to hgweb because it prevents hgweb from using
234 # known wire protocol commands and it is less confusing for machine
234 # known wire protocol commands and it is less confusing for machine
235 # clients.
235 # clients.
236 if cmd not in wireproto.commands:
236 if cmd not in wireproto.commands:
237 return None
237 return None
238
238
239 proto = webproto(req, repo.ui)
239 proto = webproto(req, repo.ui)
240
240
241 return {
241 return {
242 'cmd': cmd,
242 'cmd': cmd,
243 'proto': proto,
243 'proto': proto,
244 'dispatch': lambda: _callhttp(repo, req, proto, cmd),
244 'dispatch': lambda: _callhttp(repo, req, proto, cmd),
245 'handleerror': lambda ex: _handlehttperror(ex, req, cmd),
245 'handleerror': lambda ex: _handlehttperror(ex, req, cmd),
246 }
246 }
247
247
248 def _callhttp(repo, req, proto, cmd):
248 def _callhttp(repo, req, proto, cmd):
249 def genversion2(gen, engine, engineopts):
249 def genversion2(gen, engine, engineopts):
250 # application/mercurial-0.2 always sends a payload header
250 # application/mercurial-0.2 always sends a payload header
251 # identifying the compression engine.
251 # identifying the compression engine.
252 name = engine.wireprotosupport().name
252 name = engine.wireprotosupport().name
253 assert 0 < len(name) < 256
253 assert 0 < len(name) < 256
254 yield struct.pack('B', len(name))
254 yield struct.pack('B', len(name))
255 yield name
255 yield name
256
256
257 for chunk in gen:
257 for chunk in gen:
258 yield chunk
258 yield chunk
259
259
260 rsp = wireproto.dispatch(repo, proto, cmd)
260 rsp = wireproto.dispatch(repo, proto, cmd)
261
261
262 if not wireproto.commands.commandavailable(cmd, proto):
262 if not wireproto.commands.commandavailable(cmd, proto):
263 req.respond(HTTP_OK, HGERRTYPE,
263 req.respond(HTTP_OK, HGERRTYPE,
264 body=_('requested wire protocol command is not available '
264 body=_('requested wire protocol command is not available '
265 'over HTTP'))
265 'over HTTP'))
266 return []
266 return []
267
267
268 if isinstance(rsp, bytes):
268 if isinstance(rsp, bytes):
269 req.respond(HTTP_OK, HGTYPE, body=rsp)
269 req.respond(HTTP_OK, HGTYPE, body=rsp)
270 return []
270 return []
271 elif isinstance(rsp, wireproto.streamres_legacy):
271 elif isinstance(rsp, wireproto.streamres_legacy):
272 gen = rsp.gen
272 gen = rsp.gen
273 req.respond(HTTP_OK, HGTYPE)
273 req.respond(HTTP_OK, HGTYPE)
274 return gen
274 return gen
275 elif isinstance(rsp, wireproto.streamres):
275 elif isinstance(rsp, wireproto.streamres):
276 gen = rsp.gen
276 gen = rsp.gen
277
277
278 # This code for compression should not be streamres specific. It
278 # This code for compression should not be streamres specific. It
279 # is here because we only compress streamres at the moment.
279 # is here because we only compress streamres at the moment.
280 mediatype, engine, engineopts = proto.responsetype(
280 mediatype, engine, engineopts = proto.responsetype(
281 rsp.prefer_uncompressed)
281 rsp.prefer_uncompressed)
282 gen = engine.compressstream(gen, engineopts)
282 gen = engine.compressstream(gen, engineopts)
283
283
284 if mediatype == HGTYPE2:
284 if mediatype == HGTYPE2:
285 gen = genversion2(gen, engine, engineopts)
285 gen = genversion2(gen, engine, engineopts)
286
286
287 req.respond(HTTP_OK, mediatype)
287 req.respond(HTTP_OK, mediatype)
288 return gen
288 return gen
289 elif isinstance(rsp, wireproto.pushres):
289 elif isinstance(rsp, wireproto.pushres):
290 val = proto.restore()
290 val = proto.restore()
291 rsp = '%d\n%s' % (rsp.res, val)
291 rsp = '%d\n%s' % (rsp.res, val)
292 req.respond(HTTP_OK, HGTYPE, body=rsp)
292 req.respond(HTTP_OK, HGTYPE, body=rsp)
293 return []
293 return []
294 elif isinstance(rsp, wireproto.pusherr):
294 elif isinstance(rsp, wireproto.pusherr):
295 # drain the incoming bundle
295 # This is the httplib workaround documented in _handlehttperror().
296 req.drain()
296 req.drain()
297
297 proto.restore()
298 proto.restore()
298 rsp = '0\n%s\n' % rsp.res
299 rsp = '0\n%s\n' % rsp.res
299 req.respond(HTTP_OK, HGTYPE, body=rsp)
300 req.respond(HTTP_OK, HGTYPE, body=rsp)
300 return []
301 return []
301 elif isinstance(rsp, wireproto.ooberror):
302 elif isinstance(rsp, wireproto.ooberror):
302 rsp = rsp.message
303 rsp = rsp.message
303 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
304 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
304 return []
305 return []
305 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
306 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
306
307
307 def _handlehttperror(e, req, cmd):
308 def _handlehttperror(e, req, cmd):
308 """Called when an ErrorResponse is raised during HTTP request processing."""
309 """Called when an ErrorResponse is raised during HTTP request processing."""
309 # A client that sends unbundle without 100-continue will
310
310 # break if we respond early.
311 # Clients using Python's httplib are stateful: the HTTP client
311 if (cmd == 'unbundle' and
312 # won't process an HTTP response until all request data is
313 # sent to the server. The intent of this code is to ensure
314 # we always read HTTP request data from the client, thus
315 # ensuring httplib transitions to a state that allows it to read
316 # the HTTP response. In other words, it helps prevent deadlocks
317 # on clients using httplib.
318
319 if (req.env[r'REQUEST_METHOD'] == r'POST' and
320 # But not if Expect: 100-continue is being used.
312 (req.env.get('HTTP_EXPECT',
321 (req.env.get('HTTP_EXPECT',
313 '').lower() != '100-continue') or
322 '').lower() != '100-continue') or
323 # Or the non-httplib HTTP library is being advertised by
324 # the client.
314 req.env.get('X-HgHttp2', '')):
325 req.env.get('X-HgHttp2', '')):
315 req.drain()
326 req.drain()
316 else:
327 else:
317 req.headers.append((r'Connection', r'Close'))
328 req.headers.append((r'Connection', r'Close'))
318
329
330 # TODO This response body assumes the failed command was
331 # "unbundle." That assumption is not always valid.
319 req.respond(e, HGTYPE, body='0\n%s\n' % e)
332 req.respond(e, HGTYPE, body='0\n%s\n' % e)
320
333
321 return ''
334 return ''
322
335
323 class sshserver(abstractserverproto):
336 class sshserver(abstractserverproto):
324 def __init__(self, ui, repo):
337 def __init__(self, ui, repo):
325 self._ui = ui
338 self._ui = ui
326 self._repo = repo
339 self._repo = repo
327 self._fin = ui.fin
340 self._fin = ui.fin
328 self._fout = ui.fout
341 self._fout = ui.fout
329
342
330 hook.redirect(True)
343 hook.redirect(True)
331 ui.fout = repo.ui.fout = ui.ferr
344 ui.fout = repo.ui.fout = ui.ferr
332
345
333 # Prevent insertion/deletion of CRs
346 # Prevent insertion/deletion of CRs
334 util.setbinary(self._fin)
347 util.setbinary(self._fin)
335 util.setbinary(self._fout)
348 util.setbinary(self._fout)
336
349
337 @property
350 @property
338 def name(self):
351 def name(self):
339 return 'ssh'
352 return 'ssh'
340
353
341 def getargs(self, args):
354 def getargs(self, args):
342 data = {}
355 data = {}
343 keys = args.split()
356 keys = args.split()
344 for n in xrange(len(keys)):
357 for n in xrange(len(keys)):
345 argline = self._fin.readline()[:-1]
358 argline = self._fin.readline()[:-1]
346 arg, l = argline.split()
359 arg, l = argline.split()
347 if arg not in keys:
360 if arg not in keys:
348 raise error.Abort(_("unexpected parameter %r") % arg)
361 raise error.Abort(_("unexpected parameter %r") % arg)
349 if arg == '*':
362 if arg == '*':
350 star = {}
363 star = {}
351 for k in xrange(int(l)):
364 for k in xrange(int(l)):
352 argline = self._fin.readline()[:-1]
365 argline = self._fin.readline()[:-1]
353 arg, l = argline.split()
366 arg, l = argline.split()
354 val = self._fin.read(int(l))
367 val = self._fin.read(int(l))
355 star[arg] = val
368 star[arg] = val
356 data['*'] = star
369 data['*'] = star
357 else:
370 else:
358 val = self._fin.read(int(l))
371 val = self._fin.read(int(l))
359 data[arg] = val
372 data[arg] = val
360 return [data[k] for k in keys]
373 return [data[k] for k in keys]
361
374
362 def getfile(self, fpout):
375 def getfile(self, fpout):
363 self._sendresponse('')
376 self._sendresponse('')
364 count = int(self._fin.readline())
377 count = int(self._fin.readline())
365 while count:
378 while count:
366 fpout.write(self._fin.read(count))
379 fpout.write(self._fin.read(count))
367 count = int(self._fin.readline())
380 count = int(self._fin.readline())
368
381
369 def redirect(self):
382 def redirect(self):
370 pass
383 pass
371
384
372 def _sendresponse(self, v):
385 def _sendresponse(self, v):
373 self._fout.write("%d\n" % len(v))
386 self._fout.write("%d\n" % len(v))
374 self._fout.write(v)
387 self._fout.write(v)
375 self._fout.flush()
388 self._fout.flush()
376
389
377 def _sendstream(self, source):
390 def _sendstream(self, source):
378 write = self._fout.write
391 write = self._fout.write
379 for chunk in source.gen:
392 for chunk in source.gen:
380 write(chunk)
393 write(chunk)
381 self._fout.flush()
394 self._fout.flush()
382
395
383 def _sendpushresponse(self, rsp):
396 def _sendpushresponse(self, rsp):
384 self._sendresponse('')
397 self._sendresponse('')
385 self._sendresponse(str(rsp.res))
398 self._sendresponse(str(rsp.res))
386
399
387 def _sendpusherror(self, rsp):
400 def _sendpusherror(self, rsp):
388 self._sendresponse(rsp.res)
401 self._sendresponse(rsp.res)
389
402
390 def _sendooberror(self, rsp):
403 def _sendooberror(self, rsp):
391 self._ui.ferr.write('%s\n-\n' % rsp.message)
404 self._ui.ferr.write('%s\n-\n' % rsp.message)
392 self._ui.ferr.flush()
405 self._ui.ferr.flush()
393 self._fout.write('\n')
406 self._fout.write('\n')
394 self._fout.flush()
407 self._fout.flush()
395
408
396 def serve_forever(self):
409 def serve_forever(self):
397 while self.serve_one():
410 while self.serve_one():
398 pass
411 pass
399 sys.exit(0)
412 sys.exit(0)
400
413
401 _handlers = {
414 _handlers = {
402 str: _sendresponse,
415 str: _sendresponse,
403 wireproto.streamres: _sendstream,
416 wireproto.streamres: _sendstream,
404 wireproto.streamres_legacy: _sendstream,
417 wireproto.streamres_legacy: _sendstream,
405 wireproto.pushres: _sendpushresponse,
418 wireproto.pushres: _sendpushresponse,
406 wireproto.pusherr: _sendpusherror,
419 wireproto.pusherr: _sendpusherror,
407 wireproto.ooberror: _sendooberror,
420 wireproto.ooberror: _sendooberror,
408 }
421 }
409
422
410 def serve_one(self):
423 def serve_one(self):
411 cmd = self._fin.readline()[:-1]
424 cmd = self._fin.readline()[:-1]
412 if cmd and wireproto.commands.commandavailable(cmd, self):
425 if cmd and wireproto.commands.commandavailable(cmd, self):
413 rsp = wireproto.dispatch(self._repo, self, cmd)
426 rsp = wireproto.dispatch(self._repo, self, cmd)
414 self._handlers[rsp.__class__](self, rsp)
427 self._handlers[rsp.__class__](self, rsp)
415 elif cmd:
428 elif cmd:
416 self._sendresponse("")
429 self._sendresponse("")
417 return cmd != ''
430 return cmd != ''
418
431
419 def _client(self):
432 def _client(self):
420 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
433 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
421 return 'remote:ssh:' + client
434 return 'remote:ssh:' + client
General Comments 0
You need to be logged in to leave comments. Login now