~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/request.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

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
 
from bzrlib.lazy_import import lazy_import
46
 
lazy_import(globals(), """
47
 
from bzrlib.bundle import serializer
48
 
""")
49
 
 
50
 
 
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()
 
38
from bzrlib.bundle.serializer import write_bundle
76
39
 
77
40
 
78
41
class SmartServerRequest(object):
79
42
    """Base class for request handlers.
80
 
 
 
43
    
81
44
    To define a new request, subclass this class and override the `do` method
82
45
    (and if appropriate, `do_body` as well).  Request implementors should take
83
46
    care to call `translate_client_path` and `transport_from_client_path` as
104
67
            if not root_client_path.endswith('/'):
105
68
                root_client_path += '/'
106
69
        self._root_client_path = root_client_path
107
 
        self._body_chunks = []
108
70
 
109
71
    def _check_enabled(self):
110
72
        """Raises DisabledMethod if this method is disabled."""
112
74
 
113
75
    def do(self, *args):
114
76
        """Mandatory extension point for SmartServerRequest subclasses.
115
 
 
 
77
        
116
78
        Subclasses must implement this.
117
 
 
 
79
        
118
80
        This should return a SmartServerResponse if this command expects to
119
81
        receive no body.
120
82
        """
135
97
        """Called if the client sends a body with the request.
136
98
 
137
99
        The do() method is still called, and must have returned None.
138
 
 
 
100
        
139
101
        Must return a SmartServerResponse.
140
102
        """
141
 
        if body_bytes != '':
142
 
            raise errors.SmartProtocolError('Request does not expect a body')
 
103
        raise NotImplementedError(self.do_body)
143
104
 
144
105
    def do_chunk(self, chunk_bytes):
145
106
        """Called with each body chunk if the request has a streamed body.
146
107
 
147
108
        The do() method is still called, and must have returned None.
148
109
        """
149
 
        self._body_chunks.append(chunk_bytes)
 
110
        raise NotImplementedError(self.do_chunk)
150
111
 
151
112
    def do_end(self):
152
113
        """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
 
 
 
114
        pass
 
115
    
163
116
    def translate_client_path(self, client_path):
164
117
        """Translate a path received from a network client into a local
165
118
        relpath.
176
129
            return client_path
177
130
        if not client_path.startswith('/'):
178
131
            client_path = '/' + client_path
179
 
        if client_path + '/' == self._root_client_path:
180
 
            return '.'
181
132
        if client_path.startswith(self._root_client_path):
182
133
            path = client_path[len(self._root_client_path):]
183
134
            relpath = urlutils.joinpath('/', path)
200
151
 
201
152
class SmartServerResponse(object):
202
153
    """A response to a client request.
203
 
 
 
154
    
204
155
    This base class should not be used. Instead use
205
156
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
206
157
    """
228
179
                other.body_stream is self.body_stream)
229
180
 
230
181
    def __repr__(self):
231
 
        return "<%s args=%r body=%r>" % (self.__class__.__name__,
 
182
        status = {True: 'OK', False: 'ERR'}[self.is_successful()]
 
183
        return "<SmartServerResponse status=%s args=%r body=%r>" % (status,
232
184
            self.args, self.body)
233
185
 
234
186
 
250
202
 
251
203
class SmartServerRequestHandler(object):
252
204
    """Protocol logic for smart server.
253
 
 
 
205
    
254
206
    This doesn't handle serialization at all, it just processes requests and
255
207
    creates responses.
256
208
    """
275
227
        self._backing_transport = backing_transport
276
228
        self._root_client_path = root_client_path
277
229
        self._commands = commands
 
230
        self._body_bytes = ''
278
231
        self.response = None
279
232
        self.finished_reading = False
280
233
        self._command = None
281
234
 
282
235
    def accept_body(self, bytes):
283
236
        """Accept body data."""
284
 
        if self._command is None:
285
 
            # no active command object, so ignore the event.
286
 
            return
287
 
        self._run_handler_code(self._command.do_chunk, (bytes,), {})
288
 
 
 
237
 
 
238
        # TODO: This should be overriden for each command that desired body data
 
239
        # to handle the right format of that data, i.e. plain bytes, a bundle,
 
240
        # etc.  The deserialisation into that format should be done in the
 
241
        # Protocol object.
 
242
 
 
243
        # default fallback is to accumulate bytes.
 
244
        self._body_bytes += bytes
 
245
        
289
246
    def end_of_body(self):
290
247
        """No more body data will be received."""
291
 
        self._run_handler_code(self._command.do_end, (), {})
 
248
        self._run_handler_code(self._command.do_body, (self._body_bytes,), {})
292
249
        # cannot read after this.
293
250
        self.finished_reading = True
294
251
 
321
278
        # XXX: most of this error conversion is VFS-related, and thus ought to
322
279
        # be in SmartServerVFSRequestHandler somewhere.
323
280
        try:
324
 
            self._command.setup_jail()
325
 
            try:
326
 
                return callable(*args, **kwargs)
327
 
            finally:
328
 
                self._command.teardown_jail()
329
 
        except (KeyboardInterrupt, SystemExit):
330
 
            raise
331
 
        except Exception, err:
332
 
            err_struct = _translate_error(err)
333
 
            return FailedSmartServerResponse(err_struct)
 
281
            return callable(*args, **kwargs)
 
282
        except errors.NoSuchFile, e:
 
283
            return FailedSmartServerResponse(('NoSuchFile', e.path))
 
284
        except errors.FileExists, e:
 
285
            return FailedSmartServerResponse(('FileExists', e.path))
 
286
        except errors.DirectoryNotEmpty, e:
 
287
            return FailedSmartServerResponse(('DirectoryNotEmpty', e.path))
 
288
        except errors.ShortReadvError, e:
 
289
            return FailedSmartServerResponse(('ShortReadvError',
 
290
                e.path, str(e.offset), str(e.length), str(e.actual)))
 
291
        except UnicodeError, e:
 
292
            # If it is a DecodeError, than most likely we are starting
 
293
            # with a plain string
 
294
            str_or_unicode = e.object
 
295
            if isinstance(str_or_unicode, unicode):
 
296
                # XXX: UTF-8 might have \x01 (our seperator byte) in it.  We
 
297
                # should escape it somehow.
 
298
                val = 'u:' + str_or_unicode.encode('utf-8')
 
299
            else:
 
300
                val = 's:' + str_or_unicode.encode('base64')
 
301
            # This handles UnicodeEncodeError or UnicodeDecodeError
 
302
            return FailedSmartServerResponse((e.__class__.__name__,
 
303
                    e.encoding, val, str(e.start), str(e.end), e.reason))
 
304
        except errors.TransportNotPossible, e:
 
305
            if e.msg == "readonly transport":
 
306
                return FailedSmartServerResponse(('ReadOnlyError', ))
 
307
            else:
 
308
                raise
334
309
 
335
310
    def headers_received(self, headers):
336
311
        # Just a no-op at the moment.
346
321
        self._command = command(self._backing_transport)
347
322
        self._run_handler_code(self._command.execute, args, {})
348
323
 
 
324
    def prefixed_body_received(self, body_bytes):
 
325
        """No more body data will be received."""
 
326
        self._run_handler_code(self._command.do_body, (body_bytes,), {})
 
327
        # cannot read after this.
 
328
        self.finished_reading = True
 
329
 
 
330
    def body_chunk_received(self, chunk_bytes):
 
331
        self._run_handler_code(self._command.do_chunk, (chunk_bytes,), {})
 
332
 
349
333
    def end_received(self):
350
 
        if self._command is None:
351
 
            # no active command object, so ignore the event.
352
 
            return
353
334
        self._run_handler_code(self._command.do_end, (), {})
354
335
 
355
 
    def post_body_error_received(self, error_args):
356
 
        # Just a no-op at the moment.
357
 
        pass
358
 
 
359
 
 
360
 
def _translate_error(err):
361
 
    if isinstance(err, errors.NoSuchFile):
362
 
        return ('NoSuchFile', err.path)
363
 
    elif isinstance(err, errors.FileExists):
364
 
        return ('FileExists', err.path)
365
 
    elif isinstance(err, errors.DirectoryNotEmpty):
366
 
        return ('DirectoryNotEmpty', err.path)
367
 
    elif isinstance(err, errors.ShortReadvError):
368
 
        return ('ShortReadvError', err.path, str(err.offset), str(err.length),
369
 
                str(err.actual))
370
 
    elif isinstance(err, errors.UnstackableRepositoryFormat):
371
 
        return (('UnstackableRepositoryFormat', str(err.format), err.url))
372
 
    elif isinstance(err, errors.UnstackableBranchFormat):
373
 
        return ('UnstackableBranchFormat', str(err.format), err.url)
374
 
    elif isinstance(err, errors.NotStacked):
375
 
        return ('NotStacked',)
376
 
    elif isinstance(err, UnicodeError):
377
 
        # If it is a DecodeError, than most likely we are starting
378
 
        # with a plain string
379
 
        str_or_unicode = err.object
380
 
        if isinstance(str_or_unicode, unicode):
381
 
            # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
382
 
            # byte) in it, so this encoding could cause broken responses.
383
 
            # Newer clients use protocol v3, so will be fine.
384
 
            val = 'u:' + str_or_unicode.encode('utf-8')
385
 
        else:
386
 
            val = 's:' + str_or_unicode.encode('base64')
387
 
        # This handles UnicodeEncodeError or UnicodeDecodeError
388
 
        return (err.__class__.__name__, err.encoding, val, str(err.start),
389
 
                str(err.end), err.reason)
390
 
    elif isinstance(err, errors.TransportNotPossible):
391
 
        if err.msg == "readonly transport":
392
 
            return ('ReadOnlyError', )
393
 
    elif isinstance(err, errors.ReadError):
394
 
        # cannot read the file
395
 
        return ('ReadError', err.path)
396
 
    elif isinstance(err, errors.PermissionDenied):
397
 
        return ('PermissionDenied', err.path, err.extra)
398
 
    elif isinstance(err, errors.TokenMismatch):
399
 
        return ('TokenMismatch', err.given_token, err.lock_token)
400
 
    elif isinstance(err, errors.LockContention):
401
 
        return ('LockContention',)
402
 
    # Unserialisable error.  Log it, and return a generic error
403
 
    trace.log_exception_quietly()
404
 
    return ('error', str(err))
405
 
 
406
336
 
407
337
class HelloRequest(SmartServerRequest):
408
338
    """Answer a version request with the highest protocol version this server
423
353
        repo = control.open_repository()
424
354
        tmpf = tempfile.TemporaryFile()
425
355
        base_revision = revision.NULL_REVISION
426
 
        serializer.write_bundle(repo, revision_id, base_revision, tmpf)
 
356
        write_bundle(repo, revision_id, base_revision, tmpf)
427
357
        tmpf.seek(0)
428
358
        return SuccessfulSmartServerResponse((), tmpf.read())
429
359
 
443
373
request_handlers.register_lazy(
444
374
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
445
375
request_handlers.register_lazy(
446
 
    'Branch.get_config_file', 'bzrlib.smart.branch',
447
 
    'SmartServerBranchGetConfigFile')
448
 
request_handlers.register_lazy(
449
 
    'Branch.get_parent', 'bzrlib.smart.branch', 'SmartServerBranchGetParent')
450
 
request_handlers.register_lazy(
451
 
    'Branch.get_tags_bytes', 'bzrlib.smart.branch',
452
 
    'SmartServerBranchGetTagsBytes')
453
 
request_handlers.register_lazy(
454
 
    'Branch.set_tags_bytes', 'bzrlib.smart.branch',
455
 
    'SmartServerBranchSetTagsBytes')
456
 
request_handlers.register_lazy(
457
 
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
 
376
    'Branch.get_config_file', 'bzrlib.smart.branch', 'SmartServerBranchGetConfigFile')
458
377
request_handlers.register_lazy(
459
378
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
460
379
request_handlers.register_lazy(
461
380
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
462
 
request_handlers.register_lazy( 'Branch.revision_history',
463
 
    'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
464
 
request_handlers.register_lazy( 'Branch.set_config_option',
465
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetConfigOption')
466
 
request_handlers.register_lazy( 'Branch.set_last_revision',
467
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
 
381
request_handlers.register_lazy(
 
382
    'Branch.revision_history', 'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
 
383
request_handlers.register_lazy(
 
384
    'Branch.set_last_revision', 'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
468
385
request_handlers.register_lazy(
469
386
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
470
387
    'SmartServerBranchRequestSetLastRevisionInfo')
472
389
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
473
390
    'SmartServerBranchRequestSetLastRevisionEx')
474
391
request_handlers.register_lazy(
475
 
    'Branch.set_parent_location', 'bzrlib.smart.branch',
476
 
    'SmartServerBranchRequestSetParentLocation')
477
 
request_handlers.register_lazy(
478
392
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
479
393
request_handlers.register_lazy(
480
 
    'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
481
 
    'SmartServerBzrDirRequestCloningMetaDir')
482
 
request_handlers.register_lazy(
483
 
    'BzrDir.create_branch', 'bzrlib.smart.bzrdir',
484
 
    'SmartServerRequestCreateBranch')
485
 
request_handlers.register_lazy(
486
 
    'BzrDir.create_repository', 'bzrlib.smart.bzrdir',
487
 
    'SmartServerRequestCreateRepository')
488
 
request_handlers.register_lazy(
489
 
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir',
490
 
    'SmartServerRequestFindRepositoryV1')
491
 
request_handlers.register_lazy(
492
 
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir',
493
 
    'SmartServerRequestFindRepositoryV2')
494
 
request_handlers.register_lazy(
495
 
    'BzrDir.find_repositoryV3', 'bzrlib.smart.bzrdir',
496
 
    'SmartServerRequestFindRepositoryV3')
497
 
request_handlers.register_lazy(
498
 
    'BzrDir.get_config_file', 'bzrlib.smart.bzrdir',
499
 
    'SmartServerBzrDirRequestConfigFile')
500
 
request_handlers.register_lazy(
501
 
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir',
502
 
    'SmartServerRequestInitializeBzrDir')
503
 
request_handlers.register_lazy(
504
 
    'BzrDirFormat.initialize_ex_1.16', 'bzrlib.smart.bzrdir',
505
 
    'SmartServerRequestBzrDirInitializeEx')
506
 
request_handlers.register_lazy(
507
 
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')
508
 
request_handlers.register_lazy(
509
 
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir',
510
 
    'SmartServerRequestOpenBranch')
511
 
request_handlers.register_lazy(
512
 
    'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
513
 
    'SmartServerRequestOpenBranchV2')
 
394
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV1')
 
395
request_handlers.register_lazy(
 
396
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir', 'SmartServerRequestFindRepositoryV2')
 
397
request_handlers.register_lazy(
 
398
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir', 'SmartServerRequestInitializeBzrDir')
 
399
request_handlers.register_lazy(
 
400
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBranch')
514
401
request_handlers.register_lazy(
515
402
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
516
403
request_handlers.register_lazy(
537
424
    'readv', 'bzrlib.smart.vfs', 'ReadvRequest')
538
425
request_handlers.register_lazy(
539
426
    'rename', 'bzrlib.smart.vfs', 'RenameRequest')
540
 
request_handlers.register_lazy(
541
 
    'PackRepository.autopack', 'bzrlib.smart.packrepository',
542
 
    'SmartServerPackRepositoryAutopack')
543
427
request_handlers.register_lazy('Repository.gather_stats',
544
428
                               'bzrlib.smart.repository',
545
429
                               'SmartServerRepositoryGatherStats')
551
435
request_handlers.register_lazy(
552
436
    'Repository.has_revision', 'bzrlib.smart.repository', 'SmartServerRequestHasRevision')
553
437
request_handlers.register_lazy(
554
 
    'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
555
 
request_handlers.register_lazy(
556
 
    'Repository.insert_stream_locked', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStreamLocked')
557
 
request_handlers.register_lazy(
558
438
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
559
439
request_handlers.register_lazy(
560
440
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
561
441
request_handlers.register_lazy(
562
 
    'Repository.set_make_working_trees', 'bzrlib.smart.repository',
563
 
    'SmartServerRepositorySetMakeWorkingTrees')
564
 
request_handlers.register_lazy(
565
442
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
566
443
request_handlers.register_lazy(
567
 
    'Repository.get_rev_id_for_revno', 'bzrlib.smart.repository',
568
 
    'SmartServerRepositoryGetRevIdForRevno')
569
 
request_handlers.register_lazy(
570
 
    'Repository.get_stream', 'bzrlib.smart.repository',
571
 
    'SmartServerRepositoryGetStream')
572
 
request_handlers.register_lazy(
573
444
    'Repository.tarball', 'bzrlib.smart.repository',
574
445
    'SmartServerRepositoryTarball')
575
446
request_handlers.register_lazy(
578
449
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
579
450
request_handlers.register_lazy(
580
451
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')
 
452
request_handlers.register_lazy(
 
453
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')