X-Git-Url: https://git.rvb.name/vpproxy.git/blobdiff_plain/1e2c0e679beb33f10c2e551768c17ccd77576b54..db69bdf8a7d5d345009f548b72c4a2ae40c565cc:/vphttp.py diff --git a/vphttp.py b/vphttp.py index 9046563..33da300 100644 --- a/vphttp.py +++ b/vphttp.py @@ -9,8 +9,9 @@ import traceback import gevent import gevent.monkey # Monkeypatching and all the stuff - gevent.monkey.patch_all() +# Startup delay for daemon restart +gevent.sleep(5) import glob import os import signal @@ -26,6 +27,7 @@ import hashlib import vpconfig from vpconfig import VPConfig import vlcclient +import gc import plugins.modules.ipaddr as ipaddr from clientcounter import ClientCounter from plugins.modules.PluginInterface import VPProxyPlugin @@ -35,7 +37,9 @@ try: except ImportError: pass +import uuid +from apscheduler.schedulers.background import BackgroundScheduler class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): @@ -84,11 +88,12 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): try: while True: - + if not self.clientconnected: logger.debug("Client is not connected, terminating") break + VPStuff.vlcclient.mark(self.vlcid) data = self.video.read(4096) if data and self.clientconnected: self.wfile.write(data) @@ -133,6 +138,7 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): ''' GET request handler ''' + logger = logging.getLogger('http_HTTPHandler') self.clientconnected = True # Don't wait videodestroydelay if error happened @@ -144,6 +150,14 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): # Connected client IP address self.clientip = self.request.getpeername()[0] + req_headers = self.headers + self.client_data = { + 'ip': self.clientip, + 'forwarded-for': req_headers.get('X-Forwarded-For'), + 'client-agent': req_headers.get('User-Agent'), + 'uuid': uuid.uuid4() + } + if VPConfig.firewall: # If firewall enabled self.clientinrange = any(map(lambda i: ipaddr.IPAddress(self.clientip) \ @@ -163,7 +177,7 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.reqtype = self.splittedpath[1].lower() # If first parameter is 'pid' or 'torrent' or it should be handled # by plugin - if not (self.reqtype=='get' or self.reqtype in VPStuff.pluginshandlers): + if not (self.reqtype in ('get','mp4','ogg','ogv') or self.reqtype in VPStuff.pluginshandlers): self.dieWithError(400) # 400 Bad Request return except IndexError: @@ -184,7 +198,7 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.handleRequest(headers_only) def handleRequest(self, headers_only): - + # Limit concurrent connections if 0 < VPConfig.maxconns <= VPStuff.clientcounter.total: logger.debug("Maximum connections reached, can't serve this") @@ -193,6 +207,7 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): # Pretend to work fine with Fake UAs or HEAD request. useragent = self.headers.get('User-Agent') + logger.debug("HTTP User Agent:"+useragent) fakeua = useragent and useragent in VPConfig.fakeuas if headers_only or fakeua: if fakeua: @@ -214,26 +229,37 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.params.append('0') # Adding client to clientcounter - clients = VPStuff.clientcounter.add(self.path_unquoted, self.clientip) + clients = VPStuff.clientcounter.add(self.reqtype+'/'+self.path_unquoted, self.client_data) # If we are the one client, but sucessfully got vp instance from clientcounter, # then somebody is waiting in the videodestroydelay state # Check if we are first client - if VPStuff.clientcounter.get(self.path_unquoted)==1: - logger.debug("First client, should create VLC session") - shouldcreatevp = True - else: - logger.debug("Can reuse existing session") - shouldcreatevp = False - self.vlcid = hashlib.md5(self.path_unquoted).hexdigest() + self.vlcid = hashlib.md5(self.reqtype+'/'+self.path_unquoted).hexdigest() + + try: + if not VPStuff.vlcclient.check_stream(self.vlcid): + logger.debug("First client, should create VLC session") + shouldcreatevp = True + else: + logger.debug("Can reuse existing session") + shouldcreatevp = False + except Exception as e: + logger.error('Plugin exception: ' + repr(e)) + logger.error(traceback.format_exc()) + self.dieWithError() # Send fake headers if this User-Agent is in fakeheaderuas tuple if fakeua: logger.debug( "Sending fake headers for " + useragent) self.send_response(200) - self.send_header("Content-Type", "video/mpeg") + self.send_header('Cache-Control','no-cache, no-store, must-revalidate'); + self.send_header('Pragma','no-cache'); + if self.reqtype in ("ogg","ogv"): + self.send_header("Content-Type", "video/ogg") + else: + self.send_header("Content-Type", "video/mpeg") self.end_headers() # Do not send real headers at all self.headerssent = True @@ -243,12 +269,9 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): logger.debug("hangDetector spawned") gevent.sleep() - # Initializing VPClient - # Getting URL self.errorhappened = False - print shouldcreatevp if shouldcreatevp: logger.debug("Got url " + self.path_unquoted) # Force ffmpeg demuxing if set in config @@ -257,8 +280,9 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): else: self.vlcprefix = '' + logger.info("Starting broadcasting "+self.path) VPStuff.vlcclient.startBroadcast( - self.vlcid, self.vlcprefix + self.path_unquoted, VPConfig.vlcmux, VPConfig.vlcpreaccess) + self.vlcid, self.vlcprefix + self.path_unquoted, VPConfig.vlcmux, VPConfig.vlcpreaccess, self.reqtype) # Sleep a bit, because sometimes VLC doesn't open port in # time gevent.sleep(0.5) @@ -284,14 +308,26 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): del self.video.info().dict['server'] if self.video.info().dict.has_key('transfer-encoding'): del self.video.info().dict['transfer-encoding'] + if self.video.info().dict.has_key('content-type'): + del self.video.info().dict['content-type'] if self.video.info().dict.has_key('keep-alive'): del self.video.info().dict['keep-alive'] for key in self.video.info().dict: self.send_header(key, self.video.info().dict[key]) + + self.send_header('Cache-Control','no-cache, no-store, must-revalidate'); + self.send_header('Pragma','no-cache'); + + if self.reqtype=="ogg": + self.send_header("Content-Type", "video/ogg") + else: + self.send_header("Content-Type", "video/mpeg") + # End headers. Next goes video data self.end_headers() logger.debug("Headers sent") + self.headerssent = True # Run proxyReadWrite self.proxyReadWrite() @@ -299,7 +335,12 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): # Waiting until hangDetector is joined self.hanggreenlet.join() logger.debug("Request handler finished") - + except (vlcclient.VlcException) as e: + logger.error("Exception: " + repr(e)) + VPStuff.vlcerrors = VPStuff.vlcerrors + 1 + logger.error("%s error(s) communicating VLC") + self.errorhappened = True + self.dieWithError() except (vpclient.VPException, vlcclient.VlcException, urllib2.URLError) as e: logger.error("Exception: " + repr(e)) self.errorhappened = True @@ -311,19 +352,12 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): # Unknown exception logger.error(traceback.format_exc()) self.errorhappened = True - raise self.dieWithError() finally: logger.debug("END REQUEST") - VPStuff.clientcounter.delete(self.path_unquoted, self.clientip) - if not VPStuff.clientcounter.get(self.path_unquoted): - try: - logger.debug("That was the last client, destroying VPClient") - VPStuff.vlcclient.stopBroadcast(self.vlcid) - except: - pass - self.vp.destroy() - + logger.info("Closed connection from " + self.clientip + " path " + self.path) + VPStuff.clientcounter.delete(self.reqtype+'/'+self.path_unquoted, self.client_data) + self.vp.destroy() class HTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): @@ -337,6 +371,7 @@ class VPStuff(object): Inter-class interaction class ''' vlcclient=None + vlcerrors=0 # taken from http://stackoverflow.com/questions/2699907/dropping-root-permissions-in-python def drop_privileges(uid_name, gid_name='nogroup'): @@ -390,6 +425,7 @@ for i in pluginslist: continue logger.debug('Plugin loaded: ' + plugname) for j in plugininstance.handlers: + logger.info("Registering handler '" + j +"'") VPStuff.pluginshandlers[j] = plugininstance VPStuff.pluginlist.append(plugininstance) @@ -417,7 +453,8 @@ DEVNULL = open(os.devnull, 'wb') # Spawning procedures def spawnVLC(cmd, delay = 0): try: - VPStuff.vlc = psutil.Popen(cmd, stdout=DEVNULL, stderr=DEVNULL) + VPStuff.vlc = psutil.Popen(cmd) #, stdout=DEVNULL, stderr=DEVNULL) + VPStuff.vlcerrors = 0 gevent.sleep(delay) return True except: @@ -430,7 +467,6 @@ def connectVLC(): out_port=VPConfig.vlcoutport) return True except vlcclient.VlcException as e: - print repr(e) return False def isRunning(process): @@ -464,7 +500,18 @@ def clean_proc(): gevent.sleep(1) if isRunning(VPStuff.vlc): # or not :) - VPStuff.vlc.kill() + VPStuff.vlc.terminate() + gevent.sleep(1) + if isRunning(VPStuff.vlc): + VPStuff.vlc.kill() + del VPStuff.vlc + +def restartVLC(cmd, delay = 0): + clean_proc() + if spawnVLC(cmd, delay): + if connectVLC(): + return True + return False # This is what we call to stop the server completely def shutdown(signum = 0, frame = 0): @@ -493,6 +540,15 @@ def _reloadconfig(signum=None, frame=None): from vpconfig import VPConfig logger.info('Config reloaded') +sched = BackgroundScheduler() +sched.start() + +def clean_streams(): + if VPStuff.vlcclient: + VPStuff.vlcclient.clean_streams(VPConfig.videodestroydelay) + +job = sched.add_job(clean_streams, 'interval', seconds=15) + # setting signal handlers try: gevent.signal(signal.SIGHUP, _reloadconfig) @@ -511,11 +567,13 @@ else: try: logger.info("Using gevent %s" % gevent.__version__) - logger.info("Using psutil %s" % psutil.__version__) + logger.info("Usig psutil %s" % psutil.__version__) logger.info("Using VLC %s" % VPStuff.vlcclient._vlcver) logger.info("Server started.") while True: + if not isRunning(VPStuff.vlc): + del VPStuff.vlc if spawnVLC(VPStuff.vlcProc, VPConfig.vlcspawntimeout) and connectVLC(): logger.info("VLC died, respawned it with pid " + str(VPStuff.vlc.pid)) @@ -523,7 +581,18 @@ try: logger.error("Cannot spawn VLC!") clean_proc() sys.exit(1) + # Return to our server tasks server.handle_request() + + if VPStuff.vlcerrors>5: + if restartVLC(VPStuff.vlcProc, VPConfig.vlcspawntimeout): + logger.info("VLC hung, respawned it with pid " + str(VPStuff.vlc.pid)) + else: + logger.error("Cannot spawn VLC!") + clean_proc() + sys.exit(1) + except (KeyboardInterrupt, SystemExit): + sched.shutdown() shutdown()