Trees | Indices | Help |
---|
|
1 # -*- Mode: Python; test-case-name: flumotion.test.test_http -*- 2 # vi:si:et:sw=4:sts=4:ts=4 3 4 # Flumotion - a streaming media server 5 # Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. 6 # Copyright (C) 2010,2011 Flumotion Services, S.A. 7 # All rights reserved. 8 # 9 # This file may be distributed and/or modified under the terms of 10 # the GNU Lesser General Public License version 2.1 as published by 11 # the Free Software Foundation. 12 # This file is distributed without any warranty; without even the implied 13 # warranty of merchantability or fitness for a particular purpose. 14 # See "LICENSE.LGPL" in the source distribution for more information. 15 # 16 # Headers in this file shall remain intact. 17 18 import os 19 import time 20 import errno 21 import fcntl 22 import string 23 24 import gst 25 26 from twisted.web import server, resource as web_resource 27 from twisted.internet import reactor, defer 28 29 from flumotion.common import log 30 from flumotion.component.common.streamer import resources 31 3235 38114 115 ### resource.Resource methods 11640 if not self.streamer.hasCaps(): 41 self.debug('We have no caps yet') 42 return False 43 return True4446 # this is the callback attached to our flumotion component, 47 # not the GStreamer element 48 if fd in self._requests: 49 request = self._requests[fd] 50 self._removeClient(request, fd, stats) 51 else: 52 self.warning('[fd %5d] not found in _requests' % fd)5355 if stats: 56 bytes_sent = stats[0] 57 time_connected = int(stats[3] / gst.SECOND) 58 else: 59 bytes_sent = -1 60 time_connected = -1 61 return self.logWrite(request, bytes_sent, time_connected)6264 """ 65 Removes a request and add logging. 66 Note that it does not disconnect the client; it is called in reaction 67 to a client disconnecting. 68 69 @param request: the request 70 @type request: L{twisted.protocols.http.Request} 71 @param fd: the file descriptor for the client being removed 72 @type fd: L{int} 73 @param stats: the statistics for the removed client 74 @type stats: GValueArray 75 """ 76 # PROBE: finishing request; see httpserver.httpserver 77 self.debug('[fd %5d] (ts %f) finishing request %r', 78 request.transport.fileno(), time.time(), request) 79 80 ip = request.getClientIP() 81 if self._logRequestFromIP(ip): 82 d = self._logWrite(request, stats) 83 else: 84 d = defer.succeed(True) 85 self.info('[fd %5d] Client from %s disconnected' % (fd, ip)) 86 87 # We can't call request.finish(), since we already "stole" the fd, we 88 # just loseConnection on the transport directly, and delete the 89 # Request object, after cleaning up the bouncer bits. 90 self.httpauth.cleanupAuth(fd) 91 92 self.debug('[fd %5d] (ts %f) closing transport %r', fd, time.time(), 93 request.transport) 94 # This will close the underlying socket. We first remove the request 95 # from our fd->request map, since the moment we call this the fd might 96 # get re-added. 97 request.transport.loseConnection() 98 99 self.debug('[fd %5d] closed transport %r' % (fd, request.transport)) 100 101 def _done(_): 102 if fd in self._removing: 103 self.debug("client is removed; firing deferred") 104 removeD = self._removing.pop(fd) 105 removeD.callback(None) 106 107 resources.HTTPStreamingResource._removeClient(self, fd) 108 # PROBE: finished request; see httpserver.httpserver 109 self.debug('[fd %5d] (ts %f) finished request %r', 110 fd, time.time(), request)111 112 d.addCallback(_done) 113 return d118 # PROBE: authenticated request; see httpserver.httpfile 119 self.debug('[fd %5d] (ts %f) authenticated request %r', 120 request.transport.fileno(), time.time(), request) 121 122 if request.method == 'GET': 123 self._handleNewClient(request) 124 elif request.method == 'HEAD': 125 self.debug('handling HEAD request') 126 self._writeHeaders(request) 127 request.finish() 128 else: 129 raise AssertionError 130 131 return res132134 # Mimic Twisted as close as possible 135 headers = [] 136 for name, value in request.headers.items(): 137 headers.append('%s: %s\r\n' % (name, value)) 138 for cookie in request.cookies: 139 headers.append('%s: %s\r\n' % ("Set-Cookie", cookie)) 140 return headers141143 """ 144 Write out the HTTP headers for the incoming HTTP request. 145 146 @rtype: boolean 147 @returns: whether or not the file descriptor can be used further. 148 """ 149 fd = request.transport.fileno() 150 fdi = request.fdIncoming 151 152 # the fd could have been closed, in which case it will be -1 153 if fd == -1: 154 self.info('[fd %5d] Client gone before writing header' % fdi) 155 # FIXME: do this ? del request 156 return False 157 if fd != request.fdIncoming: 158 self.warning('[fd %5d] does not match current fd %d' % (fdi, fd)) 159 # FIXME: do this ? del request 160 return False 161 162 self._setRequestHeaders(request) 163 request.setHeader('Connection', 'close') 164 165 # Call request modifiers 166 for modifier in self.modifiers: 167 modifier.modify(request) 168 169 headers = self._formatHeaders(request) 170 171 ### FIXME: there's a window where Twisted could have removed the 172 # fd because the client disconnected. Catch EBADF correctly here. 173 try: 174 # TODO: This is a non-blocking socket, we really should check 175 # return values here, or just let twisted handle all of this 176 # normally, and not hand off the fd until after twisted has 177 # finished writing the headers. 178 os.write(fd, 'HTTP/1.0 200 OK\r\n%s\r\n' % ''.join(headers)) 179 # tell TwistedWeb we already wrote headers ourselves 180 request.startedWriting = True 181 return True 182 except OSError, (no, s): 183 if no == errno.EBADF: 184 self.info('[fd %5d] client gone before writing header' % fd) 185 elif no == errno.ECONNRESET: 186 self.info( 187 '[fd %5d] client reset connection writing header' % fd) 188 else: 189 self.info( 190 '[fd %5d] unhandled write error when writing header: %s' 191 % (fd, s)) 192 # trigger cleanup of request 193 del request 194 return False195197 # everything fulfilled, serve to client 198 fdi = request.fdIncoming 199 if not self._writeHeaders(request): 200 self.debug("[fd %5d] not adding as a client" % fdi) 201 return 202 203 # take over the file descriptor from Twisted by removing them from 204 # the reactor 205 # spiv told us to remove* on request.transport, and that works 206 # then we figured out that a new request is only a Reader, so we 207 # remove the removedWriter - this is because we never write to the 208 # socket through twisted, only with direct os.write() calls from 209 # _writeHeaders. 210 211 # see http://twistedmatrix.com/trac/ticket/1796 for a guarantee 212 # that this is a supported way of stealing the socket 213 fd = fdi 214 self.debug("[fd %5d] taking away from Twisted" % fd) 215 reactor.removeReader(request.transport) 216 #reactor.removeWriter(request.transport) 217 218 # check if it's really an open fd (i.e. that twisted didn't close it 219 # before the removeReader() call) 220 try: 221 fcntl.fcntl(fd, fcntl.F_GETFL) 222 except IOError, e: 223 if e.errno == errno.EBADF: 224 self.warning("[fd %5d] is not actually open, ignoring" % fd) 225 else: 226 self.warning("[fd %5d] error during check: %s (%d)" % ( 227 fd, e.strerror, e.errno)) 228 return 229 230 self._addClient(fd, request) 231 232 # hand it to multifdsink 233 self.streamer.add_client(fd, request) 234 ip = request.getClientIP() 235 236 # PROBE: started request; see httpfile.httpfile 237 self.debug('[fd %5d] (ts %f) started request %r', 238 fd, time.time(), request) 239 240 self.info('[fd %5d] Started streaming to %s' % (fd, ip))241243 fd = request.transport.fileno() 244 # we store the fd again in the request using it as an id for later 245 # on, so we can check when an fd went away (being -1) inbetween 246 request.fdIncoming = fd 247 248 # PROBE: incoming request; see httpserver.httpfile 249 self.debug('[fd %5d] (ts %f) incoming request %r', 250 fd, time.time(), request) 251 252 self.info('[fd %5d] Incoming client connection from %s' % ( 253 fd, request.getClientIP())) 254 self.debug('[fd %5d] _render(): request %s' % ( 255 fd, request)) 256 257 if not self.isReady(): 258 return self._handleNotReady(request) 259 elif self.reachedServerLimits(): 260 return self._handleServerFull(request) 261 262 self.debug('_render(): asked for (possible) authentication') 263 d = self.httpauth.startAuthentication(request) 264 d.addCallback(self.handleAuthenticatedRequest, request) 265 # Authentication has failed and we've written a response; nothing 266 # more to do 267 d.addErrback(lambda x: None) 268 269 # we MUST return this from our _render. 270 return server.NOT_DONE_YET271 272 render_GET = _render 273 render_HEAD = _render 274 275277 logCategory = "httproot" 278291280 # we override this method so that we can look up tree resources 281 # directly without having their parents. 282 # There's probably a more Twisted way of doing this, but ... 283 fullPath = path 284 if request.postpath: 285 fullPath += '/' + string.join(request.postpath, '/') 286 self.debug("[fd %5d] Incoming request %r for path %s", 287 request.transport.fileno(), request, fullPath) 288 r = web_resource.Resource.getChildWithDefault(self, fullPath, request) 289 self.debug("Returning resource %r" % r) 290 return r
Trees | Indices | Help |
---|
Generated by Epydoc 3.0.1 on Sat Jun 27 14:35:54 2015 | http://epydoc.sourceforge.net |