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
33 from twisted.web import server, resource, util
34 from twisted.internet import reactor, defer
35
36 from twisted.python import log, failure, reflect
37
38 from flumotion.twisted import http
39
40 try:
41 from twisted.protocols._c_urlarg import unquote
42 except ImportError:
43 from urllib import unquote
44
45 from flumotion.common import log as flog
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
161 """An exception with the RTSP status code and a str as arguments"""
162
164 logCategory = 'request'
165 code = OK
166 code_message = RESPONSES[OK]
167 host = None
168 port = None
169
171 if key.lower() in self.headers.keys():
172 del self.headers[key.lower()]
173
174
175
176
186
188
189 if self.clientproto != SERVER_PROTOCOL:
190 e = ErrorResource(BAD_REQUEST)
191 self.render(e)
192 return
193
194
195 first = "%s %s %s" % (self.method, self.path, SERVER_PROTOCOL)
196 self.debug('incoming request: %s' % first)
197
198 lines = []
199 for key, value in self.received_headers.items():
200 lines.append("%s: %s" % (key, value))
201
202 self.debug('incoming headers:\n%s\n' % "\n".join(lines))
203
204
205
206
207
208
209
210
211 site = self.channel.site
212 ip = self.getClientIP()
213 site.logRequest(ip, first, lines)
214
215 if not self._processPath():
216 return
217
218 try:
219 if self.path == "*":
220 resrc = site.resource
221 else:
222 resrc = site.getResourceFor(self)
223 self.debug("RTSPRequest.process(): got resource %r" % resrc)
224 try:
225 self.render(resrc)
226 except server.UnsupportedMethod:
227 e = ErrorResource(OPTION_NOT_SUPPORTED)
228 self.setHeader('Allow', ",".join(resrc.allowedMethods))
229 self.render(e)
230 except RTSPError, e:
231 er = ErrorResource(e.args[0])
232 self.render(er)
233 except Exception, e:
234 self.warning('failed to process %s: %s' %
235 (lines and lines[0] or "[No headers]",
236 flog.getExceptionMessage(e)))
237 self.processingFailed(failure.Failure())
238
240
241 self.log("path %s" % self.path)
242
243 self.prepath = []
244
245
246 if self.path == '*':
247 self.log('Request-URI is *')
248 return True
249
250
251 matcher = re.compile('rtspu?://([^/]*)')
252 m = matcher.match(self.path)
253 hostport = None
254 if m:
255 hostport = m.expand('\\1')
256
257 if not hostport:
258
259 self.log('Absolute rtsp URL required: %s' % self.path)
260 self.render(ErrorResource(BAD_REQUEST,
261 "Malformed Request-URI %s" % self.path))
262 return False
263
264
265 rest = self.path.split(hostport)[1]
266 self.host = hostport
267 if ':' in hostport:
268 chunks = hostport.split(':')
269 self.host = chunks[0]
270 self.port = int(chunks[1])
271
272
273 self.postpath = map(unquote, rest.split('/'))
274 self.log('split up self.path in host %s, port %r, pre %r and post %r' % (
275 self.host, self.port, self.prepath, self.postpath))
276 return True
277
279 self.warningFailure(reason)
280
281 if not True:
282 self.debug('sending traceback to client')
283 import traceback
284 tb = sys.exc_info()[2]
285 text = "".join(traceback.format_exception(
286 reason.type, reason.value, tb))
287 else:
288 text = "RTSP server failed to process your request.\n"
289
290 self.setResponseCode(INTERNAL_SERVER_ERROR)
291 self.setHeader('Content-Type', "text/plain")
292 self.setHeader('Content-Length', str(len(text)))
293 self.write(text)
294 self.finish()
295 return reason
296
297 - def _error(self, code, *lines):
298 self.setResponseCode(code)
299 self.setHeader('content-type', "text/plain")
300 body = "\n".join(lines)
301 return body
302
304 self.log('%r.render(%r)' % (resrc, self))
305 result = resrc.render(self)
306 self.log('%r.render(%r) returned result %r' % (resrc, self, result))
307 if isinstance(result, defer.Deferred):
308 result.addCallback(self._renderCallback, resrc)
309 result.addErrback(self._renderErrback, resrc)
310 else:
311 self._renderCallback(result, resrc)
312
313
326
328 body = result
329 if type(body) is not types.StringType:
330 self.warning('request did not return a string but %r' %
331 type(body))
332 body = self._error(INTERNAL_SERVER_ERROR,
333 "Request did not return a string",
334 "Request: " + reflect.safe_repr(self),
335 "Resource: " + reflect.safe_repr(resrc),
336 "Value: " + reflect.safe_repr(body))
337 self.setHeader('Content-Length', str(len(body)))
338
339 lines = []
340 for key, value in self.headers.items():
341 lines.append("%s: %s" % (key, value))
342
343 self.debug('responding to %s %s with %s (%d)' % (
344 self.method, self.path, self.code_message, self.code))
345 self.debug('outgoing headers:\n%s\n' % "\n".join(lines))
346 if body:
347 self.debug('body:\n%s\n' % body)
348 self.log('RTSPRequest._renderCallback(): outgoing response:\n%s\n' %
349 "\n".join(lines))
350 self.log("\n".join(lines))
351 self.log("\n")
352 self.log(body)
353
354 self.channel.site.logReply(self.code, self.code_message, lines, body)
355
356 self.write(body)
357 self.finish()
358
359
360
370
371
372
373
374
376 """
377 I am a ServerFactory that can be used in
378 L{twisted.internet.interfaces.IReactorTCP.listenTCP}
379 Create me with an L{RTSPResource} object.
380 """
381 protocol = RTSPChannel
382 requestFactory = RTSPRequest
383
384 - def logRequest(self, ip, requestLine, headerLines):
386 - def logReply(self, code, message, headerLines, body):
388
390 """
391 I am a base class for all RTSP Resource classes.
392
393 @type allowedMethods: tuple
394 @ivar allowedMethods: a tuple of allowed methods that can be invoked
395 on this resource.
396 """
397
398 logCategory = 'resource'
399 allowedMethods = ['OPTIONS']
400
402 return NoResource()
403
404 self.log('RTSPResource.getChild(%r, %s, <request>), pre %r, post %r' % (
405 self, path, request.prepath, request.postpath))
406 res = resource.Resource.getChild(self, path, request)
407 self.log('RTSPResource.getChild(%r, %s, <request>) returns %r' % (
408 self, path, res))
409 return res
410
412 self.log('RTSPResource.getChildWithDefault(%r, %s, <request>), pre %r, post %r' % (
413 self, path, request.prepath, request.postpath))
414 self.log('children: %r' % self.children.keys())
415 res = resource.Resource.getChildWithDefault(self, path, request)
416 self.log('RTSPResource.getChildWithDefault(%r, %s, <request>) returns %r' % (
417 self, path, res))
418 return res
419
420
422 self.log('RTSPResource.putChild(%r, %s, %r)' % (self, path, r))
423 return resource.Resource.putChild(self, path, r)
424
425
426
428 """
429 Set CSeq and Date on response to given request.
430 This should be done even for errors.
431 """
432 self.log('render_startCSeqDate, method %r' % method)
433 cseq = request.getHeader('CSeq')
434
435
436 if cseq == None:
437 cseq = 0
438 request.setHeader('CSeq', cseq)
439 request.setHeader('Date', http.datetimeToString())
440
442 ip = request.getClientIP()
443 self.log('RTSPResource.render_start(): client from %s requests %s' % (
444 ip, method))
445 self.log('RTSPResource.render_start(): uri %r' % request.path)
446
447 self.render_startCSeqDate(request, method)
448 request.setHeader('Server', SERVER_STRING)
449 request.delHeader('Content-Type')
450
451
452 request.setHeader('Last-Modified', http.datetimeToString())
453 request.setHeader('Cache-Control', 'must-revalidate')
454
455
456
457
458
459
460 if 'Real' in request.received_headers.get('user-agent', ''):
461 self.debug('Detected Real client, sending specific headers')
462
463
464
465 request.setHeader('Public', 'OPTIONS, DESCRIBE, ANNOUNCE, PLAY, SETUP, TEARDOWN')
466
467 request.setHeader('RealChallenge1', '28d49444034696e1d523f2819b8dcf4c')
468
469
471
472 raise NotImplementedError
473
476 resource.Resource.__init__(self)
477 self.code = code
478 self.body = ""
479 if lines != (None, ):
480 self.body = "\n".join(lines) + "\n\n"
481
482
483 if not hasattr(self, 'method'):
484 self.method = 'GET'
485
493
495
496 raise NotImplementedError
497
500
504