1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 RTSP - Real Time Streaming Protocol.
24
25 See RFC 2326, and its Robin, RFC 2068.
26 """
27
28 import sys
29 import re
30 import types
31
32 from twisted.web import http
33 from twisted.web import server, resource
34 from twisted.internet import defer
35
36 from twisted.python import log, failure, reflect
37
38 try:
39 from twisted.protocols._c_urlarg import unquote
40 except ImportError:
41 from urllib import unquote
42
43 from flumotion.common import log as flog
44
45 __version__ = "$Rev$"
46
47 SERVER_PROTOCOL = "RTSP/1.0"
48
49
50 SERVER_STRING = "Flumotion RTP"
51
52
53 CONTINUE = 100
54
55 OK = 200
56 CREATED = 201
57 LOW_STORAGE = 250
58
59 MULTIPLE_CHOICE = 300
60 MOVED_PERMANENTLY = 301
61 MOVED_TEMPORARILY = 302
62 SEE_OTHER = 303
63 NOT_MODIFIED = 304
64 USE_PROXY = 305
65
66 BAD_REQUEST = 400
67 UNAUTHORIZED = 401
68 PAYMENT_REQUIRED = 402
69 FORBIDDEN = 403
70 NOT_FOUND = 404
71 NOT_ALLOWED = 405
72 NOT_ACCEPTABLE = 406
73 PROXY_AUTH_REQUIRED = 407
74 REQUEST_TIMEOUT = 408
75 GONE = 410
76 LENGTH_REQUIRED = 411
77 PRECONDITION_FAILED = 412
78 REQUEST_ENTITY_TOO_LARGE = 413
79 REQUEST_URI_TOO_LONG = 414
80 UNSUPPORTED_MEDIA_TYPE = 415
81
82 PARAMETER_NOT_UNDERSTOOD = 451
83 CONFERENCE_NOT_FOUND = 452
84 NOT_ENOUGH_BANDWIDTH = 453
85 SESSION_NOT_FOUND = 454
86 METHOD_INVALID_STATE = 455
87 HEADER_FIELD_INVALID = 456
88 INVALID_RANGE = 457
89 PARAMETER_READ_ONLY = 458
90 AGGREGATE_NOT_ALLOWED = 459
91 AGGREGATE_ONLY_ALLOWED = 460
92 UNSUPPORTED_TRANSPORT = 461
93 DESTINATION_UNREACHABLE = 462
94
95 INTERNAL_SERVER_ERROR = 500
96 NOT_IMPLEMENTED = 501
97 BAD_GATEWAY = 502
98 SERVICE_UNAVAILABLE = 503
99 GATEWAY_TIMEOUT = 504
100 RTSP_VERSION_NOT_SUPPORTED = 505
101 OPTION_NOT_SUPPORTED = 551
102
103 RESPONSES = {
104
105 CONTINUE: "Continue",
106
107
108 OK: "OK",
109 CREATED: "Created",
110 LOW_STORAGE: "Low on Storage Space",
111
112
113 MULTIPLE_CHOICE: "Multiple Choices",
114 MOVED_PERMANENTLY: "Moved Permanently",
115 MOVED_TEMPORARILY: "Moved Temporarily",
116 SEE_OTHER: "See Other",
117 NOT_MODIFIED: "Not Modified",
118 USE_PROXY: "Use Proxy",
119
120
121 BAD_REQUEST: "Bad Request",
122 UNAUTHORIZED: "Unauthorized",
123 PAYMENT_REQUIRED: "Payment Required",
124 FORBIDDEN: "Forbidden",
125 NOT_FOUND: "Not Found",
126 NOT_ALLOWED: "Method Not Allowed",
127 NOT_ACCEPTABLE: "Not Acceptable",
128 PROXY_AUTH_REQUIRED: "Proxy Authentication Required",
129 REQUEST_TIMEOUT: "Request Time-out",
130 GONE: "Gone",
131 LENGTH_REQUIRED: "Length Required",
132 PRECONDITION_FAILED: "Precondition Failed",
133 REQUEST_ENTITY_TOO_LARGE: "Request Entity Too Large",
134 REQUEST_URI_TOO_LONG: "Request-URI Too Large",
135 UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type",
136
137 PARAMETER_NOT_UNDERSTOOD: "Parameter Not Understood",
138 CONFERENCE_NOT_FOUND: "Conference Not Found",
139 NOT_ENOUGH_BANDWIDTH: "Not Enough Bandwidth",
140 SESSION_NOT_FOUND: "Session Not Found",
141 METHOD_INVALID_STATE: "Method Not Valid In This State",
142 HEADER_FIELD_INVALID: "Header Field Not Valid for Resource",
143 INVALID_RANGE: "Invalid Range",
144 PARAMETER_READ_ONLY: "Parameter is Read-Only",
145 AGGREGATE_NOT_ALLOWED: "Aggregate operation not allowed",
146 AGGREGATE_ONLY_ALLOWED: "Only aggregate operation allowed",
147 UNSUPPORTED_TRANSPORT: "Unsupported transport",
148 DESTINATION_UNREACHABLE: "Destination unreachable",
149
150
151 INTERNAL_SERVER_ERROR: "Internal Server Error",
152 NOT_IMPLEMENTED: "Not Implemented",
153 BAD_GATEWAY: "Bad Gateway",
154 SERVICE_UNAVAILABLE: "Service Unavailable",
155 GATEWAY_TIMEOUT: "Gateway Time-out",
156 RTSP_VERSION_NOT_SUPPORTED: "RTSP Version not supported",
157 OPTION_NOT_SUPPORTED: "Option not supported",
158 }
159
160
162 """An exception with the RTSP status code and a str as arguments"""
163
164
166 logCategory = 'request'
167 code = OK
168 code_message = RESPONSES[OK]
169 host = None
170 port = None
171
173 if key.lower() in self.headers.keys():
174 del self.headers[key.lower()]
175
176
177
178
179
189
191
192 if self.clientproto != SERVER_PROTOCOL:
193 e = ErrorResource(BAD_REQUEST)
194 self.render(e)
195 return
196
197
198 first = "%s %s %s" % (self.method, self.path, SERVER_PROTOCOL)
199 self.debug('incoming request: %s' % first)
200
201 lines = []
202 for key, value in self.received_headers.items():
203 lines.append("%s: %s" % (key, value))
204
205 self.debug('incoming headers:\n%s\n' % "\n".join(lines))
206
207
208
209
210
211
212
213
214 site = self.channel.site
215 ip = self.getClientIP()
216 site.logRequest(ip, first, lines)
217
218 if not self._processPath():
219 return
220
221 try:
222 if self.path == "*":
223 resrc = site.resource
224 else:
225 resrc = site.getResourceFor(self)
226 self.debug("RTSPRequest.process(): got resource %r" % resrc)
227 try:
228 self.render(resrc)
229 except server.UnsupportedMethod:
230 e = ErrorResource(OPTION_NOT_SUPPORTED)
231 self.setHeader('Allow', ",".join(resrc.allowedMethods))
232 self.render(e)
233 except RTSPError, e:
234 er = ErrorResource(e.args[0])
235 self.render(er)
236 except Exception, e:
237 self.warning('failed to process %s: %s' %
238 (lines and lines[0] or "[No headers]",
239 flog.getExceptionMessage(e)))
240 self.processingFailed(failure.Failure())
241
243
244 self.log("path %s" % self.path)
245
246 self.prepath = []
247
248
249 if self.path == '*':
250 self.log('Request-URI is *')
251 return True
252
253
254 matcher = re.compile('rtspu?://([^/]*)')
255 m = matcher.match(self.path)
256 hostport = None
257 if m:
258 hostport = m.expand('\\1')
259
260 if not hostport:
261
262 self.log('Absolute rtsp URL required: %s' % self.path)
263 self.render(ErrorResource(BAD_REQUEST,
264 "Malformed Request-URI %s" % self.path))
265 return False
266
267
268 rest = self.path.split(hostport)[1]
269 self.host = hostport
270 if ':' in hostport:
271 chunks = hostport.split(':')
272 self.host = chunks[0]
273 self.port = int(chunks[1])
274
275
276 self.postpath = map(unquote, rest.split('/'))
277 self.log(
278 'split up self.path in host %s, port %r, pre %r and post %r' % (
279 self.host, self.port, self.prepath, self.postpath))
280 return True
281
283 self.warningFailure(reason)
284
285 if not True:
286 self.debug('sending traceback to client')
287 import traceback
288 tb = sys.exc_info()[2]
289 text = "".join(traceback.format_exception(
290 reason.type, reason.value, tb))
291 else:
292 text = "RTSP server failed to process your request.\n"
293
294 self.setResponseCode(INTERNAL_SERVER_ERROR)
295 self.setHeader('Content-Type', "text/plain")
296 self.setHeader('Content-Length', str(len(text)))
297 self.write(text)
298 self.finish()
299 return reason
300
301 - def _error(self, code, *lines):
302 self.setResponseCode(code)
303 self.setHeader('content-type', "text/plain")
304 body = "\n".join(lines)
305 return body
306
308 self.log('%r.render(%r)' % (resrc, self))
309 result = resrc.render(self)
310 self.log('%r.render(%r) returned result %r' % (resrc, self, result))
311 if isinstance(result, defer.Deferred):
312 result.addCallback(self._renderCallback, resrc)
313 result.addErrback(self._renderErrback, resrc)
314 else:
315 self._renderCallback(result, resrc)
316
317
318
320 body = self._error(INTERNAL_SERVER_ERROR,
321 "Request failed: %r" % failure)
322 self.setHeader('Content-Length', str(len(body)))
323 lines = []
324 for key, value in self.headers.items():
325 lines.append("%s: %s" % (key, value))
326
327 self.channel.site.logReply(self.code, self.code_message, lines, body)
328
329 self.write(body)
330 self.finish()
331
333 body = result
334 if type(body) is not types.StringType:
335 self.warning('request did not return a string but %r' %
336 type(body))
337 body = self._error(INTERNAL_SERVER_ERROR,
338 "Request did not return a string",
339 "Request: " + reflect.safe_repr(self),
340 "Resource: " + reflect.safe_repr(resrc),
341 "Value: " + reflect.safe_repr(body))
342 self.setHeader('Content-Length', str(len(body)))
343
344 lines = []
345 for key, value in self.headers.items():
346 lines.append("%s: %s" % (key, value))
347
348 self.debug('responding to %s %s with %s (%d)' % (
349 self.method, self.path, self.code_message, self.code))
350 self.debug('outgoing headers:\n%s\n' % "\n".join(lines))
351 if body:
352 self.debug('body:\n%s\n' % body)
353 self.log('RTSPRequest._renderCallback(): outgoing response:\n%s\n' %
354 "\n".join(lines))
355 self.log("\n".join(lines))
356 self.log("\n")
357 self.log(body)
358
359 self.channel.site.logReply(self.code, self.code_message, lines, body)
360
361 self.write(body)
362 self.finish()
363
364
365
366
367
377
378
379
380
381
382
384 """
385 I am a ServerFactory that can be used in
386 L{twisted.internet.interfaces.IReactorTCP}'s .listenTCP
387 Create me with an L{RTSPResource} object.
388 """
389 protocol = RTSPChannel
390 requestFactory = RTSPRequest
391
392 - def logRequest(self, ip, requestLine, headerLines):
394
395 - def logReply(self, code, message, headerLines, body):
397
398
400 """
401 I am a base class for all RTSP Resource classes.
402
403 @type allowedMethods: tuple
404 @ivar allowedMethods: a tuple of allowed methods that can be invoked
405 on this resource.
406 """
407
408 logCategory = 'resource'
409 allowedMethods = ['OPTIONS']
410
412 return NoResource()
413
414 self.log(
415 'RTSPResource.getChild(%r, %s, <request>), pre %r, post %r' % (
416 self, path, request.prepath, request.postpath))
417 res = resource.Resource.getChild(self, path, request)
418 self.log('RTSPResource.getChild(%r, %s, <request>) returns %r' % (
419 self, path, res))
420 return res
421
423 self.log(
424 'RTSPResource.getChildWithDefault(%r, %s, <request>), pre %r, '
425 'post %r' % (
426 self, path, request.prepath, request.postpath))
427 self.log('children: %r' % self.children.keys())
428 res = resource.Resource.getChildWithDefault(self, path, request)
429 self.log(
430 'RTSPResource.getChildWithDefault(%r, %s, <request>) '
431 'returns %r' % (
432 self, path, res))
433 return res
434
435
436
438 self.log('RTSPResource.putChild(%r, %s, %r)' % (self, path, r))
439 return resource.Resource.putChild(self, path, r)
440
441
442
443
445 """
446 Set CSeq and Date on response to given request.
447 This should be done even for errors.
448 """
449 self.log('render_startCSeqDate, method %r' % method)
450 cseq = request.getHeader('CSeq')
451
452
453 if cseq == None:
454 cseq = 0
455 request.setHeader('CSeq', cseq)
456 request.setHeader('Date', http.datetimeToString())
457
459 ip = request.getClientIP()
460 self.log('RTSPResource.render_start(): client from %s requests %s' % (
461 ip, method))
462 self.log('RTSPResource.render_start(): uri %r' % request.path)
463
464 self.render_startCSeqDate(request, method)
465 request.setHeader('Server', SERVER_STRING)
466 request.delHeader('Content-Type')
467
468
469 request.setHeader('Last-Modified', http.datetimeToString())
470 request.setHeader('Cache-Control', 'must-revalidate')
471
472
473
474
475
476
477 if 'Real' in request.received_headers.get('user-agent', ''):
478 self.debug('Detected Real client, sending specific headers')
479
480
481
482
483 request.setHeader(
484 'Public',
485 'OPTIONS, DESCRIBE, ANNOUNCE, PLAY, SETUP, TEARDOWN')
486
487 request.setHeader('RealChallenge1',
488 '28d49444034696e1d523f2819b8dcf4c')
489
490
492
493 raise NotImplementedError
494
495
497
499 resource.Resource.__init__(self)
500 self.code = code
501 self.body = ""
502 if lines != (None, ):
503 self.body = "\n".join(lines) + "\n\n"
504
505
506 if not hasattr(self, 'method'):
507 self.method = 'GET'
508
516
518
519 raise NotImplementedError
520
523
524
529