1
# Copyright (C) 2006-2010 Canonical Ltd
1
# Copyright (C) 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""WSGI application for bzr HTTP smart server.
23
23
from cStringIO import StringIO
25
from bzrlib.smart import protocol, medium
25
from bzrlib.smart import protocol
26
26
from bzrlib.transport import chroot, get_transport
27
27
from bzrlib.urlutils import local_path_to_url
30
def make_app(root, prefix, path_var='REQUEST_URI', readonly=True,
31
load_plugins=True, enable_logging=True):
30
def make_app(root, prefix, path_var, readonly=True):
32
31
"""Convenience function to construct a WSGI bzr smart server.
34
33
:param root: a local path that requests will be relative to.
35
34
:param prefix: See RelpathSetter.
36
35
:param path_var: See RelpathSetter.
40
39
base_transport = get_transport('readonly+' + local_url)
42
41
base_transport = get_transport(local_url)
44
from bzrlib.plugin import load_plugins
48
bzrlib.trace.enable_default_logging()
49
42
app = SmartWSGIApp(base_transport, prefix)
50
43
app = RelpathSetter(app, '', path_var)
54
47
class RelpathSetter(object):
55
48
"""WSGI middleware to set 'bzrlib.relpath' in the environ.
57
50
Different servers can invoke a SmartWSGIApp in different ways. This
58
51
middleware allows an adminstrator to configure how to the SmartWSGIApp will
59
52
determine what path it should be serving for a given request for many common
65
58
prefix="/some/prefix/" and path_var="REQUEST_URI" will set that request's
66
59
'bzrlib.relpath' variable to "repo/branch".
69
62
def __init__(self, app, prefix='', path_var='REQUEST_URI'):
106
99
# e.g. consider a smart server request for "get /etc/passwd" or
108
101
self.chroot_server = chroot.ChrootServer(backing_transport)
109
self.chroot_server.start_server()
102
self.chroot_server.setUp()
110
103
self.backing_transport = get_transport(self.chroot_server.get_url())
111
104
self.root_client_path = root_client_path
112
105
# While the chroot server can technically be torn down at this point,
113
# as all it does is remove the scheme registration from transport's
114
# protocol dictionary, we don't *just in case* there are parts of
106
# as all it does is remove the scheme registration from transport's
107
# protocol dictionary, we don't *just in case* there are parts of
115
108
# bzrlib that will invoke 'get_transport' on urls rather than cloning
116
109
# around the existing transport.
117
#self.chroot_server.stop_server()
110
#self.chroot_server.tearDown()
119
112
def __call__(self, environ, start_response):
120
113
"""WSGI application callable."""
125
118
relpath = environ['bzrlib.relpath']
119
# 1. subtract HTTP path from rcp ("adjusted rcp")
120
# 1a. If HTTP path unconsumed, clone backing transport with
121
# remainder ("adjusted transport")
122
# 2. feed HPSS request path + adjusted/backing transport + adjusted
127
125
if not relpath.startswith('/'):
128
126
relpath = '/' + relpath
129
127
if not relpath.endswith('/'):
132
# Compare the HTTP path (relpath) and root_client_path, and calculate
133
# new relpath and root_client_path accordingly, to be used to build the
130
# (relpath='/foo/bar/', rcp='/foo/' -> (rp 'bar', arcp '/')
131
# relpath='/foo/', rcp='/foo/bar/' -> (rp '/', arcp '/bar')
135
132
if relpath.startswith(self.root_client_path):
136
# The relpath traverses all of the mandatory root client path.
137
# Remove the root_client_path from the relpath, and set
138
# adjusted_tcp to None to tell the request handler that no further
139
# path translation is required.
133
# The entire root client path is part of the relpath.
140
134
adjusted_rcp = None
135
# Consume the relpath part we just subtracted
141
136
adjusted_relpath = relpath[len(self.root_client_path):]
142
137
elif self.root_client_path.startswith(relpath):
143
138
# The relpath traverses some of the mandatory root client path.
144
# Subtract the relpath from the root_client_path, and set the
139
# Subtract relpath from HTTP request
146
140
adjusted_rcp = '/' + self.root_client_path[len(relpath):]
141
# The entire relpath was consumed.
147
142
adjusted_relpath = '.'
149
144
adjusted_rcp = self.root_client_path
152
147
if adjusted_relpath.startswith('/'):
153
148
adjusted_relpath = adjusted_relpath[1:]
154
if adjusted_relpath.startswith('/'):
155
raise AssertionError(adjusted_relpath)
149
assert not adjusted_relpath.startswith('/')
157
151
transport = self.backing_transport.clone(adjusted_relpath)
156
#transport = self.backing_transport.clone(relpath)
158
157
out_buffer = StringIO()
159
158
request_data_length = int(environ['CONTENT_LENGTH'])
160
159
request_data_bytes = environ['wsgi.input'].read(request_data_length)
174
173
return [response_data]
176
175
def make_request(self, transport, write_func, request_bytes, rcp):
177
protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
179
server_protocol = protocol_factory(
180
transport, write_func, rcp, self.backing_transport)
181
server_protocol.accept_bytes(unused_bytes)
176
# XXX: This duplicates the logic in
177
# SmartServerStreamMedium._build_protocol.
178
if request_bytes.startswith(protocol.REQUEST_VERSION_TWO):
179
protocol_class = protocol.SmartServerRequestProtocolTwo
180
request_bytes = request_bytes[len(protocol.REQUEST_VERSION_TWO):]
182
protocol_class = protocol.SmartServerRequestProtocolOne
183
server_protocol = protocol_class(
184
transport, write_func, rcp)
185
server_protocol.accept_bytes(request_bytes)
182
186
return server_protocol