コード例 #1
0
    def handle(self, connection, headers_only=False):
        play = False
        # 30 minutes cache
        if not self.playlist or (gevent.time.time() - self.playlisttime > 30 * 60):
           if not self.Playlistparser(): connection.dieWithError(); return

        url = urlparse(connection.path)
        path = url.path[0:-1] if url.path.endswith('/') else url.path
        params = parse_qs(connection.query)

        if path.startswith('/%s/channel/' % connection.reqtype):
           if not path.endswith('.ts'):
              connection.dieWithError(404, 'Invalid path: %s' % unquote(path), logging.ERROR)
              return
           name = ensure_text(unquote(path[path.rfind('/')+1:]))
           url = self.channels.get('.'.join(name.split('.')[:-1]))
           if url is None:
              connection.dieWithError(404, 'Unknown channel: ' + name, logging.ERROR)
              return
           elif url.startswith('acestream://'):
              connection.path = '/content_id/%s/%s' % (url.split('/')[2], name)
           elif url.startswith('infohash://'):
              connection.path = '/infohash/%s/%s' % (url.split('/')[2], name)
           elif url.startswith(('http://', 'https://')) and url.endswith(('.acelive', '.acestream', '.acemedia', '.torrent')):
              connection.path = '/url/%s/%s' % (quote(url,''), name)
           connection.splittedpath = connection.path.split('/')
           connection.reqtype = connection.splittedpath[1].lower()
           name = name.split('.')[0]
           play = True
        elif self.etag == connection.headers.get('If-None-Match'):
           self.logger.debug('ETag matches - returning 304')
           connection.send_response(304)
           connection.send_header('Connection', 'close')
           connection.end_headers()
           return
        else:
           hostport = connection.headers['Host']
           path = '' if not self.channels else '/%s/channel' % connection.reqtype
           add_ts = True if path.endswith('/ts') else False
           exported = self.playlist.exportm3u(hostport=hostport, path=path, add_ts=add_ts, header=config.m3uheadertemplate, fmt=params.get('fmt', [''])[0])
           response_headers = { 'Content-Type': 'audio/mpegurl; charset=utf-8', 'Connection': 'close', 'Content-Length': len(exported),
                                'Access-Control-Allow-Origin': '*', 'ETag': self.etag }
           try:
              h = connection.headers.get('Accept-Encoding').split(',')[0]
              compress_method = { 'zlib': zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS),
                                  'deflate': zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS),
                                  'gzip': zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) }
              exported = compress_method[h].compress(exported) + compress_method[h].flush()
              response_headers['Content-Length'] = len(exported)
              response_headers['Content-Encoding'] = h
           except: pass

           connection.send_response(200)
           gevent.joinall([gevent.spawn(connection.send_header, k, v) for (k,v) in response_headers.items()])
           connection.end_headers()

        if play: connection.handleRequest(headers_only, name, self.picons.get(name), fmt=params.get('fmt', [''])[0])
        elif not headers_only:
           self.logger.debug('Exporting torrent-telik.m3u playlist')
           connection.wfile.write(exported)
コード例 #2
0
def StreamReader(params):
    '''
    params: dict([url=] [file_index=] [infohash= ] [ad=1 [interruptable=1]] [stream=1] [pos=position] [bitrate=] [length=])
    '''
    def checkBroadcast():
        if not params['broadcastclients']:
            params['broadcast'].kill()
        return params['broadcast'].started

    def write_chunk(client, chunk, timeout=15.0):
        try:
            client.q.put(chunk, timeout=timeout)
        except gevent.queue.Full:
            client.send_error(
                500, '[%s]: does not read data until %s sec' %
                (client.clientip, timeout), logging.ERROR)

    def StreamWriter(url):
        for chunk in s.get(url,
                           timeout=(5, AceConfig.videotimeout),
                           stream=True).iter_content(chunk_size=1048576):
            _ = params['chunkpool'].map(
                lambda client: write_chunk(client, chunk),
                params['broadcastclients'])

    try:
        params.update({
            'url':
            urlparse(unquote(params['url']))._replace(
                netloc='{aceHostIP}:{aceHTTPport}'.format(
                    **AceConfig.ace)).geturl(),
            'broadcast':
            gevent.getcurrent(),
            'broadcastclients':
            AceProxy.clientcounter.getClientsList(params['infohash']),
            'chunkpool':
            Pool(),
        })
        with requests.session() as s:
            if params['url'].endswith(
                    '.m3u8'):  # AceEngine return link for HLS stream
                import m3u8
                urls = []
                while checkBroadcast():
                    for url in m3u8.load(params['url']).segments.uri:
                        if url in urls: continue
                        else:
                            StreamWriter(url)
                            urls.append(url)
                            if len(urls) > 50: urls.pop(0)

            else:
                StreamWriter(
                    params['url'])  #AceStream return link for HTTP stream
    except (TypeError, gevent.GreenletExit):
        pass
    except Exception as err:
        _ = AceProxy.pool.map(
            lambda x: x.send_error(500, repr(err), logging.ERROR),
            params['broadcastclients'])
コード例 #3
0
    def handle(self, connection):
        # 30 minutes cache
        if not self.playlist or (gevent.time.time() - self.playlisttime > 30 * 60):
           if not self.Playlistparser():
              logging.info('Parser failed to parse')
              connection.send_error()

        connection.ext = query_get(connection.query, 'ext', 'ts')
        if connection.path.startswith('/{reqtype}/channel/'.format(**connection.__dict__)):
           if not connection.path.endswith(connection.ext):
              logging.info('Invalid path')
              connection.send_error(404, 'Invalid path: {path}'.format(**connection.__dict__), logging.ERROR)
           name = ensure_text(unquote(os.path.splitext(os.path.basename(connection.path))[0]))
           url = self.channels.get(name)
           if url is None:
              logging.info('Unknown channel')
              connection.send_error(404, '[%s]: unknown channel: %s' % (self.__class__.__name__, name), logging.ERROR)
           connection.__dict__.update({'channelName': name,
                                       'channelIcon': self.picons.get(name),
                                       'path': {'acestream': '/content_id/%s/%s.%s' % (urlparse(url).netloc, name, connection.ext),
                                                'infohash' : '/infohash/%s/%s.%s' % (urlparse(url).netloc, name, connection.ext),
                                                'http'     : '/url/%s/%s.%s' % (quote(url,''), name, connection.ext),
                                                'https'    : '/url/%s/%s.%s' % (quote(url,''), name, connection.ext),
                                               }[urlparse(url).scheme]})
           connection.__dict__.update({'splittedpath': connection.path.split('/')})
           connection.__dict__.update({'reqtype': connection.splittedpath[1].lower()})
           return

        elif self.etag == connection.headers.get('If-None-Match'):
           logging.debug('[%s]: ETag matches. Return 304 to [%s]' % (self.__class__.__name__, connection.clientip))
           connection.send_response(304)
           connection.send_header('Connection', 'close')
           connection.end_headers()
           return

        else:
           host_port = connection.headers['Host']
           exported = self.playlist.exportm3u( hostport=host_port,
                                               path='' if not self.channels else '/{reqtype}/channel'.format(**connection.__dict__),
                                               header=config.m3uheadertemplate.format(get_epg_url(host_port, config, config.tvgurl), config.tvgshift),
                                               query=connection.query
                                              )
           response_headers = {'Content-Type': 'audio/mpegurl; charset=utf-8', 'Connection': 'close', 'Access-Control-Allow-Origin': '*'}
           try:
              h = connection.headers.get('Accept-Encoding').split(',')[0]
              compress_method = { 'zlib': zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS),
                                  'deflate': zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS),
                                  'gzip': zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) }
              exported = compress_method[h].compress(exported) + compress_method[h].flush()
              response_headers['Content-Encoding'] = h
           except: pass
           response_headers['Content-Length'] = len(exported)
           if connection.request_version == 'HTTP/1.1':
              response_headers['ETag'] = self.etag
           connection.send_response(200)
           gevent.joinall([gevent.spawn(connection.send_header, k, v) for (k,v) in response_headers.items()])
           connection.end_headers()
           connection.wfile.write(exported)
           logging.debug('[%s]: plugin sent playlist to [%s]' % (self.__class__.__name__, connection.clientip))
コード例 #4
0
    def do_GET(self, headers_only=False):
        '''
        GET request handler
        '''
        # Current greenlet
        self.handlerGreenlet = gevent.getcurrent()
        # Connected client IP address
        self.clientip = self.headers[
            'X-Forwarded-For'] if 'X-Forwarded-For' in self.headers else self.client_address[
                0]
        logging.info('Accepted connection from %s path %s' %
                     (self.clientip, unquote(self.path)))
        logging.debug('Client headers: %s' % dict(self.headers))
        params = urlparse(self.path)
        self.query, self.path = params.query, params.path[:
                                                          -1] if params.path.endswith(
                                                              '/'
                                                          ) else params.path

        if AceConfig.firewall and not checkFirewall(self.clientip):
            self.dieWithError(
                401, 'Dropping connection from %s due to firewall rules' %
                self.clientip, logging.ERROR)
            return
        try:
            self.splittedpath = self.path.split('/')
            self.reqtype = self.splittedpath[1].lower()
            # backward compatibility
            old2newUrlParts = {'torrent': 'url', 'pid': 'content_id'}
            if self.reqtype in old2newUrlParts:
                self.reqtype = old2newUrlParts[self.reqtype]

            # If first parameter is 'content_id','url','infohash' .... etc or it should be handled by plugin #'direct_url', 'data', 'efile_url'
            if not (self.reqtype in ('content_id', 'url', 'infohash')
                    or self.reqtype in AceProxy.pluginshandlers):
                self.dieWithError(400, 'Bad Request',
                                  logging.WARNING)  # 400 Bad Request
                return
        except IndexError:
            self.dieWithError(400, 'Bad Request',
                              logging.WARNING)  # 400 Bad Request
            return

        # Handle request with plugin handler
        if self.reqtype in AceProxy.pluginshandlers:
            try:
                AceProxy.pluginshandlers.get(self.reqtype).handle(
                    self, headers_only)
            except Exception as e:
                import traceback
                logger.error(traceback.format_exc())
                self.dieWithError(500, 'Plugin exception: %s' % repr(e))
            finally:
                return
        self.handleRequest(headers_only)
コード例 #5
0
    def do_GET(self):
        '''
        GET request handler
        '''
        self.handlerGreenlet = gevent.getcurrent() # Current greenlet
        self.clientip = self.headers.get('X-Forwarded-For', self.address_string()) # Connected client IP address
        logging.info(unquote('[{clientip}]: {command} {request_version} request for: {path}'.format(**self.__dict__)))
        logging.debug('[%s]: Request headers: %s' % (self.clientip, dict(self.headers)))

        if AceConfig.firewall and not checkFirewall(self.clientip):
           self.send_error(401, '[{clientip}]: Dropping connection due to firewall rules'.format(**self.__dict__), logging.ERROR)

        self.path, _, self.query = self.path.partition('?')
        self.path = self.path.rstrip('/')
        # Pretend to work fine with Fake or HEAD request.
        if self.command == 'HEAD' or AceConfig.isFakeRequest(self.path, self.query, self.headers):
           # Return 200 and exit
           if self.command != 'HEAD': self.command = 'FAKE'
           logging.debug('[{clientip}]: {command} request: send headers and close the connection'.format(**self.__dict__))
           self.send_response(200)
           self.send_header('Content-Type', 'video/mp2t')
           self.send_header('Connection', 'Close')
           self.end_headers()
           return

        try:
           self.splittedpath = self.path.split('/')
           self.reqtype = self.splittedpath[1].lower()
           AceProxy.pluginshandlers[self.reqtype].handle(self) # If request should be handled by plugin
           raise IndexError()

        except (IndexError, KeyError):
           self.reqtype = {'torrent': 'url', 'pid': 'content_id'}.get(self.reqtype, self.reqtype) # For backward compatibility
           if self.reqtype in {'content_id', 'url', 'infohash', 'direct_url', 'data', 'efile_url'}:
              # Limit on the number of connected clients
              if 0 < AceConfig.maxconns <= len(AceProxy.clientcounter.getAllClientsList()):
                 self.send_error(403, "[{clientip}]: Maximum client connections reached, can't serve request".format(**self.__dict__), logging.ERROR)
              # Check if third path parameter is exists /{reqtype}/{reqtype_value}/.../.../video.mpg
              #                                                                           |_________|
              # And if it ends with regular video extension
              if not self.splittedpath[-1].endswith(('.avi', '.flv', '.m2ts', '.mkv', '.mpeg', '.mpeg4', '.mpegts',
                                                     '.mpg4', '.mp4', '.mpg', '.mov', '.mpv', '.qt', '.ts', '.wmv')):
                    self.send_error(501, '[{clientip}]: request seems like valid but no valid video extension was provided'.format(**self.__dict__), logging.ERROR)
              else:
                 self.handleRequest()
           elif self.reqtype not in AceProxy.pluginshandlers:
              self.send_error(400, '[{clientip}]: Bad Request'.format(**self.__dict__), logging.WARNING)  # 400 Bad Request

        except Exception as e:
           logging.error('Unexpected exception: %s' % repr(e))
           logging.error(traceback.format_exc())
コード例 #6
0
 def _read(self, timeout=30):
     '''
     Read telnet connection method
     '''
     while 1:
        with gevent.Timeout(timeout, False):
           try:
              recvbuffer = self._socket.read_until('\r\n', None).strip().split()
              logging.debug('[%.20s]: <<< %s' % (self._title, unquote(' '.join(recvbuffer))))
              gevent.spawn(getattr(globals()[self.__class__.__name__], '_%s_' % recvbuffer[0].lower(), '_unrecognized_'), self, recvbuffer).link_value(self._response[recvbuffer[0]])
           except gevent.socket.timeout: pass # WinOS patch
           except gevent.Timeout:
              self.ShutdownAce()
              break
           except: break # Telnet connection unexpectedly closed or telnet connection error
コード例 #7
0
def StreamReader(playback_url, cid):
    def write_chunk(client, chunk, timeout=15.0):
        try:
            client.q.put(chunk, timeout=timeout)
        except gevent.queue.Full:
            client.dieWithError(
                500, 'Client %s does not read data until %s sec' %
                (client.clientip, timeout), logging.ERROR)

    def StreamWriter(url):
        for chunk in s.get(url,
                           timeout=(5, AceConfig.videotimeout)).iter_content(
                               chunk_size=1048576):
            _ = AceProxy.pool.map(lambda client: write_chunk(client, chunk),
                                  AceProxy.clientcounter.getClientsList(cid))

    try:
        playback_url = urlparse(unquote(playback_url))._replace(
            netloc='{aceHostIP}:{aceHTTPport}'.format(
                **AceConfig.ace)).geturl()
        with requests.session() as s:
            s.verify = False
            s.stream = True
            if playback_url.endswith(
                    '.m3u8'):  # AceEngine return link for HLS stream
                used_urls = []
                while 1:
                    for url in s.get(
                            playback_url,
                            timeout=(5, AceConfig.videotimeout)).iter_lines():
                        if url.startswith(b'download not found'): return
                        if url.startswith(b'http://') and url not in used_urls:
                            StreamWriter(url)
                            used_urls.append(url)
                            if len(used_urls) > 15: used_urls.pop(0)

            else:
                StreamWriter(
                    playback_url)  #AceStream return link for HTTP stream
    except TypeError:
        pass
    except Exception as err:
        _ = AceProxy.pool.map(
            lambda x: x.dieWithError(500, repr(err), logging.ERROR),
            AceProxy.clientcounter.getClientsList(cid))
コード例 #8
0
    def handleRequest(self):
        '''
        Main request handler path: /{reqtype}/{reqtype_value}/{file_indexes}/{developer_id}/{affiliate_id}/{zone_id}/{stream_id}/{fname}.{ext}
        '''
        logger = logging.getLogger('handleRequest')
        # Make handler parameters dict
        self.__dict__.update({}.fromkeys(
            aceclient.acemessages.AceConst.START_PARAMS, '0'
        ))  # [file_indexes, developer_id, affiliate_id, zone_id, stream_id]
        self.__dict__.update({
            k: v
            for (k, v) in
            [(aceclient.acemessages.AceConst.START_PARAMS[i - 3],
              self.splittedpath[i] if self.splittedpath[i].isdigit() else '0')
             for i in range(3, len(self.splittedpath))]
        })
        self.__dict__.update({
            self.reqtype:
            unquote(self.splittedpath[2]),  # {reqtype: reqtype_value}
            'ace':
            AceConfig.ace,
            'acesex':
            AceConfig.acesex,
            'aceage':
            AceConfig.aceage,
            'acekey':
            AceConfig.acekey,
            'connect_timeout':
            AceConfig.aceconntimeout,
            'result_timeout':
            AceConfig.aceresulttimeout,
            'videoseekback':
            AceConfig.videoseekback,
            'videotimeout':
            AceConfig.videotimeout,
            'stream_type':
            ' '.join([
                '{}={}'.format(k, v)
                for k, v in AceConfig.acestreamtype.items()
            ]),  # request http or hls from AceEngine
            'sessionID':
            self.handlerGreenlet.name[
                self.handlerGreenlet.name.rfind('-') +
                1:],  # Greenlet.minimal_ident A small, unique non-negative integer
            'connectionTime':
            gevent.time.time(),
            'clientDetail':
            None,
            'channelIcon':
            self.__dict__.get(
                'channelIcon',
                'http://static.acestream.net/sites/acestream/img/ACE-logo.png'
            ),
        })
        # End parameters dict
        try:
            self.q = gevent.queue.Queue(maxsize=AceConfig.videotimeout)
            transcoder = gevent.event.AsyncResult()
            out = self.wfile
            gevent.spawn(wrap_errors(gevent.socket.error,
                                     self.rfile.read)).link(
                                         lambda x: self.handlerGreenlet.kill()
                                     )  # Client disconection watchdog
            try:
                if not AceProxy.clientcounter.idleAce:
                    logger.debug(
                        'Create a connection with AceStream on {ace[aceHostIP]}:{ace[aceAPIport]}'
                        .format(**self.__dict__))
                    AceProxy.clientcounter.idleAce = aceclient.AceClient(
                        self.__dict__)
                    AceProxy.clientcounter.idleAce.GetAUTH()
                if self.reqtype not in ('direct_url', 'efile_url'):
                    self.__dict__.update(
                        AceProxy.clientcounter.idleAce.GetCONTENTINFO(
                            self.__dict__))
                    self.channelName = ensure_str(
                        self.__dict__.get(
                            'channelName',
                            next(
                                iter([
                                    x[0] for x in self.files
                                    if x[1] == int(self.file_indexes)
                                ]), 'NoNameChannel')))
                else:
                    self.channelName = ensure_str(
                        self.__dict__.get('channelName', 'NoNameChannel'))
                    self.infohash = requests.auth.hashlib.sha1(
                        ensure_binary(self.path)).hexdigest()
                AceProxy.clientcounter.idleAce._title = self.channelName
            except Exception as e:
                AceProxy.clientcounter.idleAce._read.kill()
                logging.debug('Error 404')
                self.send_error(404, '%s' % repr(e), logging.ERROR)

            self.ext = self.__dict__.get(
                'ext', self.channelName[self.channelName.rfind('.') + 1:])
            if self.ext == self.channelName:
                self.ext = query_get(self.query, 'ext', 'ts')
            mimetype = mimetypes.guess_type(
                '{channelName}.{ext}'.format(**self.__dict__))[0]
            try:
                # If &fmt transcode key present in request
                fmt = query_get(self.query, 'fmt')
                if fmt:
                    if AceConfig.osplatform != 'Windows':
                        if fmt in AceConfig.transcodecmd:
                            stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL
                            popen_params = {
                                'bufsize': 1048576,
                                'stdin': gevent.subprocess.PIPE,
                                'stdout': self.wfile,
                                'stderr': stderr,
                                'shell': False
                            }
                            try:
                                gevent.spawn(lambda: psutil.Popen(
                                    AceConfig.transcodecmd[fmt], **popen_params
                                )).link(transcoder)
                                out = transcoder.get(timeout=2.0).stdin
                                logger.info(
                                    '[{channelName}]: Transcoding to [{clientip}] started'
                                    .format(**self.__dict__))
                            except:
                                logger.error(
                                    '[{channelName}]: Error starting transcoding to [{clientip}]! Is ffmpeg or VLC installed?'
                                    .format(**self.__dict__))
                        else:
                            logger.error(
                                "[{channelName}]: Can't found fmt key. Transcoding to [{clientip}] not started!"
                                .format(**self.__dict__))
                    elif AceConfig.osplatform == 'Windows':
                        logger.error(
                            '[{channelName}]: Not applicable in Windnows OS. Transcoding to [{clientip}] not started!'
                            .format(**self.__dict__))

                if AceProxy.clientcounter.addClient(self) == 1:
                    # Create broadcast if it does not exist yet
                    gevent.spawn(
                        StreamReader,
                        self.ace.GetBroadcastStartParams(self.__dict__)
                    ).link(lambda x: logger.debug(
                        '[{channelName}]: Broadcast destroyed. Last client disconnected'
                        .format(**self.__dict__)))
                    logger.debug('[{channelName}]: Broadcast created'.format(
                        **self.__dict__))
                else:
                    logger.debug(
                        '[{channelName}]: Broadcast already exists'.format(
                            **self.__dict__))
                logger.info(
                    '[{channelName}]: Streaming to [{clientip}] started'.
                    format(**self.__dict__))
                # Sending videostream headers to client
                response_use_chunked = False if (
                    transcoder.value or self.request_version
                    == 'HTTP/1.0') else AceConfig.use_chunked
                drop_headers = []
                proxy_headers = {
                    'Connection': 'keep-alive',
                    'Keep-Alive':
                    'timeout=%s, max=100' % AceConfig.videotimeout,
                    'Accept-Ranges': 'none',
                    'Transfer-Encoding': 'chunked',
                    'Content-Type':
                    'video/MP2T' if mimetype is None else mimetype,
                }

                if not response_use_chunked:
                    self.protocol_version = 'HTTP/1.0'
                    proxy_headers['Connection'] = 'Close'
                    drop_headers.extend(['Transfer-Encoding', 'Keep-Alive'])

                response_headers = [(k, v) for (k, v) in proxy_headers.items()
                                    if k not in drop_headers]
                self.send_response(200)
                logger.debug('[%s]: Sending HTTPAceProxy headers: %s' %
                             (self.clientip, dict(response_headers)))
                gevent.joinall([
                    gevent.spawn(self.send_header, k, v)
                    for (k, v) in response_headers
                ])
                self.end_headers()
                # write data to client while it is alive
                for chunk in self.q:
                    out.write(b'%X\r\n%s\r\n' %
                              (len(chunk),
                               chunk) if response_use_chunked else chunk)

            except aceclient.AceException as e:
                _ = AceProxy.pool.map(
                    lambda x: x.send_error(500, repr(e), logging.ERROR),
                    AceProxy.clientcounter.getClientsList(self.infohash))
            except gevent.socket.error:
                pass  # Client disconnected

        except gevent.GreenletExit:
            AceProxy.clientcounter.idleAce._read.kill()

        finally:
            AceProxy.clientcounter.deleteClient(self)
            logger.info(
                '[{channelName}]: Streaming to [{clientip}] finished'.format(
                    **self.__dict__))
            if transcoder.value:
                try:
                    transcoder.value.kill()
                    logger.info(
                        '[{channelName}]: Transcoding to [{clientip}] stoped'.
                        format(**self.__dict__))
                except:
                    pass
コード例 #9
0
ファイル: aceclient.py プロジェクト: furvovan/HTTPAceProxy
 def _recvData(self, timeout=30):
     '''
     Data receiver method for greenlet
     '''
     while 1:
         # Destroy socket connection if AceEngine STATE 0 (IDLE) and we didn't read anything from socket until Nsec
         with gevent.Timeout(timeout, False):
             try:
                 self._recvbuffer = self._socket.read_until('\r\n',
                                                            None).strip()
             except gevent.Timeout:
                 self.destroy()
             except gevent.socket.timeout:
                 pass
             except:
                 raise
             else:
                 logging.debug('<<< %s' % unquote(self._recvbuffer))
                 # Parsing everything only if the string is not empty
                 # HELLOTS
                 if self._recvbuffer.startswith('HELLOTS'):
                     #version=engine_version version_code=version_code key=request_key http_port=http_port
                     self._auth.set({
                         k: v
                         for k, v in (x.split('=')
                                      for x in self._recvbuffer.split()
                                      if '=' in x)
                     })
                 # NOTREADY
                 elif self._recvbuffer.startswith('NOTREADY'):
                     self._auth.set('NOTREADY')
                     # AUTH
                 elif self._recvbuffer.startswith('AUTH'):
                     self._auth.set(
                         self._recvbuffer.split()[1])  # user_auth_level
                     # START
                 elif self._recvbuffer.startswith('START'):
                     # url [ad=1 [interruptable=1]] [stream=1] [pos=position]
                     params = {
                         k: v
                         for k, v in (x.split('=')
                                      for x in self._recvbuffer.split()
                                      if '=' in x)
                     }
                     if not self._seekback or self._started_again.ready(
                     ) or params.get('stream', '') is not '1':
                         # If seekback is disabled, we use link in first START command.
                         # If seekback is enabled, we wait for first START command and
                         # ignore it, then do seekback in first EVENT position command
                         # AceStream sends us STOP and START again with new link.
                         # We use only second link then.
                         self._url.set(
                             self._recvbuffer.split()[1])  # url for play
                 # LOADRESP
                 elif self._recvbuffer.startswith('LOADRESP'):
                     self._loadasync.set(
                         json.loads(
                             unquote(''.join(
                                 self._recvbuffer.split()[2:]))))
                 # STATE
                 elif self._recvbuffer.startswith('STATE'):
                     self._state.set(self._recvbuffer.split()
                                     [1])  # STATE state_id -> STATE_NAME
                 # STATUS
                 elif self._recvbuffer.startswith('STATUS'):
                     self._tempstatus = self._recvbuffer.split()[1]
                     stat = [self._tempstatus.split(';')[0].split(':')[1]
                             ]  # main:????
                     if self._tempstatus.startswith('main:idle'): pass
                     elif self._tempstatus.startswith('main:loading'): pass
                     elif self._tempstatus.startswith('main:starting'): pass
                     elif self._tempstatus.startswith('main:check'): pass
                     elif self._tempstatus.startswith('main:err'):
                         pass  # err;error_id;error_message
                     elif self._tempstatus.startswith('main:dl'):  #dl;
                         stat.extend(
                             map(int,
                                 self._tempstatus.split(';')[1:]))
                     elif self._tempstatus.startswith(
                             'main:wait'):  #wait;time;
                         stat.extend(
                             map(int,
                                 self._tempstatus.split(';')[2:]))
                     elif self._tempstatus.startswith(
                         ('main:prebuf', 'main:buf')):  #buf;progress;time;
                         stat.extend(
                             map(int,
                                 self._tempstatus.split(';')[3:]))
                     try:
                         self._status.set({
                             k: v
                             for k, v in zip(AceConst.STATUS, stat)
                         })  # dl, wait, buf, prebuf
                     except:
                         self._status.set(
                             {'status':
                              stat[0]})  # idle, loading, starting, check
                 # CID
                 elif self._recvbuffer.startswith('##'):
                     self._cid.set(self._recvbuffer)
                     # INFO
                 elif self._recvbuffer.startswith('INFO'):
                     pass
                     # EVENT
                 elif self._recvbuffer.startswith('EVENT'):
                     self._tempevent = self._recvbuffer.split()
                     if self._seekback and not self._started_again.ready(
                     ) and 'livepos' in self._tempevent:
                         params = {
                             k: v
                             for k, v in (x.split('=')
                                          for x in self._tempevent
                                          if '=' in x)
                         }
                         self._write(
                             AceMessage.request.LIVESEEK(
                                 int(params['last']) - self._seekback))
                         self._started_again.set()
                     elif 'getuserdata' in self._tempevent:
                         self._write(
                             AceMessage.request.USERDATA(
                                 self._gender, self._age))
                     elif 'cansave' in self._tempevent:
                         pass
                     elif 'showurl' in self._tempevent:
                         pass
                     elif 'download_stopped' in self._tempevent:
                         pass
                 # PAUSE
                 elif self._recvbuffer.startswith('PAUSE'):
                     pass  #self._write(AceMessage.request.EVENT('pause'))
                     # RESUME
                 elif self._recvbuffer.startswith('RESUME'):
                     pass  #self._write(AceMessage.request.EVENT('play'))
                     # STOP
                 elif self._recvbuffer.startswith('STOP'):
                     pass  #self._write(AceMessage.request.EVENT('stop'))
                     # SHUTDOWN
                 elif self._recvbuffer.startswith('SHUTDOWN'):
                     self._socket.close()
                     break
コード例 #10
0
ファイル: requests_file.py プロジェクト: zella/HTTPAceProxy
    def send(self, request, **kwargs):
        """ Wraps a file, described in request, in a Response object.
            :param request: The PreparedRequest` being "sent".
            :returns: a Response object containing the file
        """

        # Check that the method makes sense. Only support GET
        if request.method not in ("GET", "HEAD"):
            raise ValueError("Invalid request method %s" % request.method)

        # Parse the URL
        url_parts = urlparse(request.url)

        # Reject URLs with a hostname component
        if url_parts.netloc and url_parts.netloc != "localhost":
            raise ValueError(
                "file: URLs with hostname components are not permitted")

        resp = Response()

        # Open the file, translate certain errors into HTTP responses
        # Use urllib's unquote to translate percent escapes into whatever
        # they actually need to be
        try:
            # Split the path on / (the URL directory separator) and decode any
            # % escapes in the parts
            path_parts = [unquote(p) for p in url_parts.path.split('/')]

            # Strip out the leading empty parts created from the leading /'s
            while path_parts and not path_parts[0]:
                path_parts.pop(0)

            # If os.sep is in any of the parts, someone fed us some shenanigans.
            # Treat is like a missing file.
            if any(os.sep in p for p in path_parts):
                raise IOError(errno.ENOENT, os.strerror(errno.ENOENT))

            # Look for a drive component. If one is present, store it separately
            # so that a directory separator can correctly be added to the real
            # path, and remove any empty path parts between the drive and the path.
            # Assume that a part ending with : or | (legacy) is a drive.
            if path_parts and (path_parts[0].endswith('|')
                               or path_parts[0].endswith(':')):
                path_drive = path_parts.pop(0)
                if path_drive.endswith('|'):
                    path_drive = path_drive[:-1] + ':'

                while path_parts and not path_parts[0]:
                    path_parts.pop(0)
            else:
                path_drive = ''

            # Try to put the path back together
            # Join the drive back in, and stick os.sep in front of the path to
            # make it absolute.
            path = path_drive + os.sep + os.path.join(*path_parts)

            # Check if the drive assumptions above were correct. If path_drive
            # is set, and os.path.splitdrive does not return a drive, it wasn't
            # reall a drive. Put the path together again treating path_drive
            # as a normal path component.
            if path_drive and not os.path.splitdrive(path):
                path = os.sep + os.path.join(path_drive, *path_parts)

            # Use io.open since we need to add a release_conn method, and
            # methods can't be added to file objects in python 2.
            resp.raw = io.open(path, "rb")
            resp.raw.release_conn = resp.raw.close
        except IOError as e:
            if e.errno == errno.EACCES:
                resp.status_code = codes.forbidden
            elif e.errno == errno.ENOENT:
                resp.status_code = codes.not_found
            else:
                resp.status_code = codes.bad_request

            # Wrap the error message in a file-like object
            # The error message will be localized, try to convert the string
            # representation of the exception into a byte stream
            resp_str = str(e).encode(locale.getpreferredencoding(False))
            resp.raw = BytesIO(resp_str)
            resp.headers['Content-Length'] = len(resp_str)

            # Add release_conn to the BytesIO object
            resp.raw.release_conn = resp.raw.close
        else:
            resp.status_code = codes.ok
            resp.url = request.url

            # If it's a regular file, set the Content-Length
            resp_stat = os.fstat(resp.raw.fileno())
            if stat.S_ISREG(resp_stat.st_mode):
                resp.headers['Content-Length'] = resp_stat.st_size

        return resp
コード例 #11
0
    def handleRequest(self,
                      headers_only,
                      channelName=None,
                      channelIcon=None,
                      fmt=None):
        logger = logging.getLogger('HandleRequest')
        self.reqparams, self.path = parse_qs(
            self.query), self.path[:-1] if self.path.endswith(
                '/') else self.path

        self.videoextdefaults = ('.3gp', '.aac', '.ape', '.asf', '.avi', '.dv',
                                 '.divx', '.flac', '.flc', '.flv', '.m2ts',
                                 '.m4a', '.mka', '.mkv', '.mpeg', '.mpeg4',
                                 '.mpegts', '.mpg4', '.mp3', '.mp4', '.mpg',
                                 '.mov', '.m4v', '.ogg', '.ogm', '.ogv',
                                 '.oga', '.ogx', '.qt', '.rm', '.swf', '.ts',
                                 '.vob', '.wmv', '.wav', '.webm')

        # Limit on the number of connected clients
        if 0 < AceConfig.maxconns <= AceProxy.clientcounter.totalClients():
            self.dieWithError(
                501,
                "Maximum client connections reached, can't serve request from %s"
                % self.clientip, logging.ERROR)
            return
        # Check if third parameter exists…/self.reqtype/blablablablabla/video.mpg
        # And if it ends with regular video extension
        try:
            if not self.path.endswith(self.videoextdefaults):
                self.dieWithError(
                    501,
                    'Request seems like valid but no valid video extension was provided',
                    logging.ERROR)
                return
        except IndexError:
            self.dieWithError(400, 'Bad Request',
                              logging.WARNING)  # 400 Bad Request
            return
        # Pretend to work fine with Fake or HEAD request.
        if headers_only or AceConfig.isFakeRequest(self.path, self.query,
                                                   self.headers):
            # Return 200 and exit
            if headers_only:
                logger.debug('Sending headers and closing connection')
            else:
                logger.debug('Fake request - closing connection')
            self.send_response(200)
            self.send_header('Content-Type', 'video/mp2t')
            self.send_header('Connection', 'Close')
            self.end_headers()
            return
        # Check is AceEngine alive
        checkAce()
        # Make dict with parameters
        # [file_indexes, developer_id, affiliate_id, zone_id, stream_id]
        paramsdict = {}.fromkeys(aceclient.acemessages.AceConst.START_PARAMS,
                                 '0')
        for i in range(3, len(self.splittedpath)):
            paramsdict[aceclient.acemessages.AceConst.START_PARAMS[
                i - 3]] = self.splittedpath[i] if self.splittedpath[i].isdigit(
                ) else '0'
        paramsdict[self.reqtype] = unquote(
            self.splittedpath[2])  #self.path_unquoted
        #End parameters dict
        CID = NAME = None
        if not AceConfig.new_api:
            try:
                if not AceProxy.clientcounter.idleAce:
                    logger.debug('Create connection to AceEngine.....')
                    AceProxy.clientcounter.idleAce = aceclient.AceClient(
                        AceProxy.clientcounter, AceConfig.ace,
                        AceConfig.aceconntimeout, AceConfig.aceresulttimeout)
                    AceProxy.clientcounter.idleAce.aceInit(
                        AceConfig.acesex, AceConfig.aceage, AceConfig.acekey,
                        AceConfig.videoseekback, AceConfig.videotimeout)
                CID, NAME = AceProxy.clientcounter.idleAce.GETINFOHASH(
                    self.reqtype, paramsdict[self.reqtype],
                    paramsdict['file_indexes'])
            except aceclient.AceException as e:
                self.dieWithError(503, '%s' % repr(e), logging.ERROR)
                AceProxy.clientcounter.idleAce = None
                return
        else:
            try:
                with requests.session() as s:
                    s.stream = s.verify = False
                    url = 'http://%s:%s/ace/%s' % (
                        AceConfig.ace['aceHostIP'],
                        AceConfig.ace['aceHTTPport'], 'manifest.m3u8'
                        if AceConfig.acestreamtype['output_format'] == 'hls'
                        else 'getstream')
                    params = {
                        'id' if self.reqtype in ('cid', 'content_id') else self.reqtype:
                        paramsdict[self.reqtype],
                        'format':
                        'json',
                        'pid':
                        str(uuid4()),
                        '_idx':
                        paramsdict['file_indexes']
                    }
                    if AceConfig.acestreamtype['output_format'] == 'hls':
                        params.update(AceConfig.acestreamtype)
                        del params['output_format']
                    self.cmd = s.get(
                        url,
                        params=params,
                        timeout=(5, AceConfig.videotimeout)).json()['response']
                    CID = urlparse(self.cmd['playback_url']).path.split('/')[3]
                    url = 'http://%s:%s/server/api' % (
                        AceConfig.ace['aceHostIP'],
                        AceConfig.ace['aceHTTPport'])
                    params = {
                        'method': 'get_media_files',
                        self.reqtype: paramsdict[self.reqtype]
                    }
                    NAME = s.get(url,
                                 params=params,
                                 timeout=(5, AceConfig.aceresulttimeout)).json(
                                 )['result'][paramsdict['file_indexes']]
            except Exception as e:
                self.dieWithError(503, '%s' % repr(e), logging.ERROR)
                return

        self.connectionTime = gevent.time.time()
        self.channelName = NAME if not channelName else channelName
        self.channelIcon = 'http://static.acestream.net/sites/acestream/img/ACE-logo.png' if not channelIcon else channelIcon
        self.clientInfo = self.transcoder = None
        try:
            self.connectGreenlet = gevent.spawn(
                self.connectDetector)  # client disconnection watchdog
            self.out = self.wfile
            # If &fmt transcode key present in request
            fmt = self.reqparams.get('fmt', [''])[0]
            if fmt and AceConfig.osplatform != 'Windows':
                if fmt in AceConfig.transcodecmd:
                    stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL
                    popen_params = {
                        'bufsize': 1048576,
                        'stdin': gevent.subprocess.PIPE,
                        'stdout': self.wfile,
                        'stderr': stderr,
                        'shell': False
                    }
                    try:
                        self.transcoder = gevent.event.AsyncResult()
                        gevent.spawn(lambda: psutil.Popen(
                            AceConfig.transcodecmd[fmt], **popen_params)).link(
                                self.transcoder)
                        self.transcoder = self.transcoder.get(timeout=2.0)
                        self.out = self.transcoder.stdin
                        logger.info('Transcoding for %s started' %
                                    self.clientip)
                    except:
                        logger.error(
                            'Error starting transcoding! Is Ffmpeg or VLC installed?'
                        )
                        self.transcoder = None
                        self.out = self.wfile
                else:
                    logger.error(
                        "Can't found fmt key. Transcoding not started!")

            self.response_use_chunked = False if self.transcoder is not None else AceConfig.use_chunked

            if AceProxy.clientcounter.addClient(CID, self) == 1:
                # If there is no existing broadcast we create it
                playback_url = self.cmd[
                    'playback_url'] if AceConfig.new_api else self.ace.START(
                        self.reqtype, paramsdict, AceConfig.acestreamtype)
                if not AceProxy.ace:  #Rewrite host:port for remote AceEngine
                    playback_url = urlparse(playback_url)._replace(
                        netloc='%s:%s' %
                        (AceConfig.ace['aceHostIP'],
                         AceConfig.ace['aceHTTPport'])).geturl()
                gevent.spawn(StreamReader, playback_url, CID)

            # Sending videostream headers to client
            logger.info('Streaming "%s" to %s started' %
                        (self.channelName, self.clientip))
            drop_headers = []
            proxy_headers = {
                'Connection': 'keep-alive',
                'Keep-Alive': 'timeout=15, max=100',
                'Accept-Ranges': 'none',
                'Transfer-Encoding': 'chunked',
                'Content-Type': 'application/octet-stream',
                'Cache-Control': 'max-age=0, no-cache, no-store',
                'Pragma': 'no-cache'
            }

            if not self.response_use_chunked or self.request_version == 'HTTP/1.0':
                self.protocol_version = 'HTTP/1.0'
                proxy_headers['Connection'] = 'Close'
                drop_headers.extend(
                    ['Transfer-Encoding', 'Keep-Alive', 'Cache-Control'])

            response_headers = [(k, v) for (k, v) in proxy_headers.items()
                                if k not in drop_headers]
            self.send_response(200)
            logger.debug('Sending HTTPAceProxy headers to client: %s' %
                         dict(response_headers))
            gevent.joinall([
                gevent.spawn(self.send_header, k, v)
                for (k, v) in response_headers
            ])
            self.end_headers()

            self.connectGreenlet.join(
            )  # Wait until request complite or client disconnected

        except aceclient.AceException as e:
            gevent.joinall([
                gevent.spawn(client.dieWithError, 503, '%s' % repr(e),
                             logging.ERROR)
                for client in AceProxy.clientcounter.getClientsList(CID)
            ])
            gevent.joinall([
                gevent.spawn(client.connectGreenlet.kill)
                for client in AceProxy.clientcounter.getClientsList(CID)
            ])
        except gevent.GreenletExit:
            pass  # Client disconnected
        except Exception as e:
            self.dieWithError(500, 'Unexpected error: %s' % repr(e))
        finally:
            logging.info('Streaming "%s" to %s finished' %
                         (self.channelName, self.clientip))
            if self.transcoder:
                try:
                    self.transcoder.kill()
                    logging.info('Transcoding for %s stoped' % self.clientip)
                except:
                    pass
            if AceProxy.clientcounter.deleteClient(CID, self) == 0:
                if AceConfig.new_api:
                    with requests.get(self.cmd['command_url'],
                                      params={'method': 'stop'},
                                      timeout=5) as r:
                        logging.debug('Stop broadcast: %s' % r.json())
                logging.debug(
                    'Broadcast "%s" stoped. Last client %s disconnected' %
                    (self.channelName, self.clientip))
コード例 #12
0
ファイル: acehttp.py プロジェクト: furvovan/HTTPAceProxy
    def handleRequest(self,
                      headers_only,
                      channelName=None,
                      channelIcon=None,
                      fmt=None):

        logger = logging.getLogger('HandleRequest')
        self.reqparams, self.path = parse_qs(
            self.query), self.path[:-1] if self.path.endswith(
                '/') else self.path

        videoextdefaults = ('.avi', '.flv', '.m2ts', '.mkv', '.mpeg', '.mpeg4',
                            '.mpegts', '.mpg4', '.mp4', '.mpg', '.mov', '.mpv',
                            '.qt', '.ts', '.wmv')

        # Limit on the number of connected clients
        if 0 < AceConfig.maxconns <= len(
                AceProxy.clientcounter.getAllClientsList()):
            self.dieWithError(
                403,
                "Maximum client connections reached, can't serve request from %s"
                % self.clientip, logging.ERROR)
            return
        # Check if third parameter exists…/pid/blablablablabla/video.mpg
        #                                                     |_________|
        # And if it ends with regular video extension
        try:
            if not self.path.endswith(videoextdefaults):
                self.dieWithError(
                    501,
                    'Request seems like valid but no valid video extension was provided',
                    logging.ERROR)
                return
        except IndexError:
            self.dieWithError(400, 'Bad Request',
                              logging.WARNING)  # 400 Bad Request
            return
        # Pretend to work fine with Fake or HEAD request.
        if headers_only or AceConfig.isFakeRequest(self.path, self.query,
                                                   self.headers):
            # Return 200 and exit
            if headers_only:
                logger.debug('Sending headers and closing connection')
            else:
                logger.debug('Fake request - closing connection')
            self.send_response(200)
            self.send_header('Content-Type', 'video/mp2t')
            self.send_header('Connection', 'Close')
            self.end_headers()
            self.closeConnection()
            return

        # Make dict with parameters
        # [file_indexes, developer_id, affiliate_id, zone_id, stream_id]
        paramsdict = {}.fromkeys(aceclient.acemessages.AceConst.START_PARAMS,
                                 '0')
        for i in range(3, len(self.splittedpath)):
            paramsdict[aceclient.acemessages.AceConst.START_PARAMS[
                i - 3]] = self.splittedpath[i] if self.splittedpath[i].isdigit(
                ) else '0'
        paramsdict[self.reqtype] = unquote(
            self.splittedpath[2])  #self.path_unquoted
        #End parameters dict

        self.connectionTime = gevent.time.time()
        self.sessionID = str(uuid4().int)[:8]
        self.clientInfo = self.transcoder = None
        self.channelIcon = 'http://static.acestream.net/sites/acestream/img/ACE-logo.png' if channelIcon is None else channelIcon

        try:
            if not AceProxy.clientcounter.idleAce:
                logger.debug('Create connection to AceEngine.....')
                AceProxy.clientcounter.idleAce = aceclient.AceClient(
                    AceProxy.clientcounter, AceConfig.ace,
                    AceConfig.aceconntimeout, AceConfig.aceresulttimeout)
                AceProxy.clientcounter.idleAce.aceInit(AceConfig.acesex,
                                                       AceConfig.aceage,
                                                       AceConfig.acekey,
                                                       AceConfig.videoseekback,
                                                       AceConfig.videotimeout)
            self.CID, self.channelName = AceProxy.clientcounter.idleAce.GETINFOHASH(
                self.reqtype, paramsdict[self.reqtype], self.sessionID,
                paramsdict['file_indexes'])
        except aceclient.AceException as e:
            AceProxy.clientcounter.idleAce = None
            self.dieWithError(404, '%s' % repr(e), logging.ERROR)
            return
        mimetype = mimetypes.guess_type(self.channelName)[0]
        try:
            gevent.spawn(wrap_errors(
                gevent.socket.error, self.rfile.read)).link(
                    lambda x: self.finish())  # Client disconection watchdog
            self.q = gevent.queue.Queue(maxsize=AceConfig.videotimeout)
            self.out = self.wfile
            # If &fmt transcode key present in request
            if fmt is None: fmt = self.reqparams.get('fmt', [''])[0]
            if fmt and AceConfig.osplatform != 'Windows':
                if fmt in AceConfig.transcodecmd:
                    stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL
                    popen_params = {
                        'bufsize': 1048576,
                        'stdin': gevent.subprocess.PIPE,
                        'stdout': self.wfile,
                        'stderr': stderr,
                        'shell': False
                    }
                    try:
                        self.transcoder = gevent.event.AsyncResult()
                        AceProxy.spawn(lambda: psutil.Popen(
                            AceConfig.transcodecmd[fmt], **popen_params)).link(
                                self.transcoder)
                        self.transcoder = self.transcoder.get(timeout=2.0)
                        self.out = self.transcoder.stdin
                        logger.info('Transcoding for %s started' %
                                    self.clientip)
                    except:
                        logger.error(
                            'Error starting transcoding! Is Ffmpeg or VLC installed?'
                        )
                        self.transcoder = None
                        self.out = self.wfile
                else:
                    logger.error(
                        "Can't found fmt key. Transcoding not started!")

            # Start broadcast if it does not exist
            if AceProxy.clientcounter.addClient(self) == 1:
                playback_url = self.ace.START(self.reqtype, paramsdict,
                                              AceConfig.acestreamtype)
                AceProxy.pool.spawn(
                    StreamReader, playback_url,
                    self.CID).link(lambda x: logging.debug(
                        'Broadcast "%s" stoped. Last client disconnected' %
                        self.channelName))

            logger.info('Streaming "%s" to %s started' %
                        (self.channelName, self.clientip))
            # Sending videostream headers to client
            self.response_use_chunked = False if (
                self.transcoder is not None or self.request_version
                == 'HTTP/1.0') else AceConfig.use_chunked
            drop_headers = []
            proxy_headers = {
                'Connection': 'keep-alive',
                'Keep-Alive': 'timeout=15, max=100',
                'Accept-Ranges': 'none',
                'Transfer-Encoding': 'chunked',
                'Content-Type': 'video/MP2T' if mimetype is None else mimetype,
                'Pragma': 'no-cache',
                'Cache-Control': 'max-age=0, no-cache, no-store'
            }

            if not self.response_use_chunked:
                self.protocol_version = 'HTTP/1.0'
                proxy_headers['Connection'] = 'Close'
                drop_headers.extend(
                    ['Transfer-Encoding', 'Keep-Alive', 'Cache-Control'])

            response_headers = [(k, v) for (k, v) in proxy_headers.items()
                                if k not in drop_headers]
            self.send_response(200)
            logger.debug('Sending HTTPAceProxy headers to client: %s' %
                         dict(response_headers))
            gevent.joinall([
                gevent.spawn(self.send_header, k, v)
                for (k, v) in response_headers
            ])
            self.end_headers()
            # write data to client while he is alive
            for chunk in self.q:
                self.out.write(chunk)

        except aceclient.AceException as e:
            AceProxy.pool.map(
                lambda x: x.dieWithError(500, repr(e), logging.ERROR),
                AceProxy.clientcounter.getClientsList(self.CID))
        except (gevent.GreenletExit, gevent.socket.error):
            pass  # Client disconnected
        finally:
            AceProxy.clientcounter.deleteClient(self)
            logging.info('Streaming "%s" to %s finished' %
                         (self.channelName, self.clientip))
            if self.transcoder:
                try:
                    self.transcoder.kill()
                    logging.info('Transcoding for %s stoped' % self.clientip)
                except:
                    pass
            self.closeConnection()
コード例 #13
0
ファイル: aceclient.py プロジェクト: sergelevin/HTTPAceProxy
 def _loadresp_(self, recvbuffer):
     '''
     LOADRESP request_id {'status': status, 'files': [["Name", idx], [....]], 'infohash': infohash, 'checksum': checksum}
     '''
     return json.loads(unquote(''.join(recvbuffer[2:])))
コード例 #14
0
    def handleRequest(self, **params):
        '''
        :params: dict() with keys: headers_only, channelName, channelIcon
        '''

        logger = logging.getLogger('HandleRequest')
        self.path = self.path[:-1] if self.path.endswith('/') else self.path

        # Limit on the number of connected clients
        if 0 < AceConfig.maxconns <= len(
                AceProxy.clientcounter.getAllClientsList()):
            self.dieWithError(
                403,
                "Maximum client connections reached, can't serve request from %s"
                % self.clientip, logging.ERROR)
            return
        # Check if third parameter exists…/pid/blablablablabla/video.mpg
        #                                                     |_________|
        # And if it ends with regular video extension
        try:
            if not self.path.endswith(
                ('.avi', '.flv', '.m2ts', '.mkv', '.mpeg', '.mpeg4', '.mpegts',
                 '.mpg4', '.mp4', '.mpg', '.mov', '.mpv', '.qt', '.ts',
                 '.wmv')):
                self.dieWithError(
                    501,
                    'Request seems like valid but no valid video extension was provided',
                    logging.ERROR)
                return
        except IndexError:
            self.dieWithError(400, 'Bad Request',
                              logging.WARNING)  # 400 Bad Request
            return
        # Pretend to work fine with Fake or HEAD request.
        if params.get('headers_only') or AceConfig.isFakeRequest(
                self.path, self.query, self.headers):
            # Return 200 and exit
            if params.get('headers_only'):
                logger.debug('Sending headers and closing connection')
            else:
                logger.debug('Fake request - closing connection')
            self.send_response(200)
            self.send_header('Content-Type', 'video/mp2t')
            self.send_header('Connection', 'Close')
            self.end_headers()
            self.closeConnection()
            return

        # Make parameters dict
        params.update({self.reqtype:
                       unquote(self.splittedpath[2])})  # {command: value}
        params.update(
            {}.fromkeys(aceclient.acemessages.AceConst.START_PARAMS, '0')
        )  # [file_indexes, developer_id, affiliate_id, zone_id, stream_id]
        params.update({
            k: v
            for (k, v) in
            [(aceclient.acemessages.AceConst.START_PARAMS[i - 3],
              self.splittedpath[i] if self.splittedpath[i].isdigit() else '0')
             for i in range(3, len(self.splittedpath))]
        })
        params.update({
            'stream_type':
            ' '.join([
                '{}={}'.format(k, v)
                for k, v in AceConfig.acestreamtype.items()
            ])
        })  # request http or hls from AceEngine
        params['request_id'] = self.sessionID = str(uuid4().int)[:8]
        # End parameters dict

        self.connectionTime = gevent.time.time()
        self.clientInfo = None
        self.channelIcon = params.get('channelIcon')
        if self.channelIcon is None:
            self.channelIcon = 'http://static.acestream.net/sites/acestream/img/ACE-logo.png'

        try:
            if not AceProxy.clientcounter.idleAce:
                logger.debug(
                    'Create connection with AceStream on {aceHostIP}:{aceAPIport}'
                    .format(**AceConfig.ace))
                AceProxy.clientcounter.idleAce = aceclient.AceClient(
                    AceConfig.ace, AceConfig.aceconntimeout,
                    AceConfig.aceresulttimeout)
                AceProxy.clientcounter.idleAce.GetAUTH(AceConfig.acesex,
                                                       AceConfig.aceage,
                                                       AceConfig.acekey,
                                                       AceConfig.videoseekback,
                                                       AceConfig.videotimeout)
            if self.reqtype not in ('direct_url', 'efile_url'):
                self.CID, self.channelName = AceProxy.clientcounter.idleAce.GetCONTENTINFO(
                    params)
            else:
                self.channelName = params.get('channelName')
                if self.channelName is None: self.channelName = 'NoNameChannel'
                self.CID = requests.auth.hashlib.sha1(
                    self.channelName.encode('utf-8')).hexdigest()
        except aceclient.AceException as e:
            AceProxy.clientcounter.idleAce = None
            self.dieWithError(404, '%s' % repr(e), logging.ERROR)
            return
        ext = self.channelName[self.channelName.rfind('.') + 1:]
        if ext == self.channelName:
            ext = parse_qs(self.query).get('ext', ['ts'])[0]
        mimetype = mimetypes.guess_type('%s.%s' % (self.channelName, ext))[0]
        try:
            AceProxy.pool.spawn(
                wrap_errors(gevent.socket.error, self.rfile.read)).link(
                    lambda x: self.finish())  # Client disconection watchdog
            self.q = gevent.queue.Queue(maxsize=AceConfig.videotimeout)
            out = self.wfile
            # If &fmt transcode key present in request
            fmt = parse_qs(self.query).get('fmt', [''])[0]
            transcoder = gevent.event.AsyncResult()
            if fmt:
                if AceConfig.osplatform != 'Windows':
                    if fmt in AceConfig.transcodecmd:
                        stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL
                        popen_params = {
                            'bufsize': 1048576,
                            'stdin': gevent.subprocess.PIPE,
                            'stdout': self.wfile,
                            'stderr': stderr,
                            'shell': False
                        }
                        try:
                            #transcoder = gevent.event.AsyncResult()
                            AceProxy.pool.spawn(lambda: psutil.Popen(
                                AceConfig.transcodecmd[fmt], **popen_params)
                                                ).link(transcoder)
                            out = transcoder.get(timeout=2.0).stdin
                            logger.info(
                                'Transcoding for {clientip} started'.format(
                                    **self.__dict__))
                        except:
                            logger.error(
                                'Error starting transcoding! Is Ffmpeg or VLC installed?'
                            )
                    else:
                        logger.error(
                            "Can't found fmt key. Transcoding not started!")
                elif AceConfig.osplatform == 'Windows':
                    logger.error(
                        'Not applicable in Windnows OS. Transcoding not started!'
                    )

            # Start broadcast if it does not exist
            if AceProxy.clientcounter.addClient(self) == 1:
                START = self.ace.GetBroadcastStartParams(params)
                self.broadcast = AceProxy.pool.spawn(StreamReader,
                                                     START['url'],
                                                     START['infohash'])
                self.broadcast.link(lambda x: logging.debug(
                    'Broadcast "{channelName}" stoped. Last client disconnected'
                    .format(**self.__dict__)))

            logger.info(
                'Streaming "{channelName}" to {clientip} started'.format(
                    **self.__dict__))
            # Sending videostream headers to client
            response_use_chunked = False if (
                transcoder is not None or self.request_version
                == 'HTTP/1.0') else AceConfig.use_chunked
            drop_headers = []
            proxy_headers = {
                'Connection': 'keep-alive',
                'Keep-Alive': 'timeout=%s, max=100' % AceConfig.videotimeout,
                'Accept-Ranges': 'none',
                'Transfer-Encoding': 'chunked',
                'Content-Type': 'video/MP2T' if mimetype is None else mimetype
            }

            if not response_use_chunked:
                self.protocol_version = 'HTTP/1.0'
                proxy_headers['Connection'] = 'Close'
                drop_headers.extend(['Transfer-Encoding', 'Keep-Alive'])

            response_headers = [(k, v) for (k, v) in proxy_headers.items()
                                if k not in drop_headers]
            self.send_response(200)
            logger.debug('Sending HTTPAceProxy headers to client: %s' %
                         dict(response_headers))
            gevent.joinall([
                gevent.spawn(self.send_header, k, v)
                for (k, v) in response_headers
            ])
            self.end_headers()
            # write data to client while he is alive
            for chunk in self.q:
                out.write(b'%x\r\n' % len(chunk) + chunk +
                          b'\r\n' if response_use_chunked else chunk)

        except aceclient.AceException as e:
            _ = AceProxy.pool.map(
                lambda x: x.dieWithError(500, repr(e), logging.ERROR),
                AceProxy.clientcounter.getClientsList(self.CID))
        except (gevent.GreenletExit, gevent.socket.error):
            pass  # Client disconnected
        finally:
            AceProxy.clientcounter.deleteClient(self)
            logging.info(
                'Streaming "{channelName}" to {clientip} finished'.format(
                    **self.__dict__))
            if transcoder.value:
                try:
                    transcoder.value.kill()
                    logging.info('Transcoding for {clientip} stoped'.format(
                        **self.__dict__))
                except:
                    pass
            self.closeConnection()
            return
コード例 #15
0
    def handle(self, connection, headers_only=False):
        play = False
        # 30 minutes cache
        if not self.playlist or (int(gevent.time.time()) - self.playlisttime >
                                 30 * 60):
            self.updatelogos = p2pconfig.email != 're.place@me' and p2pconfig.password != 'ReplaceMe'
            if not self.downloadPlaylist():
                connection.dieWithError()
                return

        url = urlparse(connection.path)
        path = url.path[0:-1] if url.path.endswith('/') else url.path
        params = parse_qs(connection.query)

        if path.startswith('/torrenttv/channel/'):
            name = path.rsplit('.', 1)
            if not name[1]:
                connection.dieWithError(404,
                                        'Invalid path: %s' % unquote(path),
                                        logging.ERROR)
                return
            name = unquote(name[0].rsplit('/', 1)[1])
            if isinstance(name, bytes): name = name.decode('utf-8')  # PY2
            url = self.channels.get(name, None)
            if url is None:
                connection.dieWithError(404, 'Unknown channel: ' + name,
                                        logging.ERROR)
                return
            elif url.startswith('acestream://'):
                connection.path = '/content_id/%s/stream.ts' % url.split(
                    '/')[2]
            elif url.startswith('infohash://'):
                connection.path = '/infohash/%s/stream.ts' % url.split('/')[2]
            elif url.startswith(('http://', 'https://')) and url.endswith(
                ('.acelive', '.acestream', '.acemedia')):
                connection.path = '/url/%s/stream.ts' % quote(url, '')
            connection.splittedpath = connection.path.split('/')
            connection.reqtype = connection.splittedpath[1].lower()
            play = True
        elif self.etag == connection.headers.get('If-None-Match'):
            self.logger.debug('ETag matches - returning 304')
            connection.send_response(304)
            connection.send_header('Connection', 'close')
            connection.end_headers()
            return
        else:
            hostport = connection.headers['Host']
            path = '' if len(self.channels) == 0 else '/torrenttv/channel'
            add_ts = True if path.endswith('/ts') else False
            exported = self.playlist.exportm3u(
                hostport=hostport,
                path=path,
                add_ts=add_ts,
                header=config.m3uheadertemplate,
                fmt=params.get('fmt', [''])[0]).encode('utf-8')
            response_headers = {
                'Content-Type': 'audio/mpegurl; charset=utf-8',
                'Connection': 'close',
                'Content-Length': len(exported),
                'Access-Control-Allow-Origin': '*',
                'ETag': self.etag
            }
            try:
                h = connection.headers.get('Accept-Encoding').split(',')[0]
                compress_method = {
                    'zlib': zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS),
                    'deflate': zlib.compressobj(9, zlib.DEFLATED,
                                                -zlib.MAX_WBITS),
                    'gzip': zlib.compressobj(9, zlib.DEFLATED,
                                             zlib.MAX_WBITS | 16)
                }
                exported = compress_method[h].compress(
                    exported) + compress_method[h].flush()
                response_headers['Content-Length'] = len(exported)
                response_headers['Content-Encoding'] = h
            except:
                pass

            connection.send_response(200)
            gevent.joinall([
                gevent.spawn(connection.send_header, k, v)
                for (k, v) in response_headers.items()
            ])
            connection.end_headers()

        if play:
            connection.handleRequest(headers_only,
                                     name,
                                     self.logomap.get(name),
                                     fmt=params.get('fmt', [''])[0])
        elif not headers_only:
            self.logger.debug('Exporting m3u playlist')
            connection.wfile.write(exported)