##// END OF EJS Templates
wireprotoserver: don't import symbol from hgweb.common...
Gregory Szorc -
r35876:1b76a9e0 default
parent child Browse files
Show More
@@ -1,200 +1,199
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 cgi
9 import cgi
10 import struct
10 import struct
11
11
12 from .hgweb.common import (
13 HTTP_OK,
14 )
15 from . import (
12 from . import (
16 error,
13 error,
17 pycompat,
14 pycompat,
18 util,
15 util,
19 wireproto,
16 wireproto,
20 )
17 )
21
18
22 stringio = util.stringio
19 stringio = util.stringio
23
20
24 urlerr = util.urlerr
21 urlerr = util.urlerr
25 urlreq = util.urlreq
22 urlreq = util.urlreq
26
23
24 HTTP_OK = 200
25
27 HGTYPE = 'application/mercurial-0.1'
26 HGTYPE = 'application/mercurial-0.1'
28 HGTYPE2 = 'application/mercurial-0.2'
27 HGTYPE2 = 'application/mercurial-0.2'
29 HGERRTYPE = 'application/hg-error'
28 HGERRTYPE = 'application/hg-error'
30
29
31 def decodevaluefromheaders(req, headerprefix):
30 def decodevaluefromheaders(req, headerprefix):
32 """Decode a long value from multiple HTTP request headers.
31 """Decode a long value from multiple HTTP request headers.
33
32
34 Returns the value as a bytes, not a str.
33 Returns the value as a bytes, not a str.
35 """
34 """
36 chunks = []
35 chunks = []
37 i = 1
36 i = 1
38 prefix = headerprefix.upper().replace(r'-', r'_')
37 prefix = headerprefix.upper().replace(r'-', r'_')
39 while True:
38 while True:
40 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
39 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
41 if v is None:
40 if v is None:
42 break
41 break
43 chunks.append(pycompat.bytesurl(v))
42 chunks.append(pycompat.bytesurl(v))
44 i += 1
43 i += 1
45
44
46 return ''.join(chunks)
45 return ''.join(chunks)
47
46
48 class webproto(wireproto.abstractserverproto):
47 class webproto(wireproto.abstractserverproto):
49 def __init__(self, req, ui):
48 def __init__(self, req, ui):
50 self.req = req
49 self.req = req
51 self.response = ''
50 self.response = ''
52 self.ui = ui
51 self.ui = ui
53 self.name = 'http'
52 self.name = 'http'
54
53
55 def getargs(self, args):
54 def getargs(self, args):
56 knownargs = self._args()
55 knownargs = self._args()
57 data = {}
56 data = {}
58 keys = args.split()
57 keys = args.split()
59 for k in keys:
58 for k in keys:
60 if k == '*':
59 if k == '*':
61 star = {}
60 star = {}
62 for key in knownargs.keys():
61 for key in knownargs.keys():
63 if key != 'cmd' and key not in keys:
62 if key != 'cmd' and key not in keys:
64 star[key] = knownargs[key][0]
63 star[key] = knownargs[key][0]
65 data['*'] = star
64 data['*'] = star
66 else:
65 else:
67 data[k] = knownargs[k][0]
66 data[k] = knownargs[k][0]
68 return [data[k] for k in keys]
67 return [data[k] for k in keys]
69 def _args(self):
68 def _args(self):
70 args = self.req.form.copy()
69 args = self.req.form.copy()
71 if pycompat.ispy3:
70 if pycompat.ispy3:
72 args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
71 args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
73 for k, vs in args.items()}
72 for k, vs in args.items()}
74 postlen = int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
73 postlen = int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
75 if postlen:
74 if postlen:
76 args.update(cgi.parse_qs(
75 args.update(cgi.parse_qs(
77 self.req.read(postlen), keep_blank_values=True))
76 self.req.read(postlen), keep_blank_values=True))
78 return args
77 return args
79
78
80 argvalue = decodevaluefromheaders(self.req, r'X-HgArg')
79 argvalue = decodevaluefromheaders(self.req, r'X-HgArg')
81 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
80 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
82 return args
81 return args
83 def getfile(self, fp):
82 def getfile(self, fp):
84 length = int(self.req.env[r'CONTENT_LENGTH'])
83 length = int(self.req.env[r'CONTENT_LENGTH'])
85 # If httppostargs is used, we need to read Content-Length
84 # If httppostargs is used, we need to read Content-Length
86 # minus the amount that was consumed by args.
85 # minus the amount that was consumed by args.
87 length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
86 length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
88 for s in util.filechunkiter(self.req, limit=length):
87 for s in util.filechunkiter(self.req, limit=length):
89 fp.write(s)
88 fp.write(s)
90 def redirect(self):
89 def redirect(self):
91 self.oldio = self.ui.fout, self.ui.ferr
90 self.oldio = self.ui.fout, self.ui.ferr
92 self.ui.ferr = self.ui.fout = stringio()
91 self.ui.ferr = self.ui.fout = stringio()
93 def restore(self):
92 def restore(self):
94 val = self.ui.fout.getvalue()
93 val = self.ui.fout.getvalue()
95 self.ui.ferr, self.ui.fout = self.oldio
94 self.ui.ferr, self.ui.fout = self.oldio
96 return val
95 return val
97
96
98 def _client(self):
97 def _client(self):
99 return 'remote:%s:%s:%s' % (
98 return 'remote:%s:%s:%s' % (
100 self.req.env.get('wsgi.url_scheme') or 'http',
99 self.req.env.get('wsgi.url_scheme') or 'http',
101 urlreq.quote(self.req.env.get('REMOTE_HOST', '')),
100 urlreq.quote(self.req.env.get('REMOTE_HOST', '')),
102 urlreq.quote(self.req.env.get('REMOTE_USER', '')))
101 urlreq.quote(self.req.env.get('REMOTE_USER', '')))
103
102
104 def responsetype(self, prefer_uncompressed):
103 def responsetype(self, prefer_uncompressed):
105 """Determine the appropriate response type and compression settings.
104 """Determine the appropriate response type and compression settings.
106
105
107 Returns a tuple of (mediatype, compengine, engineopts).
106 Returns a tuple of (mediatype, compengine, engineopts).
108 """
107 """
109 # Determine the response media type and compression engine based
108 # Determine the response media type and compression engine based
110 # on the request parameters.
109 # on the request parameters.
111 protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ')
110 protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ')
112
111
113 if '0.2' in protocaps:
112 if '0.2' in protocaps:
114 # All clients are expected to support uncompressed data.
113 # All clients are expected to support uncompressed data.
115 if prefer_uncompressed:
114 if prefer_uncompressed:
116 return HGTYPE2, util._noopengine(), {}
115 return HGTYPE2, util._noopengine(), {}
117
116
118 # Default as defined by wire protocol spec.
117 # Default as defined by wire protocol spec.
119 compformats = ['zlib', 'none']
118 compformats = ['zlib', 'none']
120 for cap in protocaps:
119 for cap in protocaps:
121 if cap.startswith('comp='):
120 if cap.startswith('comp='):
122 compformats = cap[5:].split(',')
121 compformats = cap[5:].split(',')
123 break
122 break
124
123
125 # Now find an agreed upon compression format.
124 # Now find an agreed upon compression format.
126 for engine in wireproto.supportedcompengines(self.ui, self,
125 for engine in wireproto.supportedcompengines(self.ui, self,
127 util.SERVERROLE):
126 util.SERVERROLE):
128 if engine.wireprotosupport().name in compformats:
127 if engine.wireprotosupport().name in compformats:
129 opts = {}
128 opts = {}
130 level = self.ui.configint('server',
129 level = self.ui.configint('server',
131 '%slevel' % engine.name())
130 '%slevel' % engine.name())
132 if level is not None:
131 if level is not None:
133 opts['level'] = level
132 opts['level'] = level
134
133
135 return HGTYPE2, engine, opts
134 return HGTYPE2, engine, opts
136
135
137 # No mutually supported compression format. Fall back to the
136 # No mutually supported compression format. Fall back to the
138 # legacy protocol.
137 # legacy protocol.
139
138
140 # Don't allow untrusted settings because disabling compression or
139 # Don't allow untrusted settings because disabling compression or
141 # setting a very high compression level could lead to flooding
140 # setting a very high compression level could lead to flooding
142 # the server's network or CPU.
141 # the server's network or CPU.
143 opts = {'level': self.ui.configint('server', 'zliblevel')}
142 opts = {'level': self.ui.configint('server', 'zliblevel')}
144 return HGTYPE, util.compengines['zlib'], opts
143 return HGTYPE, util.compengines['zlib'], opts
145
144
146 def iscmd(cmd):
145 def iscmd(cmd):
147 return cmd in wireproto.commands
146 return cmd in wireproto.commands
148
147
149 def callhttp(repo, req, cmd):
148 def callhttp(repo, req, cmd):
150 p = webproto(req, repo.ui)
149 p = webproto(req, repo.ui)
151
150
152 def genversion2(gen, engine, engineopts):
151 def genversion2(gen, engine, engineopts):
153 # application/mercurial-0.2 always sends a payload header
152 # application/mercurial-0.2 always sends a payload header
154 # identifying the compression engine.
153 # identifying the compression engine.
155 name = engine.wireprotosupport().name
154 name = engine.wireprotosupport().name
156 assert 0 < len(name) < 256
155 assert 0 < len(name) < 256
157 yield struct.pack('B', len(name))
156 yield struct.pack('B', len(name))
158 yield name
157 yield name
159
158
160 for chunk in gen:
159 for chunk in gen:
161 yield chunk
160 yield chunk
162
161
163 rsp = wireproto.dispatch(repo, p, cmd)
162 rsp = wireproto.dispatch(repo, p, cmd)
164 if isinstance(rsp, bytes):
163 if isinstance(rsp, bytes):
165 req.respond(HTTP_OK, HGTYPE, body=rsp)
164 req.respond(HTTP_OK, HGTYPE, body=rsp)
166 return []
165 return []
167 elif isinstance(rsp, wireproto.streamres_legacy):
166 elif isinstance(rsp, wireproto.streamres_legacy):
168 gen = rsp.gen
167 gen = rsp.gen
169 req.respond(HTTP_OK, HGTYPE)
168 req.respond(HTTP_OK, HGTYPE)
170 return gen
169 return gen
171 elif isinstance(rsp, wireproto.streamres):
170 elif isinstance(rsp, wireproto.streamres):
172 gen = rsp.gen
171 gen = rsp.gen
173
172
174 # This code for compression should not be streamres specific. It
173 # This code for compression should not be streamres specific. It
175 # is here because we only compress streamres at the moment.
174 # is here because we only compress streamres at the moment.
176 mediatype, engine, engineopts = p.responsetype(rsp.prefer_uncompressed)
175 mediatype, engine, engineopts = p.responsetype(rsp.prefer_uncompressed)
177 gen = engine.compressstream(gen, engineopts)
176 gen = engine.compressstream(gen, engineopts)
178
177
179 if mediatype == HGTYPE2:
178 if mediatype == HGTYPE2:
180 gen = genversion2(gen, engine, engineopts)
179 gen = genversion2(gen, engine, engineopts)
181
180
182 req.respond(HTTP_OK, mediatype)
181 req.respond(HTTP_OK, mediatype)
183 return gen
182 return gen
184 elif isinstance(rsp, wireproto.pushres):
183 elif isinstance(rsp, wireproto.pushres):
185 val = p.restore()
184 val = p.restore()
186 rsp = '%d\n%s' % (rsp.res, val)
185 rsp = '%d\n%s' % (rsp.res, val)
187 req.respond(HTTP_OK, HGTYPE, body=rsp)
186 req.respond(HTTP_OK, HGTYPE, body=rsp)
188 return []
187 return []
189 elif isinstance(rsp, wireproto.pusherr):
188 elif isinstance(rsp, wireproto.pusherr):
190 # drain the incoming bundle
189 # drain the incoming bundle
191 req.drain()
190 req.drain()
192 p.restore()
191 p.restore()
193 rsp = '0\n%s\n' % rsp.res
192 rsp = '0\n%s\n' % rsp.res
194 req.respond(HTTP_OK, HGTYPE, body=rsp)
193 req.respond(HTTP_OK, HGTYPE, body=rsp)
195 return []
194 return []
196 elif isinstance(rsp, wireproto.ooberror):
195 elif isinstance(rsp, wireproto.ooberror):
197 rsp = rsp.message
196 rsp = rsp.message
198 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
197 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
199 return []
198 return []
200 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
199 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
General Comments 0
You need to be logged in to leave comments. Login now