Исправлена обработка зависания VLC.
[vpproxy.git] / vphttp.py
index 904656368a089d160b6524b3c09521fcdcbbc3e4..33da300519327ebbb4e4770762463afccafd7b3b 100644 (file)
--- a/vphttp.py
+++ b/vphttp.py
@@ -9,8 +9,9 @@ import traceback
 import gevent
 import gevent.monkey
 # Monkeypatching and all the stuff
 import gevent
 import gevent.monkey
 # Monkeypatching and all the stuff
-
 gevent.monkey.patch_all()
 gevent.monkey.patch_all()
+# Startup delay for daemon restart
+gevent.sleep(5)
 import glob
 import os
 import signal
 import glob
 import os
 import signal
@@ -26,6 +27,7 @@ import hashlib
 import vpconfig
 from vpconfig import VPConfig
 import vlcclient
 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
 import plugins.modules.ipaddr as ipaddr
 from clientcounter import ClientCounter
 from plugins.modules.PluginInterface import VPProxyPlugin
@@ -35,7 +37,9 @@ try:
 except ImportError:
     pass
 
 except ImportError:
     pass
 
+import uuid
 
 
+from apscheduler.schedulers.background import BackgroundScheduler
 
 class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 
 
 class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 
@@ -84,11 +88,12 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 
         try:
             while True:
 
         try:
             while True:
-
+            
                 if not self.clientconnected:
                     logger.debug("Client is not connected, terminating")
                     break
 
                 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)
                 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
         '''
         '''
         GET request handler
         '''
+
         logger = logging.getLogger('http_HTTPHandler')
         self.clientconnected = True
         # Don't wait videodestroydelay if error happened
         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]
 
         # 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) \
         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
             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:
                 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):
         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")
         # 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')
 
         # 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:
         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
                 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 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)
 
         # 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
             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()
 
             logger.debug("hangDetector spawned")
             gevent.sleep()
 
-            # Initializing VPClient
-
             # Getting URL
             self.errorhappened = False
 
             # Getting URL
             self.errorhappened = False
 
-            print shouldcreatevp
             if shouldcreatevp:
                 logger.debug("Got url " + self.path_unquoted)
                 # Force ffmpeg demuxing if set in config
             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 = ''
 
                 else:
                     self.vlcprefix = ''
 
+                logger.info("Starting broadcasting "+self.path)                    
                 VPStuff.vlcclient.startBroadcast(
                 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)
                 # 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']
                     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])
                 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")
                 # End headers. Next goes video data
                 self.end_headers()
                 logger.debug("Headers sent")
+                self.headerssent = True
 
             # Run proxyReadWrite
             self.proxyReadWrite()
 
             # 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")
             # 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
         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
             # Unknown exception
             logger.error(traceback.format_exc())
             self.errorhappened = True
-            raise
             self.dieWithError()
         finally:
             logger.debug("END REQUEST")
             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):
 
 
 class HTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
 
@@ -337,6 +371,7 @@ class VPStuff(object):
     Inter-class interaction class
     '''
     vlcclient=None
     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'):
 
 # 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:
         continue
     logger.debug('Plugin loaded: ' + plugname)
     for j in plugininstance.handlers:
+        logger.info("Registering handler '" + j +"'")
         VPStuff.pluginshandlers[j] = plugininstance
     VPStuff.pluginlist.append(plugininstance)
 
         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:
 # 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:
         gevent.sleep(delay)
         return True
     except:
@@ -430,7 +467,6 @@ def connectVLC():
             out_port=VPConfig.vlcoutport)
         return True
     except vlcclient.VlcException as e:
             out_port=VPConfig.vlcoutport)
         return True
     except vlcclient.VlcException as e:
-        print repr(e)
         return False
 
 def isRunning(process):
         return False
 
 def isRunning(process):
@@ -464,7 +500,18 @@ def clean_proc():
         gevent.sleep(1)
     if isRunning(VPStuff.vlc):
         # or not :)
         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):
 
 # 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')
 
     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)
 # setting signal handlers
 try:
     gevent.signal(signal.SIGHUP, _reloadconfig)
@@ -511,11 +567,13 @@ else:
 
 try:
     logger.info("Using gevent %s" % gevent.__version__)
 
 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:
     logger.info("Using VLC %s" % VPStuff.vlcclient._vlcver)
     logger.info("Server started.")
     while True:
+
         if not isRunning(VPStuff.vlc):
         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))
             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)
                 logger.error("Cannot spawn VLC!")
                 clean_proc()
                 sys.exit(1)
+
         # Return to our server tasks
         server.handle_request()
         # 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):
 except (KeyboardInterrupt, SystemExit):
+    sched.shutdown()
     shutdown()
     shutdown()