~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/request.py

  • Committer: Vincent Ladeuil
  • Date: 2008-12-04 17:12:46 UTC
  • mto: (3902.1.1 bzr.integration)
  • mto: This revision was merged to the branch mainline in revision 3903.
  • Revision ID: v.ladeuil+lp@free.fr-20081204171246-p28b3u0e2alz53iv
Fix bug #270863 by preserving 'bzr+http[s]' decorator.

* bzrlib/transport/remote.py:
(RemoteHTTPTransport._redirected_to): Specific implementation to
handle the redirections.

* bzrlib/transport/http/_urllib.py:
(HttpTransport_urllib.__init__): Fix parameter order.

* bzrlib/transport/http/_pycurl.py:
(PyCurlTransport.__init__): Fix parameter order.

* bzrlib/transport/http/__init__.py:
(HttpTransportBase.external_url): Semi drive-by fix, external_url
shouldn't expose the implementation qualifier (it's private to bzr
not externally usable).

* bzrlib/transport/decorator.py:
(TransportDecorator._redirected_to): Cleanup.

* bzrlib/tests/test_smart_transport.py:
(RemoteHTTPTransportTestCase): Add specific tests for
_redirected_to.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
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
16
 
 
17
 
"""Infrastructure for server-side request handlers.
18
 
 
19
 
Interesting module attributes:
20
 
    * The request_handlers registry maps verb names to SmartServerRequest
21
 
      classes.
22
 
    * The jail_info threading.local() object is used to prevent accidental
23
 
      opening of BzrDirs outside of the backing transport, or any other
24
 
      transports placed in jail_info.transports.  The jail_info is reset on
25
 
      every call into a request handler (which can happen an arbitrary number
26
 
      of times during a request).
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Basic server-side logic for dealing with requests.
 
18
 
 
19
**XXX**:
 
20
 
 
21
The class names are a little confusing: the protocol will instantiate a
 
22
SmartServerRequestHandler, whose dispatch_command method creates an instance of
 
23
a SmartServerRequest subclass.
 
24
 
 
25
The request_handlers registry tracks SmartServerRequest classes (rather than
 
26
SmartServerRequestHandler).
27
27
"""
28
28
 
29
 
# XXX: The class names are a little confusing: the protocol will instantiate a
30
 
# SmartServerRequestHandler, whose dispatch_command method creates an instance
31
 
# of a SmartServerRequest subclass.
32
 
 
33
 
 
34
29
import tempfile
35
 
import threading
36
30
 
37
31
from bzrlib import (
38
32
    bzrdir,
39
33
    errors,
40
34
    registry,
41
35
    revision,
42
 
    trace,
43
36
    urlutils,
44
37
    )
45
38
from bzrlib.lazy_import import lazy_import
48
41
""")
49
42
 
50
43
 
51
 
jail_info = threading.local()
52
 
jail_info.transports = None
53
 
 
54
 
 
55
 
def _install_hook():
56
 
    bzrdir.BzrDir.hooks.install_named_hook(
57
 
        'pre_open', _pre_open_hook, 'checking server jail')
58
 
 
59
 
 
60
 
def _pre_open_hook(transport):
61
 
    allowed_transports = getattr(jail_info, 'transports', None)
62
 
    if allowed_transports is None:
63
 
        return
64
 
    abspath = transport.base
65
 
    for allowed_transport in allowed_transports:
66
 
        try:
67
 
            allowed_transport.relpath(abspath)
68
 
        except errors.PathNotChild:
69
 
            continue
70
 
        else:
71
 
            return
72
 
    raise errors.JailBreak(abspath)
73
 
 
74
 
 
75
 
_install_hook()
76
 
 
77
 
 
78
44
class SmartServerRequest(object):
79
45
    """Base class for request handlers.
80
 
 
 
46
    
81
47
    To define a new request, subclass this class and override the `do` method
82
48
    (and if appropriate, `do_body` as well).  Request implementors should take
83
49
    care to call `translate_client_path` and `transport_from_client_path` as
104
70
            if not root_client_path.endswith('/'):
105
71
                root_client_path += '/'
106
72
        self._root_client_path = root_client_path
107
 
        self._body_chunks = []
108
73
 
109
74
    def _check_enabled(self):
110
75
        """Raises DisabledMethod if this method is disabled."""
112
77
 
113
78
    def do(self, *args):
114
79
        """Mandatory extension point for SmartServerRequest subclasses.
115
 
 
 
80
        
116
81
        Subclasses must implement this.
117
 
 
 
82
        
118
83
        This should return a SmartServerResponse if this command expects to
119
84
        receive no body.
120
85
        """
135
100
        """Called if the client sends a body with the request.
136
101
 
137
102
        The do() method is still called, and must have returned None.
138
 
 
 
103
        
139
104
        Must return a SmartServerResponse.
140
105
        """
141
 
        if body_bytes != '':
142
 
            raise errors.SmartProtocolError('Request does not expect a body')
 
106
        raise NotImplementedError(self.do_body)
143
107
 
144
108
    def do_chunk(self, chunk_bytes):
145
109
        """Called with each body chunk if the request has a streamed body.
146
110
 
147
111
        The do() method is still called, and must have returned None.
148
112
        """
149
 
        self._body_chunks.append(chunk_bytes)
 
113
        raise NotImplementedError(self.do_chunk)
150
114
 
151
115
    def do_end(self):
152
116
        """Called when the end of the request has been received."""
153
 
        body_bytes = ''.join(self._body_chunks)
154
 
        self._body_chunks = None
155
 
        return self.do_body(body_bytes)
156
 
 
157
 
    def setup_jail(self):
158
 
        jail_info.transports = [self._backing_transport]
159
 
 
160
 
    def teardown_jail(self):
161
 
        jail_info.transports = None
162
 
 
 
117
        pass
 
118
    
163
119
    def translate_client_path(self, client_path):
164
120
        """Translate a path received from a network client into a local
165
121
        relpath.
176
132
            return client_path
177
133
        if not client_path.startswith('/'):
178
134
            client_path = '/' + client_path
179
 
        if client_path + '/' == self._root_client_path:
180
 
            return '.'
181
135
        if client_path.startswith(self._root_client_path):
182
136
            path = client_path[len(self._root_client_path):]
183
137
            relpath = urlutils.joinpath('/', path)
200
154
 
201
155
class SmartServerResponse(object):
202
156
    """A response to a client request.
203
 
 
 
157
    
204
158
    This base class should not be used. Instead use
205
159
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
206
160
    """
250
204
 
251
205
class SmartServerRequestHandler(object):
252
206
    """Protocol logic for smart server.
253
 
 
 
207
    
254
208
    This doesn't handle serialization at all, it just processes requests and
255
209
    creates responses.
256
210
    """
275
229
        self._backing_transport = backing_transport
276
230
        self._root_client_path = root_client_path
277
231
        self._commands = commands
 
232
        self._body_bytes = ''
278
233
        self.response = None
279
234
        self.finished_reading = False
280
235
        self._command = None
281
236
 
282
237
    def accept_body(self, bytes):
283
238
        """Accept body data."""
284
 
        self._run_handler_code(self._command.do_chunk, (bytes,), {})
285
 
 
 
239
 
 
240
        # TODO: This should be overriden for each command that desired body data
 
241
        # to handle the right format of that data, i.e. plain bytes, a bundle,
 
242
        # etc.  The deserialisation into that format should be done in the
 
243
        # Protocol object.
 
244
 
 
245
        # default fallback is to accumulate bytes.
 
246
        self._body_bytes += bytes
 
247
        
286
248
    def end_of_body(self):
287
249
        """No more body data will be received."""
288
 
        self._run_handler_code(self._command.do_end, (), {})
 
250
        self._run_handler_code(self._command.do_body, (self._body_bytes,), {})
289
251
        # cannot read after this.
290
252
        self.finished_reading = True
291
253
 
318
280
        # XXX: most of this error conversion is VFS-related, and thus ought to
319
281
        # be in SmartServerVFSRequestHandler somewhere.
320
282
        try:
321
 
            self._command.setup_jail()
322
 
            try:
323
 
                return callable(*args, **kwargs)
324
 
            finally:
325
 
                self._command.teardown_jail()
326
 
        except (KeyboardInterrupt, SystemExit):
327
 
            raise
328
 
        except Exception, err:
329
 
            err_struct = _translate_error(err)
330
 
            return FailedSmartServerResponse(err_struct)
 
283
            return callable(*args, **kwargs)
 
284
        except errors.NoSuchFile, e:
 
285
            return FailedSmartServerResponse(('NoSuchFile', e.path))
 
286
        except errors.FileExists, e:
 
287
            return FailedSmartServerResponse(('FileExists', e.path))
 
288
        except errors.DirectoryNotEmpty, e:
 
289
            return FailedSmartServerResponse(('DirectoryNotEmpty', e.path))
 
290
        except errors.ShortReadvError, e:
 
291
            return FailedSmartServerResponse(('ShortReadvError',
 
292
                e.path, str(e.offset), str(e.length), str(e.actual)))
 
293
        except errors.UnstackableRepositoryFormat, e:
 
294
            return FailedSmartServerResponse(('UnstackableRepositoryFormat',
 
295
                str(e.format), e.url))
 
296
        except errors.UnstackableBranchFormat, e:
 
297
            return FailedSmartServerResponse(('UnstackableBranchFormat',
 
298
                str(e.format), e.url))
 
299
        except errors.NotStacked, e:
 
300
            return FailedSmartServerResponse(('NotStacked',))
 
301
        except UnicodeError, e:
 
302
            # If it is a DecodeError, than most likely we are starting
 
303
            # with a plain string
 
304
            str_or_unicode = e.object
 
305
            if isinstance(str_or_unicode, unicode):
 
306
                # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
 
307
                # byte) in it, so this encoding could cause broken responses.
 
308
                # Newer clients use protocol v3, so will be fine.
 
309
                val = 'u:' + str_or_unicode.encode('utf-8')
 
310
            else:
 
311
                val = 's:' + str_or_unicode.encode('base64')
 
312
            # This handles UnicodeEncodeError or UnicodeDecodeError
 
313
            return FailedSmartServerResponse((e.__class__.__name__,
 
314
                    e.encoding, val, str(e.start), str(e.end), e.reason))
 
315
        except errors.TransportNotPossible, e:
 
316
            if e.msg == "readonly transport":
 
317
                return FailedSmartServerResponse(('ReadOnlyError', ))
 
318
            else:
 
319
                raise
 
320
        except errors.ReadError, e:
 
321
            # cannot read the file
 
322
            return FailedSmartServerResponse(('ReadError', e.path))
 
323
        except errors.PermissionDenied, e:
 
324
            return FailedSmartServerResponse(
 
325
                ('PermissionDenied', e.path, e.extra))
331
326
 
332
327
    def headers_received(self, headers):
333
328
        # Just a no-op at the moment.
343
338
        self._command = command(self._backing_transport)
344
339
        self._run_handler_code(self._command.execute, args, {})
345
340
 
 
341
    def prefixed_body_received(self, body_bytes):
 
342
        """No more body data will be received."""
 
343
        self._run_handler_code(self._command.do_body, (body_bytes,), {})
 
344
        # cannot read after this.
 
345
        self.finished_reading = True
 
346
 
 
347
    def body_chunk_received(self, chunk_bytes):
 
348
        self._run_handler_code(self._command.do_chunk, (chunk_bytes,), {})
 
349
 
346
350
    def end_received(self):
347
351
        self._run_handler_code(self._command.do_end, (), {})
348
352
 
349
 
    def post_body_error_received(self, error_args):
350
 
        # Just a no-op at the moment.
351
 
        pass
352
 
 
353
 
 
354
 
def _translate_error(err):
355
 
    if isinstance(err, errors.NoSuchFile):
356
 
        return ('NoSuchFile', err.path)
357
 
    elif isinstance(err, errors.FileExists):
358
 
        return ('FileExists', err.path)
359
 
    elif isinstance(err, errors.DirectoryNotEmpty):
360
 
        return ('DirectoryNotEmpty', err.path)
361
 
    elif isinstance(err, errors.ShortReadvError):
362
 
        return ('ShortReadvError', err.path, str(err.offset), str(err.length),
363
 
                str(err.actual))
364
 
    elif isinstance(err, errors.UnstackableRepositoryFormat):
365
 
        return (('UnstackableRepositoryFormat', str(err.format), err.url))
366
 
    elif isinstance(err, errors.UnstackableBranchFormat):
367
 
        return ('UnstackableBranchFormat', str(err.format), err.url)
368
 
    elif isinstance(err, errors.NotStacked):
369
 
        return ('NotStacked',)
370
 
    elif isinstance(err, UnicodeError):
371
 
        # If it is a DecodeError, than most likely we are starting
372
 
        # with a plain string
373
 
        str_or_unicode = err.object
374
 
        if isinstance(str_or_unicode, unicode):
375
 
            # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
376
 
            # byte) in it, so this encoding could cause broken responses.
377
 
            # Newer clients use protocol v3, so will be fine.
378
 
            val = 'u:' + str_or_unicode.encode('utf-8')
379
 
        else:
380
 
            val = 's:' + str_or_unicode.encode('base64')
381
 
        # This handles UnicodeEncodeError or UnicodeDecodeError
382
 
        return (err.__class__.__name__, err.encoding, val, str(err.start),
383
 
                str(err.end), err.reason)
384
 
    elif isinstance(err, errors.TransportNotPossible):
385
 
        if err.msg == "readonly transport":
386
 
            return ('ReadOnlyError', )
387
 
    elif isinstance(err, errors.ReadError):
388
 
        # cannot read the file
389
 
        return ('ReadError', err.path)
390
 
    elif isinstance(err, errors.PermissionDenied):
391
 
        return ('PermissionDenied', err.path, err.extra)
392
 
    elif isinstance(err, errors.TokenMismatch):
393
 
        return ('TokenMismatch', err.given_token, err.lock_token)
394
 
    elif isinstance(err, errors.LockContention):
395
 
        return ('LockContention', err.lock, err.msg)
396
 
    # Unserialisable error.  Log it, and return a generic error
397
 
    trace.log_exception_quietly()
398
 
    return ('error', str(err))
399
 
 
400
353
 
401
354
class HelloRequest(SmartServerRequest):
402
355
    """Answer a version request with the highest protocol version this server
437
390
request_handlers.register_lazy(
438
391
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
439
392
request_handlers.register_lazy(
440
 
    'Branch.get_config_file', 'bzrlib.smart.branch',
441
 
    'SmartServerBranchGetConfigFile')
442
 
request_handlers.register_lazy(
443
 
    'Branch.get_parent', 'bzrlib.smart.branch', 'SmartServerBranchGetParent')
444
 
request_handlers.register_lazy(
445
 
    'Branch.get_tags_bytes', 'bzrlib.smart.branch',
446
 
    'SmartServerBranchGetTagsBytes')
 
393
    'Branch.get_config_file', 'bzrlib.smart.branch', 'SmartServerBranchGetConfigFile')
447
394
request_handlers.register_lazy(
448
395
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
449
396
request_handlers.register_lazy(
450
397
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
451
398
request_handlers.register_lazy(
452
399
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
453
 
request_handlers.register_lazy( 'Branch.revision_history',
454
 
    'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
455
 
request_handlers.register_lazy( 'Branch.set_config_option',
456
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetConfigOption')
457
 
request_handlers.register_lazy( 'Branch.set_last_revision',
458
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
 
400
request_handlers.register_lazy(
 
401
    'Branch.revision_history', 'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
 
402
request_handlers.register_lazy(
 
403
    'Branch.set_last_revision', 'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
459
404
request_handlers.register_lazy(
460
405
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
461
406
    'SmartServerBranchRequestSetLastRevisionInfo')
463
408
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
464
409
    'SmartServerBranchRequestSetLastRevisionEx')
465
410
request_handlers.register_lazy(
466
 
    'Branch.set_parent_location', 'bzrlib.smart.branch',
467
 
    'SmartServerBranchRequestSetParentLocation')
468
 
request_handlers.register_lazy(
469
411
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
470
412
request_handlers.register_lazy(
471
 
    'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
472
 
    'SmartServerBzrDirRequestCloningMetaDir')
473
 
request_handlers.register_lazy(
474
 
    'BzrDir.create_branch', 'bzrlib.smart.bzrdir',
475
 
    'SmartServerRequestCreateBranch')
476
 
request_handlers.register_lazy(
477
 
    'BzrDir.create_repository', 'bzrlib.smart.bzrdir',
478
 
    'SmartServerRequestCreateRepository')
479
 
request_handlers.register_lazy(
480
 
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir',
481
 
    'SmartServerRequestFindRepositoryV1')
482
 
request_handlers.register_lazy(
483
 
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir',
484
 
    'SmartServerRequestFindRepositoryV2')
485
 
request_handlers.register_lazy(
486
 
    'BzrDir.find_repositoryV3', 'bzrlib.smart.bzrdir',
487
 
    'SmartServerRequestFindRepositoryV3')
488
 
request_handlers.register_lazy(
489
 
    'BzrDir.get_config_file', 'bzrlib.smart.bzrdir',
490
 
    'SmartServerBzrDirRequestConfigFile')
491
 
request_handlers.register_lazy(
492
 
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir',
493
 
    'SmartServerRequestInitializeBzrDir')
494
 
request_handlers.register_lazy(
495
 
    'BzrDirFormat.initialize_ex_1.16', 'bzrlib.smart.bzrdir',
496
 
    'SmartServerRequestBzrDirInitializeEx')
497
 
request_handlers.register_lazy(
498
 
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')
499
 
request_handlers.register_lazy(
500
 
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir',
501
 
    'SmartServerRequestOpenBranch')
502
 
request_handlers.register_lazy(
503
 
    'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
504
 
    'SmartServerRequestOpenBranchV2')
 
413
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV1')
 
414
request_handlers.register_lazy(
 
415
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV2')
 
416
request_handlers.register_lazy(
 
417
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir', 'SmartServerRequestInitializeBzrDir')
 
418
request_handlers.register_lazy(
 
419
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBranch')
505
420
request_handlers.register_lazy(
506
421
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
507
422
request_handlers.register_lazy(
542
457
request_handlers.register_lazy(
543
458
    'Repository.has_revision', 'bzrlib.smart.repository', 'SmartServerRequestHasRevision')
544
459
request_handlers.register_lazy(
545
 
    'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
546
 
request_handlers.register_lazy(
547
 
    'Repository.insert_stream_locked', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStreamLocked')
548
 
request_handlers.register_lazy(
549
460
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
550
461
request_handlers.register_lazy(
551
462
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
552
463
request_handlers.register_lazy(
553
 
    'Repository.set_make_working_trees', 'bzrlib.smart.repository',
554
 
    'SmartServerRepositorySetMakeWorkingTrees')
555
 
request_handlers.register_lazy(
556
464
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
557
465
request_handlers.register_lazy(
558
 
    'Repository.get_rev_id_for_revno', 'bzrlib.smart.repository',
559
 
    'SmartServerRepositoryGetRevIdForRevno')
560
 
request_handlers.register_lazy(
561
 
    'Repository.get_stream', 'bzrlib.smart.repository',
562
 
    'SmartServerRepositoryGetStream')
563
 
request_handlers.register_lazy(
564
466
    'Repository.tarball', 'bzrlib.smart.repository',
565
467
    'SmartServerRepositoryTarball')
566
468
request_handlers.register_lazy(
569
471
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
570
472
request_handlers.register_lazy(
571
473
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')
 
474
request_handlers.register_lazy(
 
475
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')